Skip to content

Commit cccf974

Browse files
committedMar 8, 2019
[processing] Port enum widget wrapper to new API
Fixes: - enum parameters set to "allow multiple" only allow a single value selection when used in modeler - optional enum parameters cannot be set to no value when used outside of modeler Fixes #20406
1 parent 160ca69 commit cccf974

14 files changed

+1371
-4
lines changed
 
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/************************************************************************
2+
* This file has been generated automatically from *
3+
* *
4+
* src/gui/processing/qgsprocessingmultipleselectiondialog.h *
5+
* *
6+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
7+
************************************************************************/
8+
9+
10+
11+
12+
13+
14+
class QgsProcessingMultipleSelectionDialog : QDialog
15+
{
16+
%Docstring
17+
Dialog for configuration of a matrix (fixed table) parameter.
18+
19+
.. note::
20+
21+
Not stable API
22+
23+
.. versionadded:: 3.6
24+
%End
25+
26+
%TypeHeaderCode
27+
#include "qgsprocessingmultipleselectiondialog.h"
28+
%End
29+
public:
30+
31+
QgsProcessingMultipleSelectionDialog( const QVariantList &availableOptions = QVariantList(),
32+
const QVariantList &selectedOptions = QVariantList(),
33+
QWidget *parent /TransferThis/ = 0, Qt::WindowFlags flags = 0 );
34+
%Docstring
35+
Constructor for QgsProcessingMultipleSelectionDialog.
36+
37+
The ``availableOptions`` list specifies the list of standard known options for the parameter,
38+
whilst the ``selectedOptions`` list specifies which options should be initially selected.
39+
40+
The ``selectedOptions`` list may contain extra options which are not present in ``availableOptions``,
41+
in which case they will be also added as existing options within the dialog.
42+
%End
43+
44+
45+
void setValueFormatter( SIP_PYCALLABLE );
46+
%Docstring
47+
Sets a callback function to use when encountering an invalid geometry and
48+
%End
49+
%MethodCode
50+
51+
Py_BEGIN_ALLOW_THREADS
52+
53+
sipCpp->setValueFormatter( [a0]( const QVariant &v )->QString
54+
{
55+
QString res;
56+
SIP_BLOCK_THREADS
57+
PyObject *s = sipCallMethod( NULL, a0, "D", &v, sipType_QVariant, NULL );
58+
int state;
59+
int sipIsError = 0;
60+
QString *t1 = reinterpret_cast<QString *>( sipConvertToType( s, sipType_QString, 0, SIP_NOT_NONE, &state, &sipIsError ) );
61+
if ( sipIsError == 0 )
62+
{
63+
res = QString( *t1 );
64+
}
65+
sipReleaseType( t1, sipType_QString, state );
66+
SIP_UNBLOCK_THREADS
67+
return res;
68+
} );
69+
70+
Py_END_ALLOW_THREADS
71+
%End
72+
73+
74+
QVariantList selectedOptions() const;
75+
%Docstring
76+
Returns the ordered list of selected options.
77+
%End
78+
79+
};
80+
81+
82+
/************************************************************************
83+
* This file has been generated automatically from *
84+
* *
85+
* src/gui/processing/qgsprocessingmultipleselectiondialog.h *
86+
* *
87+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
88+
************************************************************************/

‎python/gui/gui_auto.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@
327327
%Include auto_generated/processing/qgsprocessingalgorithmconfigurationwidget.sip
328328
%Include auto_generated/processing/qgsprocessingalgorithmdialogbase.sip
329329
%Include auto_generated/processing/qgsprocessingmodelerparameterwidget.sip
330+
%Include auto_generated/processing/qgsprocessingmultipleselectiondialog.sip
330331
%Include auto_generated/processing/qgsprocessingrecentalgorithmlog.sip
331332
%Include auto_generated/processing/qgsprocessingtoolboxmodel.sip
332333
%Include auto_generated/processing/qgsprocessingtoolboxtreeview.sip

‎python/plugins/processing/algs/qgis/CheckValidity.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ def initAlgorithm(self, config=None):
9494
self.tr('Method'), self.methods, defaultValue=2))
9595
self.parameterDefinition(self.METHOD).setMetadata({
9696
'widget_wrapper': {
97-
'class': 'processing.gui.wrappers.EnumWidgetWrapper',
9897
'useCheckBoxes': True,
9998
'columns': 3}})
10099

‎python/plugins/processing/algs/qgis/SpatialJoin.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@ def initAlgorithm(self, config=None):
107107
allowMultiple=True, defaultValue=[0])
108108
predicate.setMetadata({
109109
'widget_wrapper': {
110-
'class': 'processing.gui.wrappers.EnumWidgetWrapper',
111110
'useCheckBoxes': True,
112111
'columns': 2}})
113112
self.addParameter(predicate)

‎python/plugins/processing/algs/qgis/SpatialJoinSummary.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@ def initAlgorithm(self, config=None):
124124
allowMultiple=True, defaultValue=[0])
125125
predicate.setMetadata({
126126
'widget_wrapper': {
127-
'class': 'processing.gui.wrappers.EnumWidgetWrapper',
128127
'useCheckBoxes': True,
129128
'columns': 2}})
130129
self.addParameter(predicate)

