diff --git a/python/plugins/processing/gui/GetScriptsAndModels.py b/python/plugins/processing/gui/GetScriptsAndModels.py new file mode 100644 index 00000000000..cbf4f4055cf --- /dev/null +++ b/python/plugins/processing/gui/GetScriptsAndModels.py @@ -0,0 +1,237 @@ +# -*- coding: utf-8 -*- + +""" +*************************************************************************** + GetScriptsAndModels.py + --------------------- + Date : June 2014 + Copyright : (C) 2014 by Victor Olaya + Email : volayaf 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. * +* * +*************************************************************************** +""" +import os +from PyQt4 import QtGui +from processing.ui.ui_DlgGetScriptsAndModels import Ui_DlgGetScriptsAndModels +from processing.script.ScriptUtils import ScriptUtils +from processing.modeler.ModelerUtils import ModelerUtils +import json +from processing.gui import Help2Html +from qgis.utils import iface +from processing.gui.Help2Html import getDescription, ALG_DESC, ALG_VERSION,\ + ALG_CREATOR + +__author__ = 'Victor Olaya' +__date__ = 'June 2014' +__copyright__ = '(C) 201, Victor Olaya' + +# This will get replaced with a git SHA1 when you do a git archive + +__revision__ = '$Format:%H$' + +import urllib2 +from urllib2 import HTTPError +from PyQt4.QtCore import * +from PyQt4.QtGui import * +from processing.gui.ToolboxAction import ToolboxAction + + +class GetScriptsAction(ToolboxAction): + + def __init__(self): + self.name = "Get scripts from on-line scripts collection" + self.group = 'Tools' + + def getIcon(self): + return QIcon(':/processing/images/script.png') + + def execute(self): + try: + dlg = GetScriptsAndModelsDialog(GetScriptsAndModelsDialog.SCRIPTS) + dlg.exec_() + if dlg.updateToolbox: + self.toolbox.updateProvider('script') + + except HTTPError: + QMessageBox.critical(iface.mainWindow(), "Connection problem", "Could not connect to scripts/models repository") + +class GetModelsAction(ToolboxAction): + + def __init__(self): + self.name = "Get models from on-line scripts collection" + self.group = 'Tools' + + def getIcon(self): + return QIcon(':/processing/images/model.png') + + def execute(self): + try: + dlg = GetScriptsAndModelsDialog(GetScriptsAndModelsDialog.MODELS) + dlg.exec_() + if dlg.updateToolbox: + self.toolbox.updateProvider('model') + except HTTPError: + QMessageBox.critical(iface.mainWindow(), "Connection problem", "Could not connect to scripts/models repository") + + +def readUrl(url): + try: + QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) + return urllib2.urlopen(url).read() + finally: + QApplication.restoreOverrideCursor() + + +class GetScriptsAndModelsDialog(QDialog, Ui_DlgGetScriptsAndModels): + + HELP_TEXT = ("

Processing resources manager

" + "

Check/uncheck algorithms in the tree to select the ones that you want to install or remove

" + "

Algorithms are divided in 3 groups:

" + "") + MODELS = 0 + SCRIPTS = 1 + + def __init__(self, resourceType): + QDialog.__init__(self, iface.mainWindow()) + self.resourceType = resourceType + if self.resourceType == self.MODELS: + self.folder = ModelerUtils.modelsFolder() + self.urlBase = "https://raw.githubusercontent.com/qgis/QGIS-Processing/master/models/" + self.icon = QtGui.QIcon(os.path.dirname(__file__) + '/../images/model.png') + else: + self.folder = ScriptUtils.scriptsFolder() + self.urlBase = "https://raw.githubusercontent.com/qgis/QGIS-Processing/master/scripts/" + self.icon = QtGui.QIcon(os.path.dirname(__file__) + '/../images/script.png') + self.lastSelectedItem = None + self.setupUi(self) + self.populateTree() + self.updateToolbox = False + self.buttonBox.accepted.connect(self.okPressed) + self.buttonBox.rejected.connect(self.cancelPressed) + self.tree.currentItemChanged .connect(self.currentItemChanged) + + def populateTree(self): + self.uptodateItem = QTreeWidgetItem() + self.uptodateItem.setText(0, "Installed") + self.toupdateItem = QTreeWidgetItem() + self.toupdateItem.setText(0, "Upgradable") + self.notinstalledItem = QTreeWidgetItem() + self.notinstalledItem.setText(0, "Not installed") + self.toupdateItem.setIcon(0, self.icon) + self.uptodateItem.setIcon(0, self.icon) + self.notinstalledItem.setIcon(0, self.icon) + resources = readUrl(self.urlBase + "list.txt").splitlines() + resources = [r.split(",") for r in resources] + for filename, version, name in resources: + treeBranch = self.getTreeBranchForState(filename, float(version)) + item = TreeItem(filename, name, self.icon) + treeBranch.addChild(item) + if treeBranch != self.notinstalledItem: + item.setCheckState(0, Qt.Checked) + + self.tree.addTopLevelItem(self.toupdateItem) + self.tree.addTopLevelItem(self.notinstalledItem) + self.tree.addTopLevelItem(self.uptodateItem) + + self.webView.setHtml(self.HELP_TEXT) + + def currentItemChanged(self, item, prev): + if isinstance(item, TreeItem): + try: + url = self.urlBase + item.filename.replace(" ","%20") + ".help" + helpContent = readUrl(url) + descriptions = json.loads(helpContent) + html = "

