Skip to content

Commit 5edf06a

Browse files
committed
[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)
1 parent 8d180c1 commit 5edf06a

File tree

2 files changed

+111
-27
lines changed

2 files changed

+111
-27
lines changed

python/plugins/processing/gui/BatchAlgorithmDialog.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def runAlgorithm(self):
7878
load_layers = self.mainWidget().checkLoadLayersOnCompletion.isChecked()
7979
project = QgsProject.instance() if load_layers else None
8080

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

100100
count_visible_outputs += 1
101-
widget = self.mainWidget().tblParameters.cellWidget(row, col)
101+
widget = self.mainWidget().tblParameters.cellWidget(row + 1, col)
102102
text = widget.getValue()
103103
if out.checkValueIsAcceptable(text):
104104
if isinstance(out, (QgsProcessingParameterRasterDestination,

python/plugins/processing/gui/BatchPanel.py

+109-25
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,22 @@
3030
import warnings
3131

3232
from qgis.PyQt import uic
33-
from qgis.PyQt.QtWidgets import QTableWidgetItem, QComboBox, QHeaderView, QFileDialog, QMessageBox
34-
from qgis.PyQt.QtCore import QDir, QFileInfo
33+
from qgis.PyQt.QtWidgets import (
34+
QTableWidgetItem,
35+
QComboBox,
36+
QHeaderView,
37+
QFileDialog,
38+
QMessageBox,
39+
QToolButton,
40+
QMenu,
41+
QAction
42+
)
43+
from qgis.PyQt.QtGui import QPalette
44+
from qgis.PyQt.QtCore import (
45+
QDir,
46+
QFileInfo,
47+
QCoreApplication
48+
)
3549
from qgis.core import (Qgis,
3650
QgsApplication,
3751
QgsSettings,
@@ -59,6 +73,68 @@
5973
os.path.join(pluginPath, 'ui', 'widgetBatchPanel.ui'))
6074

6175

76+
class BatchPanelFillWidget(QToolButton):
77+
78+
def __init__(self, parameterDefinition, column, panel, parent=None):
79+
super().__init__(parent)
80+
81+
self.setBackgroundRole(QPalette.Window)
82+
self.setAutoFillBackground(True)
83+
84+
self.parameterDefinition = parameterDefinition
85+
self.column = column
86+
self.panel = panel
87+
88+
self.setText(QCoreApplication.translate('BatchPanel', 'Autofill…'))
89+
f = self.font()
90+
f.setItalic(True)
91+
self.setFont(f)
92+
self.setPopupMode(QToolButton.InstantPopup)
93+
self.setAutoRaise(True)
94+
95+
self.menu = QMenu()
96+
self.menu.aboutToShow.connect(self.createMenu)
97+
self.setMenu(self.menu)
98+
99+
def createMenu(self):
100+
self.menu.clear()
101+
self.menu.setMinimumWidth(self.width())
102+
103+
fill_down_action = QAction(self.tr('Fill Down'), self.menu)
104+
fill_down_action.triggered.connect(self.fillDown)
105+
fill_down_action.setToolTip(self.tr('Copy the first value down to all other rows'))
106+
107+
self.menu.addAction(fill_down_action)
108+
109+
def fillDown(self):
110+
"""
111+
Copy the top value down
112+
"""
113+
context = dataobjects.createContext()
114+
115+
wrapper = self.panel.wrappers[0][self.column]
116+
if wrapper is None:
117+
# e.g. double clicking on a destination header
118+
widget = self.panel.tblParameters.cellWidget(1, self.column)
119+
value = widget.getValue()
120+
else:
121+
value = wrapper.parameterValue()
122+
123+
for row in range(1, self.panel.batchRowCount()):
124+
self.setRowValue(row, value, context)
125+
126+
def setRowValue(self, row, value, context):
127+
"""
128+
Sets the value for a row, in the current column
129+
"""
130+
wrapper = self.panel.wrappers[row][self.column]
131+
if wrapper is None:
132+
# e.g. destination header
133+
self.panel.tblParameters.cellWidget(row + 1, self.column).setValue(str(value))
134+
else:
135+
wrapper.setParameterValue(value, context)
136+
137+
62138
class BatchPanel(BASE, WIDGET):
63139

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

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

108182
self.context_generator = ContextGenerator(self.processing_context)
109183

184+
self.column_to_parameter_definition = {}
185+
110186
self.initWidgets()
111187

112188
def layerRegistryChanged(self):
@@ -132,21 +208,32 @@ def initWidgets(self):
132208
column, QTableWidgetItem(param.description()))
133209
if param.flags() & QgsProcessingParameterDefinition.FlagAdvanced:
134210
self.tblParameters.setColumnHidden(column, True)
211+
212+
self.column_to_parameter_definition[column] = param.name()
135213
column += 1
136214

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

222+
self.addFillRow()
223+
143224
# Add an empty row to begin
144225
self.addRow()
145226

146227
self.tblParameters.horizontalHeader().resizeSections(QHeaderView.ResizeToContents)
147228
self.tblParameters.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
148229
self.tblParameters.horizontalHeader().setStretchLastSection(True)
149230

231+
def batchRowCount(self):
232+
"""
233+
Returns the number of rows corresponding to execution iterations
234+
"""
235+
return len(self.wrappers)
236+
150237
def load(self):
151238
context = dataobjects.createContext()
152239
settings = QgsSettings()
@@ -163,7 +250,7 @@ def load(self):
163250
# If the user clicked on the cancel button.
164251
return
165252

166-
self.tblParameters.setRowCount(0)
253+
self.tblParameters.setRowCount(1)
167254
try:
168255
for row, alg in enumerate(values):
169256
self.addRow()
@@ -186,7 +273,7 @@ def load(self):
186273
continue
187274
if out.name() in outputs:
188275
value = outputs[out.name()].strip("'")
189-
widget = self.tblParameters.cellWidget(row, column)
276+
widget = self.tblParameters.cellWidget(row + 1, column)
190277
widget.setValue(value)
191278
column += 1
192279
except TypeError:
@@ -198,7 +285,7 @@ def load(self):
198285
def save(self):
199286
toSave = []
200287
context = dataobjects.createContext()
201-
for row in range(self.tblParameters.rowCount()):
288+
for row in range(self.batchRowCount()):
202289
algParams = {}
203290
algOutputs = {}
204291
col = 0
@@ -222,7 +309,8 @@ def save(self):
222309
value = wrapper.parameterValue()
223310

224311
if not param.checkValueIsAcceptable(value, context):
225-
self.parent.messageBar().pushMessage("", self.tr('Wrong or missing parameter value: {0} (row {1})').format(
312+
self.parent.messageBar().pushMessage("", self.tr(
313+
'Wrong or missing parameter value: {0} (row {1})').format(
226314
param.description(), row + 1),
227315
level=Qgis.Warning, duration=5)
228316
return
@@ -231,15 +319,16 @@ def save(self):
231319
for out in alg.destinationParameterDefinitions():
232320
if out.flags() & QgsProcessingParameterDefinition.FlagHidden:
233321
continue
234-
widget = self.tblParameters.cellWidget(row, col)
322+
widget = self.tblParameters.cellWidget(row + 1, col)
235323
text = widget.getValue()
236324
if text.strip() != '':
237325
algOutputs[out.name()] = text.strip()
238326
col += 1
239327
else:
240-
self.parent.messageBar().pushMessage("", self.tr('Wrong or missing output value: {0} (row {1})').format(
241-
out.description(), row + 1),
242-
level=Qgis.Warning, duration=5)
328+
self.parent.messageBar().pushMessage("",
329+
self.tr('Wrong or missing output value: {0} (row {1})').format(
330+
out.description(), row + 1),
331+
level=Qgis.Warning, duration=5)
243332
return
244333
toSave.append({self.PARAMETERS: algParams, self.OUTPUTS: algOutputs})
245334

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

260349
def setCellWrapper(self, row, column, wrapper, context):
261-
self.wrappers[row][column] = wrapper
350+
self.wrappers[row - 1][column] = wrapper
262351

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

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

373+
def addFillRow(self):
374+
self.tblParameters.setRowCount(1)
375+
for col, name in self.column_to_parameter_definition.items():
376+
param_definition = self.alg.parameterDefinition(self.column_to_parameter_definition[col])
377+
self.tblParameters.setCellWidget(0, col, BatchPanelFillWidget(param_definition, col, self))
378+
284379
def addRow(self):
285380
self.wrappers.append([None] * self.tblParameters.columnCount())
286381
self.tblParameters.setRowCount(self.tblParameters.rowCount() + 1)
@@ -319,21 +414,10 @@ def addRow(self):
319414
wrapper.postInitialize(list(wrappers.values()))
320415

321416
def removeRows(self):
322-
if self.tblParameters.rowCount() > 1:
417+
if self.tblParameters.rowCount() > 2:
323418
self.wrappers.pop()
324419
self.tblParameters.setRowCount(self.tblParameters.rowCount() - 1)
325420

326-
def fillParameterValues(self, column):
327-
context = dataobjects.createContext()
328-
329-
wrapper = self.wrappers[0][column]
330-
if wrapper is None:
331-
# e.g. double clicking on a destination header
332-
return
333-
334-
for row in range(1, self.tblParameters.rowCount()):
335-
self.wrappers[row][column].setParameterValue(wrapper.parameterValue(), context)
336-
337421
def toggleAdvancedMode(self, checked):
338422
for column, param in enumerate(self.alg.parameterDefinitions()):
339423
if param.flags() & QgsProcessingParameterDefinition.FlagAdvanced:

0 commit comments

Comments
 (0)