‎src/analysis/processing/qgsalgorithmextractbylocation.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ void QgsLocationBasedAlgorithm::addPredicateParameter()
2929

3030
QVariantMap predicateMetadata;
3131
QVariantMap widgetMetadata;
32-
widgetMetadata.insert( QStringLiteral( "class" ), QStringLiteral( "processing.gui.wrappers.EnumWidgetWrapper" ) );
3332
widgetMetadata.insert( QStringLiteral( "useCheckBoxes" ), true );
3433
widgetMetadata.insert( QStringLiteral( "columns" ), 2 );
3534
predicateMetadata.insert( QStringLiteral( "widget_wrapper" ), widgetMetadata );

‎src/gui/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ SET(QGIS_GUI_SRCS
201201
processing/qgsprocessingguiregistry.cpp
202202
processing/qgsprocessingmatrixparameterdialog.cpp
203203
processing/qgsprocessingmodelerparameterwidget.cpp
204+
processing/qgsprocessingmultipleselectiondialog.cpp
204205
processing/qgsprocessingrecentalgorithmlog.cpp
205206
processing/qgsprocessingtoolboxmodel.cpp
206207
processing/qgsprocessingtoolboxtreeview.cpp
@@ -744,6 +745,7 @@ SET(QGIS_GUI_MOC_HDRS
744745
processing/qgsprocessingconfigurationwidgets.h
745746
processing/qgsprocessingmatrixparameterdialog.h
746747
processing/qgsprocessingmodelerparameterwidget.h
748+
processing/qgsprocessingmultipleselectiondialog.h
747749
processing/qgsprocessingrecentalgorithmlog.h
748750
processing/qgsprocessingtoolboxmodel.h
749751
processing/qgsprocessingtoolboxtreeview.h

‎src/gui/processing/qgsprocessingguiregistry.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ QgsProcessingGuiRegistry::QgsProcessingGuiRegistry()
3737
addParameterWidgetFactory( new QgsProcessingMatrixWidgetWrapper() );
3838
addParameterWidgetFactory( new QgsProcessingFileWidgetWrapper() );
3939
addParameterWidgetFactory( new QgsProcessingExpressionWidgetWrapper() );
40+
addParameterWidgetFactory( new QgsProcessingEnumWidgetWrapper() );
4041
}
4142

4243
QgsProcessingGuiRegistry::~QgsProcessingGuiRegistry()
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/***************************************************************************
2+
qgsprocessingmultipleselectiondialog.cpp
3+
------------------------------------
4+
Date : February 2019
5+
Copyright : (C) 2019 Nyall Dawson
6+
Email : nyall dot dawson at gmail dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#include "qgsprocessingmultipleselectiondialog.h"
17+
#include "qgsgui.h"
18+
#include <QStandardItemModel>
19+
#include <QStandardItem>
20+
#include <QPushButton>
21+
#include <QLineEdit>
22+
#include <QToolButton>
23+
24+
///@cond NOT_STABLE
25+
26+
QgsProcessingMultipleSelectionDialog::QgsProcessingMultipleSelectionDialog( const QVariantList &availableOptions,
27+
const QVariantList &selectedOptions,
28+
QWidget *parent, Qt::WindowFlags flags )
29+
: QDialog( parent, flags )
30+
, mValueFormatter( []( const QVariant & v )->QString { return v.toString(); } )
31+
{
32+
setupUi( this );
33+
34+
QgsGui::enableAutoGeometryRestore( this );
35+
36+
mSelectionList->setSelectionBehavior( QAbstractItemView::SelectRows );
37+
mSelectionList->setSelectionMode( QAbstractItemView::ExtendedSelection );
38+
mSelectionList->setDragDropMode( QAbstractItemView::InternalMove );
39+
40+
mButtonSelectAll = new QPushButton( tr( "Select All" ) );
41+
mButtonBox->addButton( mButtonSelectAll, QDialogButtonBox::ActionRole );
42+
43+
mButtonClearSelection = new QPushButton( tr( "Clear Selection" ) );
44+
mButtonBox->addButton( mButtonClearSelection, QDialogButtonBox::ActionRole );
45+
46+
mButtonToggleSelection = new QPushButton( tr( "Toggle Selection" ) );
47+
mButtonBox->addButton( mButtonToggleSelection, QDialogButtonBox::ActionRole );
48+
49+
connect( mButtonSelectAll, &QPushButton::clicked, this, [ = ] { selectAll( true ); } );
50+
connect( mButtonClearSelection, &QPushButton::clicked, this, [ = ] { selectAll( false ); } );
51+
connect( mButtonToggleSelection, &QPushButton::clicked, this, &QgsProcessingMultipleSelectionDialog::toggleSelection );
52+
53+
populateList( availableOptions, selectedOptions );
54+
}
55+
56+
void QgsProcessingMultipleSelectionDialog::setValueFormatter( const std::function<QString( const QVariant & )> &formatter )
57+
{
58+
mValueFormatter = formatter;
59+
// update item text using new formatter
60+
for ( int i = 0; i < mModel->rowCount(); ++i )
61+
{
62+
mModel->item( i )->setText( mValueFormatter( mModel->item( i )->data( Qt::UserRole ) ) );
63+
}
64+
}
65+
66+
QVariantList QgsProcessingMultipleSelectionDialog::selectedOptions() const
67+
{
68+
QVariantList options;
69+
options.reserve( mModel->rowCount() );
70+
for ( int i = 0; i < mModel->rowCount(); ++i )
71+
{
72+
if ( mModel->item( i )->checkState() == Qt::Checked )
73+
options << mModel->item( i )->data( Qt::UserRole );
74+
}
75+
return options;
76+
}
77+
78+
void QgsProcessingMultipleSelectionDialog::selectAll( const bool checked )
79+
{
80+
const QList<QStandardItem *> items = currentItems();
81+
for ( QStandardItem *item : items )
82+
{
83+
item->setCheckState( checked ? Qt::Checked : Qt::Unchecked );
84+
}
85+
}
86+
87+
void QgsProcessingMultipleSelectionDialog::toggleSelection()
88+
{
89+
const QList<QStandardItem *> items = currentItems();
90+
for ( QStandardItem *item : items )
91+
{
92+
item->setCheckState( item->checkState() == Qt::Unchecked ? Qt::Checked : Qt::Unchecked );
93+
}
94+
}
95+
96+
QList<QStandardItem *> QgsProcessingMultipleSelectionDialog::currentItems()
97+
{
98+
QList<QStandardItem *> items;
99+
const QModelIndexList selection = mSelectionList->selectionModel()->selectedIndexes();
100+
if ( selection.size() > 1 )
101+
{
102+
items.reserve( selection.size() );
103+
for ( const QModelIndex &index : selection )
104+
{
105+
items << mModel->itemFromIndex( index );
106+
}
107+
}
108+
else
109+
{
110+
items.reserve( mModel->rowCount() );
111+
for ( int i = 0; i < mModel->rowCount(); ++i )
112+
{
113+
items << mModel->item( i );
114+
}
115+
}
116+
return items;
117+
}
118+
119+
void QgsProcessingMultipleSelectionDialog::populateList( const QVariantList &availableOptions, const QVariantList &selectedOptions )
120+
{
121+
mModel = new QStandardItemModel( this );
122+
123+
QVariantList remainingOptions = availableOptions;
124+
125+
// we add selected options first, keeping the existing order of options
126+
for ( const QVariant &option : selectedOptions )
127+
{
128+
// if isinstance(t, QgsProcessingModelChildParameterSource):
129+
// item = QStandardItem(t.staticValue())
130+
// else:
131+
std::unique_ptr< QStandardItem > item = qgis::make_unique< QStandardItem >( mValueFormatter( option ) );
132+
item->setData( option, Qt::UserRole );
133+
item->setCheckState( Qt::Checked );
134+
item->setCheckable( true );
135+
item->setDropEnabled( false );
136+
mModel->appendRow( item.release() );
137+
remainingOptions.removeAll( option );
138+
}
139+
140+
for ( const QVariant &option : qgis::as_const( remainingOptions ) )
141+
{
142+
std::unique_ptr< QStandardItem > item = qgis::make_unique< QStandardItem >( mValueFormatter( option ) );
143+
item->setData( option, Qt::UserRole );
144+
item->setCheckState( Qt::Unchecked );
145+
item->setCheckable( true );
146+
item->setDropEnabled( false );
147+
mModel->appendRow( item.release() );
148+
}
149+
150+
mSelectionList->setModel( mModel );
151+
}
152+
153+
///@endcond
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/***************************************************************************
2+
qgsprocessingmultipleselectiondialog.h
3+
----------------------------------
4+
Date : February 2019
5+
Copyright : (C) 2019 Nyall Dawson
6+
Email : nyall dot dawson at gmail dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#ifndef QGSPROCESSINGMULTIPLESELECTIONDIALOG_H
17+
#define QGSPROCESSINGMULTIPLESELECTIONDIALOG_H
18+
19+
#include "qgis.h"
20+
#include "qgis_gui.h"
21+
#include "ui_qgsprocessingmultipleselectiondialogbase.h"
22+
#include "qgsprocessingparameters.h"
23+
24+
25+
class QStandardItemModel;
26+
class QToolButton;
27+
class QStandardItem;
28+
29+
///@cond NOT_STABLE
30+
31+
/**
32+
* \ingroup gui
33+
* \brief Dialog for configuration of a matrix (fixed table) parameter.
34+
* \note Not stable API
35+
* \since QGIS 3.6
36+
*/
37+
class GUI_EXPORT QgsProcessingMultipleSelectionDialog : public QDialog, private Ui::QgsProcessingMultipleSelectionDialogBase
38+
{
39+
Q_OBJECT
40+
41+
public:
42+
43+
/**
44+
* Constructor for QgsProcessingMultipleSelectionDialog.
45+
*
46+
* The \a availableOptions list specifies the list of standard known options for the parameter,
47+
* whilst the \a selectedOptions list specifies which options should be initially selected.
48+
*
49+
* The \a selectedOptions list may contain extra options which are not present in \a availableOptions,
50+
* in which case they will be also added as existing options within the dialog.
51+
*/
52+
QgsProcessingMultipleSelectionDialog( const QVariantList &availableOptions = QVariantList(),
53+
const QVariantList &selectedOptions = QVariantList(),
54+
QWidget *parent SIP_TRANSFERTHIS = nullptr, Qt::WindowFlags flags = nullptr );
55+
56+
57+
/**
58+
* Sets a callback function to use when encountering an invalid geometry and
59+
*/
60+
#ifndef SIP_RUN
61+
void setValueFormatter( const std::function< QString( const QVariant & )> &formatter );
62+
#else
63+
void setValueFormatter( SIP_PYCALLABLE );
64+
% MethodCode
65+
66+
Py_BEGIN_ALLOW_THREADS
67+
68+
sipCpp->setValueFormatter( [a0]( const QVariant &v )->QString
69+
{
70+
QString res;
71+
SIP_BLOCK_THREADS
72+
PyObject *s = sipCallMethod( NULL, a0, "D", &v, sipType_QVariant, NULL );
73+
int state;
74+
int sipIsError = 0;
75+
QString *t1 = reinterpret_cast<QString *>( sipConvertToType( s, sipType_QString, 0, SIP_NOT_NONE, &state, &sipIsError ) );
76+
if ( sipIsError == 0 )
77+
{
78+
res = QString( *t1 );
79+
}
80+
sipReleaseType( t1, sipType_QString, state );
81+
SIP_UNBLOCK_THREADS
82+
return res;
83+
} );
84+
85+
Py_END_ALLOW_THREADS
86+
% End
87+
#endif
88+
89+
90+
/**
91+
* Returns the ordered list of selected options.
92+
*/
93+
QVariantList selectedOptions() const;
94+
95+
private slots:
96+
97+
void selectAll( bool checked );
98+
void toggleSelection();
99+
100+
private:
101+
std::function< QString( const QVariant & )> mValueFormatter;
102+
103+
QPushButton *mButtonSelectAll = nullptr;
104+
QPushButton *mButtonClearSelection = nullptr;
105+
QPushButton *mButtonToggleSelection = nullptr;
106+
QStandardItemModel *mModel = nullptr;
107+
108+
QList< QStandardItem * > currentItems();
109+
110+
void populateList( const QVariantList &availableOptions, const QVariantList &selectedOptions );
111+
112+
friend class TestProcessingGui;
113+
};
114+
115+
///@endcond
116+
117+
#endif // QGSPROCESSINGMULTIPLESELECTIONDIALOG_H

‎src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp

Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,16 @@
2929
#include "qgssettings.h"
3030
#include "qgsexpressionlineedit.h"
3131
#include "qgsfieldexpressionwidget.h"
32+
#include "qgsprocessingmultipleselectiondialog.h"
3233
#include <QLabel>
3334
#include <QHBoxLayout>
3435
#include <QCheckBox>
3536
#include <QComboBox>
3637
#include <QLineEdit>
3738
#include <QPlainTextEdit>
39+
#include <QRadioButton>
40+
#include <QButtonGroup>
41+
#include <QMenu>
3842

3943
///@cond PRIVATE
4044

@@ -1397,5 +1401,339 @@ QgsAbstractProcessingParameterWidgetWrapper *QgsProcessingExpressionWidgetWrappe
13971401
}
13981402

