Skip to content

Commit

Permalink
Merge pull request #32487 from elpaso/value-relation-restore
Browse files Browse the repository at this point in the history
[feature] Value relation restore missing layers from DBs
  • Loading branch information
elpaso committed Nov 4, 2019
2 parents 144f6a8 + e0e96fa commit 696c407
Show file tree
Hide file tree
Showing 15 changed files with 369 additions and 19 deletions.
9 changes: 9 additions & 0 deletions python/core/auto_generated/qgsmaplayer.sip.in
Expand Up @@ -1538,6 +1538,15 @@ Emitted whenever the layer's data source has been changed.
.. versionadded:: 3.5
%End

void styleLoaded( QgsMapLayer::StyleCategories categories );
%Docstring
Emitted when a style has been loaded

:param categories: style categories

.. versionadded:: 3.12
%End


protected:

Expand Down
Expand Up @@ -187,6 +187,7 @@ of attribute values failing enforced field constraints.
.. versionadded:: 3.0
%End


QString constraintFailureReason() const;
%Docstring
Returns the reason why a constraint check has failed (or an empty string
Expand Down
168 changes: 158 additions & 10 deletions src/app/qgisapp.cpp
Expand Up @@ -84,6 +84,7 @@
#include "qgsgeometryvalidationservice.h"
#include "qgssourceselectproviderregistry.h"
#include "qgssourceselectprovider.h"
#include "qgsprovidermetadata.h"

#include "qgsanalysis.h"
#include "qgsgeometrycheckregistry.h"
Expand Down Expand Up @@ -661,6 +662,18 @@ void QgisApp::onActiveLayerChanged( QgsMapLayer *layer )
emit activeLayerChanged( layer );
}

void QgisApp::vectorLayerStyleLoaded( QgsMapLayer::StyleCategories categories )
{
if ( categories.testFlag( QgsMapLayer::StyleCategory::Forms ) )
{
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( sender() );
if ( vl && vl->isValid( ) )
{
checkVectorLayerDependencies( vl );
}
}
}

