Skip to content

Commit

Permalink
[processing][needs-docs] Move batch execution double-click-to-fill-down
Browse files Browse the repository at this point in the history
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 30, 2019
1 parent 8d180c1 commit 5edf06a
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 27 deletions.
4 changes: 2 additions & 2 deletions python/plugins/processing/gui/BatchAlgorithmDialog.py
Expand Up @@ -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():
Expand All @@ -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,
Expand Down
134 changes: 109 additions & 25 deletions python/plugins/processing/gui/BatchPanel.py
Expand Up @@ -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,
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand All @@ -107,6 +181,8 @@ def processingContext(self):

self.context_generator = ContextGenerator(self.processing_context)

self.column_to_parameter_definition = {}

self.initWidgets()

def layerRegistryChanged(self):
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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:
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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})

Expand All @@ -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())
Expand All @@ -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)
Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit 5edf06a

Please sign in to comment.