13991403

1404+
1405+
//
1406+
// QgsProcessingEnumPanelWidget
1407+
//
1408+
1409+
QgsProcessingEnumPanelWidget::QgsProcessingEnumPanelWidget( QWidget *parent, const QgsProcessingParameterEnum *param )
1410+
: QWidget( parent )
1411+
, mParam( param )
1412+
{
1413+
QHBoxLayout *hl = new QHBoxLayout();
1414+
hl->setMargin( 0 );
1415+
hl->setContentsMargins( 0, 0, 0, 0 );
1416+
1417+
mLineEdit = new QLineEdit();
1418+
mLineEdit->setEnabled( false );
1419+
hl->addWidget( mLineEdit, 1 );
1420+
1421+
mToolButton = new QToolButton();
1422+
mToolButton->setText( tr( "" ) );
1423+
hl->addWidget( mToolButton );
1424+
1425+
setLayout( hl );
1426+
1427+
if ( mParam )
1428+
{
1429+
mLineEdit->setText( tr( "%1 options selected" ).arg( 0 ) );
1430+
}
1431+
1432+
connect( mToolButton, &QToolButton::clicked, this, &QgsProcessingEnumPanelWidget::showDialog );
1433+
}
1434+
1435+
void QgsProcessingEnumPanelWidget::setValue( const QVariant &value )
1436+
{
1437+
if ( value.isValid() )
1438+
mValue = value.type() == QVariant::List ? value.toList() : QVariantList() << value;
1439+
else
1440+
mValue.clear();
1441+
1442+
updateSummaryText();
1443+
emit changed();
1444+
}
1445+
1446+
void QgsProcessingEnumPanelWidget::showDialog()
1447+
{
1448+
QVariantList availableOptions;
1449+
if ( mParam )
1450+
{
1451+
availableOptions.reserve( mParam->options().size() );
1452+
for ( int i = 0; i < mParam->options().count(); ++i )
1453+
availableOptions << i;
1454+
}
1455+
1456+
QgsProcessingMultipleSelectionDialog dlg( availableOptions, mValue, this, nullptr );
1457+
const QStringList options = mParam ? mParam->options() : QStringList();
1458+
dlg.setValueFormatter( [options]( const QVariant & v ) -> QString
1459+
{
1460+
const int i = v.toInt();
1461+
return options.size() > i ? options.at( i ) : QString();
1462+
} );
1463+
if ( dlg.exec() )
1464+
{
1465+
setValue( dlg.selectedOptions() );
1466+
}
1467+
}
1468+
1469+
void QgsProcessingEnumPanelWidget::updateSummaryText()
1470+
{
1471+
if ( mParam )
1472+
mLineEdit->setText( tr( "%1 options selected" ).arg( mValue.count() ) );
1473+
}
1474+
1475+
1476+
//
1477+
// QgsProcessingEnumCheckboxPanelWidget
1478+
//
1479+
QgsProcessingEnumCheckboxPanelWidget::QgsProcessingEnumCheckboxPanelWidget( QWidget *parent, const QgsProcessingParameterEnum *param, int columns )
1480+
: QWidget( parent )
1481+
, mParam( param )
1482+
, mButtonGroup( new QButtonGroup( this ) )
1483+
, mColumns( columns )
1484+
{
1485+
mButtonGroup->setExclusive( !mParam->allowMultiple() );
1486+
1487+
QGridLayout *l = new QGridLayout();
1488+
l->setContentsMargins( 0, 0, 0, 0 );
1489+
l->setMargin( 0 );
1490+
1491+
int rows = static_cast< int >( std::ceil( mParam->options().count() / static_cast< double >( mColumns ) ) );
1492+
for ( int i = 0; i < mParam->options().count(); ++i )
1493+
{
1494+
QAbstractButton *button = nullptr;
1495+
if ( mParam->allowMultiple() )
1496+
button = new QCheckBox( mParam->options().at( i ) );
1497+
else
1498+
button = new QRadioButton( mParam->options().at( i ) );
1499+
1500+
connect( button, &QAbstractButton::toggled, this, [ = ]
1501+
{
1502+
if ( !mBlockChangedSignal )
1503+
emit changed();
1504+
} );
1505+
1506+
mButtons.insert( i, button );
1507+
mButtonGroup->addButton( button, i );
1508+
l->addWidget( button, i % rows, i / rows );
1509+
}
1510+
l->addItem( new QSpacerItem( 0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum ), 0, mColumns );
1511+
setLayout( l );
1512+
1513+
if ( mParam->allowMultiple() )
1514+
{
1515+
setContextMenuPolicy( Qt::CustomContextMenu );
1516+
connect( this, &QWidget::customContextMenuRequested, this, &QgsProcessingEnumCheckboxPanelWidget::showPopupMenu );
1517+
}
1518+
}
1519+
1520+
QVariant QgsProcessingEnumCheckboxPanelWidget::value() const
1521+
{
1522+
if ( mParam->allowMultiple() )
1523+
{
1524+
QVariantList value;
1525+
for ( auto it = mButtons.constBegin(); it != mButtons.constEnd(); ++it )
1526+
{
1527+
if ( it.value()->isChecked() )
1528+
value.append( it.key() );
1529+
}
1530+
return value;
1531+
}
1532+
else
1533+
{
1534+
return mButtonGroup->checkedId() >= 0 ? mButtonGroup->checkedId() : QVariant();
1535+
}
1536+
}
1537+
1538+
void QgsProcessingEnumCheckboxPanelWidget::setValue( const QVariant &value )
1539+
{
1540+
mBlockChangedSignal = true;
1541+
if ( mParam->allowMultiple() )
1542+
{
1543+
QVariantList selected;
1544+
if ( value.isValid() )
1545+
selected = value.type() == QVariant::List ? value.toList() : QVariantList() << value;
1546+
for ( auto it = mButtons.constBegin(); it != mButtons.constEnd(); ++it )
1547+
{
1548+
it.value()->setChecked( selected.contains( it.key() ) );
1549+
}
1550+
}
1551+
else
1552+
{
1553+
QVariant v = value;
1554+
if ( v.type() == QVariant::List )
1555+
v = v.toList().value( 0 );
1556+
if ( mButtons.contains( v ) )
1557+
mButtons.value( v )->setChecked( true );
1558+
}
1559+
mBlockChangedSignal = false;
1560+
emit changed();
1561+
}
1562+
1563+
void QgsProcessingEnumCheckboxPanelWidget::showPopupMenu()
1564+
{
1565+
QMenu popupMenu;
1566+
QAction *selectAllAction = new QAction( tr( "Select All" ), &popupMenu );
1567+
connect( selectAllAction, &QAction::triggered, this, &QgsProcessingEnumCheckboxPanelWidget::selectAll );
1568+
QAction *clearAllAction = new QAction( tr( "Clear Selection" ), &popupMenu );
1569+
connect( clearAllAction, &QAction::triggered, this, &QgsProcessingEnumCheckboxPanelWidget::deselectAll );
1570+
popupMenu.addAction( selectAllAction );
1571+
popupMenu.addAction( clearAllAction );
1572+
popupMenu.exec( QCursor::pos() );
1573+
}
1574+
1575+
void QgsProcessingEnumCheckboxPanelWidget::selectAll()
1576+
{
1577+
mBlockChangedSignal = true;
1578+
for ( auto it = mButtons.constBegin(); it != mButtons.constEnd(); ++it )
1579+
it.value()->setChecked( true );
1580+
mBlockChangedSignal = false;
1581+
emit changed();
1582+
}
1583+
1584+
void QgsProcessingEnumCheckboxPanelWidget::deselectAll()
1585+
{
1586+
mBlockChangedSignal = true;
1587+
for ( auto it = mButtons.constBegin(); it != mButtons.constEnd(); ++it )
1588+
it.value()->setChecked( false );
1589+
mBlockChangedSignal = false;
1590+
emit changed();
1591+
}
1592+
1593+
1594+
//
1595+
// QgsProcessingEnumWidgetWrapper
1596+
//
1597+
1598+
QgsProcessingEnumWidgetWrapper::QgsProcessingEnumWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type, QWidget *parent )
1599+
: QgsAbstractProcessingParameterWidgetWrapper( parameter, type, parent )
1600+
{
1601+
1602+
}
1603+
1604+
QWidget *QgsProcessingEnumWidgetWrapper::createWidget()
1605+
{
1606+
const QgsProcessingParameterEnum *expParam = dynamic_cast< const QgsProcessingParameterEnum *>( parameterDefinition() );
1607+
switch ( type() )
1608+
{
1609+
case QgsProcessingGui::Standard:
1610+
{
1611+
// checkbox panel only for use outside in standard gui!
1612+
if ( expParam->metadata().value( QStringLiteral( "widget_wrapper" ) ).toMap().value( QStringLiteral( "useCheckBoxes" ), false ).toBool() )
1613+
{
1614+
const int columns = expParam->metadata().value( QStringLiteral( "widget_wrapper" ) ).toMap().value( QStringLiteral( "columns" ), 2 ).toInt();
1615+
mCheckboxPanel = new QgsProcessingEnumCheckboxPanelWidget( nullptr, expParam, columns );
1616+
mCheckboxPanel->setToolTip( parameterDefinition()->toolTip() );
1617+
connect( mCheckboxPanel, &QgsProcessingEnumCheckboxPanelWidget::changed, this, [ = ]
1618+
{
1619+
emit widgetValueHasChanged( this );
1620+
} );
1621+
return mCheckboxPanel;
1622+
}
1623+
}
1624+
FALLTHROUGH
1625+
case QgsProcessingGui::Modeler:
1626+
case QgsProcessingGui::Batch:
1627+
{
1628+
if ( expParam->allowMultiple() )
1629+
{
1630+
mPanel = new QgsProcessingEnumPanelWidget( nullptr, expParam );
1631+
mPanel->setToolTip( parameterDefinition()->toolTip() );
1632+
connect( mPanel, &QgsProcessingEnumPanelWidget::changed, this, [ = ]
1633+
{
1634+
emit widgetValueHasChanged( this );
1635+
} );
1636+
return mPanel;
1637+
}
1638+
else
1639+
{
1640+
mComboBox = new QComboBox();
1641+
1642+
if ( expParam->flags() & QgsProcessingParameterDefinition::FlagOptional )
1643+
mComboBox->addItem( tr( "[Not selected]" ), QVariant() );
1644+
const QStringList options = expParam->options();
1645+
for ( int i = 0; i < options.count(); ++i )
1646+
mComboBox->addItem( options.at( i ), i );
1647+
1648+
mComboBox->setToolTip( parameterDefinition()->toolTip() );
1649+
connect( mComboBox, qgis::overload<int>::of( &QComboBox::currentIndexChanged ), this, [ = ]( int )
1650+
{
1651+
emit widgetValueHasChanged( this );
1652+
} );
1653+
return mComboBox;
1654+
}
1655+
};
1656+
}
1657+
return nullptr;
1658+
}
1659+
1660+
void QgsProcessingEnumWidgetWrapper::setWidgetValue( const QVariant &value, QgsProcessingContext &context )
1661+
{
1662+
if ( mComboBox )
1663+
{
1664+
if ( !value.isValid() )
1665+
mComboBox->setCurrentIndex( mComboBox->findData( QVariant() ) );
1666+
else
1667+
{
1668+
const int v = QgsProcessingParameters::parameterAsEnum( parameterDefinition(), value, context );
1669+
mComboBox->setCurrentIndex( mComboBox->findData( v ) );
1670+
}
1671+
}
1672+
else if ( mPanel || mCheckboxPanel )
1673+
{
1674+
QVariantList opts;
1675+
if ( value.isValid() )
1676+
{
1677+
const QList< int > v = QgsProcessingParameters::parameterAsEnums( parameterDefinition(), value, context );
1678+
opts.reserve( v.size() );
1679+
for ( int i : v )
1680+
opts << i;
1681+
}
1682+
if ( mPanel )
1683+
mPanel->setValue( opts );
1684+
else if ( mCheckboxPanel )
1685+
mCheckboxPanel->setValue( opts );
1686+
}
1687+
}
1688+
1689+
QVariant QgsProcessingEnumWidgetWrapper::widgetValue() const
1690+
{
1691+
if ( mComboBox )
1692+
return mComboBox->currentData();
1693+
else if ( mPanel )
1694+
return mPanel->value();
1695+
else if ( mCheckboxPanel )
1696+
return mCheckboxPanel->value();
1697+
else
1698+
return QVariant();
1699+
}
1700+
1701+
QStringList QgsProcessingEnumWidgetWrapper::compatibleParameterTypes() const
1702+
{
1703+
return QStringList()
1704+
<< QgsProcessingParameterEnum::typeName()
1705+
<< QgsProcessingParameterString::typeName()
1706+
<< QgsProcessingParameterNumber::typeName();
1707+
}
1708+
1709+
QStringList QgsProcessingEnumWidgetWrapper::compatibleOutputTypes() const
1710+
{
1711+
return QStringList()
1712+
<< QgsProcessingOutputString::typeName()
1713+
<< QgsProcessingOutputNumber::typeName();
1714+
}
1715+
1716+
QList<int> QgsProcessingEnumWidgetWrapper::compatibleDataTypes() const
1717+
{
1718+
return QList<int>();
1719+
}
1720+
1721+
QString QgsProcessingEnumWidgetWrapper::modelerExpressionFormatString() const
1722+
{
1723+
return tr( "selected option index (starting from 0), array of indices, or comma separated string of options (e.g. '1,3')" );
1724+
}
1725+
1726+
QString QgsProcessingEnumWidgetWrapper::parameterType() const
1727+
{
1728+
return QgsProcessingParameterEnum::typeName();
1729+
}
1730+
1731+
QgsAbstractProcessingParameterWidgetWrapper *QgsProcessingEnumWidgetWrapper::createWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type )
1732+
{
1733+
return new QgsProcessingEnumWidgetWrapper( parameter, type );
1734+
}
1735+
1736+
1737+
14001738
///@endcond PRIVATE
14011739

