Skip to content

Commit 1f2ea02

Browse files
committed
Allow adding layers which aren't open in the project to processing
parameters which accept lists of multiple layers E.g. build vrt alg, merge vector layers alg Otherwise you may need to load 100's of layers temporarily into a project to perform algs on them
1 parent a5a4d3b commit 1f2ea02

File tree

3 files changed

+77
-19
lines changed

3 files changed

+77
-19
lines changed

python/plugins/processing/gui/MultipleInputDialog.py

+60-6
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,16 @@
2929

3030
import os
3131

32-
from qgis.core import QgsSettings
32+
from qgis.core import (QgsSettings,
33+
QgsProcessing,
34+
QgsVectorFileWriter,
35+
QgsProviderRegistry)
3336
from qgis.PyQt import uic
3437
from qgis.PyQt.QtCore import Qt
3538
from qgis.PyQt.QtCore import QByteArray
36-
from qgis.PyQt.QtWidgets import QDialog, QAbstractItemView, QPushButton, QDialogButtonBox
39+
from qgis.PyQt.QtWidgets import QDialog, QAbstractItemView, QPushButton, QDialogButtonBox, QFileDialog
3740
from qgis.PyQt.QtGui import QStandardItemModel, QStandardItem
41+
from processing.tools import dataobjects
3842

3943
pluginPath = os.path.split(os.path.dirname(__file__))[0]
4044
WIDGET, BASE = uic.loadUiType(
@@ -43,9 +47,11 @@
4347

4448
class MultipleInputDialog(BASE, WIDGET):
4549

46-
def __init__(self, options, selectedoptions=None):
50+
def __init__(self, options, selectedoptions=None, datatype=None):
4751
super(MultipleInputDialog, self).__init__(None)
4852
self.setupUi(self)
53+
self.datatype = datatype
54+
self.model = None
4955

5056
self.lstLayers.setSelectionMode(QAbstractItemView.NoSelection)
5157

@@ -68,6 +74,11 @@ def __init__(self, options, selectedoptions=None):
6874
self.btnToggleSelection = QPushButton(self.tr('Toggle selection'))
6975
self.buttonBox.addButton(self.btnToggleSelection,
7076
QDialogButtonBox.ActionRole)
77+
if self.datatype is not None:
78+
btnAddFile = QPushButton(self.tr('Add file(s)…'))
79+
btnAddFile.clicked.connect(self.addFiles)
80+
self.buttonBox.addButton(btnAddFile,
81+
QDialogButtonBox.ActionRole)
7182

7283
self.btnSelectAll.clicked.connect(lambda: self.selectAll(True))
7384
self.btnClearSelection.clicked.connect(lambda: self.selectAll(False))
@@ -83,15 +94,23 @@ def saveWindowGeometry(self):
8394
self.settings.setValue("/Processing/multipleInputDialogGeometry", self.saveGeometry())
8495

8596
def populateList(self):
86-
model = QStandardItemModel()
97+
self.model = QStandardItemModel()
8798
for value, text in self.options:
8899
item = QStandardItem(text)
89100
item.setData(value, Qt.UserRole)
90101
item.setCheckState(Qt.Checked if value in self.selectedoptions else Qt.Unchecked)
91102
item.setCheckable(True)
92-
model.appendRow(item)
103+
self.model.appendRow(item)
93104

94-
self.lstLayers.setModel(model)
105+
# add extra options (e.g. manually added layers)
106+
for t in [o for o in self.selectedoptions if not isinstance(o, int)]:
107+
item = QStandardItem(t)
108+
item.setData(t, Qt.UserRole)
109+
item.setCheckState(Qt.Checked)
110+
item.setCheckable(True)
111+
self.model.appendRow(item)
112+
113+
self.lstLayers.setModel(self.model)
95114

96115
def accept(self):
97116
self.selectedoptions = []
@@ -118,3 +137,38 @@ def toggleSelection(self):
118137
item = model.item(i)
119138
checked = item.checkState() == Qt.Checked
120139
item.setCheckState(Qt.Unchecked if checked else Qt.Checked)
140+
141+
def getFileFilter(self, datatype):
142+
"""
143+
Returns a suitable file filter pattern for the specified parameter definition
144+
:param param:
145+
:return:
146+
"""
147+
if datatype == QgsProcessing.TypeRaster:
148+
return QgsProviderRegistry.instance().fileRasterFilters()
149+
elif datatype == QgsProcessing.TypeFile:
150+
return self.tr('All files (*.*)')
151+
else:
152+
exts = QgsVectorFileWriter.supportedFormatExtensions()
153+
for i in range(len(exts)):
154+
exts[i] = self.tr('{0} files (*.{1})').format(exts[i].upper(), exts[i].lower())
155+
return self.tr('All files (*.*)') + ';;' + ';;'.join(exts)
156+
157+
def addFiles(self):
158+
filter = self.getFileFilter(self.datatype)
159+
160+
settings = QgsSettings()
161+
path = str(settings.value('/Processing/LastInputPath'))
162+
163+
ret, selected_filter = QFileDialog.getOpenFileNames(self, self.tr('Select file(s)'),
164+
path, filter)
165+
if ret:
166+
files = list(ret)
167+
settings.setValue('/Processing/LastInputPath',
168+
os.path.dirname(str(files[0])))
169+
for filename in files:
170+
item = QStandardItem(filename)
171+
item.setData(filename, Qt.UserRole)
172+
item.setCheckState(Qt.Checked)
173+
item.setCheckable(True)
174+
self.model.appendRow(item)

python/plugins/processing/gui/MultipleInputPanel.py

+14-10
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@
2828

2929
import os
3030

31+
from qgis.core import QgsProcessing
3132
from qgis.PyQt import uic
3233
from qgis.PyQt.QtCore import pyqtSignal
33-
34+
''
3435
from processing.gui.MultipleInputDialog import MultipleInputDialog
3536
from processing.gui.MultipleFileInputDialog import MultipleFileInputDialog
3637

@@ -63,10 +64,10 @@ def setSelectedItems(self, selected):
6364
self.tr('{0} elements selected').format(len(self.selectedoptions)))
6465

