Skip to content
Permalink
Browse files

[processing][needs-docs] Move batch execution double-click-to-fill-down

to an explicit widget in the table

The double-click-header action is very hidden, and many users will
not stumble upon this. By moving it to an explicit "Autofill" widget at the
top of the table, we make this important action much more user
discoverable.

It also gives the possibility of other, parameter specific, autofill
actions (e.g. fill by expression, fill by file pattern)
  • Loading branch information
nyalldawson committed Apr 26, 2019
1 parent 8d180c1 commit 5edf06a5bcbb9792a62a1a5879bb1cc6c0a9a45f
Showing with 111 additions and 27 deletions.
  1. +2 −2 python/plugins/processing/gui/BatchAlgorithmDialog.py
  2. +109 −25 python/plugins/processing/gui/BatchPanel.py
@@ -78,7 +78,7 @@ def runAlgorithm(self):
load_layers = self.mainWidget().checkLoadLayersOnCompletion.isChecked()
project = QgsProject.instance() if load_layers else None

for row in range(self.mainWidget().tblParameters.rowCount()):
for row in range(len(self.mainWidget().batchRowCount())):
col = 0
parameters = {}
for param in self.algorithm().parameterDefinitions():
@@ -98,7 +98,7 @@ def runAlgorithm(self):
continue