‎src/gui/processing/qgsprocessingwidgetwrapperimpl.h

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@
2121

2222
#define SIP_NO_FILE
2323
#include "qgsprocessingwidgetwrapper.h"
24+
#include <QAbstractButton>
2425

2526
class QCheckBox;
2627
class QComboBox;
2728
class QLineEdit;
2829
class QPlainTextEdit;
30+
class QToolButton;
31+
class QButtonGroup;
2932
class QgsProjectionSelectionWidget;
3033
class QgsSpinBox;
3134
class QgsDoubleSpinBox;
@@ -34,6 +37,7 @@ class QgsProcessingMatrixParameterPanel;
3437
class QgsFileWidget;
3538
class QgsFieldExpressionWidget;
3639
class QgsExpressionLineEdit;
40+
class QgsProcessingParameterEnum;
3741

3842
///@cond PRIVATE
3943

@@ -401,6 +405,107 @@ class GUI_EXPORT QgsProcessingExpressionWidgetWrapper : public QgsAbstractProces
401405
friend class TestProcessingGui;
402406
};
403407

408+
409+
class GUI_EXPORT QgsProcessingEnumCheckboxPanelWidget : public QWidget
410+
{
411+
Q_OBJECT
412+
413+
public:
414+
415+
QgsProcessingEnumCheckboxPanelWidget( QWidget *parent = nullptr, const QgsProcessingParameterEnum *param = nullptr, int columns = 2 );
416+
QVariant value() const;
417+
void setValue( const QVariant &value );
418+
419+
signals:
420+
421+
void changed();
422+
423+
private slots:
424+
425+
void showPopupMenu();
426+
void selectAll();
427+
void deselectAll();
428+
429+
private:
430+
431+
const QgsProcessingParameterEnum *mParam = nullptr;
432+
QMap< QVariant, QAbstractButton * > mButtons;
433+
QButtonGroup *mButtonGroup = nullptr;
434+
int mColumns = 2;
435+
bool mBlockChangedSignal = false;
436+
437+
friend class TestProcessingGui;
438+
};
439+
440+
class GUI_EXPORT QgsProcessingEnumPanelWidget : public QWidget
441+
{
442+
Q_OBJECT
443+
444+
public:
445+
446+
QgsProcessingEnumPanelWidget( QWidget *parent = nullptr, const QgsProcessingParameterEnum *param = nullptr );
447+
QVariant value() const { return mValue; }
448+
void setValue( const QVariant &value );
449+
450+
signals:
451+
452+
void changed();
453+
454+
private slots:
455+
456+
void showDialog();
457+
458+
private:
459+
460+
void updateSummaryText();
461+
462+
const QgsProcessingParameterEnum *mParam = nullptr;
463+
QLineEdit *mLineEdit = nullptr;
464+
QToolButton *mToolButton = nullptr;
465+
466+
QVariantList mValue;
467+
468+
friend class TestProcessingGui;
469+
};
470+
471+
472+
class GUI_EXPORT QgsProcessingEnumWidgetWrapper : public QgsAbstractProcessingParameterWidgetWrapper, public QgsProcessingParameterWidgetFactoryInterface
473+
{
474+
Q_OBJECT
475+
476+
public:
477+
478+
QgsProcessingEnumWidgetWrapper( const QgsProcessingParameterDefinition *parameter = nullptr,
479+
QgsProcessingGui::WidgetType type = QgsProcessingGui::Standard, QWidget *parent = nullptr );
480+
481+
// QgsProcessingParameterWidgetFactoryInterface
482+
QString parameterType() const override;
483+
QgsAbstractProcessingParameterWidgetWrapper *createWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type ) override;
484+
485+
// QgsProcessingParameterWidgetWrapper interface
486+
QWidget *createWidget() override SIP_FACTORY;
487+
488+
protected:
489+
490+
void setWidgetValue( const QVariant &value, QgsProcessingContext &context ) override;
491+
QVariant widgetValue() const override;
492+
493+
QStringList compatibleParameterTypes() const override;
494+
495+
QStringList compatibleOutputTypes() const override;
496+
497+
QList< int > compatibleDataTypes() const override;
498+
QString modelerExpressionFormatString() const override;
499+
private:
500+
501+
QComboBox *mComboBox = nullptr;
502+
QgsProcessingEnumPanelWidget *mPanel = nullptr;
503+
QgsProcessingEnumCheckboxPanelWidget *mCheckboxPanel = nullptr;
504+
505+
friend class TestProcessingGui;
506+
};
507+
508+
404509
///@endcond PRIVATE
405510

