Skip to content

Commit

Permalink
Merge pull request #5171 from nyalldawson/processing_selectioncheckboxes
Browse files Browse the repository at this point in the history
[processing] Add useCheckBoxes option to EnumWidgetWrapper
  • Loading branch information
nyalldawson committed Sep 16, 2017
2 parents 7a1b9f9 + 085687d commit f9bc925
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 31 deletions.
5 changes: 5 additions & 0 deletions python/plugins/processing/algs/qgis/CheckValidity.py
Expand Up @@ -80,6 +80,11 @@ def initAlgorithm(self, config=None):
self.tr('Input layer')))
self.addParameter(QgsProcessingParameterEnum(self.METHOD,
self.tr('Method'), self.methods))
self.parameterDefinition(self.METHOD).setMetadata({
'widget_wrapper': {
'class': 'processing.gui.wrappers.EnumWidgetWrapper',
'useCheckBoxes': True,
'columns': 3}})

self.addParameter(QgsProcessingParameterFeatureSink(self.VALID_OUTPUT, self.tr('Valid output'), QgsProcessing.TypeVectorAnyGeometry, '', True))
self.addOutput(QgsProcessingOutputNumber(self.VALID_COUNT, self.tr('Count of valid features')))
Expand Down
15 changes: 11 additions & 4 deletions python/plugins/processing/algs/qgis/SpatialJoin.py
Expand Up @@ -96,10 +96,17 @@ def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterFeatureSource(self.JOIN,
self.tr('Join layer'),
[QgsProcessing.TypeVectorAnyGeometry]))
self.addParameter(QgsProcessingParameterEnum(self.PREDICATE,
self.tr('Geometric predicate'),
options=[p[1] for p in self.predicates],
allowMultiple=True, defaultValue=[0]))

predicate = QgsProcessingParameterEnum(self.PREDICATE,
self.tr('Geometric predicate'),
options=[p[1] for p in self.predicates],
allowMultiple=True, defaultValue=[0])
predicate.setMetadata({
'widget_wrapper': {
'class': 'processing.gui.wrappers.EnumWidgetWrapper',
'useCheckBoxes': True,
'columns': 2}})
self.addParameter(predicate)
self.addParameter(QgsProcessingParameterField(self.JOIN_FIELDS,
self.tr('Fields to add (leave empty to use all fields)'),
parentLayerParameterName=self.JOIN,
Expand Down
14 changes: 10 additions & 4 deletions python/plugins/processing/algs/qgis/SpatialJoinSummary.py
Expand Up @@ -110,10 +110,16 @@ def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterFeatureSource(self.JOIN,
self.tr('Join layer'),
[QgsProcessing.TypeVectorAnyGeometry]))
self.addParameter(QgsProcessingParameterEnum(self.PREDICATE,
self.tr('Geometric predicate'),
options=[p[1] for p in self.predicates],
allowMultiple=True, defaultValue=[0]))
predicate = QgsProcessingParameterEnum(self.PREDICATE,
self.tr('Geometric predicate'),
options=[p[1] for p in self.predicates],
allowMultiple=True, defaultValue=[0])
predicate.setMetadata({
'widget_wrapper': {
'class': 'processing.gui.wrappers.EnumWidgetWrapper',
'useCheckBoxes': True,
'columns': 2}})
self.addParameter(predicate)
self.addParameter(QgsProcessingParameterField(self.JOIN_FIELDS,
self.tr('Fields to summarise (leave empty to use all fields)'),
parentLayerParameterName=self.JOIN,
Expand Down
4 changes: 2 additions & 2 deletions python/plugins/processing/core/parameters.py
Expand Up @@ -371,8 +371,8 @@ def __init__(self, name='', description='', optional=False, showSublayersDialog=
class ParameterSelection(Parameter):

def __init__(self, name='', description='', options=[], default=None, isSource=False,
multiple=False, optional=False):
Parameter.__init__(self, name, description, default, optional)
multiple=False, optional=False, metadata={}):
Parameter.__init__(self, name, description, default, optional, metadata)
self.multiple = multiple
isSource = parseBool(isSource)
self.options = options
Expand Down
116 changes: 116 additions & 0 deletions python/plugins/processing/gui/CheckboxesPanel.py
@@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-

"""
***************************************************************************
CheckBoxesPanel.py
---------------------
Date : January 2015
Copyright : (C) 2015 by Arnaud Morvan
Email : arnaud dot morvan at camptocamp dot com
Contributors : Arnaud Morvan
***************************************************************************
* *
* 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. *
* *
***************************************************************************
"""
from builtins import range

__author__ = 'Arnaud Morvan'
__date__ = 'January 2015'
__copyright__ = '(C) 2015, Arnaud Morvan'

# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'

from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtWidgets import (
QCheckBox,
QRadioButton,
QGridLayout,
QButtonGroup,
QSizePolicy,
QSpacerItem,
QWidget,
QMenu,
QAction
)
from qgis.PyQt.QtGui import QCursor


class CheckboxesPanel(QWidget):

def __init__(self, options, multiple, columns=2, parent=None):
super(CheckboxesPanel, self).__init__(parent)

self._options = []
for i, option in enumerate(options):
if isinstance(option, str):
self._options.append((i, option))
else:
self.options.append(option)
self._multiple = multiple
self._buttons = []
rows = len(options) / columns

self._buttonGroup = QButtonGroup()
self._buttonGroup.setExclusive(not multiple)
layout = QGridLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.setMargin(0)
for i, (v, t) in enumerate(self._options):
if multiple:
button = QCheckBox(t)
else:
button = QRadioButton(t)
self._buttons.append((v, button))
self._buttonGroup.addButton(button, i)
layout.addWidget(button, i % rows, i / rows)
layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum),
0, columns)
self.setLayout(layout)

if multiple:
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.showPopupMenu)