6566
def showSelectionDialog(self):
66-
if self.datatype is None:
67-
dlg = MultipleInputDialog(self.options, self.selectedoptions)
68-
else:
67+
if self.datatype == QgsProcessing.TypeFile:
6968
dlg = MultipleFileInputDialog(self.selectedoptions)
69+
else:
70+
dlg = MultipleInputDialog(self.options, self.selectedoptions, datatype=self.datatype)
7071
dlg.exec_()
7172
if dlg.selectedoptions is not None:
7273
self.selectedoptions = dlg.selectedoptions
@@ -76,12 +77,15 @@ def showSelectionDialog(self):
7677

7778
def updateForOptions(self, options):
7879
selectedoptions = []
79-
selected = [self.options[i] for i in self.selectedoptions]
80+
selected = [self.options[i] if isinstance(i, int) else i for i in self.selectedoptions]
8081
for sel in selected:
81-
try:
82-
idx = options.index(sel)
83-
selectedoptions.append(idx)
84-
except ValueError:
85-
pass
82+
if isinstance(sel, int):
83+
try:
84+
idx = options.index(sel)
85+
selectedoptions.append(idx)
86+
except ValueError:
87+
pass
88+
else:
89+
selectedoptions.append(sel)
8690
self.options = options
8791
self.setSelectedItems(selectedoptions)

python/plugins/processing/gui/wrappers.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ def _getOptions(self):
546546
def createWidget(self):
547547
if self.dialogType == DIALOG_STANDARD:
548548
if self.param.layerType() == QgsProcessing.TypeFile:
549-
return MultipleInputPanel(datatype=dataobjects.TYPE_FILE)
549+
return MultipleInputPanel(datatype=QgsProcessing.TypeFile)
550550
else:
551551
if self.param.layerType() == QgsProcessing.TypeRaster:
552552
options = QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance(), False)
@@ -555,7 +555,7 @@ def createWidget(self):
555555
else:
556556
options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [self.param.layerType()], False)
557557
opts = [getExtendedLayerName(opt) for opt in options]
558-
return MultipleInputPanel(opts)
558+
return MultipleInputPanel(opts, datatype=self.param.layerType())
559559
elif self.dialogType == DIALOG_BATCH:
560560
widget = BatchInputSelectionPanel(self.param, self.row, self.col, self.dialog)
561561
widget.valueChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
@@ -599,7 +599,7 @@ def value(self):
599599
options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [], False)
600600
else:
601601
options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [self.param.layerType()], False)
602-
return [options[i] for i in self.widget.selectedoptions]
602+
return [options[i] if isinstance(i, int) else i for i in self.widget.selectedoptions]
603603
elif self.dialogType == DIALOG_BATCH:
604604
return self.widget.getText()
605605
else:

0 commit comments

Comments
 (0)