406511
#endif // QGSPROCESSINGWIDGETWRAPPERIMPL_H
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ui version="4.0">
3+
<class>QgsProcessingMultipleSelectionDialogBase</class>
4+
<widget class="QDialog" name="QgsProcessingMultipleSelectionDialogBase">
5+
<property name="geometry">
6+
<rect>
7+
<x>0</x>
8+
<y>0</y>
9+
<width>380</width>
10+
<height>320</height>
11+
</rect>
12+
</property>
13+
<property name="windowTitle">
14+
<string>Multiple selection</string>
15+
</property>
16+
<layout class="QHBoxLayout" name="horizontalLayout">
17+
<property name="spacing">
18+
<number>6</number>
19+
</property>
20+
<property name="leftMargin">
21+
<number>9</number>
22+
</property>
23+
<property name="topMargin">
24+
<number>9</number>
25+
</property>
26+
<property name="rightMargin">
27+
<number>9</number>
28+
</property>
29+
<property name="bottomMargin">
30+
<number>9</number>
31+
</property>
32+
<item>
33+
<widget class="QListView" name="mSelectionList">
34+
<property name="editTriggers">
35+
<set>QAbstractItemView::NoEditTriggers</set>
36+
</property>
37+
<property name="alternatingRowColors">
38+
<bool>true</bool>
39+
</property>
40+
</widget>
41+
</item>
42+
<item>
43+
<widget class="QDialogButtonBox" name="mButtonBox">
44+
<property name="orientation">
45+
<enum>Qt::Vertical</enum>
46+
</property>
47+
<property name="standardButtons">
48+
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
49+
</property>
50+
</widget>
51+
</item>
52+
</layout>
53+
</widget>
54+
<resources/>
55+
<connections>
56+
<connection>
57+
<sender>mButtonBox</sender>
58+
<signal>accepted()</signal>
59+
<receiver>QgsProcessingMultipleSelectionDialogBase</receiver>
60+
<slot>accept()</slot>
61+
<hints>
62+
<hint type="sourcelabel">
63+
<x>248</x>
64+
<y>254</y>
65+
</hint>
66+
<hint type="destinationlabel">
67+
<x>157</x>
68+
<y>274</y>
69+
</hint>
70+
</hints>
71+
</connection>
72+
<connection>
73+
<sender>mButtonBox</sender>
74+
<signal>rejected()</signal>
75+
<receiver>QgsProcessingMultipleSelectionDialogBase</receiver>
76+
<slot>reject()</slot>
77+
<hints>
78+
<hint type="sourcelabel">
79+
<x>316</x>
80+
<y>260</y>
81+
</hint>
82+
<hint type="destinationlabel">
83+
<x>286</x>
84+
<y>274</y>
85+
</hint>
86+
</hints>
87+
</connection>
88+
</connections>
89+
</ui>

‎tests/src/gui/testprocessinggui.cpp

Lines changed: 477 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.