/*
* This function contains forced validation of CRS used in QGIS.
* There are 4 options depending on the settings:
Expand Down Expand Up @@ -1942,6 +1955,131 @@ QgsMessageBar *QgisApp::visibleMessageBar()
}
}

QList<QgsVectorLayerRef> QgisApp::findBrokenWidgetDependencies( QgsVectorLayer *vl )
{
QList<QgsVectorLayerRef> brokenDependencies;
// Check for missing layer widget dependencies
for ( int i = 0; i < vl->fields().count(); i++ )
{
std::unique_ptr<QgsEditorWidgetWrapper> ww;
ww.reset( QgsGui::editorWidgetRegistry()->create( vl, i, nullptr, nullptr ) );
// ww should never be null in real life, but it is in QgisApp tests because
// QgsEditorWidgetRegistry widget factories is empty
if ( ww )
{
const auto constDependencies { ww->layerDependencies() };
for ( const QgsVectorLayerRef &dependency : constDependencies )
{
const QgsVectorLayer *depVl { QgsVectorLayerRef( dependency ).resolveWeakly(
QgsProject::instance(),
QgsVectorLayerRef::MatchType::Name ) };
if ( ! depVl || ! depVl->isValid() )
{
brokenDependencies.append( dependency );
}
}
}
}
return brokenDependencies;
}

void QgisApp::checkVectorLayerDependencies( QgsVectorLayer *vl )
{
if ( vl && vl->isValid() )
{
const auto constDependencies { findBrokenWidgetDependencies( vl ) };
for ( const QgsVectorLayerRef &dependency : constDependencies )
{
if ( ! vl || ! vl->isValid() )
{
// try to aggressively resolve the broken dependencies
bool loaded = false;
const QString providerName { vl->dataProvider()->name() };
QgsProviderMetadata *providerMetadata { QgsProviderRegistry::instance()->providerMetadata( providerName ) };
if ( providerMetadata )
{
// Retrieve the DB connection (if any)
std::unique_ptr< QgsAbstractDatabaseProviderConnection > conn { static_cast<QgsAbstractDatabaseProviderConnection *>( providerMetadata->createConnection( vl->dataProvider()->uri().uri(), {} ) ) };
if ( conn )
{
QString tableSchema;
QString tableName;
const QVariantMap sourceParts { providerMetadata->decodeUri( dependency.source ) };

// This part should really be abstracted out to the connection classes or to the providers directly.
// Different providers decode the uri differently, for example we don't get the table name out of OGR
// but the layerName/layerId instead, so let's try different approaches

// This works for GPKG
tableName = sourceParts.value( QStringLiteral( "layerName" ) ).toString();

// This works for PG and spatialite
if ( tableName.isEmpty() )
{
tableName = sourceParts.value( QStringLiteral( "table" ) ).toString();
tableSchema = sourceParts.value( QStringLiteral( "schema" ) ).toString();
}

// Helper to find layers in connections
auto layerFinder = [ &conn, &dependency, &providerName ]( const QString & tableSchema, const QString & tableName ) -> bool
{
// First try the current schema (or no schema if it's not supported from the provider)
try
{
const QString layerUri { conn->tableUri( tableSchema, tableName )};
// Load it!
std::unique_ptr< QgsVectorLayer > newVl = qgis::make_unique< QgsVectorLayer >( layerUri, dependency.name, providerName );
if ( newVl->isValid() )
{
QgsProject::instance()->addMapLayer( newVl.release() );
return true;
}
}
catch ( QgsProviderConnectionException & )
{
// Do nothing!
}
return false;
};

loaded = layerFinder( tableSchema, tableName );

// Try different schemas
if ( ! loaded && conn->capabilities().testFlag( QgsAbstractDatabaseProviderConnection::Capability::Schemas ) && ! tableSchema.isEmpty() )
{
const QStringList schemas { conn->schemas() };
for ( const QString &schemaName : schemas )
{
if ( schemaName != tableSchema )
{
loaded = layerFinder( schemaName, tableName );
}
if ( loaded )
{
break;
}
}
}
}
}
if ( ! loaded )
{
const QString msg { tr( "layer '%1' requires layer '%2' to be loaded but '%2' could not be found, please load it manually if possible." )
.arg( vl->name() )
.arg( dependency.name ) };
messageBar()->pushWarning( tr( "Missing layer form dependency" ), msg );
}
else
{
messageBar()->pushSuccess( tr( "Missing layer form dependency" ), tr( "Layer dependency '%2' required by '%1' was automatically loaded." )
.arg( vl->name() )
.arg( dependency.name ) );
}
}
}
}
}

void QgisApp::dataSourceManager( const QString &pageName )
{
if ( ! mDataSourceManagerDialog )
Expand Down Expand Up @@ -2809,7 +2947,6 @@ void QgisApp::createToolBars()
case 3:
defSelectionAction = mActionInvertSelection;
break;
break;
}
bt->setDefaultAction( defSelectionAction );
QAction *selectionAction = mAttributesToolBar->insertWidget( mActionDeselectAll, bt );
Expand Down Expand Up @@ -2968,7 +3105,7 @@ void QgisApp::createToolBars()
case 1:
defMapServiceAction = mActionAddAmsLayer;
break;
};
}
bt->setDefaultAction( defMapServiceAction );
QAction *mapServiceAction = mLayerToolBar->insertWidget( mActionAddWmsLayer, bt );
mLayerToolBar->removeAction( mActionAddWmsLayer );
Expand All @@ -2989,7 +3126,7 @@ void QgisApp::createToolBars()
case 1:
defFeatureServiceAction = mActionAddAfsLayer;
break;
};
}
bt->setDefaultAction( defFeatureServiceAction );
QAction *featureServiceAction = mLayerToolBar->insertWidget( mActionAddWfsLayer, bt );
mLayerToolBar->removeAction( mActionAddWfsLayer );
Expand Down Expand Up @@ -3049,7 +3186,7 @@ void QgisApp::createToolBars()
case 1:
defActionCircularString = mActionCircularStringRadius;
break;
};
}
tbAddCircularString->setDefaultAction( defActionCircularString );
QAction *addCircularAction = mShapeDigitizeToolBar->insertWidget( mActionVertexTool, tbAddCircularString );
addCircularAction->setObjectName( QStringLiteral( "ActionAddCircularString" ) );
Expand Down Expand Up @@ -3081,7 +3218,7 @@ void QgisApp::createToolBars()
case 4:
defActionCircle = mActionCircleCenterPoint;
break;
};
}
tbAddCircle->setDefaultAction( defActionCircle );
QAction *addCircleAction = mShapeDigitizeToolBar->insertWidget( mActionVertexTool, tbAddCircle );
addCircleAction->setObjectName( QStringLiteral( "ActionAddCircle" ) );
Expand Down Expand Up @@ -3109,7 +3246,7 @@ void QgisApp::createToolBars()
case 3:
defActionEllipse = mActionEllipseFoci;
break;
};
}
tbAddEllipse->setDefaultAction( defActionEllipse );
QAction *addEllipseAction = mShapeDigitizeToolBar->insertWidget( mActionVertexTool, tbAddEllipse );
addEllipseAction->setObjectName( QStringLiteral( "ActionAddEllipse" ) );
Expand Down Expand Up @@ -3137,7 +3274,7 @@ void QgisApp::createToolBars()
case 3:
defActionRectangle = mActionRectangle3PointsProjected;
break;
};
}
tbAddRectangle->setDefaultAction( defActionRectangle );
QAction *addRectangleAction = mShapeDigitizeToolBar->insertWidget( mActionVertexTool, tbAddRectangle );
addRectangleAction->setObjectName( QStringLiteral( "ActionAddRectangle" ) );
Expand All @@ -3161,7 +3298,7 @@ void QgisApp::createToolBars()
case 2:
defActionRegularPolygon = mActionRegularPolygonCenterCorner;
break;
};
}
tbAddRegularPolygon->setDefaultAction( defActionRegularPolygon );
QAction *addRegularPolygonAction = mShapeDigitizeToolBar->insertWidget( mActionVertexTool, tbAddRegularPolygon );
addRegularPolygonAction->setObjectName( QStringLiteral( "ActionAddRegularPolygon" ) );
Expand All @@ -3184,7 +3321,7 @@ void QgisApp::createToolBars()
case 1:
defAction = mActionMoveFeatureCopy;
break;
};
}
moveFeatureButton->setDefaultAction( defAction );
QAction *moveToolAction = mAdvancedDigitizeToolBar->insertWidget( mActionRotateFeature, moveFeatureButton );
moveToolAction->setObjectName( QStringLiteral( "ActionMoveFeatureTool" ) );
Expand All @@ -3204,7 +3341,7 @@ void QgisApp::createToolBars()
case QgsVertexTool::ActiveLayer:
defActionVertexTool = mActionVertexToolActiveLayer;
break;
};
}
vertexToolButton->setDefaultAction( defActionVertexTool );
connect( vertexToolButton, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );

Expand Down Expand Up @@ -6317,6 +6454,16 @@ bool QgisApp::addProject( const QString &projectFile )
}
#endif

// Check for missing layer widget dependencies
const auto constVLayers { QgsProject::instance()->layers<QgsVectorLayer *>( ) };
for ( QgsVectorLayer *vl : constVLayers )
{
if ( vl->isValid() )
{
checkVectorLayerDependencies( vl );
}
}

emit projectRead(); // let plug-ins know that we've read in a new
// project so that they can check any project
// specific plug-in state
Expand Down Expand Up @@ -12570,6 +12717,7 @@ void QgisApp::layersWereAdded( const QList<QgsMapLayer *> &layers )
connect( vlayer, &QgsVectorLayer::editingStopped, this, &QgisApp::layerEditStateChanged );
connect( vlayer, &QgsVectorLayer::readOnlyChanged, this, &QgisApp::layerEditStateChanged );
connect( vlayer, &QgsVectorLayer::raiseError, this, &QgisApp::onLayerError );
connect( vlayer, &QgsVectorLayer::styleLoaded, this, &QgisApp::vectorLayerStyleLoaded );

provider = vProvider;
}
Expand Down
20 changes: 20 additions & 0 deletions src/app/qgisapp.h
Expand Up @@ -161,6 +161,7 @@ class QgsNetworkRequestParameters;
#include "ogr/qgsvectorlayersaveasdialog.h"
#include "ui_qgisapp.h"
#include "qgis_app.h"
#include "qgsvectorlayerref.h"

#include <QGestureEvent>
#include <QTapAndHoldGesture>
Expand Down Expand Up @@ -1700,6 +1701,13 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow

void onActiveLayerChanged( QgsMapLayer *layer );

/**
* Triggered when a vector layer style has changed, checks for widget config layer dependencies
* \param categories style categories
* \since QGIS 3.12
*/
void vectorLayerStyleLoaded( const QgsMapLayer::StyleCategories categories );

signals:

/**
Expand Down Expand Up @@ -2008,8 +2016,20 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
//! Returns the message bar of the datasource manager dialog if it is visible, the canvas's message bar otherwise.
QgsMessageBar *visibleMessageBar();

/**
* Searches for layer widget dependencies
* \return a list of weak references to broken widget layer dependencies
*/
QList< QgsVectorLayerRef > findBrokenWidgetDependencies( QgsVectorLayer *vectorLayer );

/**
* Scans the \a vectorLayer for broken dependencies and warns the user
*/
void checkVectorLayerDependencies( QgsVectorLayer *vectorLayer );

QgisAppStyleSheet *mStyleSheetBuilder = nullptr;


// actions for menus and toolbars -----------------

#ifdef Q_OS_MAC
Expand Down
7 changes: 7 additions & 0 deletions src/core/qgsmaplayer.h
Expand Up @@ -1370,6 +1370,13 @@ class CORE_EXPORT QgsMapLayer : public QObject
*/
void dataSourceChanged();

/**
* Emitted when a style has been loaded
* \param categories style categories
* \since QGIS 3.12
*/
void styleLoaded( QgsMapLayer::StyleCategories categories );


private slots:

Expand Down

0 comments on commit 696c407

Please sign in to comment.