Skip to content

Commit

Permalink
[FEATURE][processing] New parameter types for layouts and layout items
Browse files Browse the repository at this point in the history
Allows processing algorithms which operate on a specific layout
and layout item selection
  • Loading branch information
nyalldawson committed Mar 11, 2019
1 parent f9fb408 commit 021d1ef
Show file tree
Hide file tree
Showing 7 changed files with 696 additions and 1 deletion.
1 change: 1 addition & 0 deletions python/plugins/processing/core/parameters.py
Expand Up @@ -79,6 +79,7 @@
PARAMETER_CRS = 'crs'
PARAMETER_MULTIPLE = 'multilayer'
PARAMETER_BAND = 'band'
PARAMETER_LAYOUTITEM = 'layoutitem'
PARAMETER_MAP_LAYER = 'Map Layer'
PARAMETER_RANGE = 'range'
PARAMETER_ENUM = 'enum'
Expand Down
Expand Up @@ -64,6 +64,8 @@
QgsProcessingParameterField,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterBand,
QgsProcessingParameterLayout,
QgsProcessingParameterLayoutItem,
QgsProcessingDestinationParameter,
QgsProcessingParameterFeatureSink,
QgsProcessingParameterFileDestination,
Expand Down Expand Up @@ -180,6 +182,20 @@ def setupUi(self):
self.parentCombo.setCurrentIndex(idx)
idx += 1
self.verticalLayout.addWidget(self.parentCombo)
elif self.paramType == parameters.PARAMETER_LAYOUTITEM or \
isinstance(self.param, QgsProcessingParameterLayoutItem):
self.verticalLayout.addWidget(QLabel(self.tr('Parent layout')))
self.parentCombo = QComboBox()
idx = 0
for param in list(self.alg.parameterComponents().values()):
definition = self.alg.parameterDefinition(param.parameterName())
if isinstance(definition, (QgsProcessingParameterLayout)):
self.parentCombo.addItem(definition.description(), definition.name())
if self.param is not None:
if self.param.parentLayoutParameterName() == definition.name():
self.parentCombo.setCurrentIndex(idx)
idx += 1
self.verticalLayout.addWidget(self.parentCombo)
elif (self.paramType in (
parameters.PARAMETER_VECTOR, parameters.PARAMETER_TABLE) or
isinstance(self.param, (QgsProcessingParameterFeatureSource, QgsProcessingParameterVectorLayer))):
Expand Down Expand Up @@ -405,6 +421,14 @@ def accept(self):
return
parent = self.parentCombo.currentData()
self.param = QgsProcessingParameterBand(name, description, None, parent)
elif (self.paramType == parameters.PARAMETER_LAYOUTITEM or
isinstance(self.param, QgsProcessingParameterLayoutItem)):
if self.parentCombo.currentIndex() < 0:
QMessageBox.warning(self, self.tr('Unable to define parameter'),
self.tr('Wrong or missing parameter values'))
return
parent = self.parentCombo.currentData()
self.param = QgsProcessingParameterLayoutItem(name, description, None, parent)
elif (self.paramType == parameters.PARAMETER_MAP_LAYER or
isinstance(self.param, QgsProcessingParameterMapLayer)):
self.param = QgsProcessingParameterMapLayer(
Expand Down
4 changes: 3 additions & 1 deletion src/gui/layout/qgslayoutitemcombobox.cpp
Expand Up @@ -27,12 +27,14 @@ QgsLayoutItemComboBox::QgsLayoutItemComboBox( QWidget *parent, QgsLayout *layout

void QgsLayoutItemComboBox::setCurrentLayout( QgsLayout *layout )
{
const bool prevAllowEmpty = mProxyModel && mProxyModel->allowEmptyItem();
mProxyModel = qgis::make_unique< QgsLayoutProxyModel >( layout, this );
connect( mProxyModel.get(), &QAbstractItemModel::rowsInserted, this, &QgsLayoutItemComboBox::rowsChanged );
connect( mProxyModel.get(), &QAbstractItemModel::rowsRemoved, this, &QgsLayoutItemComboBox::rowsChanged );
setModel( mProxyModel.get() );
setModelColumn( QgsLayoutModel::ItemId );
mProxyModel->sort( 0, Qt::AscendingOrder );
mProxyModel->setAllowEmptyItem( prevAllowEmpty );
}

QgsLayout *QgsLayoutItemComboBox::currentLayout()
Expand All @@ -55,7 +57,7 @@ void QgsLayoutItemComboBox::setItem( const QgsLayoutItem *item )
return;
}
}
setCurrentIndex( mProxyModel->allowEmptyItem() ? 0 : -1 );
setCurrentIndex( mProxyModel->allowEmptyItem() ? mProxyModel->rowCount() - 1 : -1 );
}

