Merge pull request #33283 from elpaso/relation-style-restore

Relation style restore
This commit is contained in:
Alessandro Pasotti 2019-12-19 13:24:35 +01:00 committed by GitHub
commit 6654870c0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 661 additions and 53 deletions

View File

@ -591,6 +591,7 @@
<file>themes/default/propertyicons/system.svg</file>
<file>themes/default/propertyicons/transparency.svg</file>
<file>themes/default/propertyicons/spacer.svg</file>
<file>themes/default/propertyicons/relations.svg</file>
<file>themes/default/rendererCategorizedSymbol.svg</file>
<file>themes/default/rendererGraduatedSymbol.svg</file>
<file>themes/default/rendererNullSymbol.svg</file>

View File

@ -0,0 +1,31 @@
<svg height="32" width="32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<linearGradient id="a" gradientUnits="userSpaceOnUse" x1="5.6324644" x2="10.6324644" y1="3.7047943" y2="15.7047943">
<stop offset="0" stop-color="#f1f1f1"/>
<stop offset="1" stop-color="#d6d6d6"/>
</linearGradient>
<path d="m10.283011 8.3578359l10.139192.00203v7.8920306" fill="none" stroke="#2b3b4d"/>
<g fill-rule="evenodd" stroke="#888a85" stroke-linecap="round" stroke-linejoin="round" transform="translate(-.431455 -.14381833)">
<rect fill="url(#a)" height="15" overflow="visible" rx="1" width="13" x="2.6324642" y="1.7047944"/>
<path d="m2.6324644 5.7047943l12.9999996 0" fill="#eeeeec" overflow="visible"/>
<path d="m4.6324644 8.7047943l2 0" fill="#eeeeec" overflow="visible"/>
<path d="m4.6324644 11.704794l2 0" fill="#eeeeec" overflow="visible"/>
<path d="m4.6324644 14.704795l2 0" fill="#eeeeec" overflow="visible"/>
<path d="m9.6324641 8.7047943l3.9999999 0" fill="#eeeeec" overflow="visible"/>
<path d="m9.5703417 11.663069l4.0621223.04172" fill="#eeeeec" overflow="visible"/>
<path d="m9.6324641 14.704795l3.9999999 0" fill="#eeeeec" overflow="visible"/>
<path d="m4.7035514 3.7105107l1.928913-.00572" fill="#eeeeec" overflow="visible"/>
<path d="m9.6324641 3.7047943l3.9999999 0" fill="#eeeeec" overflow="visible"/>
</g>
<g fill-rule="evenodd" stroke="#888a85" stroke-linecap="round" stroke-linejoin="round" transform="translate(14.885197 13.80656)">
<rect fill="url(#a)" height="15" overflow="visible" rx="1" width="13" x="2.6324642" y="1.7047944"/>
<path d="m2.6324644 5.7047943l12.9999996 0" fill="#eeeeec" overflow="visible"/>
<path d="m4.6324644 8.7047943l2 0" fill="#eeeeec" overflow="visible"/>
<path d="m4.6324644 11.704794l2 0" fill="#eeeeec" overflow="visible"/>
<path d="m4.6324644 14.704795l2 0" fill="#eeeeec" overflow="visible"/>
<path d="m9.6324641 8.7047943l3.9999999 0" fill="#eeeeec" overflow="visible"/>
<path d="m9.5703417 11.663069l4.0621223.04172" fill="#eeeeec" overflow="visible"/>
<path d="m9.6324641 14.704795l3.9999999 0" fill="#eeeeec" overflow="visible"/>
<path d="m4.7035514 3.7105107l1.928913-.00572" fill="#eeeeec" overflow="visible"/>
<path d="m9.6324641 3.7047943l3.9999999 0" fill="#eeeeec" overflow="visible"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -91,6 +91,7 @@ This is the base class for all map layer types (vector, raster).
Rendering,
CustomProperties,
GeometryOptions,
Relations,
AllStyleCategories
};
typedef QFlags<QgsMapLayer::StyleCategory> StyleCategories;

View File

@ -104,7 +104,7 @@ Gets all relations where the specified layer (and field) is the referencing part
:return: A list of relations matching the given layer and fieldIdx.
%End
QList<QgsRelation> referencedRelations( QgsVectorLayer *layer = 0 ) const;
QList<QgsRelation> referencedRelations( const QgsVectorLayer *layer = 0 ) const;
%Docstring
Gets all relations where this layer is the referenced part (i.e. the parent table with the primary key being referenced from another layer).

View File

@ -2014,6 +2014,8 @@ Returns the layer's relations, where the foreign key is on this layer.
:return: A list of relations
%End
QgsVectorLayerEditBuffer *editBuffer();
%Docstring
Buffer with uncommitted editing operations. Only valid after editing has been turned on.

View File

