[processing] support multiple scripts/models folders (fix #10476)

This commit is contained in:
Alexander Bruy 2016-05-24 21:56:17 +03:00
parent d17094611b
commit d16f04bf3a
11 changed files with 231 additions and 43 deletions

View File

@ -62,8 +62,8 @@ class RAlgorithmProvider(AlgorithmProvider):
AlgorithmProvider.initializeSettings(self) AlgorithmProvider.initializeSettings(self)
ProcessingConfig.addSetting(Setting( ProcessingConfig.addSetting(Setting(
self.getDescription(), RUtils.RSCRIPTS_FOLDER, self.getDescription(), RUtils.RSCRIPTS_FOLDER,
self.tr('R Scripts folder'), RUtils.RScriptsFolder(), self.tr('R Scripts folder'), RUtils.defaultRScriptsFolder(),
valuetype=Setting.FOLDER)) valuetype=Setting.MULTIPLE_FOLDERS))
if isWindows(): if isWindows():
ProcessingConfig.addSetting(Setting( ProcessingConfig.addSetting(Setting(
self.getDescription(), self.getDescription(),
@ -95,8 +95,11 @@ class RAlgorithmProvider(AlgorithmProvider):
return 'r' return 'r'
def _loadAlgorithms(self): def _loadAlgorithms(self):
folder = RUtils.RScriptsFolder() folders = RUtils.RScriptsFolders()
self.loadFromFolder(folder) self.algs = []
for f in folders:
self.loadFromFolder(f)
folder = os.path.join(os.path.dirname(__file__), 'scripts') folder = os.path.join(os.path.dirname(__file__), 'scripts')
self.loadFromFolder(folder) self.loadFromFolder(folder)

View File

@ -84,18 +84,19 @@ class RUtils:
return os.path.abspath(unicode(folder)) return os.path.abspath(unicode(folder))
@staticmethod @staticmethod
def RScriptsFolder(): def defaultRScriptsFolder():
folder = ProcessingConfig.getSetting(RUtils.RSCRIPTS_FOLDER) folder = unicode(os.path.join(userFolder(), 'rscripts'))
if folder is None: mkdir(folder)
folder = unicode(os.path.join(userFolder(), 'rscripts'))
try:
mkdir(folder)
except:
folder = unicode(os.path.join(userFolder(), 'rscripts'))
mkdir(folder)
return os.path.abspath(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 @staticmethod
def createRScriptFromRCommands(commands): def createRScriptFromRCommands(commands):
scriptfile = open(RUtils.getRScriptFilename(), 'w') scriptfile = open(RUtils.getRScriptFilename(), 'w')

View File

@ -228,6 +228,7 @@ class Setting:
SELECTION = 3 SELECTION = 3
FLOAT = 4 FLOAT = 4
INT = 5 INT = 5
MULTIPLE_FOLDERS = 6
def __init__(self, group, name, description, default, hidden=False, valuetype=None, def __init__(self, group, name, description, default, hidden=False, valuetype=None,
validator=None, options=None): validator=None, options=None):
@ -264,6 +265,13 @@ class Setting:
if v and not os.path.exists(v): if v and not os.path.exists(v):
raise ValueError(self.tr('Specified path does not exist:\n%s') % unicode(v)) raise ValueError(self.tr('Specified path does not exist:\n%s') % unicode(v))
validator = checkFileOrFolder 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: else:
def validator(x): def validator(x):
return True return True

View File

@ -54,6 +54,7 @@ from processing.core.ProcessingConfig import (ProcessingConfig,
settingsWatcher, settingsWatcher,
Setting) Setting)
from processing.core.Processing import Processing from processing.core.Processing import Processing
from processing.gui.DirectorySelectorDialog import DirectorySelectorDialog
from processing.gui.menus import updateMenus from processing.gui.menus import updateMenus
from processing.gui.menus import menusSettingsGroup from processing.gui.menus import menusSettingsGroup
@ -284,12 +285,7 @@ class SettingDelegate(QStyledItemDelegate):
def __init__(self, parent=None): def __init__(self, parent=None):
QStyledItemDelegate.__init__(self, parent) QStyledItemDelegate.__init__(self, parent)
def createEditor( def createEditor(self, parent, options, index):
self,
parent,
options,
index,
):
setting = index.model().data(index, Qt.UserRole) setting = index.model().data(index, Qt.UserRole)
if setting.valuetype == Setting.FOLDER: if setting.valuetype == Setting.FOLDER:
return FileDirectorySelector(parent) return FileDirectorySelector(parent)
@ -299,6 +295,8 @@ class SettingDelegate(QStyledItemDelegate):
combo = QComboBox(parent) combo = QComboBox(parent)
combo.addItems(setting.options) combo.addItems(setting.options)
return combo return combo
elif setting.valuetype == Setting.MULTIPLE_FOLDERS:
return MultipleDirectorySelector(parent)
else: else:
value = self.convertValue(index.model().data(index, Qt.EditRole)) value = self.convertValue(index.model().data(index, Qt.EditRole))
if isinstance(value, (int, long)): if isinstance(value, (int, long)):
@ -398,3 +396,44 @@ class FileDirectorySelector(QWidget):
def setText(self, value): def setText(self, value):
self.lineEdit.setText(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)

View File

@ -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)

View File

@ -193,10 +193,10 @@ class ScriptEditorDialog(BASE, WIDGET):
return return
if self.algType == self.SCRIPT_PYTHON: if self.algType == self.SCRIPT_PYTHON:
scriptDir = ScriptUtils.scriptsFolder() scriptDir = ScriptUtils.defaultScriptsFolder()
filterName = self.tr('Python scripts (*.py)') filterName = self.tr('Python scripts (*.py)')
elif self.algType == self.SCRIPT_R: elif self.algType == self.SCRIPT_R:
scriptDir = RUtils.RScriptsFolder() scriptDir = RUtils.defaultRScriptsFolder()
filterName = self.tr('Processing R script (*.rsx)') filterName = self.tr('Processing R script (*.rsx)')
self.filename = QFileDialog.getOpenFileName( self.filename = QFileDialog.getOpenFileName(
@ -224,10 +224,10 @@ class ScriptEditorDialog(BASE, WIDGET):
def saveScript(self, saveAs): def saveScript(self, saveAs):
if self.filename is None or saveAs: if self.filename is None or saveAs:
if self.algType == self.SCRIPT_PYTHON: if self.algType == self.SCRIPT_PYTHON:
scriptDir = ScriptUtils.scriptsFolder() scriptDir = ScriptUtils.defaultScriptsFolder()
filterName = self.tr('Python scripts (*.py)') filterName = self.tr('Python scripts (*.py)')
elif self.algType == self.SCRIPT_R: elif self.algType == self.SCRIPT_R:
scriptDir = RUtils.RScriptsFolder() scriptDir = RUtils.defaultRScriptsFolder()
filterName = self.tr('Processing R script (*.rsx)') filterName = self.tr('Processing R script (*.rsx)')
self.filename = unicode(QFileDialog.getSaveFileName(self, self.filename = unicode(QFileDialog.getSaveFileName(self,

View File

@ -55,7 +55,7 @@ class ModelerAlgorithmProvider(AlgorithmProvider):
AlgorithmProvider.initializeSettings(self) AlgorithmProvider.initializeSettings(self)
ProcessingConfig.addSetting(Setting(self.getDescription(), ProcessingConfig.addSetting(Setting(self.getDescription(),
ModelerUtils.MODELS_FOLDER, self.tr('Models folder', 'ModelerAlgorithmProvider'), ModelerUtils.MODELS_FOLDER, self.tr('Models folder', 'ModelerAlgorithmProvider'),
ModelerUtils.modelsFolder(), valuetype=Setting.FOLDER)) ModelerUtils.defaultModelsFolder(), valuetype=Setting.MULTIPLE_FOLDERS))
def modelsFolder(self): def modelsFolder(self):
return ModelerUtils.modelsFolder() return ModelerUtils.modelsFolder()
@ -70,11 +70,12 @@ class ModelerAlgorithmProvider(AlgorithmProvider):
return QIcon(os.path.join(pluginPath, 'images', 'model.png')) return QIcon(os.path.join(pluginPath, 'images', 'model.png'))
def _loadAlgorithms(self): def _loadAlgorithms(self):
folder = ModelerUtils.modelsFolder() folders = ModelerUtils.modelsFolders()
self.loadFromFolder(folder) self.algs = []
for f in folders:
self.loadFromFolder(f)
def loadFromFolder(self, folder): def loadFromFolder(self, folder):
self.algs = []
if not os.path.exists(folder): if not os.path.exists(folder):
return return
for path, subdirs, files in os.walk(folder): for path, subdirs, files in os.walk(folder):

View File

@ -310,7 +310,7 @@ class ModelerDialog(BASE, WIDGET):
else: else:
filename = unicode(QFileDialog.getSaveFileName(self, filename = unicode(QFileDialog.getSaveFileName(self,
self.tr('Save Model'), self.tr('Save Model'),
ModelerUtils.modelsFolder(), ModelerUtils.defaultModelsFolder(),
self.tr('Processing models (*.model)'))) self.tr('Processing models (*.model)')))
if filename: if filename:
if not filename.endswith('.model'): if not filename.endswith('.model'):
@ -341,7 +341,7 @@ class ModelerDialog(BASE, WIDGET):
def openModel(self): def openModel(self):
filename = unicode(QFileDialog.getOpenFileName(self, filename = unicode(QFileDialog.getOpenFileName(self,
self.tr('Open Model'), ModelerUtils.modelsFolder(), self.tr('Open Model'), ModelerUtils.defaultModelsFolder(),
self.tr('Processing models (*.model *.MODEL)'))) self.tr('Processing models (*.model *.MODEL)')))
if filename: if filename:
try: try:

View File

@ -36,11 +36,16 @@ class ModelerUtils:
ACTIVATE_MODELS = 'ACTIVATE_MODELS' ACTIVATE_MODELS = 'ACTIVATE_MODELS'
@staticmethod @staticmethod
def modelsFolder(): def defaultModelsFolder():
folder = ProcessingConfig.getSetting(ModelerUtils.MODELS_FOLDER) folder = unicode(os.path.join(userFolder(), 'models'))
if folder is None:
folder = unicode(os.path.join(userFolder(), 'models'))
mkdir(folder) mkdir(folder)
return os.path.abspath(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()]

View File

@ -58,7 +58,7 @@ class ScriptAlgorithmProvider(AlgorithmProvider):
ProcessingConfig.addSetting(Setting(self.getDescription(), ProcessingConfig.addSetting(Setting(self.getDescription(),
ScriptUtils.SCRIPTS_FOLDER, ScriptUtils.SCRIPTS_FOLDER,
self.tr('Scripts folder', 'ScriptAlgorithmProvider'), self.tr('Scripts folder', 'ScriptAlgorithmProvider'),
ScriptUtils.scriptsFolder(), valuetype=Setting.FOLDER)) ScriptUtils.defaultScriptsFolder(), valuetype=Setting.MULTIPLE_FOLDERS))
def unload(self): def unload(self):
AlgorithmProvider.unload(self) AlgorithmProvider.unload(self)
@ -74,8 +74,10 @@ class ScriptAlgorithmProvider(AlgorithmProvider):
return self.tr('Scripts', 'ScriptAlgorithmProvider') return self.tr('Scripts', 'ScriptAlgorithmProvider')
def _loadAlgorithms(self): def _loadAlgorithms(self):
folder = ScriptUtils.scriptsFolder() folders = ScriptUtils.scriptsFolders()
self.algs = ScriptUtils.loadFromFolder(folder) self.algs = []
for f in folders:
self.algs.extend(ScriptUtils.loadFromFolder(f))
def addAlgorithmsFromFolder(self, folder): def addAlgorithmsFromFolder(self, folder):
self.algs.extend(ScriptUtils.loadFromFolder(folder)) self.algs.extend(ScriptUtils.loadFromFolder(folder))

View File

@ -39,14 +39,19 @@ class ScriptUtils(object):
ACTIVATE_SCRIPTS = 'ACTIVATE_SCRIPTS' ACTIVATE_SCRIPTS = 'ACTIVATE_SCRIPTS'
@staticmethod @staticmethod
def scriptsFolder(): def defaultScriptsFolder():
folder = ProcessingConfig.getSetting(ScriptUtils.SCRIPTS_FOLDER) folder = unicode(os.path.join(userFolder(), 'scripts'))
if folder is None:
folder = unicode(os.path.join(userFolder(), 'scripts'))
mkdir(folder) mkdir(folder)
return os.path.abspath(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 @staticmethod
def loadFromFolder(folder): def loadFromFolder(folder):
if not os.path.exists(folder): if not os.path.exists(folder):