Merge pull request #4675 from alexbruy/processing-help

[processing] improve help system
This commit is contained in:
Alexander Bruy 2017-06-06 13:08:22 +03:00 committed by GitHub
commit 80911c6e74
11 changed files with 216 additions and 193 deletions

View File

@ -44,7 +44,7 @@ from processing.gui.ConfigDialog import ConfigOptionsPage
from processing.gui.ResultsDock import ResultsDock
from processing.gui.AlgorithmLocatorFilter import AlgorithmLocatorFilter
from processing.modeler.ModelerDialog import ModelerDialog
from processing.tools.system import tempFolder
from processing.tools.system import tempFolder, tempHelpFolder
from processing.gui.menus import removeMenus, initializeMenus, createMenus
from processing.core.ProcessingResults import resultsList
@ -146,6 +146,11 @@ class ProcessingPlugin(object):
if QDir(folder).exists():
shutil.rmtree(folder, True)
# also delete temporary help files
folder = tempHelpFolder()
if QDir(folder).exists():
shutil.rmtree(folder, True)
self.iface.unregisterMainWindowAction(self.toolboxAction)
self.iface.unregisterMainWindowAction(self.modelerAction)
self.iface.unregisterMainWindowAction(self.historyAction)

View File

@ -80,19 +80,15 @@ class GdalAlgorithm(GeoAlgorithm):
commands[i] = c
GdalUtils.runGdal(commands, feedback)
def shortHelpString(self):
def helpUrl(self):
helpPath = GdalUtils.gdalHelpPath()
if helpPath == '':
return
return None
if os.path.exists(helpPath):
url = QUrl.fromLocalFile(os.path.join(helpPath, '{}.html'.format(self.commandName()))).toString()
return QUrl.fromLocalFile(os.path.join(helpPath, '{}.html'.format(self.commandName()))).toString()
else:
url = helpPath + '{}.html'.format(self.commandName())
return '''This algorithm is based on the GDAL {} module.
For more info, see the <a href={}> module help</a>
'''.format(self.commandName(), url)
return helpPath + '{}.html'.format(self.commandName())
def commandName(self):
parameters = {}

View File

@ -25,6 +25,9 @@ __copyright__ = '(C) 2012, Victor Olaya'
__revision__ = '$Format:%H$'
from builtins import str
from builtins import object
import os.path
import traceback
import subprocess
@ -39,9 +42,8 @@ from qgis.core import (QgsProcessingFeedback,
QgsProcessingUtils,
QgsProcessingParameterDefinition,
QgsMessageLog)
from qgis.gui import QgsHelp
from builtins import str
from builtins import object
from processing.core.ProcessingConfig import ProcessingConfig
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
from processing.core.parameters import ParameterRaster, ParameterVector, ParameterMultipleInput, ParameterTable, Parameter
@ -390,3 +392,7 @@ def executeAlgorithm(alg, parameters, context=None, feedback=None, model=None):
# lines.append(traceback.format_exc())
#QgsMessageLog.logMessage('\n'.join(lines), self.tr('Processing'), QgsMessageLog.CRITICAL)
#raise GeoAlgorithmExecutionException(str(e) + self.tr('\nSee log for more details'), lines, e)
def helpUrl(self):
return QgsHelp.helpUrl("processing_algs/{}/{}".format(
self.provider().id(), self.id())).toString()

View File

@ -157,7 +157,7 @@ class AlgorithmDialog(AlgorithmDialogBase):
return hasExtent and unmatchingCRS
def accept(self):
self.settings.setValue("/Processing/dialogBase", self.saveGeometry())
super(AlgorithmDialog, self)._saveGeometry()
context = dataobjects.createContext()
@ -273,7 +273,7 @@ class AlgorithmDialog(AlgorithmDialogBase):
self.tr('HTML output has been generated by this algorithm.'
'\nOpen the results dialog to check it.'))
def closeEvent(self, evt):
def closeEvent(self, event):
QgsProject.instance().layerWasAdded.disconnect(self.mainWidget.layerRegistryChanged)
QgsProject.instance().layersWillBeRemoved.disconnect(self.mainWidget.layerRegistryChanged)
super(AlgorithmDialog, self).closeEvent(evt)
super(AlgorithmDialog, self).closeEvent(event)

View File

