diff --git a/python/plugins/processing/algs/r/RAlgorithmProvider.py b/python/plugins/processing/algs/r/RAlgorithmProvider.py index e5d16fe9cd6..2b0cf0701c1 100644 --- a/python/plugins/processing/algs/r/RAlgorithmProvider.py +++ b/python/plugins/processing/algs/r/RAlgorithmProvider.py @@ -62,8 +62,8 @@ class RAlgorithmProvider(AlgorithmProvider): AlgorithmProvider.initializeSettings(self) ProcessingConfig.addSetting(Setting( self.getDescription(), RUtils.RSCRIPTS_FOLDER, - self.tr('R Scripts folder'), RUtils.RScriptsFolder(), - valuetype=Setting.FOLDER)) + self.tr('R Scripts folder'), RUtils.defaultRScriptsFolder(), + valuetype=Setting.MULTIPLE_FOLDERS)) if isWindows(): ProcessingConfig.addSetting(Setting( self.getDescription(), @@ -95,8 +95,11 @@ class RAlgorithmProvider(AlgorithmProvider): return 'r' def _loadAlgorithms(self): - folder = RUtils.RScriptsFolder() - self.loadFromFolder(folder) + folders = RUtils.RScriptsFolders() + self.algs = [] + for f in folders: + self.loadFromFolder(f) + folder = os.path.join(os.path.dirname(__file__), 'scripts') self.loadFromFolder(folder) diff --git a/python/plugins/processing/algs/r/RUtils.py b/python/plugins/processing/algs/r/RUtils.py index 564d5a68399..1fdc7e2127b 100644 --- a/python/plugins/processing/algs/r/RUtils.py +++ b/python/plugins/processing/algs/r/RUtils.py @@ -84,18 +84,19 @@ class RUtils: return os.path.abspath(unicode(folder)) @staticmethod - def RScriptsFolder(): - folder = ProcessingConfig.getSetting(RUtils.RSCRIPTS_FOLDER) - if folder is None: - folder = unicode(os.path.join(userFolder(), 'rscripts')) - try: - mkdir(folder) - except: - folder = unicode(os.path.join(userFolder(), 'rscripts')) - mkdir(folder) - + def defaultRScriptsFolder(): + folder = unicode(os.path.join(userFolder(), 'rscripts')) + mkdir(folder) return os.path.abspath(folder) + @staticmethod + def RScriptsFolders(): + folder = ProcessingConfig.getSetting(RUtils.RSCRIPTS_FOLDER) + if folder is not None: + return folder.split(';') + else: + return [RUtils.defaultRScriptsFolder()] + @staticmethod def createRScriptFromRCommands(commands): scriptfile = open(RUtils.getRScriptFilename(), 'w') diff --git a/python/plugins/processing/core/ProcessingConfig.py b/python/plugins/processing/core/ProcessingConfig.py index af44d9aa1ac..95ff91c2099 100644 --- a/python/plugins/processing/core/ProcessingConfig.py +++ b/python/plugins/processing/core/ProcessingConfig.py @@ -228,6 +228,7 @@ class Setting: SELECTION = 3 FLOAT = 4 INT = 5 + MULTIPLE_FOLDERS = 6 def __init__(self, group, name, description, default, hidden=False, valuetype=None, validator=None, options=None): @@ -264,6 +265,13 @@ class Setting: if v and not os.path.exists(v): raise ValueError(self.tr('Specified path does not exist:\n%s') % unicode(v)) validator = checkFileOrFolder + elif valuetype == self.MULTIPLE_FOLDERS: + def checkMultipleFolders(v): + folders = v.split(';') + for f in folders: + if f and not os.path.exists(f): + raise ValueError(self.tr('Specified path does not exist:\n%s') % unicode(f)) + validator = checkMultipleFolders else: def validator(x): return True diff --git a/python/plugins/processing/gui/ConfigDialog.py b/python/plugins/processing/gui/ConfigDialog.py index 18c43d4d3fe..7f532b99f2f 100644 --- a/python/plugins/processing/gui/ConfigDialog.py +++ b/python/plugins/processing/gui/ConfigDialog.py @@ -54,6 +54,7 @@ from processing.core.ProcessingConfig import (ProcessingConfig, settingsWatcher, Setting) from processing.core.Processing import Processing +from processing.gui.DirectorySelectorDialog import DirectorySelectorDialog from processing.gui.menus import updateMenus from processing.gui.menus import menusSettingsGroup @@ -284,12 +285,7 @@ class SettingDelegate(QStyledItemDelegate): def __init__(self, parent=None): QStyledItemDelegate.__init__(self, parent) - def createEditor( - self, - parent, - options, - index, - ): + def createEditor(self, parent, options, index): setting = index.model().data(index, Qt.UserRole) if setting.valuetype == Setting.FOLDER: return FileDirectorySelector(parent) @@ -299,6 +295,8 @@ class SettingDelegate(QStyledItemDelegate): combo = QComboBox(parent) combo.addItems(setting.options) return combo + elif setting.valuetype == Setting.MULTIPLE_FOLDERS: + return MultipleDirectorySelector(parent) else: value = self.convertValue(index.model().data(index, Qt.EditRole)) if isinstance(value, (int, long)): @@ -398,3 +396,44 @@ class FileDirectorySelector(QWidget): def setText(self, value): self.lineEdit.setText(value) + + +class MultipleDirectorySelector(QWidget): + + def __init__(self, parent=None): + QWidget.__init__(self, parent) + + # create gui + self.btnSelect = QToolButton() + self.btnSelect.setText(self.tr('...')) + self.lineEdit = QLineEdit() + self.hbl = QHBoxLayout() + self.hbl.setMargin(0) + self.hbl.setSpacing(0) + self.hbl.addWidget(self.lineEdit) + self.hbl.addWidget(self.btnSelect) + + self.setLayout(self.hbl) + + self.canFocusOut = False + + self.setFocusPolicy(Qt.StrongFocus) + self.btnSelect.clicked.connect(self.select) + + def select(self): + text = self.lineEdit.text() + if text != '': + items = text.split(';') + + dlg = DirectorySelectorDialog(None, items) + if dlg.exec_(): + text = dlg.value() + self.lineEdit.setText(text) + + self.canFocusOut = True + + def text(self): + return self.lineEdit.text() + + def setText(self, value): + self.lineEdit.setText(value) diff --git a/python/plugins/processing/gui/DirectorySelectorDialog.py b/python/plugins/processing/gui/DirectorySelectorDialog.py new file mode 100644 index 00000000000..9748751569e --- /dev/null +++ b/python/plugins/processing/gui/DirectorySelectorDialog.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- + +""" +*************************************************************************** + DirectorySelectorDialog.py + --------------------- + Date : May 2016 + Copyright : (C) 2016 by Alexander Bruy + Email : alexander dot bruy at gmail dot com +*************************************************************************** +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +*************************************************************************** +""" + +__author__ = 'Alexander Bruy' +__date__ = 'May 2016' +__copyright__ = '(C) 2016, Victor Olaya' + +# This will get replaced with a git SHA1 when you do a git archive + +__revision__ = '$Format:%H$' + +import os + +from qgis.PyQt import uic +from qgis.PyQt.QtCore import QSettings +from qgis.PyQt.QtWidgets import QDialog, QAbstractItemView, QPushButton, QDialogButtonBox, QFileDialog +from qgis.PyQt.QtGui import QStandardItemModel, QStandardItem + +pluginPath = os.path.split(os.path.dirname(__file__))[0] +WIDGET, BASE = uic.loadUiType( + os.path.join(pluginPath, 'ui', 'DlgMultipleSelection.ui')) + + +class DirectorySelectorDialog(BASE, WIDGET): + + def __init__(self, parent, options): + super(DirectorySelectorDialog, self).__init__(None) + self.setupUi(self) + + self.lstLayers.setSelectionMode(QAbstractItemView.ExtendedSelection) + + self.options = options + + # Additional buttons + self.btnAdd = QPushButton(self.tr('Add')) + self.buttonBox.addButton(self.btnAdd, + QDialogButtonBox.ActionRole) + self.btnRemove = QPushButton(self.tr('Remove')) + self.buttonBox.addButton(self.btnRemove, + QDialogButtonBox.ActionRole) + self.btnRemoveAll = QPushButton(self.tr('Remove all')) + self.buttonBox.addButton(self.btnRemoveAll, + QDialogButtonBox.ActionRole) + + self.btnAdd.clicked.connect(self.addDirectory) + self.btnRemove.clicked.connect(lambda: self.removeRows()) + self.btnRemoveAll.clicked.connect(lambda: self.removeRows(True)) + + self.populateList() + + def populateList(self): + model = QStandardItemModel() + for option in self.options: + item = QStandardItem(option) + model.appendRow(item) + + self.lstLayers.setModel(model) + + def accept(self): + self.selectedoptions = [] + model = self.lstLayers.model() + for i in xrange(model.rowCount()): + item = model.item(i) + self.selectedoptions.append(item.text()) + QDialog.accept(self) + + def reject(self): + QDialog.reject(self) + + def addDirectory(self): + settings = QSettings() + if settings.contains('/Processing/lastDirectory'): + path = settings.value('/Processing/lastDirectory') + else: + path = '' + + folder = QFileDialog.getExistingDirectory(self, + self.tr('Select directory'), + path, + QFileDialog.ShowDirsOnly) + + if folder == '': + return + + model = self.lstLayers.model() + item = QStandardItem(folder) + model.appendRow(item) + + settings.setValue('/Processing/lastDirectory', + os.path.dirname(folder)) + + def removeRows(self, removeAll=False): + if removeAll: + self.lstLayers.model().clear() + else: + self.lstLayers.setUpdatesEnabled(False) + indexes = sorted(self.lstLayers.selectionModel().selectedIndexes()) + for i in reversed(indexes): + self.lstLayers.model().removeRow(i.row()) + self.lstLayers.setUpdatesEnabled(True) + + def value(self): + folders = [] + model = self.lstLayers.model() + for i in xrange(model.rowCount()): + folders.append(model.item(i).text()) + + return ';'.join(folders) diff --git a/python/plugins/processing/gui/ScriptEditorDialog.py b/python/plugins/processing/gui/ScriptEditorDialog.py index b3818f4fafc..6beadbbc791 100644 --- a/python/plugins/processing/gui/ScriptEditorDialog.py +++ b/python/plugins/processing/gui/ScriptEditorDialog.py @@ -193,10 +193,10 @@ class ScriptEditorDialog(BASE, WIDGET): return if self.algType == self.SCRIPT_PYTHON: - scriptDir = ScriptUtils.scriptsFolder() + scriptDir = ScriptUtils.defaultScriptsFolder() filterName = self.tr('Python scripts (*.py)') elif self.algType == self.SCRIPT_R: - scriptDir = RUtils.RScriptsFolder() + scriptDir = RUtils.defaultRScriptsFolder() filterName = self.tr('Processing R script (*.rsx)') self.filename = QFileDialog.getOpenFileName( @@ -224,10 +224,10 @@ class ScriptEditorDialog(BASE, WIDGET): def saveScript(self, saveAs): if self.filename is None or saveAs: if self.algType == self.SCRIPT_PYTHON: - scriptDir = ScriptUtils.scriptsFolder() + scriptDir = ScriptUtils.defaultScriptsFolder() filterName = self.tr('Python scripts (*.py)') elif self.algType == self.SCRIPT_R: - scriptDir = RUtils.RScriptsFolder() + scriptDir = RUtils.defaultRScriptsFolder() filterName = self.tr('Processing R script (*.rsx)') self.filename = unicode(QFileDialog.getSaveFileName(self, diff --git a/python/plugins/processing/modeler/ModelerAlgorithmProvider.py b/python/plugins/processing/modeler/ModelerAlgorithmProvider.py index 7925efbc2c0..bb06a8a5eb1 100644 --- a/python/plugins/processing/modeler/ModelerAlgorithmProvider.py +++ b/python/plugins/processing/modeler/ModelerAlgorithmProvider.py @@ -55,7 +55,7 @@ class ModelerAlgorithmProvider(AlgorithmProvider): AlgorithmProvider.initializeSettings(self) ProcessingConfig.addSetting(Setting(self.getDescription(), ModelerUtils.MODELS_FOLDER, self.tr('Models folder', 'ModelerAlgorithmProvider'), - ModelerUtils.modelsFolder(), valuetype=Setting.FOLDER)) + ModelerUtils.defaultModelsFolder(), valuetype=Setting.MULTIPLE_FOLDERS)) def modelsFolder(self): return ModelerUtils.modelsFolder() @@ -70,11 +70,12 @@ class ModelerAlgorithmProvider(AlgorithmProvider): return QIcon(os.path.join(pluginPath, 'images', 'model.png')) def _loadAlgorithms(self): - folder = ModelerUtils.modelsFolder() - self.loadFromFolder(folder) + folders = ModelerUtils.modelsFolders() + self.algs = [] + for f in folders: + self.loadFromFolder(f) def loadFromFolder(self, folder): - self.algs = [] if not os.path.exists(folder): return for path, subdirs, files in os.walk(folder): diff --git a/python/plugins/processing/modeler/ModelerDialog.py b/python/plugins/processing/modeler/ModelerDialog.py index 01882f3f47b..1ade5941fd3 100644 --- a/python/plugins/processing/modeler/ModelerDialog.py +++ b/python/plugins/processing/modeler/ModelerDialog.py @@ -310,7 +310,7 @@ class ModelerDialog(BASE, WIDGET): else: filename = unicode(QFileDialog.getSaveFileName(self, self.tr('Save Model'), - ModelerUtils.modelsFolder(), + ModelerUtils.defaultModelsFolder(), self.tr('Processing models (*.model)'))) if filename: if not filename.endswith('.model'): @@ -341,7 +341,7 @@ class ModelerDialog(BASE, WIDGET): def openModel(self): filename = unicode(QFileDialog.getOpenFileName(self, - self.tr('Open Model'), ModelerUtils.modelsFolder(), + self.tr('Open Model'), ModelerUtils.defaultModelsFolder(), self.tr('Processing models (*.model *.MODEL)'))) if filename: try: diff --git a/python/plugins/processing/modeler/ModelerUtils.py b/python/plugins/processing/modeler/ModelerUtils.py index 4481856d13b..b7fba8135a5 100644 --- a/python/plugins/processing/modeler/ModelerUtils.py +++ b/python/plugins/processing/modeler/ModelerUtils.py @@ -36,11 +36,16 @@ class ModelerUtils: ACTIVATE_MODELS = 'ACTIVATE_MODELS' @staticmethod - def modelsFolder(): - folder = ProcessingConfig.getSetting(ModelerUtils.MODELS_FOLDER) - if folder is None: - folder = unicode(os.path.join(userFolder(), 'models')) + def defaultModelsFolder(): + folder = unicode(os.path.join(userFolder(), 'models')) mkdir(folder) - return os.path.abspath(folder) + @staticmethod + def modelsFolders(): + folder = ProcessingConfig.getSetting(ModelerUtils.MODELS_FOLDER) + if folder is not None: + return folder.split(';') + else: + return [ModelerUtils.defaultModelsFolder()] + diff --git a/python/plugins/processing/script/ScriptAlgorithmProvider.py b/python/plugins/processing/script/ScriptAlgorithmProvider.py index 404c478c6ec..5f2c2e03ef7 100644 --- a/python/plugins/processing/script/ScriptAlgorithmProvider.py +++ b/python/plugins/processing/script/ScriptAlgorithmProvider.py @@ -58,7 +58,7 @@ class ScriptAlgorithmProvider(AlgorithmProvider): ProcessingConfig.addSetting(Setting(self.getDescription(), ScriptUtils.SCRIPTS_FOLDER, self.tr('Scripts folder', 'ScriptAlgorithmProvider'), - ScriptUtils.scriptsFolder(), valuetype=Setting.FOLDER)) + ScriptUtils.defaultScriptsFolder(), valuetype=Setting.MULTIPLE_FOLDERS)) def unload(self): AlgorithmProvider.unload(self) @@ -74,8 +74,10 @@ class ScriptAlgorithmProvider(AlgorithmProvider): return self.tr('Scripts', 'ScriptAlgorithmProvider') def _loadAlgorithms(self): - folder = ScriptUtils.scriptsFolder() - self.algs = ScriptUtils.loadFromFolder(folder) + folders = ScriptUtils.scriptsFolders() + self.algs = [] + for f in folders: + self.algs.extend(ScriptUtils.loadFromFolder(f)) def addAlgorithmsFromFolder(self, folder): self.algs.extend(ScriptUtils.loadFromFolder(folder)) diff --git a/python/plugins/processing/script/ScriptUtils.py b/python/plugins/processing/script/ScriptUtils.py index 11c0b12ec90..021f6e579fd 100644 --- a/python/plugins/processing/script/ScriptUtils.py +++ b/python/plugins/processing/script/ScriptUtils.py @@ -39,14 +39,19 @@ class ScriptUtils(object): ACTIVATE_SCRIPTS = 'ACTIVATE_SCRIPTS' @staticmethod - def scriptsFolder(): - folder = ProcessingConfig.getSetting(ScriptUtils.SCRIPTS_FOLDER) - if folder is None: - folder = unicode(os.path.join(userFolder(), 'scripts')) + def defaultScriptsFolder(): + folder = unicode(os.path.join(userFolder(), 'scripts')) mkdir(folder) - return os.path.abspath(folder) + @staticmethod + def scriptsFolders(): + folder = ProcessingConfig.getSetting(ScriptUtils.SCRIPTS_FOLDER) + if folder is not None: + return folder.split(';') + else: + return [ScriptUtils.defaultScriptsFolder()] + @staticmethod def loadFromFolder(folder): if not os.path.exists(folder):