QgsLayoutItem *QgsLayoutItemComboBox::currentItem() const
Expand Down
2 changes: 2 additions & 0 deletions src/gui/processing/qgsprocessingguiregistry.cpp
Expand Up @@ -38,6 +38,8 @@ QgsProcessingGuiRegistry::QgsProcessingGuiRegistry()
addParameterWidgetFactory( new QgsProcessingFileWidgetWrapper() );
addParameterWidgetFactory( new QgsProcessingExpressionWidgetWrapper() );
addParameterWidgetFactory( new QgsProcessingEnumWidgetWrapper() );
addParameterWidgetFactory( new QgsProcessingLayoutWidgetWrapper() );
addParameterWidgetFactory( new QgsProcessingLayoutItemWidgetWrapper() );
}

QgsProcessingGuiRegistry::~QgsProcessingGuiRegistry()
Expand Down
286 changes: 286 additions & 0 deletions src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp
Expand Up @@ -30,6 +30,11 @@
#include "qgsexpressionlineedit.h"
#include "qgsfieldexpressionwidget.h"
#include "qgsprocessingmultipleselectiondialog.h"
#include "qgslayoutmanager.h"
#include "qgsproject.h"
#include "qgslayoutcombobox.h"
#include "qgslayoutitemcombobox.h"
#include "qgsprintlayout.h"
#include <QLabel>
#include <QHBoxLayout>
#include <QCheckBox>
Expand Down Expand Up @@ -1735,5 +1740,286 @@ QgsAbstractProcessingParameterWidgetWrapper *QgsProcessingEnumWidgetWrapper::cre



//
// QgsProcessingLayoutWidgetWrapper
//

QgsProcessingLayoutWidgetWrapper::QgsProcessingLayoutWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type, QWidget *parent )
: QgsAbstractProcessingParameterWidgetWrapper( parameter, type, parent )
{

}

QWidget *QgsProcessingLayoutWidgetWrapper::createWidget()
{
const QgsProcessingParameterLayout *layoutParam = dynamic_cast< const QgsProcessingParameterLayout *>( parameterDefinition() );
switch ( type() )
{
case QgsProcessingGui::Standard:
case QgsProcessingGui::Batch:
{
// combobox only for use outside modeler!
mComboBox = new QgsLayoutComboBox( nullptr, widgetContext().project() ? widgetContext().project()->layoutManager() : nullptr );
if ( layoutParam->flags() & QgsProcessingParameterDefinition::FlagOptional )
mComboBox->setAllowEmptyLayout( true );
mComboBox->setFilters( QgsLayoutManagerProxyModel::FilterPrintLayouts );

mComboBox->setToolTip( parameterDefinition()->toolTip() );
connect( mComboBox, &QgsLayoutComboBox::layoutChanged, this, [ = ]( QgsMasterLayoutInterface * )
{
emit widgetValueHasChanged( this );
} );
return mComboBox;
}

case QgsProcessingGui::Modeler:
{
mLineEdit = new QLineEdit();
mLineEdit->setToolTip( tr( "Name of an existing print layout" ) );
connect( mLineEdit, &QLineEdit::textChanged, this, [ = ]( const QString & )
{
emit widgetValueHasChanged( this );
} );
return mLineEdit;
}
}
return nullptr;
}

