Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #2936 from sept-en/master
[processing] fix filter in Processing settings dialog. Add ability to specify mininal required number of items for MultipleInput parameter (fix #11469, fix #12580)
  • Loading branch information
alexbruy committed Mar 23, 2016
2 parents 4b57964 + f0be045 commit 70e2696
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 31 deletions.
6 changes: 5 additions & 1 deletion python/plugins/processing/algs/qgis/PointsLayerFromTable.py
Expand Up @@ -25,7 +25,11 @@

__revision__ = '$Format:%H$'

from qgis.core import QGis, QgsCoordinateReferenceSystem, QgsFeature, QgsGeometry, QgsPoint
from qgis.core import QGis
from qgis.core import QgsCoordinateReferenceSystem
from qgis.core import QgsFeature
from qgis.core import QgsGeometry
from qgis.core import QgsPoint
from processing.core.GeoAlgorithm import GeoAlgorithm
from processing.core.parameters import ParameterTable
from processing.core.parameters import ParameterTableField
Expand Down
13 changes: 11 additions & 2 deletions python/plugins/processing/algs/saga/SagaAlgorithm213.py
Expand Up @@ -31,8 +31,17 @@
from processing.core.ProcessingConfig import ProcessingConfig
from processing.core.ProcessingLog import ProcessingLog
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
from processing.core.parameters import ParameterRaster, ParameterVector, ParameterTable, ParameterMultipleInput, ParameterBoolean, ParameterFixedTable, ParameterExtent, ParameterNumber, ParameterSelection
from processing.core.outputs import OutputRaster, OutputVector
from processing.core.parameters import ParameterRaster
from processing.core.parameters import ParameterVector
from processing.core.parameters import ParameterTable
from processing.core.parameters import ParameterMultipleInput
from processing.core.parameters import ParameterBoolean
from processing.core.parameters import ParameterFixedTable
from processing.core.parameters import ParameterExtent
from processing.core.parameters import ParameterNumber
from processing.core.parameters import ParameterSelection
from processing.core.outputs import OutputRaster
from processing.core.outputs import OutputVector
from . import SagaUtils
from processing.tools import dataobjects
from processing.tools.system import getTempFilename
Expand Down
48 changes: 45 additions & 3 deletions python/plugins/processing/core/parameters.py
Expand Up @@ -29,9 +29,12 @@
import sys
import os

from processing.tools.vector import resolveFieldIndex, features
from processing.tools.vector import resolveFieldIndex
from processing.tools.vector import features
from PyQt.QtCore import QCoreApplication
from qgis.core import QgsRasterLayer, QgsVectorLayer
from qgis.core import QgsRasterLayer
from qgis.core import QgsVectorLayer
from processing.tools.system import isWindows
from processing.tools import dataobjects


Expand Down Expand Up @@ -338,6 +341,38 @@ def __init__(self, name='', description='', datatype=-1, optional=False):
ParameterDataObject.__init__(self, name, description, None, optional)
self.datatype = int(float(datatype))
self.exported = None
self.minNumInputs = 0

""" Set minimum required number of inputs for parameter
By default minimal number of inputs is set to 1
@type _minNumInputs: numeric type or None
@param _minNumInputs: required minimum number of inputs for parameter. \
If user will pass None as parameter, we will use default minimal number of inputs (1)
@return: result, if the minimum number of inputs were set.
"""

def setMinNumInputs(self, _minNumInputs):
if _minNumInputs is None:
self.minNumInputs = 0
return True

if _minNumInputs < 1 and not self.optional:
# dont allow to set negative or null number of inputs if parameter isn't optional
return False

self.minNumInputs = int(_minNumInputs)
return True

""" Get minimum required number of inputs for parameter
@return: minimum number of inputs required for this parameter
@see: setMinNumInputs()
"""

def getMinNumInputs(self):
return self.minNumInputs

def setValue(self, obj):
self.exported = None
Expand All @@ -350,17 +385,23 @@ def setValue(self, obj):
if isinstance(obj, list):
if len(obj) == 0:
if self.optional:
self.value = None
return True
else:
return False
# prevent setting value if we didn't provide required minimal number of inputs
elif len(obj) < self.minNumInputs:
return False

self.value = ";".join([self.getAsString(lay) for lay in obj])
return True
else:
self.value = unicode(obj)
return True

def getSafeExportedLayers(self):
"""Returns not the value entered by the user, but a string with
"""
Returns not the value entered by the user, but a string with
semicolon-separated filenames which contains the data of the
selected layers, but saved in a standard format (currently
shapefiles for vector layers and GeoTiff for raster) so that
Expand Down Expand Up @@ -420,6 +461,7 @@ def getAsString(self, value):
if layer.name() == s:
return unicode(layer.dataProvider().dataSourceUri())
return s

if self.datatype == ParameterMultipleInput.TYPE_FILE:
return unicode(value)
else:
Expand Down
108 changes: 83 additions & 25 deletions python/plugins/processing/gui/ConfigDialog.py
Expand Up @@ -28,14 +28,31 @@
import os

from PyQt import uic
from PyQt.QtCore import Qt, QEvent, QPyNullVariant
from PyQt.QtWidgets import QFileDialog, QDialog, QStyle, QMessageBox, QStyledItemDelegate, QLineEdit, QWidget, QToolButton, QHBoxLayout, QComboBox
from PyQt.QtGui import QIcon, QStandardItemModel, QStandardItem
from qgis.gui import QgsDoubleSpinBox, QgsSpinBox

from processing.core.ProcessingConfig import ProcessingConfig, Setting
from PyQt.QtCore import (Qt,
QEvent,
QPyNullVariant)
from PyQt.QtWidgets import (QFileDialog,
QDialog,
QStyle,
QMessageBox,
QStyledItemDelegate,
QLineEdit,
QWidget,
QToolButton,
QHBoxLayout,
QComboBox)
from PyQt.QtGui import (QIcon,
QStandardItemModel,
QStandardItem)

from qgis.gui import QgsDoubleSpinBox
from qgis.gui import QgsSpinBox

from processing.core.ProcessingConfig import ProcessingConfig
from processing.core.ProcessingConfig import Setting
from processing.core.Processing import Processing
from processing.gui.menus import updateMenus, menusSettingsGroup
from processing.gui.menus import updateMenus
from processing.gui.menus import menusSettingsGroup


pluginPath = os.path.split(os.path.dirname(__file__))[0]
Expand Down Expand Up @@ -65,22 +82,51 @@ def __init__(self, toolbox):
self.delegate = SettingDelegate()
self.tree.setItemDelegateForColumn(1, self.delegate)

self.searchBox.textChanged.connect(self.fillTree)
self.searchBox.textChanged.connect(self.textChanged)

self.fillTree()

self.tree.expanded.connect(self.adjustColumns)

def textChanged(self):
text = unicode(self.searchBox.text().lower())
self._filterItem(self.model.invisibleRootItem(), text)
if text:
self.tree.expandAll()
else:
self.tree.collapseAll()

def _filterItem(self, item, text):
if item.hasChildren():
show = False
for i in xrange(item.rowCount()):
child = item.child(i)
showChild = self._filterItem(child, text)
show = (showChild or show)
self.tree.setRowHidden(item.row(), item.index().parent(), not show)
return show

elif isinstance(item, QStandardItem):
hide = bool(text) and (text not in item.text().lower())
self.tree.setRowHidden(item.row(), item.index().parent(), hide)
return not hide

def fillTree(self):
self.fillTreeUsingProviders()

def fillTreeUsingProviders(self):
self.items = {}
self.model.clear()
self.model.setHorizontalHeaderLabels([self.tr('Setting'),
self.tr('Value')])

text = unicode(self.searchBox.text())
settings = ProcessingConfig.getSettings()

rootItem = self.model.invisibleRootItem()

"""
Filter 'General', 'Models' and 'Scripts' items
"""
priorityKeys = [self.tr('General'), self.tr('Models'), self.tr('Scripts')]
for group in priorityKeys:
groupItem = QStandardItem(group)
Expand All @@ -89,27 +135,29 @@ def fillTree(self):
groupItem.setEditable(False)
emptyItem = QStandardItem()
emptyItem.setEditable(False)

rootItem.insertRow(0, [groupItem, emptyItem])
# add menu item only if it has any search matches
for setting in settings[group]:
if setting.hidden or setting.name.startswith("MENU_"):
continue

if text == '' or text.lower() in setting.description.lower():
labelItem = QStandardItem(setting.description)
labelItem.setIcon(icon)
labelItem.setEditable(False)
self.items[setting] = SettingItem(setting)
groupItem.insertRow(0, [labelItem, self.items[setting]])

if text != '':
self.tree.expand(groupItem.index())
labelItem = QStandardItem(setting.description)
labelItem.setIcon(icon)
labelItem.setEditable(False)
self.items[setting] = SettingItem(setting)
groupItem.insertRow(0, [labelItem, self.items[setting]])

"""
Filter 'Providers' items
"""
providersItem = QStandardItem(self.tr('Providers'))
icon = QIcon(os.path.join(pluginPath, 'images', 'alg.png'))
providersItem.setIcon(icon)
providersItem.setEditable(False)
emptyItem = QStandardItem()
emptyItem.setEditable(False)

rootItem.insertRow(0, [providersItem, emptyItem])
for group in settings.keys():
if group in priorityKeys or group == menusSettingsGroup:
Expand All @@ -119,34 +167,41 @@ def fillTree(self):
icon = ProcessingConfig.getGroupIcon(group)
groupItem.setIcon(icon)
groupItem.setEditable(False)

for setting in settings[group]:
if setting.hidden:
continue

if text == '' or text.lower() in setting.description.lower():
labelItem = QStandardItem(setting.description)
labelItem.setIcon(icon)
labelItem.setEditable(False)
self.items[setting] = SettingItem(setting)
groupItem.insertRow(0, [labelItem, self.items[setting]])
labelItem = QStandardItem(setting.description)
labelItem.setIcon(icon)
labelItem.setEditable(False)
self.items[setting] = SettingItem(setting)
groupItem.insertRow(0, [labelItem, self.items[setting]])

emptyItem = QStandardItem()
emptyItem.setEditable(False)
providersItem.appendRow([groupItem, emptyItem])

"""
Filter 'Menus' items
"""
menusItem = QStandardItem(self.tr('Menus (requires restart)'))
icon = QIcon(os.path.join(pluginPath, 'images', 'menu.png'))
menusItem.setIcon(icon)
menusItem.setEditable(False)
emptyItem = QStandardItem()
emptyItem.setEditable(False)

rootItem.insertRow(0, [menusItem, emptyItem])

providers = Processing.providers
for provider in providers:
groupItem = QStandardItem(provider.getDescription())
providerDescription = provider.getDescription()
groupItem = QStandardItem(providerDescription)
icon = provider.getIcon()
groupItem.setIcon(icon)
groupItem.setEditable(False)

for alg in provider.algs:
labelItem = QStandardItem(alg.name)
labelItem.setIcon(icon)
Expand All @@ -156,9 +211,12 @@ def fillTree(self):
except:
continue
self.items[setting] = SettingItem(setting)

groupItem.insertRow(0, [labelItem, self.items[setting]])

emptyItem = QStandardItem()
emptyItem.setEditable(False)

menusItem.appendRow([groupItem, emptyItem])

self.tree.sortByColumn(0, Qt.AscendingOrder)
Expand Down
13 changes: 13 additions & 0 deletions python/plugins/processing/tests/ParametersTest.py
Expand Up @@ -298,6 +298,19 @@ def testOptional(self):
self.assertFalse(parameter.setValue(None))
self.assertEqual(parameter.value, "myLayerFile.shp")

def testMultipleInput(self):
parameter = ParameterMultipleInput('myName', 'myDesc', optional=True)
self.assertTrue(parameter.setMinNumInputs(1))

parameter = ParameterMultipleInput('myName', 'myDesc', optional=False)
self.assertFalse(parameter.setMinNumInputs(0))

parameter.setMinNumInputs(2)
self.assertTrue(parameter.setValue(['myLayerFile.shp', 'myLayerFile2.shp']))

parameter.setMinNumInputs(3)
self.assertFalse(parameter.setValue(['myLayerFile.shp', 'myLayerFile2.shp']))

def testGetAsStringWhenRaster(self):
parameter = ParameterMultipleInput('myName', 'myDesc', datatype=ParameterMultipleInput.TYPE_RASTER)

Expand Down

0 comments on commit 70e2696

Please sign in to comment.