%s

" % item.name + html+="

Description: " + getDescription(ALG_DESC, descriptions)+"

" + html+="

Created by: " + getDescription(ALG_CREATOR, descriptions)+"

" + html+="

Version: " + getDescription(ALG_VERSION, descriptions)+"

" + except HTTPError, e: + html = "

No detailed description available for this script

" + self.webView.setHtml(html) + else: + self.webView.setHtml(self.HELP_TEXT) + + def getTreeBranchForState(self, filename, version): + if not os.path.exists(os.path.join(self.folder, filename)): + return self.notinstalledItem + else: + helpFile = os.path.join(self.folder, filename + ".help") + if not os.path.exists(helpFile): + currentVersion = 1 + else: + with open(helpFile) as f: + helpContent = json.load(f) + try: + currentVersion = float(helpContent[Help2Html.ALG_VERSION]) + except: + currentVersion = 1 + print filename, currentVersion, version + if version > currentVersion: + print version - currentVersion + return self.toupdateItem + else: + return self.uptodateItem + + + def cancelPressed(self): + self.close() + + def okPressed(self): + toDownload = [] + for i in xrange(self.toupdateItem.childCount()): + item = self.toupdateItem.child(i) + if item.checkState(0) == Qt.Checked: + toDownload.append(item.filename) + for i in xrange(self.notinstalledItem.childCount()): + item = self.notinstalledItem.child(i) + if item.checkState(0) == Qt.Checked: + toDownload.append(item.filename) + + if toDownload: + self.progressBar.setMaximum(len(toDownload)) + for i, filename in enumerate(toDownload): + QCoreApplication.processEvents() + url = self.urlBase + filename.replace(" ","%20") + code = readUrl(url) + path = os.path.join(self.folder, filename) + with open(path, "w") as f: + f.write(code) + self.progressBar.setValue(i + 1) + + toDelete = [] + for i in xrange(self.uptodateItem.childCount()): + item = self.uptodateItem.child(i) + if item.checkState(0) == Qt.Unchecked: + toDelete.append(item.filename) + for filename in toDelete: + path = os.path.join(self.folder, filename) + os.remove(path) + + self.updateToolbox = len(toDownload) + len(toDelete)> 0 + self.close() + + +class TreeItem(QTreeWidgetItem): + + def __init__(self, filename, name, icon): + QTreeWidgetItem.__init__(self) + self.name = name + self.filename = filename + self.setText(0, name) + self.setIcon(0, icon) + self.setCheckState(0, Qt.Unchecked) + + + + + + + \ No newline at end of file diff --git a/python/plugins/processing/gui/Help2Html.py b/python/plugins/processing/gui/Help2Html.py index a508f7f01c8..a001d1a1f19 100644 --- a/python/plugins/processing/gui/Help2Html.py +++ b/python/plugins/processing/gui/Help2Html.py @@ -33,6 +33,7 @@ import json ALG_DESC = 'ALG_DESC' ALG_CREATOR = 'ALG_CREATOR' ALG_HELP_CREATOR = 'ALG_HELP_CREATOR' +ALG_VERSION = 'ALG_VERSION' exps = [(r"\*(.*?)\*", r"\1"), ("``(.*?)``", r'\1'), @@ -56,7 +57,6 @@ def getHtmlFromRstFile(rst): def getHtmlFromHelpFile(alg, helpFile): if not os.path.exists(helpFile): return None - alg = alg with open(helpFile) as f: descriptions = json.load(f) s = '

Algorithm description

\n' @@ -72,6 +72,7 @@ def getHtmlFromHelpFile(alg, helpFile): s += '
' s += '

Algorithm author: ' + getDescription(ALG_CREATOR, descriptions) + '

' s += '

Help author: ' + getDescription(ALG_HELP_CREATOR, descriptions) + '

' + s += '

Algorithm version: ' + getDescription(ALG_VERSION, descriptions) + '

