QGIS/src/gui/processing/qgsprocessingfieldmapwidgetwrapper.cpp
Nyall Dawson 69f73e3a74 [processing] When running in toolbox mode, set the source layer for
the field expression widgets in the refactor fields widget so that
the expression editor can generate feature based previews

Fixes #37912
2020-07-24 10:26:17 +10:00

398 lines
14 KiB
C++

/***************************************************************************
qgsprocessingfieldmapwidgetwrapper.cpp
---------------------
Date : June 2020
Copyright : (C) 2020 by Nyall Dawson
Email : nyall dot dawson at gmail dot 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 "qgsprocessingfieldmapwidgetwrapper.h"
#include <QBoxLayout>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QStandardItemModel>
#include <QToolButton>
#include "qgspanelwidget.h"
#include "qgsprocessingcontext.h"
#include "qgsprocessingmodelalgorithm.h"
#include "qgsprocessingparameterfieldmap.h"
/// @cond private
//
// QgsProcessingFieldMapPanelWidget
//
QgsProcessingFieldMapPanelWidget::QgsProcessingFieldMapPanelWidget( QWidget *parent )
: QgsPanelWidget( parent )
{
setupUi( this );
mModel = mFieldsView->model();
mFieldsView->setDestinationEditable( true );
mLayerCombo->setAllowEmptyLayer( true );
mLayerCombo->setFilters( QgsMapLayerProxyModel::VectorLayer );
connect( mResetButton, &QPushButton::clicked, this, &QgsProcessingFieldMapPanelWidget::loadFieldsFromLayer );
connect( mAddButton, &QPushButton::clicked, this, &QgsProcessingFieldMapPanelWidget::addField );
connect( mDeleteButton, &QPushButton::clicked, mFieldsView, &QgsFieldMappingWidget::removeSelectedFields );
connect( mUpButton, &QPushButton::clicked, mFieldsView, &QgsFieldMappingWidget::moveSelectedFieldsUp );
connect( mDownButton, &QPushButton::clicked, mFieldsView, &QgsFieldMappingWidget::moveSelectedFieldsDown );
connect( mLoadLayerFieldsButton, &QPushButton::clicked, this, &QgsProcessingFieldMapPanelWidget::loadLayerFields );
connect( mFieldsView, &QgsFieldMappingWidget::changed, this, [ = ]
{
if ( !mBlockChangedSignal )
{
emit changed();
}
} );
}
void QgsProcessingFieldMapPanelWidget::setLayer( QgsVectorLayer *layer )
{
if ( layer == mLayer )
return;
mLayer = layer;
mFieldsView->setSourceLayer( mLayer );
if ( mModel->rowCount() == 0 )
{
loadFieldsFromLayer();
return;
}
QMessageBox dlg( this );
dlg.setText( tr( "Do you want to reset the field mapping?" ) );
dlg.setStandardButtons(
QMessageBox::StandardButtons( QMessageBox::Yes |
QMessageBox::No ) );
dlg.setDefaultButton( QMessageBox::No );
if ( dlg.exec() == QMessageBox::Yes )
{
loadFieldsFromLayer();
}
}
QgsVectorLayer *QgsProcessingFieldMapPanelWidget::layer()
{
return mLayer;
}
QVariant QgsProcessingFieldMapPanelWidget::value() const
{
const QList<QgsFieldMappingModel::Field> mapping = mFieldsView->mapping();
QVariantList results;
results.reserve( mapping.size() );
for ( const QgsFieldMappingModel::Field &field : mapping )
{
QVariantMap def;
def.insert( QStringLiteral( "name" ), field.field.name() );
def.insert( QStringLiteral( "type" ), static_cast< int >( field.field.type() ) );
def.insert( QStringLiteral( "length" ), field.field.length() );
def.insert( QStringLiteral( "precision" ), field.field.precision() );
def.insert( QStringLiteral( "expression" ), field.expression );
results.append( def );
}
return results;
}
void QgsProcessingFieldMapPanelWidget::setValue( const QVariant &value )
{
if ( value.type() != QVariant::List )
return;
QgsFields destinationFields;
QMap<QString, QString> expressions;
const QVariantList fields = value.toList();
for ( const QVariant &field : fields )
{
const QVariantMap map = field.toMap();
QgsField f( map.value( QStringLiteral( "name" ) ).toString(),
static_cast< QVariant::Type >( map.value( QStringLiteral( "type" ), QVariant::Invalid ).toInt() ),
QVariant::typeToName( static_cast< QVariant::Type >( map.value( QStringLiteral( "type" ), QVariant::Invalid ).toInt() ) ),
map.value( QStringLiteral( "length" ), 0 ).toInt(),
map.value( QStringLiteral( "precision" ), 0 ).toInt() );
if ( !map.value( QStringLiteral( "expression" ) ).toString().isEmpty() )
{
expressions.insert( f.name(), map.value( QStringLiteral( "expression" ) ).toString() );
}
destinationFields.append( f );
}
mBlockChangedSignal = true;
if ( destinationFields.size() > 0 )
mFieldsView->setDestinationFields( destinationFields, expressions );
mBlockChangedSignal = false;
emit changed();
}
void QgsProcessingFieldMapPanelWidget::registerExpressionContextGenerator( const QgsExpressionContextGenerator *generator )
{
mFieldsView->registerExpressionContextGenerator( generator );
}
void QgsProcessingFieldMapPanelWidget::loadFieldsFromLayer()
{
if ( mLayer )
{
mFieldsView->setSourceFields( mLayer->fields() );
mFieldsView->setDestinationFields( mLayer->fields() );
}
}
void QgsProcessingFieldMapPanelWidget::addField()
{
const int rowCount = mModel->rowCount();
mModel->appendField( QgsField( QStringLiteral( "new_field" ) ) );
QModelIndex index = mModel->index( rowCount, 0 );
mFieldsView->selectionModel()->select(
index,
QItemSelectionModel::SelectionFlags(
QItemSelectionModel::Clear |
QItemSelectionModel::Select |
QItemSelectionModel::Current |
QItemSelectionModel::Rows ) );
mFieldsView->scrollTo( index );
}
void QgsProcessingFieldMapPanelWidget::loadLayerFields()
{
if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( mLayerCombo->currentLayer() ) )
{
mFieldsView->setSourceFields( vl->fields() );
mFieldsView->setDestinationFields( vl->fields() );
}
}
//
// QgsProcessingFieldMapParameterDefinitionWidget
//
QgsProcessingFieldMapParameterDefinitionWidget::QgsProcessingFieldMapParameterDefinitionWidget( QgsProcessingContext &context, const QgsProcessingParameterWidgetContext &widgetContext, const QgsProcessingParameterDefinition *definition, const QgsProcessingAlgorithm *algorithm, QWidget *parent )
: QgsProcessingAbstractParameterDefinitionWidget( context, widgetContext, definition, algorithm, parent )
{
QVBoxLayout *vlayout = new QVBoxLayout();
vlayout->setMargin( 0 );
vlayout->setContentsMargins( 0, 0, 0, 0 );
vlayout->addWidget( new QLabel( tr( "Parent layer" ) ) );
mParentLayerComboBox = new QComboBox();
mParentLayerComboBox->addItem( tr( "None" ), QVariant() );
QString initialParent;
if ( const QgsProcessingParameterFieldMapping *mapParam = dynamic_cast<const QgsProcessingParameterFieldMapping *>( definition ) )
initialParent = mapParam->parentLayerParameterName();
if ( widgetContext.model() )
{
// populate combo box with other model input choices
const QMap<QString, QgsProcessingModelParameter> components = widgetContext.model()->parameterComponents();
for ( auto it = components.constBegin(); it != components.constEnd(); ++it )
{
if ( const QgsProcessingParameterFeatureSource *definition = dynamic_cast< const QgsProcessingParameterFeatureSource * >( widgetContext.model()->parameterDefinition( it.value().parameterName() ) ) )
{
mParentLayerComboBox-> addItem( definition->description(), definition->name() );
if ( !initialParent.isEmpty() && initialParent == definition->name() )
{
mParentLayerComboBox->setCurrentIndex( mParentLayerComboBox->count() - 1 );
}
}
else if ( const QgsProcessingParameterVectorLayer *definition = dynamic_cast< const QgsProcessingParameterVectorLayer * >( widgetContext.model()->parameterDefinition( it.value().parameterName() ) ) )
{
mParentLayerComboBox-> addItem( definition->description(), definition->name() );
if ( !initialParent.isEmpty() && initialParent == definition->name() )
{
mParentLayerComboBox->setCurrentIndex( mParentLayerComboBox->count() - 1 );
}
}
}
}
if ( mParentLayerComboBox->count() == 1 && !initialParent.isEmpty() )
{
// if no parent candidates found, we just add the existing one as a placeholder
mParentLayerComboBox->addItem( initialParent, initialParent );
mParentLayerComboBox->setCurrentIndex( mParentLayerComboBox->count() - 1 );
}
vlayout->addWidget( mParentLayerComboBox );
setLayout( vlayout );
}
QgsProcessingParameterDefinition *QgsProcessingFieldMapParameterDefinitionWidget::createParameter( const QString &name, const QString &description, QgsProcessingParameterDefinition::Flags flags ) const
{
auto param = qgis::make_unique< QgsProcessingParameterFieldMapping >( name, description, mParentLayerComboBox->currentData().toString() );
param->setFlags( flags );
return param.release();
}
//
// QgsProcessingFieldMapWidgetWrapper
//
QgsProcessingFieldMapWidgetWrapper::QgsProcessingFieldMapWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type, QWidget *parent )
: QgsAbstractProcessingParameterWidgetWrapper( parameter, type, parent )
{
}
QString QgsProcessingFieldMapWidgetWrapper::parameterType() const
{
return QgsProcessingParameterFieldMapping::typeName();
}
QgsAbstractProcessingParameterWidgetWrapper *QgsProcessingFieldMapWidgetWrapper::createWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type )
{
return new QgsProcessingFieldMapWidgetWrapper( parameter, type );
}
QWidget *QgsProcessingFieldMapWidgetWrapper::createWidget()
{
mPanel = new QgsProcessingFieldMapPanelWidget( nullptr );
mPanel->setToolTip( parameterDefinition()->toolTip() );
mPanel->registerExpressionContextGenerator( this );
connect( mPanel, &QgsProcessingFieldMapPanelWidget::changed, this, [ = ]
{
emit widgetValueHasChanged( this );
} );
return mPanel;
}
QgsProcessingAbstractParameterDefinitionWidget *QgsProcessingFieldMapWidgetWrapper::createParameterDefinitionWidget( QgsProcessingContext &context, const QgsProcessingParameterWidgetContext &widgetContext, const QgsProcessingParameterDefinition *definition, const QgsProcessingAlgorithm *algorithm )
{
return new QgsProcessingFieldMapParameterDefinitionWidget( context, widgetContext, definition, algorithm );
}
void QgsProcessingFieldMapWidgetWrapper::postInitialize( const QList<QgsAbstractProcessingParameterWidgetWrapper *> &wrappers )
{
QgsAbstractProcessingParameterWidgetWrapper::postInitialize( wrappers );
switch ( type() )
{
case QgsProcessingGui::Standard:
case QgsProcessingGui::Batch:
{
for ( const QgsAbstractProcessingParameterWidgetWrapper *wrapper : wrappers )
{
if ( wrapper->parameterDefinition()->name() == static_cast< const QgsProcessingParameterFieldMapping * >( parameterDefinition() )->parentLayerParameterName() )
{
setParentLayerWrapperValue( wrapper );
connect( wrapper, &QgsAbstractProcessingParameterWidgetWrapper::widgetValueHasChanged, this, [ = ]
{
setParentLayerWrapperValue( wrapper );
} );
break;
}
}
break;
}
case QgsProcessingGui::Modeler:
break;
}
}
int QgsProcessingFieldMapWidgetWrapper::stretch() const
{
return 1;
}
void QgsProcessingFieldMapWidgetWrapper::setParentLayerWrapperValue( const QgsAbstractProcessingParameterWidgetWrapper *parentWrapper )
{
// evaluate value to layer
QgsProcessingContext *context = nullptr;
std::unique_ptr< QgsProcessingContext > tmpContext;
if ( mProcessingContextGenerator )
context = mProcessingContextGenerator->processingContext();
if ( !context )
{
tmpContext = qgis::make_unique< QgsProcessingContext >();
context = tmpContext.get();
}
QgsVectorLayer *layer = QgsProcessingParameters::parameterAsVectorLayer( parentWrapper->parameterDefinition(), parentWrapper->parameterValue(), *context );
if ( !layer )
{
if ( mPanel )
mPanel->setLayer( nullptr );
return;
}
// need to grab ownership of layer if required - otherwise layer may be deleted when context
// goes out of scope
std::unique_ptr< QgsMapLayer > ownedLayer( context->takeResultLayer( layer->id() ) );
if ( ownedLayer && ownedLayer->type() == QgsMapLayerType::VectorLayer )
{
mParentLayer.reset( qobject_cast< QgsVectorLayer * >( ownedLayer.release() ) );
layer = mParentLayer.get();
}
else
{
// don't need ownership of this layer - it wasn't owned by context (so e.g. is owned by the project)
}
if ( mPanel )
mPanel->setLayer( layer );
}
void QgsProcessingFieldMapWidgetWrapper::setWidgetValue( const QVariant &value, QgsProcessingContext & )
{
if ( mPanel )
mPanel->setValue( value );
}
QVariant QgsProcessingFieldMapWidgetWrapper::widgetValue() const
{
return mPanel ? mPanel->value() : QVariant();
}
QStringList QgsProcessingFieldMapWidgetWrapper::compatibleParameterTypes() const
{
return QStringList()
<< QgsProcessingParameterFieldMapping::typeName();
}
QStringList QgsProcessingFieldMapWidgetWrapper::compatibleOutputTypes() const
{
return QStringList();
}
QString QgsProcessingFieldMapWidgetWrapper::modelerExpressionFormatString() const
{
return tr( "an array of map items, each containing a 'name', 'type' and 'expression' values (and optional 'length' and 'precision' values)." );
}
const QgsVectorLayer *QgsProcessingFieldMapWidgetWrapper::linkedVectorLayer() const
{
if ( mPanel && mPanel->layer() )
return mPanel->layer();
return QgsAbstractProcessingParameterWidgetWrapper::linkedVectorLayer();
}
/// @endcond