count_visible_outputs += 1
widget = self.mainWidget().tblParameters.cellWidget(row, col)
widget = self.mainWidget().tblParameters.cellWidget(row + 1, col)
text = widget.getValue()
if out.checkValueIsAcceptable(text):
if isinstance(out, (QgsProcessingParameterRasterDestination,
@@ -30,8 +30,22 @@
import warnings

from qgis.PyQt import uic
from qgis.PyQt.QtWidgets import QTableWidgetItem, QComboBox, QHeaderView, QFileDialog, QMessageBox
from qgis.PyQt.QtCore import QDir, QFileInfo
from qgis.PyQt.QtWidgets import (
QTableWidgetItem,
QComboBox,
QHeaderView,
QFileDialog,
QMessageBox,
QToolButton,
QMenu,
QAction
)
from qgis.PyQt.QtGui import QPalette
from qgis.PyQt.QtCore import (
QDir,
QFileInfo,
QCoreApplication
)
from qgis.core import (Qgis,
QgsApplication,
QgsSettings,
@@ -59,6 +73,68 @@
os.path.join(pluginPath, 'ui', 'widgetBatchPanel.ui'))


class BatchPanelFillWidget(QToolButton):

def __init__(self, parameterDefinition, column, panel, parent=None):
super().__init__(parent)

self.setBackgroundRole(QPalette.Window)
self.setAutoFillBackground(True)

self.parameterDefinition = parameterDefinition
self.column = column
self.panel = panel

self.setText(QCoreApplication.translate('BatchPanel', 'Autofill…'))
f = self.font()
f.setItalic(True)
self.setFont(f)
self.setPopupMode(QToolButton.InstantPopup)
self.setAutoRaise(True)

self.menu = QMenu()
self.menu.aboutToShow.connect(self.createMenu)
self.setMenu(self.menu)

def createMenu(self):
self.menu.clear()
self.menu.setMinimumWidth(self.width())

fill_down_action = QAction(self.tr('Fill Down'), self.menu)
fill_down_action.triggered.connect(self.fillDown)
fill_down_action.setToolTip(self.tr('Copy the first value down to all other rows'))

self.menu.addAction(fill_down_action)

def fillDown(self):
"""
Copy the top value down
"""
context = dataobjects.createContext()

wrapper = self.panel.wrappers[0][self.column]
if wrapper is None:
# e.g. double clicking on a destination header
widget = self.panel.tblParameters.cellWidget(1, self.column)
value = widget.getValue()
else:
value = wrapper.parameterValue()

for row in range(1, self.panel.batchRowCount()):
self.setRowValue(row, value, context)

def setRowValue(self, row, value, context):
"""
Sets the value for a row, in the current column
"""
wrapper = self.panel.wrappers[row][self.column]
if wrapper is None:
# e.g. destination header
self.panel.tblParameters.cellWidget(row + 1, self.column).setValue(str(value))
else:
wrapper.setParameterValue(value, context)


class BatchPanel(BASE, WIDGET):

PARAMETERS = "PARAMETERS"
@@ -87,8 +163,6 @@ def __init__(self, parent, alg):
self.btnOpen.clicked.connect(self.load)
self.btnSave.clicked.connect(self.save)
self.btnAdvanced.toggled.connect(self.toggleAdvancedMode)
self.tblParameters.horizontalHeader().sectionDoubleClicked.connect(
self.fillParameterValues)

self.tblParameters.horizontalHeader().resizeSections(QHeaderView.ResizeToContents)
self.tblParameters.horizontalHeader().setDefaultSectionSize(250)
@@ -107,6 +181,8 @@ def processingContext(self):

self.context_generator = ContextGenerator(self.processing_context)

self.column_to_parameter_definition = {}

self.initWidgets()

def layerRegistryChanged(self):
@@ -132,21 +208,32 @@ def initWidgets(self):
column, QTableWidgetItem(param.description()))
if param.flags() & QgsProcessingParameterDefinition.FlagAdvanced:
self.tblParameters.setColumnHidden(column, True)

self.column_to_parameter_definition[column] = param.name()
column += 1

for out in self.alg.destinationParameterDefinitions():
if not out.flags() & QgsProcessingParameterDefinition.FlagHidden:
self.tblParameters.setHorizontalHeaderItem(
column, QTableWidgetItem(out.description()))
self.column_to_parameter_definition[column] = out.name()
column += 1

self.addFillRow()

# Add an empty row to begin
self.addRow()

self.tblParameters.horizontalHeader().resizeSections(QHeaderView.ResizeToContents)
self.tblParameters.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
self.tblParameters.horizontalHeader().setStretchLastSection(True)

def batchRowCount(self):
"""
Returns the number of rows corresponding to execution iterations
"""
return len(self.wrappers)

def load(self):
context = dataobjects.createContext()
settings = QgsSettings()
@@ -163,7 +250,7 @@ def load(self):
# If the user clicked on the cancel button.
return

self.tblParameters.setRowCount(0)
self.tblParameters.setRowCount(1)
try:
for row, alg in enumerate(values):
self.addRow()
@@ -186,7 +273,7 @@ def load(self):
continue
if out.name() in outputs:
value = outputs[out.name()].strip("'")
widget = self.tblParameters.cellWidget(row, column)
widget = self.tblParameters.cellWidget(row + 1, column)
widget.setValue(value)
column += 1
except TypeError:
@@ -198,7 +285,7 @@ def load(self):
def save(self):
toSave = []
context = dataobjects.createContext()
for row in range(self.tblParameters.rowCount()):
for row in range(self.batchRowCount()):
algParams = {}
algOutputs = {}
col = 0
@@ -222,7 +309,8 @@ def save(self):
value = wrapper.parameterValue()

if not param.checkValueIsAcceptable(value, context):
self.parent.messageBar().pushMessage("", self.tr('Wrong or missing parameter value: {0} (row {1})').format(
self.parent.messageBar().pushMessage("", self.tr(
'Wrong or missing parameter value: {0} (row {1})').format(
param.description(), row + 1),
level=Qgis.Warning, duration=5)
return
@@ -231,15 +319,16 @@ def save(self):
for out in alg.destinationParameterDefinitions():
if out.flags() & QgsProcessingParameterDefinition.FlagHidden:
continue
widget = self.tblParameters.cellWidget(row, col)
widget = self.tblParameters.cellWidget(row + 1, col)
text = widget.getValue()
if text.strip() != '':
algOutputs[out.name()] = text.strip()
col += 1
else:
self.parent.messageBar().pushMessage("", self.tr('Wrong or missing output value: {0} (row {1})').format(
out.description(), row + 1),
level=Qgis.Warning, duration=5)
self.parent.messageBar().pushMessage("",
self.tr('Wrong or missing output value: {0} (row {1})').format(
out.description(), row + 1),
level=Qgis.Warning, duration=5)
return
toSave.append({self.PARAMETERS: algParams, self.OUTPUTS: algOutputs})

@@ -258,7 +347,7 @@ def save(self):
json.dump(toSave, f)

def setCellWrapper(self, row, column, wrapper, context):
self.wrappers[row][column] = wrapper
self.wrappers[row - 1][column] = wrapper

widget_context = QgsProcessingParameterWidgetContext()
widget_context.setProject(QgsProject.instance())
@@ -281,6 +370,12 @@ def setCellWrapper(self, row, column, wrapper, context):

self.tblParameters.setCellWidget(row, column, widget)

def addFillRow(self):
self.tblParameters.setRowCount(1)
for col, name in self.column_to_parameter_definition.items():
param_definition = self.alg.parameterDefinition(self.column_to_parameter_definition[col])
self.tblParameters.setCellWidget(0, col, BatchPanelFillWidget(param_definition, col, self))

def addRow(self):
self.wrappers.append([None] * self.tblParameters.columnCount())
self.tblParameters.setRowCount(self.tblParameters.rowCount() + 1)
@@ -319,21 +414,10 @@ def addRow(self):
wrapper.postInitialize(list(wrappers.values()))

def removeRows(self):
if self.tblParameters.rowCount() > 1:
if self.tblParameters.rowCount() > 2:
self.wrappers.pop()
self.tblParameters.setRowCount(self.tblParameters.rowCount() - 1)

def fillParameterValues(self, column):
context = dataobjects.createContext()

wrapper = self.wrappers[0][column]
if wrapper is None:
# e.g. double clicking on a destination header
return

for row in range(1, self.tblParameters.rowCount()):
self.wrappers[row][column].setParameterValue(wrapper.parameterValue(), context)

def toggleAdvancedMode(self, checked):
for column, param in enumerate(self.alg.parameterDefinitions()):
if param.flags() & QgsProcessingParameterDefinition.FlagAdvanced:

0 comments on commit 5edf06a

Please sign in to comment.
You can’t perform that action at this time.