def showPopupMenu(self):
popup_menu = QMenu()
select_all_action = QAction(self.tr('Select All'), popup_menu)
select_all_action.triggered.connect(self.selectAll)
clear_all_action = QAction(self.tr('Clear Selection'), popup_menu)
clear_all_action.triggered.connect(self.deselectAll)
popup_menu.addAction(select_all_action)
popup_menu.addAction(clear_all_action)
popup_menu.exec_(QCursor.pos())

def selectAll(self):
for (v, button) in self._buttons:
button.setChecked(True)

def deselectAll(self):
for (v, button) in self._buttons:
button.setChecked(False)

def value(self):
if self._multiple:
value = []
for (v, checkbox) in self._buttons:
if checkbox.isChecked():
value.append(v)
return value
else:
return self._options[self._buttonGroup.checkedId()][0]

def setValue(self, value):
if self._multiple:
for (v, button) in self._buttons:
if v in value:
button.setChecked(True)
else:
for v, button in self._buttons:
if v == value:
button.setChecked(True)
13 changes: 12 additions & 1 deletion python/plugins/processing/gui/wrappers.py
Expand Up @@ -118,6 +118,7 @@
from processing.core.outputs import (OutputFile, OutputRaster, OutputVector,
OutputString, OutputTable, OutputExtent)
from processing.tools import dataobjects
from processing.gui.CheckboxesPanel import CheckboxesPanel
from processing.gui.MultipleInputPanel import MultipleInputPanel
from processing.gui.BatchInputSelectionPanel import BatchInputSelectionPanel
from processing.gui.FixedTablePanel import FixedTablePanel
Expand Down Expand Up @@ -803,7 +804,12 @@ def selectFile(self):

class EnumWidgetWrapper(WidgetWrapper):

def createWidget(self):
def createWidget(self, useCheckBoxes=False, columns=1):
self._useCheckBoxes = useCheckBoxes
if self._useCheckBoxes and not self.dialogType == DIALOG_BATCH:
return CheckboxesPanel(options=self.param.options(),
multiple=self.param.allowMultiple(),
columns=columns)
if self.param.allowMultiple():
return MultipleInputPanel(options=self.param.options())
else:
Expand All @@ -815,12 +821,17 @@ def createWidget(self):
return widget