' s += '' return s @@ -80,3 +81,4 @@ def getDescription(name, descriptions): return descriptions[name].replace("\n", "
") else: return '' + diff --git a/python/plugins/processing/gui/HelpEditionDialog.py b/python/plugins/processing/gui/HelpEditionDialog.py index 80878e94cc9..5336f1a6731 100644 --- a/python/plugins/processing/gui/HelpEditionDialog.py +++ b/python/plugins/processing/gui/HelpEditionDialog.py @@ -40,6 +40,7 @@ class HelpEditionDialog(QDialog, Ui_DlgHelpEdition): ALG_DESC = 'ALG_DESC' ALG_CREATOR = 'ALG_CREATOR' ALG_HELP_CREATOR = 'ALG_HELP_CREATOR' + ALG_VERSION = 'ALG_VERSION' def __init__(self, alg): QDialog.__init__(self) @@ -117,6 +118,9 @@ class HelpEditionDialog(QDialog, Ui_DlgHelpEdition): item = TreeDescriptionItem('Algorithm help written by', self.ALG_HELP_CREATOR) self.tree.addTopLevelItem(item) + item = TreeDescriptionItem('Algorithm version', + self.ALG_VERSION) + self.tree.addTopLevelItem(item) def changeItem(self): item = self.tree.currentItem() diff --git a/python/plugins/processing/modeler/ModelerAlgorithmProvider.py b/python/plugins/processing/modeler/ModelerAlgorithmProvider.py index 2b092f0e563..6f77f9d2900 100644 --- a/python/plugins/processing/modeler/ModelerAlgorithmProvider.py +++ b/python/plugins/processing/modeler/ModelerAlgorithmProvider.py @@ -40,13 +40,14 @@ from processing.modeler.EditModelAction import EditModelAction from processing.modeler.CreateNewModelAction import CreateNewModelAction from processing.modeler.DeleteModelAction import DeleteModelAction from processing.modeler.AddModelFromFileAction import AddModelFromFileAction +from processing.gui.GetScriptsAndModels import GetModelsAction class ModelerAlgorithmProvider(AlgorithmProvider): def __init__(self): AlgorithmProvider.__init__(self) - self.actions = [CreateNewModelAction(), AddModelFromFileAction()] + self.actions = [CreateNewModelAction(), AddModelFromFileAction(), GetModelsAction()] self.contextMenuActions = [EditModelAction(), DeleteModelAction(), SaveAsPythonScriptAction()] diff --git a/python/plugins/processing/script/ScriptAlgorithmProvider.py b/python/plugins/processing/script/ScriptAlgorithmProvider.py index d259696899b..ebc888d14bd 100644 --- a/python/plugins/processing/script/ScriptAlgorithmProvider.py +++ b/python/plugins/processing/script/ScriptAlgorithmProvider.py @@ -38,7 +38,7 @@ from processing.script.ScriptAlgorithm import ScriptAlgorithm from processing.script.ScriptUtils import ScriptUtils from processing.script.WrongScriptException import WrongScriptException from processing.script.AddScriptFromFileAction import AddScriptFromFileAction - +from processing.gui.GetScriptsAndModels import GetScriptsAction import processing.resources_rc @@ -48,7 +48,8 @@ class ScriptAlgorithmProvider(AlgorithmProvider): AlgorithmProvider.__init__(self) self.actions.extend([CreateNewScriptAction('Create new script', CreateNewScriptAction.SCRIPT_PYTHON), - AddScriptFromFileAction()]) + AddScriptFromFileAction(), + GetScriptsAction()]) self.contextMenuActions = \ [EditScriptAction(EditScriptAction.SCRIPT_PYTHON), DeleteScriptAction(DeleteScriptAction.SCRIPT_PYTHON)] diff --git a/python/plugins/processing/script/scripts/Unique_values_count.py b/python/plugins/processing/script/scripts/Unique_values_count.py index bf466803ca3..682d29688d1 100644 --- a/python/plugins/processing/script/scripts/Unique_values_count.py +++ b/python/plugins/processing/script/scripts/Unique_values_count.py @@ -1,4 +1,4 @@ -##[Example scripts]aster processing=group +##[Example scripts]=group ##input=raster ##round_values_to_ndigits=number 3 ##output_file=output html diff --git a/python/plugins/processing/ui/DlgGetScriptsAndModels.ui b/python/plugins/processing/ui/DlgGetScriptsAndModels.ui new file mode 100644 index 00000000000..735a0ce3ad2 --- /dev/null +++ b/python/plugins/processing/ui/DlgGetScriptsAndModels.ui @@ -0,0 +1,154 @@ + + + DlgGetScriptsAndModels + + + + 0 + 0 + 826 + 520 + + + + Get scripts and models + + + + + + Qt::Horizontal + + + + + 350 + 0 + + + + + 100000 + 100000 + + + + QAbstractItemView::SingleSelection + + + false + + + false + + + 350 + + + false + + + true + + + true + + + + 1 + + + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + 0 + + + 0 + + + + + + 10000 + 10000 + + + + + about:blank + + + + + + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + 0 + + + false + + + + + + + + 0 + 0 + + + + + 200 + 16777215 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + QWebView + QWidget +
QtWebKit/QWebView
+
+
+ + +
diff --git a/python/plugins/processing/ui/ui_DlgGetScriptsAndModels.py b/python/plugins/processing/ui/ui_DlgGetScriptsAndModels.py new file mode 100644 index 00000000000..507c77ec167 --- /dev/null +++ b/python/plugins/processing/ui/ui_DlgGetScriptsAndModels.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'DlgGetScriptsAndModels.ui' +# +# Created: Mon Jun 02 20:12:43 2014 +# by: PyQt4 UI code generator 4.9.6 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) + +class Ui_DlgGetScriptsAndModels(object): + def setupUi(self, DlgGetScriptsAndModels): + DlgGetScriptsAndModels.setObjectName(_fromUtf8("DlgGetScriptsAndModels")) + DlgGetScriptsAndModels.resize(826, 520) + self.verticalLayout = QtGui.QVBoxLayout(DlgGetScriptsAndModels) + self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) + self.splitter = QtGui.QSplitter(DlgGetScriptsAndModels) + self.splitter.setOrientation(QtCore.Qt.Horizontal) + self.splitter.setObjectName(_fromUtf8("splitter")) + self.tree = QtGui.QTreeWidget(self.splitter) + self.tree.setMinimumSize(QtCore.QSize(350, 0)) + self.tree.setMaximumSize(QtCore.QSize(100000, 100000)) + self.tree.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) + self.tree.setObjectName(_fromUtf8("tree")) + self.tree.headerItem().setText(0, _fromUtf8("1")) + self.tree.header().setVisible(False) + self.tree.header().setCascadingSectionResizes(False) + self.tree.header().setDefaultSectionSize(350) + self.tree.header().setHighlightSections(False) + self.tree.header().setSortIndicatorShown(True) + self.tree.header().setStretchLastSection(True) + self.frame = QtGui.QFrame(self.splitter) + self.frame.setFrameShape(QtGui.QFrame.StyledPanel) + self.frame.setFrameShadow(QtGui.QFrame.Sunken) + self.frame.setObjectName(_fromUtf8("frame")) + self.horizontalLayout = QtGui.QHBoxLayout(self.frame) + self.horizontalLayout.setSpacing(0) + self.horizontalLayout.setMargin(0) + self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) + self.webView = QtWebKit.QWebView(self.frame) + self.webView.setMaximumSize(QtCore.QSize(10000, 10000)) + self.webView.setUrl(QtCore.QUrl(_fromUtf8("about:blank"))) + self.webView.setObjectName(_fromUtf8("webView")) + self.horizontalLayout.addWidget(self.webView) + self.verticalLayout.addWidget(self.splitter) + self.horizontalLayout_2 = QtGui.QHBoxLayout() + self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2")) + self.progressBar = QtGui.QProgressBar(DlgGetScriptsAndModels) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.progressBar.sizePolicy().hasHeightForWidth()) + self.progressBar.setSizePolicy(sizePolicy) + self.progressBar.setMinimumSize(QtCore.QSize(0, 0)) + self.progressBar.setProperty("value", 0) + self.progressBar.setInvertedAppearance(False) + self.progressBar.setObjectName(_fromUtf8("progressBar")) + self.horizontalLayout_2.addWidget(self.progressBar) + self.buttonBox = QtGui.QDialogButtonBox(DlgGetScriptsAndModels) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.buttonBox.sizePolicy().hasHeightForWidth()) + self.buttonBox.setSizePolicy(sizePolicy) + self.buttonBox.setMaximumSize(QtCore.QSize(200, 16777215)) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) + self.buttonBox.setObjectName(_fromUtf8("buttonBox")) + self.horizontalLayout_2.addWidget(self.buttonBox) + self.verticalLayout.addLayout(self.horizontalLayout_2) + + self.retranslateUi(DlgGetScriptsAndModels) + QtCore.QMetaObject.connectSlotsByName(DlgGetScriptsAndModels) + + def retranslateUi(self, DlgGetScriptsAndModels): + DlgGetScriptsAndModels.setWindowTitle(_translate("DlgGetScriptsAndModels", "Get scripts and models", None)) + +from PyQt4 import QtWebKit