@ -75,9 +75,11 @@
#include "qgssettings.h"
#include "qgsnetworkaccessmanager.h"
#include "qgsrelationmanager.h"
#include "qgsapplication.h"
#include "qgslayerstylingwidget.h"
#include "qgstaskmanager.h"
#include "qgsweakrelation.h"
#include "qgsziputils.h"
#include "qgsbrowserguimodel.h"
#include "qgsvectorlayerjoinbuffer.h"
@ -667,13 +669,24 @@ void QgisApp::onActiveLayerChanged( QgsMapLayer *layer )
void QgisApp::vectorLayerStyleLoaded( QgsMapLayer::StyleCategories categories )
{
if ( categories.testFlag( QgsMapLayer::StyleCategory::Forms ) )
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( sender() );
if ( vl && vl->isValid( ) )
{
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( sender() );
if ( vl && vl->isValid( ) )
// Check broken dependencies in forms
if ( categories.testFlag( QgsMapLayer::StyleCategory::Forms ) )
{
checkVectorLayerDependencies( vl );
resolveVectorLayerDependencies( vl );
}
// Check broken relations and try to restore them
if ( categories.testFlag( QgsMapLayer::StyleCategory::Relations ) )
{
resolveVectorLayerWeakRelations( vl );
}
}
}
@ -1995,25 +2008,85 @@ QgsMessageBar *QgisApp::visibleMessageBar()
}
}
QList<QgsVectorLayerRef> QgisApp::findBrokenWidgetDependencies( QgsVectorLayer *vl )
const QList<QgsVectorLayerRef> QgisApp::findBrokenLayerDependencies( QgsVectorLayer *vl, QgsMapLayer::StyleCategories categories ) const
{
QList<QgsVectorLayerRef> brokenDependencies;
// Check for missing layer widget dependencies
for ( int i = 0; i < vl->fields().count(); i++ )
if ( categories.testFlag( QgsMapLayer::StyleCategory::Forms ) )
{
const QgsEditorWidgetSetup setup = QgsGui::editorWidgetRegistry()->findBest( vl, vl->fields().field( i ).name() );
QgsFieldFormatter *fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
if ( fieldFormatter )
for ( int i = 0; i < vl->fields().count(); i++ )
{
const auto constDependencies { fieldFormatter->layerDependencies( setup.config() ) };
for ( const QgsVectorLayerRef &dependency : constDependencies )
const QgsEditorWidgetSetup setup = QgsGui::editorWidgetRegistry()->findBest( vl, vl->fields().field( i ).name() );
QgsFieldFormatter *fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
if ( fieldFormatter )
{
const QgsVectorLayer *depVl { QgsVectorLayerRef( dependency ).resolveWeakly(
QgsProject::instance(),
QgsVectorLayerRef::MatchType::Name ) };
if ( ! depVl || ! depVl->isValid() )
const QList<QgsVectorLayerRef> constDependencies { fieldFormatter->layerDependencies( setup.config() ) };
for ( const QgsVectorLayerRef &dependency : constDependencies )
{
brokenDependencies.append( dependency );
const QgsVectorLayer *depVl { QgsVectorLayerRef( dependency ).resolveWeakly(
QgsProject::instance(),
QgsVectorLayerRef::MatchType::Name ) };
if ( ! depVl || ! depVl->isValid() )
{
brokenDependencies.append( dependency );
}
}
}
}
}
if ( categories.testFlag( QgsMapLayer::StyleCategory::Relations ) )
{
// Check for layer weak relations
const QList<QgsWeakRelation> constWeakRelations { vl->weakRelations() };
for ( const QgsWeakRelation &rel : constWeakRelations )
{
QgsRelation relation { rel.resolvedRelation( QgsProject::instance(), QgsVectorLayerRef::MatchType::Name ) };
QgsVectorLayerRef dependency;
bool found = false;
if ( ! relation.isValid() )
{
// This is the big question: do we really
// want to automatically load the referencing layer(s) too?
// This could potentially lead to a cascaded load of a
// long list of layers.
// The code is in place but let's leave it disabled for now.
if ( relation.referencedLayer() == vl )
{
// Do nothing because vl is the referenced layer
#if 0
dependency = rel.referencingLayer();
found = true;
#endif
}
else if ( relation.referencingLayer() == vl )
{
dependency = rel.referencedLayer();
found = true;
}
else
{
// Something wrong is going on here, maybe this relation
// does not really apply to this layer?
QgsMessageLog::logMessage( tr( "None of the layers in the relation stored in the style match the current layer, skipping relation id: %1." ).arg( relation.id() ) );
}
if ( found )
{
// Make sure we don't add it twice
bool refFound = false;
for ( const QgsVectorLayerRef &otherRef : qgis::as_const( brokenDependencies ) )
{
if ( dependency.layerId == otherRef.layerId || ( dependency.source == otherRef.source && dependency.provider == otherRef.provider ) )
{
refFound = true;
break;
}
}
if ( ! refFound )
{
brokenDependencies.append( dependency );
}
}
}
}
@ -2021,11 +2094,11 @@ QList<QgsVectorLayerRef> QgisApp::findBrokenWidgetDependencies( QgsVectorLayer *
return brokenDependencies;
}
void QgisApp::checkVectorLayerDependencies( QgsVectorLayer *vl )
void QgisApp::resolveVectorLayerDependencies( QgsVectorLayer *vl, QgsMapLayer::StyleCategories categories )
{
if ( vl && vl->isValid() )
{
const auto constDependencies { findBrokenWidgetDependencies( vl ) };
const auto constDependencies { findBrokenLayerDependencies( vl, categories ) };
for ( const QgsVectorLayerRef &dependency : constDependencies )
{
// try to aggressively resolve the broken dependencies
@ -2115,6 +2188,31 @@ void QgisApp::checkVectorLayerDependencies( QgsVectorLayer *vl )
}
}
void QgisApp::resolveVectorLayerWeakRelations( QgsVectorLayer *vectorLayer )
{
if ( vectorLayer && vectorLayer->isValid() )
{
const QList<QgsWeakRelation> constWeakRelations { vectorLayer->weakRelations( ) };
for ( const QgsWeakRelation &rel : constWeakRelations )
{
QgsRelation relation { rel.resolvedRelation( QgsProject::instance(), QgsVectorLayerRef::MatchType::Name ) };
if ( relation.isValid() )
{
// Avoid duplicates
const QList<QgsRelation> constRelations { QgsProject::instance()->relationManager()->relations().values() };
for ( const QgsRelation &other : constRelations )
{
if ( relation.hasEqualDefinition( other ) )
{
continue;
}
}
QgsProject::instance()->relationManager()->addRelation( relation );
}
}
}
}
void QgisApp::dataSourceManager( const QString &pageName )
{
if ( ! mDataSourceManagerDialog )
@ -6506,7 +6604,7 @@ bool QgisApp::addProject( const QString &projectFile )
{
if ( vl->isValid() )
{
checkVectorLayerDependencies( vl );
resolveVectorLayerDependencies( vl );
}
}

View File

@ -2025,15 +2025,31 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
QgsMessageBar *visibleMessageBar();
/**
* Searches for layer widget dependencies
* \return a list of weak references to broken widget layer dependencies
* Searches for layer dependencies by querying the form widgets and the
* \a vectorLayer itself for broken relations. Style \a categories can be
* used to limit the search to one or more of the currently implemented search
* categories ("Forms" for the form widgets and "Relations" for layer weak relations).
* \return a list of weak references to broken layer dependencies
*/
QList< QgsVectorLayerRef > findBrokenWidgetDependencies( QgsVectorLayer *vectorLayer );
const QList< QgsVectorLayerRef > findBrokenLayerDependencies( QgsVectorLayer *vectorLayer,
QgsMapLayer::StyleCategories categories = QgsMapLayer::StyleCategory::AllStyleCategories ) const;
/**
* Scans the \a vectorLayer for broken dependencies and warns the user
* Scans the \a vectorLayer for broken dependencies and automatically
* try to load the missing layers, users are notified about the operation
* result. Style \a categories can be
* used to exclude one of the currently implemented search categories
* ("Forms" for the form widgets and "Relations" for layer weak relations).
*/
void checkVectorLayerDependencies( QgsVectorLayer *vectorLayer );
void resolveVectorLayerDependencies( QgsVectorLayer *vectorLayer,
QgsMapLayer::StyleCategories categories = QgsMapLayer::AllStyleCategories );
/**
* Scans the \a vectorLayer for weak relations and automatically
* try to resolve and create the broken relations.
*/
void resolveVectorLayerWeakRelations( QgsVectorLayer *vectorLayer );
QgisAppStyleSheet *mStyleSheetBuilder = nullptr;

View File

@ -77,7 +77,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
switch ( category )
{
case QgsMapLayer::LayerConfiguration:
case QgsMapLayer::StyleCategory::LayerConfiguration:
switch ( role )
{
case Qt::DisplayRole:
@ -88,7 +88,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/system.svg" ) );
}
break;
case QgsMapLayer::Symbology:
case QgsMapLayer::StyleCategory::Symbology:
switch ( role )
{
case Qt::DisplayRole:
@ -99,7 +99,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/symbology.svg" ) );
}
break;
case QgsMapLayer::Symbology3D:
case QgsMapLayer::StyleCategory::Symbology3D:
switch ( role )
{
case Qt::DisplayRole:
@ -110,7 +110,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/3d.svg" ) );
}
break;
case QgsMapLayer::Labeling:
case QgsMapLayer::StyleCategory::Labeling:
switch ( role )
{
case Qt::DisplayRole:
@ -121,7 +121,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/labels.svg" ) );
}
break;
case QgsMapLayer::Fields:
case QgsMapLayer::StyleCategory::Fields:
switch ( role )
{
case Qt::DisplayRole:
@ -132,7 +132,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/mSourceFields.svg" ) );
}
break;
case QgsMapLayer::Forms:
case QgsMapLayer::StyleCategory::Forms:
switch ( role )
{
case Qt::DisplayRole:
@ -143,7 +143,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/mActionFormView.svg" ) );
}
break;
case QgsMapLayer::Actions:
case QgsMapLayer::StyleCategory::Actions:
switch ( role )
{
case Qt::DisplayRole:
@ -154,7 +154,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/action.svg" ) );
}
break;
case QgsMapLayer::MapTips:
case QgsMapLayer::StyleCategory::MapTips:
switch ( role )
{
case Qt::DisplayRole:
@ -165,7 +165,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/display.svg" ) );
}
break;
case QgsMapLayer::Diagrams:
case QgsMapLayer::StyleCategory::Diagrams:
switch ( role )
{
case Qt::DisplayRole:
@ -176,7 +176,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/diagram.svg" ) );
}
break;
case QgsMapLayer::AttributeTable:
case QgsMapLayer::StyleCategory::AttributeTable:
switch ( role )
{
case Qt::DisplayRole:
@ -187,7 +187,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/mActionOpenTable.svg" ) );
}
break;
case QgsMapLayer::Rendering:
case QgsMapLayer::StyleCategory::Rendering:
switch ( role )
{
case Qt::DisplayRole:
@ -198,7 +198,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/rendering.svg" ) );
}
break;
case QgsMapLayer::CustomProperties:
case QgsMapLayer::StyleCategory::CustomProperties:
switch ( role )
{
case Qt::DisplayRole:
@ -209,7 +209,7 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/mActionOptions.svg" ) );
}
break;
case QgsMapLayer::GeometryOptions:
case QgsMapLayer::StyleCategory::GeometryOptions:
switch ( role )
{
case Qt::DisplayRole:
@ -220,7 +220,18 @@ QVariant QgsMapLayerStyleCategoriesModel::data( const QModelIndex &index, int ro
return QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/digitizing.svg" ) );
}
break;
case QgsMapLayer::AllStyleCategories:
case QgsMapLayer::StyleCategory::Relations:
switch ( role )
{
case Qt::DisplayRole:
return tr( "Relations" );
case Qt::ToolTipRole:
return tr( "Relations with other layers" );
case Qt::DecorationRole:
return QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/relations.svg" ) );
}
break;
case QgsMapLayer::StyleCategory::AllStyleCategories:
switch ( role )
{
case Qt::DisplayRole:

View File

@ -339,6 +339,7 @@ SET(QGIS_CORE_SRCS
qgsreadwritecontext.cpp
qgsreadwritelocker.cpp
qgsrelation.cpp
qgsweakrelation.cpp
qgsrelationmanager.cpp
qgsrenderchecker.cpp
qgsrendercontext.cpp
@ -840,6 +841,7 @@ SET(QGIS_CORE_HDRS
qgsreadwritecontext.h
qgsreadwritelocker.h
qgsrelation.h
qgsweakrelation.h
qgsrelationmanager.h
qgsrenderchecker.h
qgsrendercontext.h

View File

@ -267,15 +267,6 @@ bool QgsMapLayer::readLayerXml( const QDomElement &layerElement, QgsReadWriteCon
QgsReadWriteContextCategoryPopper p = context.enterCategory( tr( "Layer" ), mne.text() );
// now let the children grab what they need from the Dom node.
layerError = !readXml( layerElement, context );
// overwrite CRS with what we read from project file before the raster/vector
// file reading functions changed it. They will if projections is specified in the file.
// FIXME: is this necessary?
QgsCoordinateReferenceSystem::setCustomCrsValidation( savedValidation );
mCRS = savedCRS;
// the internal name is just the data source basename
//QFileInfo dataSourceFileInfo( mDataSource );
//internalName = dataSourceFileInfo.baseName();
@ -296,7 +287,6 @@ bool QgsMapLayer::readLayerXml( const QDomElement &layerElement, QgsReadWriteCon
setRefreshOnNofifyMessage( layerElement.attribute( QStringLiteral( "refreshOnNotifyMessage" ), QString() ) );
setRefreshOnNotifyEnabled( layerElement.attribute( QStringLiteral( "refreshOnNotifyEnabled" ), QStringLiteral( "0" ) ).toInt() );
// set name
mnl = layerElement.namedItem( QStringLiteral( "layername" ) );
mne = mnl.toElement();
@ -304,6 +294,15 @@ bool QgsMapLayer::readLayerXml( const QDomElement &layerElement, QgsReadWriteCon
//name can be translated
setName( context.projectTranslator()->translate( QStringLiteral( "project:layers:%1" ).arg( layerElement.namedItem( QStringLiteral( "id" ) ).toElement().text() ), mne.text() ) );
// now let the children grab what they need from the Dom node.
layerError = !readXml( layerElement, context );
// overwrite CRS with what we read from project file before the raster/vector
// file reading functions changed it. They will if projections is specified in the file.
// FIXME: is this necessary? Yes, it is (autumn 2019)
QgsCoordinateReferenceSystem::setCustomCrsValidation( savedValidation );
mCRS = savedCRS;
//short name
QDomElement shortNameElem = layerElement.firstChildElement( QStringLiteral( "shortname" ) );
if ( !shortNameElem.isNull() )

View File

@ -161,8 +161,9 @@ class CORE_EXPORT QgsMapLayer : public QObject
Rendering = 1 << 10, //!< Rendering: scale visibility, simplify method, opacity
CustomProperties = 1 << 11, //!< Custom properties (by plugins for instance)
GeometryOptions = 1 << 12, //!< Geometry validation configuration
Relations = 1 << 13, //!< Relations
AllStyleCategories = LayerConfiguration | Symbology | Symbology3D | Labeling | Fields | Forms | Actions |
MapTips | Diagrams | AttributeTable | Rendering | CustomProperties | GeometryOptions,
MapTips | Diagrams | AttributeTable | Rendering | CustomProperties | GeometryOptions | Relations,
};
Q_ENUM( StyleCategory )
Q_DECLARE_FLAGS( StyleCategories, StyleCategory )

View File

@ -27,8 +27,11 @@ QgsRelationManager::QgsRelationManager( QgsProject *project )
{
if ( mProject )
{
// TODO: QGIS 4 remove: relations are now stored with the layer style
connect( project, &QgsProject::readProjectWithContext, this, &QgsRelationManager::readProject );
// TODO: QGIS 4 remove: relations are now stored with the layer style
connect( project, &QgsProject::writeProject, this, &QgsRelationManager::writeProject );
connect( project, &QgsProject::layersRemoved, this, &QgsRelationManager::layersRemoved );
}
}
@ -148,7 +151,7 @@ QList<QgsRelation> QgsRelationManager::referencingRelations( const QgsVectorLaye
return relations;
}
QList<QgsRelation> QgsRelationManager::referencedRelations( QgsVectorLayer *layer ) const
QList<QgsRelation> QgsRelationManager::referencedRelations( const QgsVectorLayer *layer ) const
{
if ( !layer )
{

View File

@ -121,7 +121,7 @@ class CORE_EXPORT QgsRelationManager : public QObject
*
* \returns A list of relations where the specified layer is the referenced part.
*/
QList<QgsRelation> referencedRelations( QgsVectorLayer *layer = nullptr ) const;
QList<QgsRelation> referencedRelations( const QgsVectorLayer *layer = nullptr ) const;
/**
* Discover all the relations available from the current layers.

View File

@ -67,6 +67,7 @@
#include "qgsproviderregistry.h"
#include "qgsrectangle.h"
#include "qgsrelationmanager.h"
#include "qgsweakrelation.h"
#include "qgsrendercontext.h"
#include "qgsvectordataprovider.h"
#include "qgsvectorlayereditbuffer.h"
@ -2026,6 +2027,80 @@ bool QgsVectorLayer::readSymbology( const QDomNode &layerNode, QString &errorMes
updateFields();
}
if ( categories.testFlag( Relations ) )
{
const QgsPathResolver resolver { QgsProject::instance()->pathResolver() };
// Restore referenced layers: relations where "this" is the child layer (the referencing part, that holds the FK)
QDomNodeList referencedLayersNodeList = layerNode.toElement().elementsByTagName( QStringLiteral( "referencedLayers" ) );
if ( referencedLayersNodeList.size() > 0 )
{
const QDomNodeList relationNodes { referencedLayersNodeList.at( 0 ).childNodes() };
for ( int i = 0; i < relationNodes.length(); ++i )
{
const QDomElement relationElement = relationNodes.at( i ).toElement();
QList<QgsRelation::FieldPair> fieldPairs;
const QDomNodeList fieldPairNodes { relationElement.elementsByTagName( QStringLiteral( "fieldPair" ) ) };
for ( int j = 0; j < fieldPairNodes.length(); ++j )
{
const QDomElement fieldPairElement = fieldPairNodes.at( i ).toElement();
fieldPairs.push_back( { fieldPairElement.attribute( QStringLiteral( "referencing" ) ),
fieldPairElement.attribute( QStringLiteral( "referenced" ) ) } );
}
mWeakRelations.push_back( QgsWeakRelation { relationElement.attribute( QStringLiteral( "id" ) ),
relationElement.attribute( QStringLiteral( "name" ) ),
static_cast<QgsRelation::RelationStrength>( relationElement.attribute( QStringLiteral( "strength" ) ).toInt() ),
// Referencing
id(),
name(),
resolver.writePath( publicSource() ),
providerType(),
// Referenced
relationElement.attribute( QStringLiteral( "layerId" ) ),
relationElement.attribute( QStringLiteral( "layerName" ) ),
relationElement.attribute( QStringLiteral( "dataSource" ) ),
relationElement.attribute( QStringLiteral( "providerKey" ) ),
fieldPairs
} );
}
}
// Restore referencing layers: relations where "this" is the parent layer (the referenced part where the FK points to)
QDomNodeList referencingLayersNodeList = layerNode.toElement().elementsByTagName( QStringLiteral( "referencingLayers" ) );
if ( referencingLayersNodeList.size() > 0 )
{
const QDomNodeList relationNodes { referencingLayersNodeList.at( 0 ).childNodes() };
for ( int i = 0; i < relationNodes.length(); ++i )
{
const QDomElement relationElement = relationNodes.at( i ).toElement();
QList<QgsRelation::FieldPair> fieldPairs;
const QDomNodeList fieldPairNodes { relationElement.elementsByTagName( QStringLiteral( "fieldPair" ) ) };
for ( int j = 0; j < fieldPairNodes.length(); ++j )
{
const QDomElement fieldPairElement = fieldPairNodes.at( i ).toElement();
fieldPairs.push_back( { fieldPairElement.attribute( QStringLiteral( "referencing" ) ),
fieldPairElement.attribute( QStringLiteral( "referenced" ) ) } );
}
mWeakRelations.push_back( QgsWeakRelation { relationElement.attribute( QStringLiteral( "id" ) ),
relationElement.attribute( QStringLiteral( "name" ) ),
static_cast<QgsRelation::RelationStrength>( relationElement.attribute( QStringLiteral( "strength" ) ).toInt() ),
// Referencing
relationElement.attribute( QStringLiteral( "layerId" ) ),
relationElement.attribute( QStringLiteral( "layerName" ) ),
relationElement.attribute( QStringLiteral( "dataSource" ) ),
relationElement.attribute( QStringLiteral( "providerKey" ) ),
// Referenced
id(),
name(),
resolver.writePath( publicSource() ),
providerType(),
fieldPairs
} );
}
}
}
QDomElement layerElement = layerNode.toElement();
readCommonStyle( layerElement, context, categories );
@ -2445,6 +2520,74 @@ bool QgsVectorLayer::writeSymbology( QDomNode &node, QDomDocument &doc, QString
if ( categories.testFlag( GeometryOptions ) )
mGeometryOptions->writeXml( node );
// Relation information for both referenced and referencing sides
if ( categories.testFlag( Relations ) )
{
const QgsPathResolver resolver { QgsProject::instance()->pathResolver() };
// Store referenced layers: relations where "this" is the child layer (the referencing part, that holds the FK)
QDomElement referencedLayersElement = doc.createElement( QStringLiteral( "referencedLayers" ) );
node.appendChild( referencedLayersElement );
const auto constReferencingRelations { QgsProject::instance()->relationManager()->referencingRelations( this ) };
for ( const auto &rel : constReferencingRelations )
{
QDomElement relationElement = doc.createElement( QStringLiteral( "relation" ) );
referencedLayersElement.appendChild( relationElement );
relationElement.setAttribute( QStringLiteral( "id" ), rel.id() );
relationElement.setAttribute( QStringLiteral( "name" ), rel.name() );
relationElement.setAttribute( QStringLiteral( "strength" ), rel.strength() );
relationElement.setAttribute( QStringLiteral( "layerId" ), rel.referencedLayer()->id() );
relationElement.setAttribute( QStringLiteral( "layerName" ), rel.referencedLayer()->name() );
relationElement.setAttribute( QStringLiteral( "dataSource" ), resolver.writePath( rel.referencedLayer()->publicSource() ) );
relationElement.setAttribute( QStringLiteral( "providerKey" ), rel.referencedLayer()->providerType() );
const QList<QgsRelation::FieldPair> constFieldPairs { rel.fieldPairs() };
for ( const QgsRelation::FieldPair &fp : constFieldPairs )
{
QDomElement fieldPair = doc.createElement( QStringLiteral( "fieldPair" ) );
relationElement.appendChild( fieldPair );
fieldPair.setAttribute( QStringLiteral( "referenced" ), fp.referencedField() );
fieldPair.setAttribute( QStringLiteral( "referencing" ), fp.referencingField() );
}
}
// Store referencing layers: relations where "this" is the parent layer (the referenced part where the FK points to)
QDomElement referencingLayersElement = doc.createElement( QStringLiteral( "referencingLayers" ) );
node.appendChild( referencingLayersElement );
const auto constReferencedRelations { QgsProject::instance()->relationManager()->referencedRelations( this ) };
for ( const auto &rel : constReferencedRelations )
{
QDomElement relationElement = doc.createElement( QStringLiteral( "relation" ) );
referencingLayersElement.appendChild( relationElement );
relationElement.setAttribute( QStringLiteral( "id" ), rel.id() );
relationElement.setAttribute( QStringLiteral( "name" ), rel.name() );
relationElement.setAttribute( QStringLiteral( "strength" ), rel.strength() );
relationElement.setAttribute( QStringLiteral( "layerId" ), rel.referencingLayer()->id() );
relationElement.setAttribute( QStringLiteral( "layerName" ), rel.referencingLayer()->name() );
relationElement.setAttribute( QStringLiteral( "dataSource" ), resolver.writePath( rel.referencingLayer()->publicSource() ) );
relationElement.setAttribute( QStringLiteral( "providerKey" ), rel.referencingLayer()->providerType() );
const QList<QgsRelation::FieldPair> constFieldPairs { rel.fieldPairs() };
for ( const QgsRelation::FieldPair &fp : constFieldPairs )
{
QDomElement fieldPair = doc.createElement( QStringLiteral( "fieldPair" ) );
relationElement.appendChild( fieldPair );
fieldPair.setAttribute( QStringLiteral( "referenced" ), fp.referencedField() );
fieldPair.setAttribute( QStringLiteral( "referencing" ), fp.referencingField() );
}
}
}
if ( categories.testFlag( Fields ) )
{
QDomElement fieldConfigurationElement = doc.createElement( QStringLiteral( "fieldConfiguration" ) );
@ -4766,6 +4909,11 @@ QList<QgsRelation> QgsVectorLayer::referencingRelations( int idx ) const
return QgsProject::instance()->relationManager()->referencingRelations( this, idx );
}
QList<QgsWeakRelation> QgsVectorLayer::weakRelations() const
{
return mWeakRelations;
}
int QgsVectorLayer::listStylesInDatabase( QStringList &ids, QStringList &names, QStringList &descriptions, QString &msgError )
{
return QgsProviderRegistry::instance()->listStyles( mProviderKey, mDataSource, ids, names, descriptions, msgError );

View File

@ -62,6 +62,7 @@ class QgsMapToPixel;
class QgsRectangle;
class QgsRectangle;
class QgsRelation;
class QgsWeakRelation;
class QgsRelationManager;
class QgsSingleSymbolRenderer;
class QgsStoredExpressionManager;
@ -1872,6 +1873,15 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
*/
QList<QgsRelation> referencingRelations( int idx ) const;
/**
* Returns the layer's weak relations as specified in the layer's style.
* \returns A list of weak relations
* \note not available in Python bindings
* \since QGIS 3.12
*/
QList<QgsWeakRelation> weakRelations( ) const SIP_SKIP;
//! Buffer with uncommitted editing operations. Only valid after editing has been turned on.
Q_INVOKABLE QgsVectorLayerEditBuffer *editBuffer() { return mEditBuffer; }
@ -2827,6 +2837,8 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
//! To avoid firing multiple time dataChanged signal on circular layer circular dependencies
bool mDataChangedFired = false;
QList<QgsWeakRelation> mWeakRelations;
};

View File

@ -0,0 +1,75 @@
/***************************************************************************
qgsweakrelation.cpp - QgsWeakRelation
---------------------
begin : 5.12.2019
copyright : (C) 2019 by Alessandro Pasotti
email : elpaso at itopen dot it
***************************************************************************
* *
* 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 "qgsweakrelation.h"
QgsWeakRelation::QgsWeakRelation( const QString &relationId, const QString &relationName, const QgsRelation::RelationStrength strength,
const QString &referencingLayerId, const QString &referencingLayerName, const QString &referencingLayerSource, const QString &referencingLayerProviderKey,
const QString &referencedLayerId, const QString &referencedLayerName, const QString &referencedLayerSource, const QString &referencedLayerProviderKey,
const QList<QgsRelation::FieldPair> &fieldPairs )
: mReferencingLayer( referencingLayerId, referencingLayerName, referencingLayerSource, referencingLayerProviderKey )
, mReferencedLayer( referencedLayerId, referencedLayerName, referencedLayerSource, referencedLayerProviderKey )
, mRelationId( relationId )
, mRelationName( relationName )
, mStrength( strength )
, mFieldPairs( fieldPairs )
{
}
QgsRelation QgsWeakRelation::resolvedRelation( const QgsProject *project, QgsVectorLayerRef::MatchType matchType ) const
{
QgsRelation relation;
relation.setId( mRelationId );
relation.setName( mRelationName );
relation.setStrength( mStrength );
QgsVectorLayerRef referencedLayerRef { mReferencedLayer };
QgsMapLayer *referencedLayer { referencedLayerRef.resolveWeakly( project, matchType ) };
if ( referencedLayer )
{
relation.setReferencedLayer( referencedLayer->id() );
}
QgsVectorLayerRef referencingLayerRef { mReferencingLayer };
QgsMapLayer *referencingLayer { referencingLayerRef.resolveWeakly( project, matchType ) };
if ( referencingLayer )
{
relation.setReferencingLayer( referencingLayer->id() );
}
for ( const auto &fp : qgis::as_const( mFieldPairs ) )
{
relation.addFieldPair( fp );
}
return relation;
}
QgsVectorLayerRef QgsWeakRelation::referencingLayer() const
{
return mReferencingLayer;
}
QgsVectorLayerRef QgsWeakRelation::referencedLayer() const
{
return mReferencedLayer;
}
QgsRelation::RelationStrength QgsWeakRelation::strength() const
{
return mStrength;
}
QList<QgsRelation::FieldPair> QgsWeakRelation::fieldPairs() const
{
return mFieldPairs;
}

101
src/core/qgsweakrelation.h Normal file
View File

@ -0,0 +1,101 @@
/***************************************************************************
qgsweakrelation.h - QgsWeakRelation
---------------------
begin : 5.12.2019
copyright : (C) 2019 by Alessandro Pasotti
email : elpaso at itopen dot it
***************************************************************************
* *
* 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 QGSWEAKRELATION_H
#define QGSWEAKRELATION_H
#define SIP_NO_FILE
#include "qgis_core.h"
#include "qgsrelation.h"
#include "qgsvectorlayerref.h"
/**
* The QgsWeakRelation class represent a QgsRelation with possibly
* unresolved layers or unmatched fields.
*
* This class is used to store relation information attached to a
* layer style, a method to attempt relation resolution is also
* implemented and can be used to create a QgsRelation after the
* dependent layers are loaded and available.
*
* \note not available in Python bindings
* \ingroup core
* \since QGIS 3.12
*/
class CORE_EXPORT QgsWeakRelation
{
public:
/**
* Creates a QgsWeakRelation
*/
QgsWeakRelation( const QString &relationId,
const QString &relationName,
const QgsRelation::RelationStrength strength,
const QString &referencingLayerId,
const QString &referencingLayerName,
const QString &referencingLayerSource,
const QString &referencingLayerProviderKey,
const QString &referencedLayerId,
const QString &referencedLayerName,
const QString &referencedLayerSource,
const QString &referencedLayerProviderKey,
const QList<QgsRelation::FieldPair> &fieldPairs
);
/**
* Resolves a weak relation in the given \a project returning a possibly invalid QgsRelation
* and without performing any kind of validity check.
*
* \note Client code should never assume that the returned relation is valid and the
* layer components are not NULL.
*/
QgsRelation resolvedRelation( const QgsProject *project, QgsVectorLayerRef::MatchType matchType = QgsVectorLayerRef::MatchType::All ) const;
/**
* Returns a weak reference to the referencing layer
*/
QgsVectorLayerRef referencingLayer() const;
/**
* Returns a weak reference to the referenced layer
*/
QgsVectorLayerRef referencedLayer() const;
/**
* Returns the strength of the relation
*/
QgsRelation::RelationStrength strength() const;
/**
* Returns the list of field pairs
*/
QList<QgsRelation::FieldPair> fieldPairs() const;
private:
QgsVectorLayerRef mReferencingLayer;
QgsVectorLayerRef mReferencedLayer;
QString mRelationId;
QString mRelationName;
QgsRelation::RelationStrength mStrength = QgsRelation::RelationStrength::Association;
QList<QgsRelation::FieldPair> mFieldPairs;
friend class TestQgsWeakRelation;
};
#endif // QGSWEAKRELATION_H

View File

@ -241,6 +241,7 @@ SET(TESTS
testqobjectuniqueptr.cpp
testqgspostgresstringutils.cpp
testqgsstoredexpressionmanager.cpp
testqgsweakrelation.cpp
)
IF(WITH_QTWEBKIT)

View File

@ -0,0 +1,106 @@
/***************************************************************************
testqgsweakrelation.cpp
----------------
Date : December 2019
Copyright : (C) 2019 Alessandro Pasotti
Email : elpaso at itopen dot it
***************************************************************************
* *
* 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 "qgstest.h"
#include "qgsapplication.h"
#include "qgsvectorlayer.h"
#include "qgsproject.h"
#include "qgsweakrelation.h"
#include "qgsrelation.h"
class TestQgsWeakRelation: public QObject
{
Q_OBJECT
private slots:
void initTestCase();// will be called before the first testfunction is executed.
void cleanupTestCase();// will be called after the last testfunction was executed.
void init();// will be called before each testfunction is executed.
void cleanup();// will be called after every testfunction.
void testResolved(); // Test if relation can be resolved
private:
};
void TestQgsWeakRelation::initTestCase()
{
// Set up the QgsSettings environment
QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) );
QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) );
QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) );
QgsApplication::init();
QgsApplication::initQgis();
}
void TestQgsWeakRelation::cleanupTestCase()
{
QgsApplication::exitQgis();
}
void TestQgsWeakRelation::init()
{
QLocale::setDefault( QLocale::English );
}
void TestQgsWeakRelation::cleanup()
{
QLocale::setDefault( QLocale::English );
}
void TestQgsWeakRelation::testResolved()
{
QList<QgsRelation::FieldPair> fieldPairs {{ "fk_province", "pk" }};
QgsWeakRelation weakRel( QStringLiteral( "my_relation_id" ),
QStringLiteral( "my_relation_name" ),
QgsRelation::RelationStrength::Association,
QStringLiteral( "referencingLayerId" ),
QStringLiteral( "referencingLayerName" ),
QStringLiteral( "Point?crs=epsg:4326&field=pk:int&field=fk_province:int&field=fk_municipality:int" ),
QStringLiteral( "memory" ),
QStringLiteral( "referencedLayerId" ),
QStringLiteral( "referencedLayerName" ),
QStringLiteral( "Polygon?crs=epsg:4326&field=pk:int&field=province:int&field=municipality:string" ),
QStringLiteral( "memory" ),
fieldPairs
);
QVERIFY( ! weakRel.resolvedRelation( QgsProject::instance(), QgsVectorLayerRef::MatchType::Name ).isValid() );
// create a vector layer
QgsVectorLayer referencedLayer( QStringLiteral( "Polygon?crs=epsg:4326&field=pk:int&field=province:int&field=municipality:string" ), QStringLiteral( "referencedLayerName" ), QStringLiteral( "memory" ) );
QgsProject::instance()->addMapLayer( &referencedLayer, false, false );
QVERIFY( ! weakRel.resolvedRelation( QgsProject::instance(), QgsVectorLayerRef::MatchType::Name ).isValid() );
QgsVectorLayer referencingLayer( QStringLiteral( "Point?crs=epsg:4326&field=pk:int&field=fk_province:int&field=fk_municipality:int" ), QStringLiteral( "referencingLayerName" ), QStringLiteral( "memory" ) );
QgsProject::instance()->addMapLayer( &referencingLayer, false, false );
QVERIFY( weakRel.resolvedRelation( QgsProject::instance(), QgsVectorLayerRef::MatchType::Name ).isValid() );
QVERIFY( weakRel.resolvedRelation( QgsProject::instance(), static_cast<QgsVectorLayerRef::MatchType>( QgsVectorLayerRef::MatchType::Name | QgsVectorLayerRef::MatchType::Provider ) ).isValid() );
// This fails because memory provider stores an UUID in the data source definition ...
QVERIFY( !weakRel.resolvedRelation( QgsProject::instance(), static_cast<QgsVectorLayerRef::MatchType>( QgsVectorLayerRef::MatchType::Name | QgsVectorLayerRef::MatchType::Source ) ).isValid() );
// ... let's fix it
weakRel.mReferencedLayer.source = referencedLayer.publicSource();
weakRel.mReferencingLayer.source = referencingLayer.publicSource();
QVERIFY( weakRel.resolvedRelation( QgsProject::instance(), static_cast<QgsVectorLayerRef::MatchType>( QgsVectorLayerRef::MatchType::Name | QgsVectorLayerRef::MatchType::Source ) ).isValid() );
// Just to be sure
QVERIFY( weakRel.resolvedRelation( QgsProject::instance() ).isValid() );
}
QGSTEST_MAIN( TestQgsWeakRelation )
#include "testqgsweakrelation.moc"