def setValue(self, value):
if self._useCheckBoxes and not self.dialogType == DIALOG_BATCH:
self.widget.setValue(value)
return
if self.param.allowMultiple():
self.widget.setSelectedItems(value)
else:
self.widget.setCurrentIndex(self.widget.findData(value))

def value(self):
if self._useCheckBoxes and not self.dialogType == DIALOG_BATCH:
return self.widget.value()
if self.param.allowMultiple():
return self.widget.selectedoptions
else:
Expand Down
46 changes: 27 additions & 19 deletions src/core/processing/qgsnativealgorithms.cpp
Expand Up @@ -1356,12 +1356,7 @@ void QgsSelectByLocationAlgorithm::initAlgorithm( const QVariantMap & )

addParameter( new QgsProcessingParameterVectorLayer( QStringLiteral( "INPUT" ), QObject::tr( "Select features from" ),
QList< int >() << QgsProcessing::TypeVectorAnyGeometry ) );


addParameter( new QgsProcessingParameterEnum( QStringLiteral( "PREDICATE" ),
QObject::tr( "Where the features are (geometric predicate)" ),
predicateOptionsList(), true, QVariant::fromValue( QList< int >() << 0 ) ) );

addPredicateParameter();
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INTERSECT" ),
QObject::tr( "By comparing to the features from" ),
QList< int >() << QgsProcessing::TypeVectorAnyGeometry ) );
Expand Down Expand Up @@ -1529,6 +1524,23 @@ void QgsLocationBasedAlgorithm::process( QgsFeatureSource *targetSource,
}
}

void QgsLocationBasedAlgorithm::addPredicateParameter()
{
std::unique_ptr< QgsProcessingParameterEnum > predicateParam( new QgsProcessingParameterEnum( QStringLiteral( "PREDICATE" ),
QObject::tr( "Where the features (geometric predicate)" ),
predicateOptionsList(), true, QVariant::fromValue( QList< int >() << 0 ) ) );

QVariantMap predicateMetadata;
QVariantMap widgetMetadata;
widgetMetadata.insert( QStringLiteral( "class" ), QStringLiteral( "processing.gui.wrappers.EnumWidgetWrapper" ) );
widgetMetadata.insert( QStringLiteral( "useCheckBoxes" ), true );
widgetMetadata.insert( QStringLiteral( "columns" ), 2 );
predicateMetadata.insert( QStringLiteral( "widget_wrapper" ), widgetMetadata );
predicateParam->setMetadata( predicateMetadata );

addParameter( predicateParam.release() );
}

QgsLocationBasedAlgorithm::Predicate QgsLocationBasedAlgorithm::reversePredicate( QgsLocationBasedAlgorithm::Predicate predicate ) const
{
switch ( predicate )
Expand Down Expand Up @@ -1556,14 +1568,14 @@ QgsLocationBasedAlgorithm::Predicate QgsLocationBasedAlgorithm::reversePredicate

QStringList QgsLocationBasedAlgorithm::predicateOptionsList() const
{
return QStringList() << QObject::tr( "intersects" )
<< QObject::tr( "contains" )
<< QObject::tr( "is disjoint" )
<< QObject::tr( "equals" )
<< QObject::tr( "touches" )
<< QObject::tr( "overlaps" )
<< QObject::tr( "within" )
<< QObject::tr( "crosses" );
return QStringList() << QObject::tr( "intersect" )
<< QObject::tr( "contain" )
<< QObject::tr( "disjoint" )
<< QObject::tr( "equal" )
<< QObject::tr( "touch" )
<< QObject::tr( "overlap" )
<< QObject::tr( "are within" )
<< QObject::tr( "cross" );
}


Expand All @@ -1572,11 +1584,7 @@ void QgsExtractByLocationAlgorithm::initAlgorithm( const QVariantMap & )
{
addParameter( new QgsProcessingParameterVectorLayer( QStringLiteral( "INPUT" ), QObject::tr( "Extract features from" ),
QList< int >() << QgsProcessing::TypeVectorAnyGeometry ) );

addParameter( new QgsProcessingParameterEnum( QStringLiteral( "PREDICATE" ),
QObject::tr( "Where the features are (geometric predicate)" ),
predicateOptionsList(), true, QVariant::fromValue( QList< int >() << 0 ) ) );

addPredicateParameter();
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INTERSECT" ),
QObject::tr( "By comparing to the features from" ),
QList< int >() << QgsProcessing::TypeVectorAnyGeometry ) );
Expand Down
1 change: 1 addition & 0 deletions src/core/processing/qgsnativealgorithms.h
Expand Up @@ -504,6 +504,7 @@ class QgsLocationBasedAlgorithm : public QgsProcessingAlgorithm
Crosses,
};