@ -30,13 +30,11 @@ import os
import webbrowser
from qgis.PyQt import uic
from qgis.PyQt.QtCore import QCoreApplication, QByteArray, QUrl
from qgis.PyQt.QtWidgets import QApplication, QDialogButtonBox
from qgis.PyQt.QtNetwork import QNetworkRequest, QNetworkReply
from qgis.PyQt.QtCore import Qt, QCoreApplication, QByteArray, QUrl
from qgis.PyQt.QtWidgets import QApplication, QDialogButtonBox, QVBoxLayout, QToolButton
from qgis.utils import iface
from qgis.core import (QgsNetworkAccessManager,
QgsProject,
from qgis.core import (QgsProject,
QgsProcessingFeedback,
QgsSettings)
@ -82,17 +80,37 @@ class AlgorithmDialogBase(BASE, WIDGET):
super(AlgorithmDialogBase, self).__init__(iface.mainWindow())
self.setupUi(self)
# don't collapse parameters panel
self.splitter.setCollapsible(0, False)
# add collapse button to splitter
splitterHandle = self.splitter.handle(1)
handleLayout = QVBoxLayout()
handleLayout.setContentsMargins(0, 0, 0, 0)
self.btnCollapse = QToolButton(splitterHandle)
self.btnCollapse.setAutoRaise(True)
self.btnCollapse.setFixedSize(12, 12)
self.btnCollapse.setCursor(Qt.ArrowCursor)
handleLayout.addWidget(self.btnCollapse)
handleLayout.addStretch()
splitterHandle.setLayout(handleLayout)
self.feedback = AlgorithmDialogFeedback(self)
self.feedback.progressChanged.connect(self.setPercentage)
self.buttonCancel.clicked.connect(self.feedback.cancel)
self.settings = QgsSettings()
self.splitter.restoreState(self.settings.value("/Processing/dialogBaseSplitter", QByteArray()))
self.restoreGeometry(self.settings.value("/Processing/dialogBase", QByteArray()))
self.splitterState = self.splitter.saveState()
self.splitterChanged(0, 0)
self.executed = False
self.mainWidget = None
self.alg = alg
self.setWindowTitle(self.alg.displayName())
# Rename OK button to Run
self.btnRun = self.buttonBox.button(QDialogButtonBox.Ok)
self.btnRun.setText(self.tr('Run'))
@ -100,8 +118,10 @@ class AlgorithmDialogBase(BASE, WIDGET):
self.buttonCancel.setEnabled(False)
self.btnClose = self.buttonBox.button(QDialogButtonBox.Close)
self.buttonBox.helpRequested.connect(self.openHelp)
self.setWindowTitle(self.alg.displayName())
self.btnCollapse.clicked.connect(self.toggleCollapsed)
self.splitter.splitterMoved.connect(self.splitterChanged)
# desktop = QDesktopWidget()
# if desktop.physicalDpiX() > 96:
@ -109,7 +129,7 @@ class AlgorithmDialogBase(BASE, WIDGET):
algHelp = self.formatHelp(self.alg)
if algHelp is None:
self.textShortHelp.setVisible(False)
self.textShortHelp.hide()
else:
self.textShortHelp.document().setDefaultStyleSheet('''.summary { margin-left: 10px; margin-right: 10px; }
h2 { color: #555555; padding-bottom: 15px; }
@ -119,30 +139,11 @@ class AlgorithmDialogBase(BASE, WIDGET):
dl dd { margin-bottom: 5px; }''')
self.textShortHelp.setHtml(algHelp)
self.textShortHelp.setOpenLinks(False)
def linkClicked(url):
webbrowser.open(url.toString())
self.textShortHelp.anchorClicked.connect(linkClicked)
if self.alg.helpString() is not None:
try:
self.txtHelp.setHtml(self.alg.helpString())
except Exception:
self.tabWidget.removeTab(2)
elif self.alg.helpUrl() is not None:
try:
html = self.tr('<p>Downloading algorithm help... Please wait.</p>')
self.txtHelp.setHtml(html)
rq = QNetworkRequest(QUrl(self.alg.helpUrl()))
self.reply = QgsNetworkAccessManager.instance().get(rq)
self.reply.finished.connect(self.requestFinished)
except Exception:
self.tabWidget.removeTab(2)
else:
self.tabWidget.removeTab(2)
self.showDebug = ProcessingConfig.getSetting(
ProcessingConfig.SHOW_DEBUG_IN_DIALOG)
@ -152,19 +153,9 @@ class AlgorithmDialogBase(BASE, WIDGET):
return None
return "<h2>%s</h2>%s" % (alg.displayName(), "".join(["<p>%s</p>" % s for s in text.split("\n")]))
def requestFinished(self):
"""Change the webview HTML content"""
reply = self.sender()
if reply.error() != QNetworkReply.NoError:
html = self.tr('<h2>No help available for this algorithm</h2><p>{}</p>'.format(reply.errorString()))
else:
html = str(reply.readAll())
reply.deleteLater()
self.txtHelp.setHtml(html)
def closeEvent(self, evt):
self.settings.setValue("/Processing/dialogBase", self.saveGeometry())
super(AlgorithmDialogBase, self).closeEvent(evt)
def closeEvent(self, event):
self._saveGeometry()
super(AlgorithmDialogBase, self).closeEvent(event)
def setMainWidget(self, widget):
if self.mainWidget is not None:
@ -228,9 +219,40 @@ class AlgorithmDialogBase(BASE, WIDGET):
def accept(self):
pass
def reject(self):
self._saveGeometry()
super(AlgorithmDialogBase, self).reject()
def finish(self, context):
pass
def toggleCollapsed(self):
if self.helpCollapsed:
self.splitter.restoreState(self.splitterState)
self.btnCollapse.setArrowType(Qt.RightArrow)
else:
self.splitterState = self.splitter.saveState()
self.splitter.setSizes([1, 0])
self.btnCollapse.setArrowType(Qt.LeftArrow)
self.helpCollapsed = not self.helpCollapsed
def splitterChanged(self, pos, index):
if self.splitter.sizes()[1] == 0:
self.helpCollapsed = True
self.btnCollapse.setArrowType(Qt.LeftArrow)
else:
self.helpCollapsed = False
self.btnCollapse.setArrowType(Qt.RightArrow)
def openHelp(self):
algHelp = self.alg.helpUrl()
if algHelp not in [None, ""]:
webbrowser.open(algHelp)
def _saveGeometry(self):
self.settings.setValue("/Processing/dialogBaseSplitter", self.splitter.saveState())
self.settings.setValue("/Processing/dialogBase", self.saveGeometry())
class InvalidParameterValue(Exception):
def __init__(self, param, widget):

View File

@ -27,11 +27,14 @@ __copyright__ = '(C) 2012, Victor Olaya'
__revision__ = '$Format:%H$'
from qgis.PyQt.QtCore import QCoreApplication
import os
import re
import json
from qgis.PyQt.QtCore import QCoreApplication, QUrl
from processing.tools import system
ALG_DESC = 'ALG_DESC'
ALG_CREATOR = 'ALG_CREATOR'
ALG_HELP_CREATOR = 'ALG_HELP_CREATOR'
@ -63,7 +66,13 @@ def getHtmlFromHelpFile(alg, helpFile):
try:
with open(helpFile) as f:
descriptions = json.load(f)
return getHtmlFromDescriptionsDict(alg, descriptions)
content = getHtmlFromDescriptionsDict(alg, descriptions)
algGroup, algName = alg.id().split(':')
filePath = os.path.join(system.tempHelpFolder(), "{}_{}.html".format(algGroup, algName))
with open(filePath, 'w', encoding='utf-8') as f:
f.write(content)
return QUrl.fromLocalFile(filePath).toString()
except:
return None

View File

@ -539,7 +539,7 @@ class ModelerAlgorithm(GeoAlgorithm):
if self.modelerdialog:
self.modelerdialog.repaintModel()
def helpString(self):
def helpUrl(self):
try:
return getHtmlFromDescriptionsDict(self, self.helpContent)
except:

View File

@ -27,15 +27,14 @@ __copyright__ = '(C) 2012, Victor Olaya'
__revision__ = '$Format:%H$'
import webbrowser
from qgis.PyQt.QtCore import Qt, QUrl, QMetaObject
from qgis.PyQt.QtWidgets import (QDialog, QDialogButtonBox, QLabel, QLineEdit,
QFrame, QPushButton, QSizePolicy, QVBoxLayout,
QHBoxLayout, QTabWidget, QWidget,
QTextBrowser)
from qgis.PyQt.QtNetwork import QNetworkRequest, QNetworkReply
QHBoxLayout, QWidget)
from qgis.core import (QgsNetworkAccessManager,
QgsProcessingParameterDefinition)
from qgis.core import (QgsProcessingParameterDefinition)
from qgis.gui import (QgsMessageBar,
QgsScrollArea)
@ -89,7 +88,8 @@ class ModelerParametersDialog(QDialog):
self.buttonBox = QDialogButtonBox()
self.buttonBox.setOrientation(Qt.Horizontal)
self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel |
QDialogButtonBox.Ok)
+ QDialogButtonBox.Ok |
+ QDialogButtonBox.Help)
self.setSizePolicy(QSizePolicy.Expanding,
QSizePolicy.Expanding)
self.verticalLayout = QVBoxLayout()
@ -182,53 +182,24 @@ class ModelerParametersDialog(QDialog):
self.verticalLayout2 = QVBoxLayout()
self.verticalLayout2.setSpacing(2)
self.verticalLayout2.setMargin(0)
self.tabWidget = QTabWidget()
self.tabWidget.setMinimumWidth(300)
self.paramPanel = QWidget()
self.paramPanel.setLayout(self.verticalLayout)
self.scrollArea = QgsScrollArea()
self.scrollArea.setWidget(self.paramPanel)
self.scrollArea.setWidgetResizable(True)
self.tabWidget.addTab(self.scrollArea, self.tr('Parameters'))
self.txtHelp = QTextBrowser()
html = None
isText, algHelp = self._alg.help()
if algHelp is not None:
algHelp = algHelp if isText else QUrl(algHelp)
try:
if isText:
self.txtHelp.setHtml(algHelp)
else:
html = self.tr('<p>Downloading algorithm help... Please wait.</p>')
self.txtHelp.setHtml(html)
self.tabWidget.addTab(self.txtHelp, 'Help')
self.reply = QgsNetworkAccessManager.instance().get(QNetworkRequest(algHelp))
self.reply.finished.connect(self.requestFinished)
except:
pass
self.verticalLayout2.addWidget(self.tabWidget)
self.verticalLayout2.addWidget(self.scrollArea)
self.verticalLayout2.addWidget(self.buttonBox)
self.setLayout(self.verticalLayout2)
self.buttonBox.accepted.connect(self.okPressed)
self.buttonBox.rejected.connect(self.cancelPressed)
self.buttonBox.helpRequested.connect(self.openHelp)
QMetaObject.connectSlotsByName(self)
for wrapper in list(self.wrappers.values()):
wrapper.postInitialize(list(self.wrappers.values()))
def requestFinished(self):
"""Change the webview HTML content"""
reply = self.sender()
if reply.error() != QNetworkReply.NoError:
html = self.tr('<h2>No help available for this algorithm</h2><p>{}</p>'.format(reply.errorString()))
else:
html = str(reply.readAll())
reply.deleteLater()
self.txtHelp.setHtml(html)
def getAvailableDependencies(self): # spellok
if self._algName is None:
dependent = []
@ -353,3 +324,8 @@ class ModelerParametersDialog(QDialog):
def cancelPressed(self):
self.alg = None
self.close()
def openHelp(self):
algHelp = self._alg.help()
if algHelp is not None:
webbrowser.open(algHelp)

