Skip to content

Commit d16f04b

Browse files
committed
[processing] support multiple scripts/models folders (fix #10476)
1 parent d170946 commit d16f04b

11 files changed

+231
-43
lines changed

python/plugins/processing/algs/r/RAlgorithmProvider.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ def initializeSettings(self):
6262
AlgorithmProvider.initializeSettings(self)
6363
ProcessingConfig.addSetting(Setting(
6464
self.getDescription(), RUtils.RSCRIPTS_FOLDER,
65-
self.tr('R Scripts folder'), RUtils.RScriptsFolder(),
66-
valuetype=Setting.FOLDER))
65+
self.tr('R Scripts folder'), RUtils.defaultRScriptsFolder(),
66+
valuetype=Setting.MULTIPLE_FOLDERS))
6767
if isWindows():
6868
ProcessingConfig.addSetting(Setting(
6969
self.getDescription(),
@@ -95,8 +95,11 @@ def getName(self):
9595
return 'r'
9696

9797
def _loadAlgorithms(self):
98-
folder = RUtils.RScriptsFolder()
99-
self.loadFromFolder(folder)
98+
folders = RUtils.RScriptsFolders()
99+
self.algs = []
100+
for f in folders:
101+
self.loadFromFolder(f)
102+
100103
folder = os.path.join(os.path.dirname(__file__), 'scripts')
101104
self.loadFromFolder(folder)
102105

python/plugins/processing/algs/r/RUtils.py

+11-10
Original file line numberDiff line numberDiff line change
@@ -84,18 +84,19 @@ def RLibs():
8484
return os.path.abspath(unicode(folder))
8585

8686
@staticmethod
87-
def RScriptsFolder():
88-
folder = ProcessingConfig.getSetting(RUtils.RSCRIPTS_FOLDER)
89-
if folder is None:
90-
folder = unicode(os.path.join(userFolder(), 'rscripts'))
91-
try:
92-
mkdir(folder)
93-
except:
94-
folder = unicode(os.path.join(userFolder(), 'rscripts'))
95-
mkdir(folder)
96-
87+
def defaultRScriptsFolder():
88+
folder = unicode(os.path.join(userFolder(), 'rscripts'))
89+
mkdir(folder)
9790
return os.path.abspath(folder)
9891

92+
@staticmethod
93+
def RScriptsFolders():
94+
folder = ProcessingConfig.getSetting(RUtils.RSCRIPTS_FOLDER)
95+
if folder is not None:
96+
return folder.split(';')
97+
else:
98+
return [RUtils.defaultRScriptsFolder()]
99+
99100
@staticmethod
100101
def createRScriptFromRCommands(commands):
101102
scriptfile = open(RUtils.getRScriptFilename(), 'w')

python/plugins/processing/core/ProcessingConfig.py

+8
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ class Setting:
228228
SELECTION = 3
229229
FLOAT = 4
230230
INT = 5
231+
MULTIPLE_FOLDERS = 6
231232

232233
def __init__(self, group, name, description, default, hidden=False, valuetype=None,
233234
validator=None, options=None):
@@ -264,6 +265,13 @@ def checkFileOrFolder(v):
264265
if v and not os.path.exists(v):
265266
raise ValueError(self.tr('Specified path does not exist:\n%s') % unicode(v))
266267
validator = checkFileOrFolder
268+
elif valuetype == self.MULTIPLE_FOLDERS:
269+
def checkMultipleFolders(v):
270+
folders = v.split(';')
271+
for f in folders:
272+
if f and not os.path.exists(f):
273+
raise ValueError(self.tr('Specified path does not exist:\n%s') % unicode(f))
274+
validator = checkMultipleFolders
267275
else:
268276
def validator(x):
269277
return True

python/plugins/processing/gui/ConfigDialog.py

+45-6
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
settingsWatcher,
5555
Setting)
5656
from processing.core.Processing import Processing
57+
from processing.gui.DirectorySelectorDialog import DirectorySelectorDialog
5758
from processing.gui.menus import updateMenus
5859
from processing.gui.menus import menusSettingsGroup
5960

@@ -284,12 +285,7 @@ class SettingDelegate(QStyledItemDelegate):
284285
def __init__(self, parent=None):
285286
QStyledItemDelegate.__init__(self, parent)
286287

287-
def createEditor(
288-
self,
289-
parent,
290-
options,
291-
index,
292-
):
288+
def createEditor(self, parent, options, index):
293289
setting = index.model().data(index, Qt.UserRole)
294290
if setting.valuetype == Setting.FOLDER:
295291
return FileDirectorySelector(parent)
@@ -299,6 +295,8 @@ def createEditor(
299295
combo = QComboBox(parent)
300296
combo.addItems(setting.options)
301297
return combo
298+
elif setting.valuetype == Setting.MULTIPLE_FOLDERS:
299+
return MultipleDirectorySelector(parent)
302300
else:
303301
value = self.convertValue(index.model().data(index, Qt.EditRole))
304302
if isinstance(value, (int, long)):
@@ -398,3 +396,44 @@ def text(self):
398396

399397
def setText(self, value):
400398
self.lineEdit.setText(value)
399+
400+
401+
class MultipleDirectorySelector(QWidget):
402+
403+
def __init__(self, parent=None):
404+
QWidget.__init__(self, parent)
405+
406+
# create gui
407+
self.btnSelect = QToolButton()
408+
self.btnSelect.setText(self.tr('...'))
409+
self.lineEdit = QLineEdit()
410+
self.hbl = QHBoxLayout()
411+
self.hbl.setMargin(0)
412+
self.hbl.setSpacing(0)
413+
self.hbl.addWidget(self.lineEdit)
414+
self.hbl.addWidget(self.btnSelect)
415+
416+
self.setLayout(self.hbl)
417+
418+
self.canFocusOut = False
419+
420+
self.setFocusPolicy(Qt.StrongFocus)
421+
self.btnSelect.clicked.connect(self.select)
422+
423+
def select(self):
424+
text = self.lineEdit.text()
425+
if text != '':
426+
items = text.split(';')
427+
428+
dlg = DirectorySelectorDialog(None, items)
429+
if dlg.exec_():
430+
text = dlg.value()
431+
self.lineEdit.setText(text)
432+
433+
self.canFocusOut = True
434+
435+
def text(self):
436+
return self.lineEdit.text()
437+
438+
def setText(self, value):
439+
self.lineEdit.setText(value)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
***************************************************************************
5+
DirectorySelectorDialog.py
6+
---------------------
7+
Date : May 2016
8+
Copyright : (C) 2016 by Alexander Bruy
9+
Email : alexander dot bruy at gmail dot com
10+
***************************************************************************
11+
* *
12+
* This program is free software; you can redistribute it and/or modify *
13+
* it under the terms of the GNU General Public License as published by *
14+
* the Free Software Foundation; either version 2 of the License, or *
15+
* (at your option) any later version. *
16+
* *
17+
***************************************************************************
18+
"""
19+
20+
__author__ = 'Alexander Bruy'
21+
__date__ = 'May 2016'
22+
__copyright__ = '(C) 2016, Victor Olaya'
23+
24+
# This will get replaced with a git SHA1 when you do a git archive
25+
26+
__revision__ = '$Format:%H$'
27+
28+
import os
29+
30+
from qgis.PyQt import uic
31+
from qgis.PyQt.QtCore import QSettings
32+
from qgis.PyQt.QtWidgets import QDialog, QAbstractItemView, QPushButton, QDialogButtonBox, QFileDialog
33+
from qgis.PyQt.QtGui import QStandardItemModel, QStandardItem
34+
35+
pluginPath = os.path.split(os.path.dirname(__file__))[0]
36+
WIDGET, BASE = uic.loadUiType(
37+
os.path.join(pluginPath, 'ui', 'DlgMultipleSelection.ui'))
38+
39+
40+
class DirectorySelectorDialog(BASE, WIDGET):
41+
42+
def __init__(self, parent, options):
43+
super(DirectorySelectorDialog, self).__init__(None)
44+
self.setupUi(self)
45+
46+
self.lstLayers.setSelectionMode(QAbstractItemView.ExtendedSelection)
47+
48+
self.options = options
49+
50+
# Additional buttons
51+
self.btnAdd = QPushButton(self.tr('Add'))
52+
self.buttonBox.addButton(self.btnAdd,
53+
QDialogButtonBox.ActionRole)
54+
self.btnRemove = QPushButton(self.tr('Remove'))
55+
self.buttonBox.addButton(self.btnRemove,
56+
QDialogButtonBox.ActionRole)
57+
self.btnRemoveAll = QPushButton(self.tr('Remove all'))
58+
self.buttonBox.addButton(self.btnRemoveAll,
59+
QDialogButtonBox.ActionRole)
60+
61+
self.btnAdd.clicked.connect(self.addDirectory)
62+
self.btnRemove.clicked.connect(lambda: self.removeRows())
63+
self.btnRemoveAll.clicked.connect(lambda: self.removeRows(True))
64+
65+
self.populateList()
66+
67+
def populateList(self):
68+
model = QStandardItemModel()
69+
for option in self.options:
70+
item = QStandardItem(option)
71+
model.appendRow(item)
72+
73+
self.lstLayers.setModel(model)
74+
75+
def accept(self):
76+
self.selectedoptions = []
77+
model = self.lstLayers.model()
78+
for i in xrange(model.rowCount()):
79+
item = model.item(i)
80+
self.selectedoptions.append(item.text())
81+
QDialog.accept(self)
82+
83+
def reject(self):
84+
QDialog.reject(self)
85+
86+
def addDirectory(self):
87+
settings = QSettings()
88+
if settings.contains('/Processing/lastDirectory'):
89+
path = settings.value('/Processing/lastDirectory')
90+
else:
91+
path = ''
92+
93+
folder = QFileDialog.getExistingDirectory(self,
94+
self.tr('Select directory'),
95+
path,
96+
QFileDialog.ShowDirsOnly)
97+
98+
if folder == '':
99+
return
100+
101+
model = self.lstLayers.model()
102+
item = QStandardItem(folder)
103+
model.appendRow(item)
104+
105+
settings.setValue('/Processing/lastDirectory',
106+
os.path.dirname(folder))
107+
108+
def removeRows(self, removeAll=False):
109+
if removeAll:
110+
self.lstLayers.model().clear()
111+
else:
112+
self.lstLayers.setUpdatesEnabled(False)
113+
indexes = sorted(self.lstLayers.selectionModel().selectedIndexes())
114+
for i in reversed(indexes):
115+
self.lstLayers.model().removeRow(i.row())
116+
self.lstLayers.setUpdatesEnabled(True)
117+
118+
def value(self):
119+
folders = []
120+
model = self.lstLayers.model()
121+
for i in xrange(model.rowCount()):
122+
folders.append(model.item(i).text())
123+
124+
return ';'.join(folders)

python/plugins/processing/gui/ScriptEditorDialog.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -193,10 +193,10 @@ def openScript(self):
193193
return
194194

195195
if self.algType == self.SCRIPT_PYTHON:
196-
scriptDir = ScriptUtils.scriptsFolder()
196+
scriptDir = ScriptUtils.defaultScriptsFolder()
197197
filterName = self.tr('Python scripts (*.py)')
198198
elif self.algType == self.SCRIPT_R:
199-
scriptDir = RUtils.RScriptsFolder()
199+
scriptDir = RUtils.defaultRScriptsFolder()
200200
filterName = self.tr('Processing R script (*.rsx)')
201201

202202
self.filename = QFileDialog.getOpenFileName(
@@ -224,10 +224,10 @@ def saveAs(self):
224224
def saveScript(self, saveAs):
225225
if self.filename is None or saveAs:
226226
if self.algType == self.SCRIPT_PYTHON:
227-
scriptDir = ScriptUtils.scriptsFolder()
227+
scriptDir = ScriptUtils.defaultScriptsFolder()
228228
filterName = self.tr('Python scripts (*.py)')
229229
elif self.algType == self.SCRIPT_R:
230-
scriptDir = RUtils.RScriptsFolder()
230+
scriptDir = RUtils.defaultRScriptsFolder()
231231
filterName = self.tr('Processing R script (*.rsx)')
232232

233233
self.filename = unicode(QFileDialog.getSaveFileName(self,

python/plugins/processing/modeler/ModelerAlgorithmProvider.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def initializeSettings(self):
5555
AlgorithmProvider.initializeSettings(self)
5656
ProcessingConfig.addSetting(Setting(self.getDescription(),
5757
ModelerUtils.MODELS_FOLDER, self.tr('Models folder', 'ModelerAlgorithmProvider'),
58-
ModelerUtils.modelsFolder(), valuetype=Setting.FOLDER))
58+
ModelerUtils.defaultModelsFolder(), valuetype=Setting.MULTIPLE_FOLDERS))
5959

6060
def modelsFolder(self):
6161
return ModelerUtils.modelsFolder()
@@ -70,11 +70,12 @@ def getIcon(self):
7070
return QIcon(os.path.join(pluginPath, 'images', 'model.png'))
7171

7272
def _loadAlgorithms(self):
73-
folder = ModelerUtils.modelsFolder()
74-
self.loadFromFolder(folder)
73+
folders = ModelerUtils.modelsFolders()
74+
self.algs = []
75+
for f in folders:
76+
self.loadFromFolder(f)
7577

7678
def loadFromFolder(self, folder):
77-
self.algs = []
7879
if not os.path.exists(folder):
7980
return
8081
for path, subdirs, files in os.walk(folder):

python/plugins/processing/modeler/ModelerDialog.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ def saveModel(self, saveAs):
310310
else:
311311
filename = unicode(QFileDialog.getSaveFileName(self,
312312
self.tr('Save Model'),
313-
ModelerUtils.modelsFolder(),
313+
ModelerUtils.defaultModelsFolder(),
314314
self.tr('Processing models (*.model)')))
315315
if filename:
316316
if not filename.endswith('.model'):
@@ -341,7 +341,7 @@ def saveModel(self, saveAs):
341341

342342
def openModel(self):
343343
filename = unicode(QFileDialog.getOpenFileName(self,
344-
self.tr('Open Model'), ModelerUtils.modelsFolder(),
344+
self.tr('Open Model'), ModelerUtils.defaultModelsFolder(),
345345
self.tr('Processing models (*.model *.MODEL)')))
346346
if filename:
347347
try:

python/plugins/processing/modeler/ModelerUtils.py

+10-5
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,16 @@ class ModelerUtils:
3636
ACTIVATE_MODELS = 'ACTIVATE_MODELS'
3737

3838
@staticmethod
39-
def modelsFolder():
40-
folder = ProcessingConfig.getSetting(ModelerUtils.MODELS_FOLDER)
41-
if folder is None:
42-
folder = unicode(os.path.join(userFolder(), 'models'))
39+
def defaultModelsFolder():
40+
folder = unicode(os.path.join(userFolder(), 'models'))
4341
mkdir(folder)
44-
4542
return os.path.abspath(folder)
4643

44+
@staticmethod
45+
def modelsFolders():
46+
folder = ProcessingConfig.getSetting(ModelerUtils.MODELS_FOLDER)
47+
if folder is not None:
48+
return folder.split(';')
49+
else:
50+
return [ModelerUtils.defaultModelsFolder()]
51+

python/plugins/processing/script/ScriptAlgorithmProvider.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def initializeSettings(self):
5858
ProcessingConfig.addSetting(Setting(self.getDescription(),
5959
ScriptUtils.SCRIPTS_FOLDER,
6060
self.tr('Scripts folder', 'ScriptAlgorithmProvider'),
61-
ScriptUtils.scriptsFolder(), valuetype=Setting.FOLDER))
61+
ScriptUtils.defaultScriptsFolder(), valuetype=Setting.MULTIPLE_FOLDERS))
6262

6363
def unload(self):
6464
AlgorithmProvider.unload(self)
@@ -74,8 +74,10 @@ def getDescription(self):
7474
return self.tr('Scripts', 'ScriptAlgorithmProvider')
7575

7676
def _loadAlgorithms(self):
77-
folder = ScriptUtils.scriptsFolder()
78-
self.algs = ScriptUtils.loadFromFolder(folder)
77+
folders = ScriptUtils.scriptsFolders()
78+
self.algs = []
79+
for f in folders:
80+
self.algs.extend(ScriptUtils.loadFromFolder(f))
7981

8082
def addAlgorithmsFromFolder(self, folder):
8183
self.algs.extend(ScriptUtils.loadFromFolder(folder))

0 commit comments

Comments
 (0)