Skip to content

Commit

Permalink
Merge pull request #4675 from alexbruy/processing-help
Browse files Browse the repository at this point in the history
[processing] improve help system
  • Loading branch information
alexbruy committed Jun 6, 2017
2 parents 51e7efe + e89502f commit 80911c6
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 174 deletions.
7 changes: 6 additions & 1 deletion python/plugins/processing/ProcessingPlugin.py
Expand Up @@ -44,7 +44,7 @@
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

Expand Down Expand Up @@ -146,6 +146,11 @@ def unload(self):
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)
Expand Down
12 changes: 4 additions & 8 deletions python/plugins/processing/algs/gdal/GdalAlgorithm.py
Expand Up @@ -80,19 +80,15 @@ def processAlgorithm(self, parameters, context, feedback):
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 = {}
Expand Down
10 changes: 8 additions & 2 deletions python/plugins/processing/core/GeoAlgorithm.py
Expand Up @@ -25,6 +25,9 @@

__revision__ = '$Format:%H$'

from builtins import str
from builtins import object

import os.path
import traceback
import subprocess
Expand All @@ -39,9 +42,8 @@
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
Expand Down Expand Up @@ -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()
6 changes: 3 additions & 3 deletions python/plugins/processing/gui/AlgorithmDialog.py
Expand Up @@ -157,7 +157,7 @@ def checkExtentCRS(self):
return hasExtent and unmatchingCRS

def accept(self):
self.settings.setValue("/Processing/dialogBase", self.saveGeometry())
super(AlgorithmDialog, self)._saveGeometry()

context = dataobjects.createContext()

Expand Down Expand Up @@ -273,7 +273,7 @@ def finish(self, result, context):
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)
100 changes: 61 additions & 39 deletions python/plugins/processing/gui/AlgorithmDialogBase.py
Expand Up @@ -30,13 +30,11 @@
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)

Expand Down Expand Up @@ -82,34 +80,56 @@ def __init__(self, alg):
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'))

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:
# self.txtHelp.setZoomFactor(desktop.physicalDpiX() / 96)

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; }
Expand All @@ -119,30 +139,11 @@ def __init__(self, alg):
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)

Expand All @@ -152,19 +153,9 @@ def formatHelp(self, alg):
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:
Expand Down Expand Up @@ -228,9 +219,40 @@ def getParamValues(self):
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):
Expand Down
13 changes: 11 additions & 2 deletions python/plugins/processing/gui/Help2Html.py
Expand Up @@ -27,11 +27,14 @@

__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'
Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion python/plugins/processing/modeler/ModelerAlgorithm.py
Expand Up @@ -539,7 +539,7 @@ def updateModelerView(self):
if self.modelerdialog:
self.modelerdialog.repaintModel()

def helpString(self):
def helpUrl(self):
try:
return getHtmlFromDescriptionsDict(self, self.helpContent)
except:
Expand Down
52 changes: 14 additions & 38 deletions python/plugins/processing/modeler/ModelerParametersDialog.py
Expand Up @@ -27,15 +27,14 @@

__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)
Expand Down Expand Up @@ -89,7 +88,8 @@ def setupUi(self):
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()
Expand Down Expand Up @@ -182,53 +182,24 @@ def setupUi(self):
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 = []
Expand Down Expand Up @@ -353,3 +324,8 @@ def okPressed(self):
def cancelPressed(self):
self.alg = None
self.close()

def openHelp(self):
algHelp = self._alg.help()
if algHelp is not None:
webbrowser.open(algHelp)

0 comments on commit 80911c6

Please sign in to comment.