View File

@ -198,9 +198,9 @@ class ScriptAlgorithm(GeoAlgorithm):
for out in self.outputs:
out.setValue(ns[out.name])
def helpString(self):
def helpUrl(self):
if self.descriptionFile is None:
return False, None
return None
helpfile = self.descriptionFile + '.help'
if os.path.exists(helpfile):
return getHtmlFromHelpFile(self, helpfile)

View File

@ -142,6 +142,14 @@ def mkdir(newdir):
os.mkdir(newdir)
def tempHelpFolder():
tmp = os.path.join(str(QDir.tempPath()), 'processing_help')
if not QDir(tmp).exists():
QDir().mkpath(tmp)
return str(os.path.abspath(tmp))
def escapeAndJoin(strList):
joined = ''
for s in strList:

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>841</width>
<width>685</width>
<height>525</height>
</rect>
</property>
@ -15,20 +15,37 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Parameters</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="spacing">
<number>2</number>
</property>
<widget class="QSplitter" name="splitter">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="handleWidth">
<number>16</number>
</property>
<widget class="QTabWidget" name="tabWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>2</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Parameters</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
@ -39,89 +56,73 @@
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Log</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTextEdit" name="txtLog">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_3">
<attribute name="title">
<string>Help</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTextBrowser" name="txtHelp"/>
</item>
</layout>
</widget>
<number>0</number>
</property>
</layout>
</widget>
</item>
<item>
<widget class="QTextBrowser" name="textShortHelp">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>300</width>
<height>16777215</height>
</size>
</property>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Log</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTextEdit" name="txtLog">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QTextBrowser" name="textShortHelp">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="accessibleName">
<string/>
</property>
<property name="openLinks">
<bool>false</bool>
</property>
</widget>
</widget>
</item>
<item>
<widget class="QLabel" name="lblProgress">
@ -157,7 +158,7 @@
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close|QDialogButtonBox::Ok</set>
<set>QDialogButtonBox::Close|QDialogButtonBox::Help|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>