void QgsProcessingLayoutWidgetWrapper::setWidgetValue( const QVariant &value, QgsProcessingContext &context )
{
if ( mComboBox )
{
if ( !value.isValid() )
mComboBox->setCurrentLayout( nullptr );
else
{
if ( QgsPrintLayout *l = QgsProcessingParameters::parameterAsLayout( parameterDefinition(), value, context ) )
mComboBox->setCurrentLayout( l );
else
mComboBox->setCurrentLayout( nullptr );
}
}
else if ( mLineEdit )
{
const QString v = QgsProcessingParameters::parameterAsString( parameterDefinition(), value, context );
mLineEdit->setText( v );
}
}

QVariant QgsProcessingLayoutWidgetWrapper::widgetValue() const
{
if ( mComboBox )
{
const QgsMasterLayoutInterface *l = mComboBox->currentLayout();
return l ? l->name() : QVariant();
}
else if ( mLineEdit )
return mLineEdit->text().isEmpty() ? QVariant() : mLineEdit->text();
else
return QVariant();
}

QStringList QgsProcessingLayoutWidgetWrapper::compatibleParameterTypes() const
{
return QStringList()
<< QgsProcessingParameterLayout::typeName()
<< QgsProcessingParameterString::typeName();
}

QStringList QgsProcessingLayoutWidgetWrapper::compatibleOutputTypes() const
{
return QStringList()
<< QgsProcessingOutputString::typeName();
}

QList<int> QgsProcessingLayoutWidgetWrapper::compatibleDataTypes() const
{
return QList<int>();
}

QString QgsProcessingLayoutWidgetWrapper::modelerExpressionFormatString() const
{
return tr( "string representing the name of an existing print layout" );
}

QString QgsProcessingLayoutWidgetWrapper::parameterType() const
{
return QgsProcessingParameterLayout::typeName();
}

QgsAbstractProcessingParameterWidgetWrapper *QgsProcessingLayoutWidgetWrapper::createWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type )
{
return new QgsProcessingLayoutWidgetWrapper( parameter, type );
}




//
// QgsProcessingLayoutItemWidgetWrapper
//

QgsProcessingLayoutItemWidgetWrapper::QgsProcessingLayoutItemWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type, QWidget *parent )
: QgsAbstractProcessingParameterWidgetWrapper( parameter, type, parent )
{

}

QWidget *QgsProcessingLayoutItemWidgetWrapper::createWidget()
{
const QgsProcessingParameterLayoutItem *layoutParam = dynamic_cast< const QgsProcessingParameterLayoutItem *>( parameterDefinition() );
switch ( type() )
{
case QgsProcessingGui::Standard:
case QgsProcessingGui::Batch:
{
// combobox only for use outside modeler!
mComboBox = new QgsLayoutItemComboBox( nullptr, nullptr );
if ( layoutParam->flags() & QgsProcessingParameterDefinition::FlagOptional )
mComboBox->setAllowEmptyItem( true );
if ( layoutParam->itemType() >= 0 )
mComboBox->setItemType( static_cast< QgsLayoutItemRegistry::ItemType >( layoutParam->itemType() ) );

mComboBox->setToolTip( parameterDefinition()->toolTip() );
connect( mComboBox, &QgsLayoutItemComboBox::itemChanged, this, [ = ]( QgsLayoutItem * )
{
emit widgetValueHasChanged( this );
} );
return mComboBox;
}

case QgsProcessingGui::Modeler:
{
mLineEdit = new QLineEdit();
mLineEdit->setToolTip( tr( "UUID or ID of an existing print layout item" ) );
connect( mLineEdit, &QLineEdit::textChanged, this, [ = ]( const QString & )
{
emit widgetValueHasChanged( this );
} );
return mLineEdit;
}
}
return nullptr;
}