void addPredicateParameter();
Predicate reversePredicate( Predicate predicate ) const;
QStringList predicateOptionsList() const;
void process( QgsFeatureSource *targetSource, QgsFeatureSource *intersectSource, const QList<int> &selectedPredicates, const std::function< void( const QgsFeature & )> &handleFeatureFunction, bool onlyRequireTargetIds, QgsFeedback *feedback );
Expand Down
6 changes: 5 additions & 1 deletion src/core/processing/qgsprocessingparameters.cpp
Expand Up @@ -1988,7 +1988,11 @@ bool QgsProcessingParameterEnum::checkValueIsAcceptable( const QVariant &input,
if ( !mAllowMultiple )
return false;

Q_FOREACH ( const QVariant &val, input.toList() )
const QVariantList values = input.toList();
if ( values.empty() && !( mFlags & FlagOptional ) )
return false;

for ( const QVariant &val : values )
{
bool ok = false;
int res = val.toInt( &ok );
Expand Down
4 changes: 4 additions & 0 deletions tests/src/core/testqgsprocessing.cpp
Expand Up @@ -2784,6 +2784,7 @@ void TestQgsProcessing::parameterEnum()
QVERIFY( def->checkValueIsAcceptable( "1" ) );
QVERIFY( !def->checkValueIsAcceptable( "1,2" ) );
QVERIFY( def->checkValueIsAcceptable( 0 ) );
QVERIFY( !def->checkValueIsAcceptable( QVariantList() ) );
QVERIFY( !def->checkValueIsAcceptable( QVariantList() << 1 ) );
QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "a" ) );
QVERIFY( !def->checkValueIsAcceptable( 15 ) );
Expand Down Expand Up @@ -2853,6 +2854,7 @@ void TestQgsProcessing::parameterEnum()
QVERIFY( def->checkValueIsAcceptable( "1" ) );
QVERIFY( def->checkValueIsAcceptable( "1,2" ) );
QVERIFY( def->checkValueIsAcceptable( 0 ) );
QVERIFY( !def->checkValueIsAcceptable( QVariantList() ) ); // since non-optional, empty list not allowed
QVERIFY( def->checkValueIsAcceptable( QVariantList() << 1 ) );
QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "a" ) );
QVERIFY( !def->checkValueIsAcceptable( 15 ) );
Expand Down Expand Up @@ -2893,6 +2895,7 @@ void TestQgsProcessing::parameterEnum()
QVERIFY( def->checkValueIsAcceptable( "1" ) );
QVERIFY( !def->checkValueIsAcceptable( "1,2" ) );
QVERIFY( def->checkValueIsAcceptable( 0 ) );
QVERIFY( !def->checkValueIsAcceptable( QVariantList() ) );
QVERIFY( !def->checkValueIsAcceptable( QVariantList() << 1 ) );
QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "a" ) );
QVERIFY( !def->checkValueIsAcceptable( 15 ) );
Expand Down Expand Up @@ -2925,6 +2928,7 @@ void TestQgsProcessing::parameterEnum()
QVERIFY( def->checkValueIsAcceptable( "1" ) );
QVERIFY( def->checkValueIsAcceptable( "1,2" ) );
QVERIFY( def->checkValueIsAcceptable( 0 ) );
QVERIFY( def->checkValueIsAcceptable( QVariantList() ) );
QVERIFY( def->checkValueIsAcceptable( QVariantList() << 1 ) );
QVERIFY( !def->checkValueIsAcceptable( QVariantList() << "a" ) );
QVERIFY( !def->checkValueIsAcceptable( 15 ) );
Expand Down

0 comments on commit f9bc925

Please sign in to comment.