void QgsProcessingLayoutItemWidgetWrapper::postInitialize( const QList<QgsAbstractProcessingParameterWidgetWrapper *> &wrappers )
{
switch ( type() )
{
case QgsProcessingGui::Standard:
case QgsProcessingGui::Batch:
{
for ( const QgsAbstractProcessingParameterWidgetWrapper *wrapper : wrappers )
{
if ( wrapper->parameterDefinition()->name() == static_cast< const QgsProcessingParameterLayoutItem * >( parameterDefinition() )->parentLayoutParameterName() )
{
setLayoutParameterValue( wrapper->parameterValue() );
connect( wrapper, &QgsAbstractProcessingParameterWidgetWrapper::widgetValueHasChanged, this, [ = ]
{
setLayoutParameterValue( wrapper->parameterValue() );
} );
break;
}
}
break;
}

case QgsProcessingGui::Modeler:
break;
}
}

void QgsProcessingLayoutItemWidgetWrapper::setLayoutParameterValue( const QVariant &value )
{
QgsPrintLayout *layout = nullptr;

// evaluate value to layout
QgsProcessingContext *context = nullptr;
std::unique_ptr< QgsProcessingContext > tmpContext;
if ( mProcessingContextGenerator )
context = mProcessingContextGenerator->processingContext();

if ( !context )
{
tmpContext = qgis::make_unique< QgsProcessingContext >();
context = tmpContext.get();
}

layout = QgsProcessingParameters::parameterAsLayout( parameterDefinition(), value, *context );
setLayout( layout );
}

void QgsProcessingLayoutItemWidgetWrapper::setLayout( QgsPrintLayout *layout )
{
if ( mComboBox )
mComboBox->setCurrentLayout( layout );
}

void QgsProcessingLayoutItemWidgetWrapper::setWidgetValue( const QVariant &value, QgsProcessingContext &context )
{
if ( mComboBox )
{
if ( !value.isValid() )
mComboBox->setItem( nullptr );
else
{
QgsLayoutItem *item = QgsProcessingParameters::parameterAsLayoutItem( parameterDefinition(), value, context, qobject_cast< QgsPrintLayout * >( mComboBox->currentLayout() ) );
mComboBox->setItem( item );
}
}
else if ( mLineEdit )
{
const QString v = QgsProcessingParameters::parameterAsString( parameterDefinition(), value, context );
mLineEdit->setText( v );
}
}

QVariant QgsProcessingLayoutItemWidgetWrapper::widgetValue() const
{
if ( mComboBox )
{
const QgsLayoutItem *i = mComboBox->currentItem();
return i ? i->uuid() : QVariant();
}
else if ( mLineEdit )
return mLineEdit->text().isEmpty() ? QVariant() : mLineEdit->text();
else
return QVariant();
}

QStringList QgsProcessingLayoutItemWidgetWrapper::compatibleParameterTypes() const
{
return QStringList()
<< QgsProcessingParameterLayoutItem::typeName()
<< QgsProcessingParameterString::typeName();
}

QStringList QgsProcessingLayoutItemWidgetWrapper::compatibleOutputTypes() const
{
return QStringList()
<< QgsProcessingOutputString::typeName();
}

QList<int> QgsProcessingLayoutItemWidgetWrapper::compatibleDataTypes() const
{
return QList<int>();
}

QString QgsProcessingLayoutItemWidgetWrapper::modelerExpressionFormatString() const
{
return tr( "string representing the UUID or ID of an existing print layout item" );
}

QString QgsProcessingLayoutItemWidgetWrapper::parameterType() const
{
return QgsProcessingParameterLayoutItem::typeName();
}

QgsAbstractProcessingParameterWidgetWrapper *QgsProcessingLayoutItemWidgetWrapper::createWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type )
{
return new QgsProcessingLayoutItemWidgetWrapper( parameter, type );
}

///@endcond PRIVATE

1 comment on commit 021d1ef

@havatv
Copy link
Contributor

@havatv havatv commented on 021d1ef Mar 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great addition!
It would be nice to have an ALG decorator for it. Could it be added to python/processing/algfactory.py (and python/plugins/processing/core/parameters.py)?

Please sign in to comment.