diff --git a/images/images.qrc b/images/images.qrc
index 196d6eb94a83..8a9d117d34e0 100644
--- a/images/images.qrc
+++ b/images/images.qrc
@@ -1013,6 +1013,7 @@
themes/default/propertyicons/notes.svg
themes/default/stacked-diagram.svg
themes/default/mIconStac.svg
+ themes/default/mIconQt.svg
qgis_tips/symbol_levels.png
diff --git a/images/themes/default/mIconQt.svg b/images/themes/default/mIconQt.svg
new file mode 100644
index 000000000000..e88e37c56a29
--- /dev/null
+++ b/images/themes/default/mIconQt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/python/PyQt6/core/auto_additions/qgis.py b/python/PyQt6/core/auto_additions/qgis.py
index 9f09110d5044..cecc1b4ab3e0 100644
--- a/python/PyQt6/core/auto_additions/qgis.py
+++ b/python/PyQt6/core/auto_additions/qgis.py
@@ -10883,6 +10883,36 @@
"""
# --
Qgis.ColorModel.baseClass = Qgis
+# monkey patching scoped based enum
+Qgis.DocumentationApi.PyQgis.__doc__ = "PyQgis API documentation"
+Qgis.DocumentationApi.PyQgisSearch.__doc__ = "Search in PyQgis API documentation"
+Qgis.DocumentationApi.CppQgis.__doc__ = "C++ QGIS API documentation"
+Qgis.DocumentationApi.Qt.__doc__ = "Qt API documentation"
+Qgis.DocumentationApi.__doc__ = """Documentation API
+
+.. versionadded:: 3.42
+
+* ``PyQgis``: PyQgis API documentation
+* ``PyQgisSearch``: Search in PyQgis API documentation
+* ``CppQgis``: C++ QGIS API documentation
+* ``Qt``: Qt API documentation
+
+"""
+# --
+Qgis.DocumentationApi.baseClass = Qgis
+# monkey patching scoped based enum
+Qgis.DocumentationBrowser.DeveloperToolsPanel.__doc__ = "Embedded webview in the DevTools panel"
+Qgis.DocumentationBrowser.SystemWebBrowser.__doc__ = "Default system web browser"
+Qgis.DocumentationBrowser.__doc__ = """Documentation API browser
+
+.. versionadded:: 3.42
+
+* ``DeveloperToolsPanel``: Embedded webview in the DevTools panel
+* ``SystemWebBrowser``: Default system web browser
+
+"""
+# --
+Qgis.DocumentationBrowser.baseClass = Qgis
try:
Qgis.__attribute_docs__ = {'QGIS_DEV_VERSION': 'The development version', 'DEFAULT_SEARCH_RADIUS_MM': 'Identify search radius in mm', 'DEFAULT_MAPTOPIXEL_THRESHOLD': 'Default threshold between map coordinates and device coordinates for map2pixel simplification', 'DEFAULT_HIGHLIGHT_COLOR': 'Default highlight color. The transparency is expected to only be applied to polygon\nfill. Lines and outlines are rendered opaque.', 'DEFAULT_HIGHLIGHT_BUFFER_MM': 'Default highlight buffer in mm.', 'DEFAULT_HIGHLIGHT_MIN_WIDTH_MM': 'Default highlight line/stroke minimum width in mm.', 'SCALE_PRECISION': 'Fudge factor used to compare two scales. The code is often going from scale to scale\ndenominator. So it looses precision and, when a limit is inclusive, can lead to errors.\nTo avoid that, use this factor instead of using <= or >=.\n\n.. deprecated:: 3.40\n\n No longer used by QGIS and will be removed in QGIS 4.0.', 'DEFAULT_Z_COORDINATE': 'Default Z coordinate value.\nThis value have to be assigned to the Z coordinate for the vertex.', 'DEFAULT_M_COORDINATE': 'Default M coordinate value.\nThis value have to be assigned to the M coordinate for the vertex.\n\n.. versionadded:: 3.20', 'UI_SCALE_FACTOR': 'UI scaling factor. This should be applied to all widget sizes obtained from font metrics,\nto account for differences in the default font sizes across different platforms.', 'DEFAULT_SNAP_TOLERANCE': 'Default snapping distance tolerance.', 'DEFAULT_SNAP_UNITS': 'Default snapping distance units.'}
Qgis.version = staticmethod(Qgis.version)
diff --git a/python/PyQt6/core/auto_generated/qgis.sip.in b/python/PyQt6/core/auto_generated/qgis.sip.in
index de6703eaf11d..72554d931866 100644
--- a/python/PyQt6/core/auto_generated/qgis.sip.in
+++ b/python/PyQt6/core/auto_generated/qgis.sip.in
@@ -3173,6 +3173,20 @@ The development version
Cmyk,
};
+ enum class DocumentationApi /BaseType=IntEnum/
+ {
+ PyQgis,
+ PyQgisSearch,
+ CppQgis,
+ Qt,
+ };
+
+ enum class DocumentationBrowser /BaseType=IntEnum/
+ {
+ DeveloperToolsPanel,
+ SystemWebBrowser,
+ };
+
static const double DEFAULT_SEARCH_RADIUS_MM;
static const float DEFAULT_MAPTOPIXEL_THRESHOLD;
diff --git a/python/PyQt6/gui/auto_additions/qgscodeeditor.py b/python/PyQt6/gui/auto_additions/qgscodeeditor.py
index 9ca000f68875..668fbcdafcd1 100644
--- a/python/PyQt6/gui/auto_additions/qgscodeeditor.py
+++ b/python/PyQt6/gui/auto_additions/qgscodeeditor.py
@@ -57,13 +57,14 @@
QgsCodeEditor.Flags.baseClass = QgsCodeEditor
Flags = QgsCodeEditor # dirty hack since SIP seems to introduce the flags in module
try:
- QgsCodeEditor.__attribute_docs__ = {'SEARCH_RESULT_INDICATOR': 'Indicator index for search results', 'sessionHistoryCleared': 'Emitted when the history of commands run in the current session is cleared.\n\n.. versionadded:: 3.30\n', 'persistentHistoryCleared': 'Emitted when the persistent history of commands run in the editor is cleared.\n\n.. versionadded:: 3.30\n'}
+ QgsCodeEditor.__attribute_docs__ = {'SEARCH_RESULT_INDICATOR': 'Indicator index for search results', 'sessionHistoryCleared': 'Emitted when the history of commands run in the current session is cleared.\n\n.. versionadded:: 3.30\n', 'persistentHistoryCleared': 'Emitted when the persistent history of commands run in the editor is cleared.\n\n.. versionadded:: 3.30\n', 'helpRequested': 'Emitted when documentation was requested for the specified ``word``.\n\n.. versionadded:: 3.42\n'}
QgsCodeEditor.languageToString = staticmethod(QgsCodeEditor.languageToString)
QgsCodeEditor.defaultColor = staticmethod(QgsCodeEditor.defaultColor)
QgsCodeEditor.color = staticmethod(QgsCodeEditor.color)
QgsCodeEditor.setColor = staticmethod(QgsCodeEditor.setColor)
QgsCodeEditor.getMonospaceFont = staticmethod(QgsCodeEditor.getMonospaceFont)
QgsCodeEditor.isFixedPitch = staticmethod(QgsCodeEditor.isFixedPitch)
+ QgsCodeEditor.__signal_arguments__ = {'helpRequested': ['word: str']}
QgsCodeEditor.__group__ = ['codeeditors']
except NameError:
pass
diff --git a/python/PyQt6/gui/auto_additions/qgsgui.py b/python/PyQt6/gui/auto_additions/qgsgui.py
index a6c8829b3eb9..510f82f0a465 100644
--- a/python/PyQt6/gui/auto_additions/qgsgui.py
+++ b/python/PyQt6/gui/auto_additions/qgsgui.py
@@ -48,5 +48,6 @@ def _force_int(v): return int(v.value) if isinstance(v, Enum) else v
QgsGui.higFlags = staticmethod(QgsGui.higFlags)
QgsGui.sampleColor = staticmethod(QgsGui.sampleColor)
QgsGui.findScreenAt = staticmethod(QgsGui.findScreenAt)
+ QgsGui.hasWebEngine = staticmethod(QgsGui.hasWebEngine)
except NameError:
pass
diff --git a/python/PyQt6/gui/auto_generated/codeeditors/qgscodeeditor.sip.in b/python/PyQt6/gui/auto_generated/codeeditors/qgscodeeditor.sip.in
index 30eccb100b18..aa184913a5e0 100644
--- a/python/PyQt6/gui/auto_generated/codeeditors/qgscodeeditor.sip.in
+++ b/python/PyQt6/gui/auto_generated/codeeditors/qgscodeeditor.sip.in
@@ -519,6 +519,14 @@ Emitted when the persistent history of commands run in the editor is cleared.
.. versionadded:: 3.30
%End
+
+ void helpRequested( const QString &word );
+%Docstring
+Emitted when documentation was requested for the specified ``word``.
+
+.. versionadded:: 3.42
+%End
+
protected:
static bool isFixedPitch( const QFont &font );
diff --git a/python/PyQt6/gui/auto_generated/codeeditors/qgscodeeditorpython.sip.in b/python/PyQt6/gui/auto_generated/codeeditors/qgscodeeditorpython.sip.in
index 6ff2e3befbbb..e42a32690ce3 100644
--- a/python/PyQt6/gui/auto_generated/codeeditors/qgscodeeditorpython.sip.in
+++ b/python/PyQt6/gui/auto_generated/codeeditors/qgscodeeditorpython.sip.in
@@ -94,6 +94,13 @@ Updates the editor capabilities.
Searches the selected text in the official PyQGIS online documentation.
.. versionadded:: 3.16
+%End
+
+ virtual void showApiDocumentation( const QString &item );
+%Docstring
+Displays the given text in the official APIs (PyQGIS, C++ QGIS or Qt) documentation.
+
+.. versionadded:: 3.42
%End
virtual void toggleComment();
diff --git a/python/PyQt6/gui/auto_generated/qgisinterface.sip.in b/python/PyQt6/gui/auto_generated/qgisinterface.sip.in
index d78806ba761b..04d4e289c185 100644
--- a/python/PyQt6/gui/auto_generated/qgisinterface.sip.in
+++ b/python/PyQt6/gui/auto_generated/qgisinterface.sip.in
@@ -1490,6 +1490,18 @@ Unregister a previously registered tool factory from the development/debugging t
.. seealso:: :py:func:`registerDevToolWidgetFactory`
.. versionadded:: 3.14
+%End
+
+ virtual void showApiDocumentation( Qgis::DocumentationApi api = Qgis::DocumentationApi::PyQgis, Qgis::DocumentationBrowser browser = Qgis::DocumentationBrowser::DeveloperToolsPanel, const QString &object = QString(), const QString &module = QString() ) = 0;
+%Docstring
+Show a page of the API documentation
+
+:param api: Which API to display
+:param browser: Web browser used to display the API documentation
+:param object: object to show in the documentation
+:param module: used only if api = :py:class:`Qgis`.DocumentationApi.PyQgis
+
+.. versionadded:: 3.42
%End
virtual void registerApplicationExitBlocker( QgsApplicationExitBlockerInterface *blocker ) = 0;
diff --git a/python/PyQt6/gui/auto_generated/qgsgui.sip.in b/python/PyQt6/gui/auto_generated/qgsgui.sip.in
index d18c77a26b6c..26210306af85 100644
--- a/python/PyQt6/gui/auto_generated/qgsgui.sip.in
+++ b/python/PyQt6/gui/auto_generated/qgsgui.sip.in
@@ -251,6 +251,13 @@ Returns the screen at the given global ``point`` (pixel).
+ static bool hasWebEngine();
+%Docstring
+Checks whether QWebEngineView is available to display HTML content.
+
+.. versionadded:: 3.42
+%End
+
signals:
diff --git a/python/console/console_editor.py b/python/console/console_editor.py
index edf83b352ec8..b1929e1478ed 100644
--- a/python/console/console_editor.py
+++ b/python/console/console_editor.py
@@ -123,6 +123,9 @@ def __init__(self,
self.modificationChanged.connect(self.editor_tab.modified)
self.modificationAttempted.connect(self.fileReadOnly)
+ def showApiDocumentation(self, text):
+ self.console_widget.shell.showApiDocumentation(text)
+
def set_code_editor_widget(self, widget: QgsCodeEditorWidget):
self.code_editor_widget = widget
self.code_editor_widget.loadedExternalChanges.connect(
@@ -154,11 +157,15 @@ def contextMenuEvent(self, e):
runSelected.setShortcut('Ctrl+E') # spellok
menu.addAction(runSelected) # spellok
- pyQGISHelpAction = QAction(QgsApplication.getThemeIcon("console/iconHelpConsole.svg"),
- QCoreApplication.translate("PythonConsole", "Search Selection in PyQGIS Documentation"),
- menu)
- pyQGISHelpAction.triggered.connect(self.searchSelectedTextInPyQGISDocs)
- menu.addAction(pyQGISHelpAction)
+ word = self.selectedText() or self.wordAtPoint(e.pos())
+ if word:
+ context_help_action = QAction(
+ QgsApplication.getThemeIcon("mActionHelpContents.svg"),
+ QCoreApplication.translate("PythonConsole", "Context Help"),
+ menu)
+ context_help_action.triggered.connect(partial(self.console_widget.shell.showApiDocumentation, word, force_search=True))
+ context_help_action.setShortcut('F1')
+ menu.addAction(context_help_action)
start_action = QAction(QgsApplication.getThemeIcon("mActionStart.svg"),
QCoreApplication.translate("PythonConsole", "Run Script"),
@@ -246,7 +253,6 @@ def contextMenuEvent(self, e):
self.console_widget.openSettings)
syntaxCheckAction.setEnabled(False)
pasteAction.setEnabled(False)
- pyQGISHelpAction.setEnabled(False)
cutAction.setEnabled(False)
runSelected.setEnabled(False) # spellok
copyAction.setEnabled(False)
@@ -258,7 +264,6 @@ def contextMenuEvent(self, e):
runSelected.setEnabled(True) # spellok
copyAction.setEnabled(True)
cutAction.setEnabled(True)
- pyQGISHelpAction.setEnabled(True)
if not self.text() == '':
selectAllAction.setEnabled(True)
syntaxCheckAction.setEnabled(True)
diff --git a/python/console/console_output.py b/python/console/console_output.py
index 61ea81b599d5..b1544c687e65 100644
--- a/python/console/console_output.py
+++ b/python/console/console_output.py
@@ -20,6 +20,7 @@
from __future__ import annotations
import sys
+from functools import partial
from typing import TYPE_CHECKING
from qgis.PyQt import sip
@@ -239,11 +240,15 @@ def contextMenuEvent(self, e):
clearAction.triggered.connect(self.clearConsole)
menu.addAction(clearAction)
- pyQGISHelpAction = QAction(QgsApplication.getThemeIcon("console/iconHelpConsole.svg"),
- QCoreApplication.translate("PythonConsole", "Search Selection in PyQGIS Documentation"),
- menu)
- pyQGISHelpAction.triggered.connect(self.searchSelectedTextInPyQGISDocs)
- menu.addAction(pyQGISHelpAction)
+ word = self.selectedText() or self.wordAtPoint(e.pos())
+ if word:
+ context_help_action = QAction(
+ QgsApplication.getThemeIcon("mActionHelpContents.svg"),
+ QCoreApplication.translate("PythonConsole", "Context Help"),
+ menu)
+ context_help_action.triggered.connect(partial(self.shell_editor.showApiDocumentation, word, force_search=True))
+ context_help_action.setShortcut('F1')
+ menu.addAction(context_help_action)
menu.addSeparator()
copyAction = QAction(
@@ -271,13 +276,11 @@ def contextMenuEvent(self, e):
runAction.setEnabled(False)
clearAction.setEnabled(False)
copyAction.setEnabled(False)
- pyQGISHelpAction.setEnabled(False)
selectAllAction.setEnabled(False)
showEditorAction.setEnabled(True)
if self.hasSelectedText():
runAction.setEnabled(True)
copyAction.setEnabled(True)
- pyQGISHelpAction.setEnabled(True)
if not self.text(3) == '':
selectAllAction.setEnabled(True)
clearAction.setEnabled(True)
@@ -311,17 +314,8 @@ def enteredSelected(self):
self.shell_editor.insertFromDropPaste(cmd)
self.shell_editor.entered()
- def keyPressEvent(self, e):
- # empty text indicates possible shortcut key sequence so stay in output
- txt = e.text()
- if len(txt) and txt >= " ":
- self.shell_editor.append(txt)
- self.shell_editor.moveCursorToEnd()
- self.shell_editor.setFocus()
- e.ignore()
- else:
- # possible shortcut key sequence, accept it
- e.accept()
-
def widgetMessageBar(self, text: str):
self.infoBar.pushMessage(text, Qgis.MessageLevel.Info)
+
+ def showApiDocumentation(self, text):
+ self.shell_editor.showApiDocumentation(text)
diff --git a/python/console/console_sci.py b/python/console/console_sci.py
index ba0688c8d9df..5b0d9d8d270b 100644
--- a/python/console/console_sci.py
+++ b/python/console/console_sci.py
@@ -24,6 +24,7 @@
import re
import sys
import traceback
+from functools import partial
from typing import (
Optional,
TYPE_CHECKING
@@ -33,12 +34,13 @@
from qgis.PyQt.Qsci import QsciScintilla
from qgis.PyQt.QtCore import Qt, QCoreApplication
-from qgis.PyQt.QtGui import QKeySequence, QFontMetrics, QClipboard
-from qgis.PyQt.QtWidgets import QShortcut, QApplication
+from qgis.PyQt.QtGui import QKeySequence, QFontMetrics, QClipboard, QCursor
+from qgis.PyQt.QtWidgets import QShortcut, QApplication, QAction
from qgis.core import (
QgsApplication,
Qgis,
- QgsProcessingUtils
+ QgsProcessingUtils,
+ QgsSettingsTree,
)
from qgis.gui import (
QgsCodeEditorPython,
@@ -101,6 +103,52 @@ def __parse_object(object=None):
module = match[1]
obj = match[2]
return 'qt', module, obj
+""",
+ r"""
+def _help(object=None, api=Qgis.DocumentationApi.PyQgis, force_search=False):
+ '''
+ Link to the C++ or PyQGIS API documentation for the given object.
+ If no object is given, the main PyQGIS API page is opened.
+ If the object is not part of the QGIS API but is a Qt object the Qt documentation is opened.
+ '''
+
+ pythonSettingsTreeNode = QgsSettingsTree.node("gui").childNode("code-editor").childNode("python")
+ browserName = pythonSettingsTreeNode.childSetting('context-help-browser').valueAsVariant()
+ try:
+ browser = Qgis.DocumentationBrowser[browserName]
+ except KeyError:
+ browser = Qgis.DocumentationBrowser.DeveloperToolsPanel
+
+ if not object:
+ return iface.showApiDocumentation(api, browser=browser)
+
+ def search_or_home(object_str):
+ if not object_str:
+ return iface.showApiDocumentation(api, browser=browser)
+ if browser == Qgis.DocumentationBrowser.DeveloperToolsPanel and not QgsGui.hasWebEngine():
+ if force_search:
+ return iface.showApiDocumentation(Qgis.DocumentationApi.PyQgisSearch, object=object, browser=Qgis.DocumentationBrowser.SystemWebBrowser)
+ else:
+ return iface.showApiDocumentation(api, browser=browser)
+ else:
+ return iface.showApiDocumentation(Qgis.DocumentationApi.PyQgisSearch, object=object, browser=browser)
+
+ if isinstance(object, str):
+ try:
+ object = eval(object)
+ except (SyntaxError, NameError):
+ return search_or_home(object)
+
+ obj_info = __parse_object(object)
+ if not obj_info:
+ return search_or_home(object if isinstance(object, str) else None)
+
+ obj_type, module, class_name = obj_info
+ if obj_type == "qt":
+ api = Qgis.DocumentationApi.Qt
+
+ iface.showApiDocumentation(api, browser=browser, object=class_name, module=module)
+
""",
r"""
def _api(object=None):
@@ -109,18 +157,7 @@ def _api(object=None):
If no object is given, the main API page is opened.
If the object is not part of the QGIS API but is a Qt object the Qt documentation is opened.
'''
- import webbrowser
- api = __parse_object(object)
-
- version = '' if 'master' in Qgis.QGIS_VERSION.lower() else re.findall(r'^\d.[0-9]*', Qgis.QGIS_VERSION)[0]
-
- if not api:
- webbrowser.open(f"https://qgis.org/api/{version}")
- elif api[0] == 'qgis':
- webbrowser.open(f"https://api.qgis.org/api/{version}/class{api[2]}.html")
- elif api[0] == 'qt':
- qtversion = '.'.join(qVersion().split(".")[:2])
- webbrowser.open(f"https://doc.qt.io/qt-{qtversion}/{api[2].lower()}.html")
+ return _help(object, api=Qgis.DocumentationApi.CppQgis)
""",
r"""
def _pyqgis(object=None):
@@ -129,18 +166,7 @@ def _pyqgis(object=None):
If no object is given, the main PyQGIS API page is opened.
If the object is not part of the QGIS API but is a Qt object the Qt documentation is opened.
'''
- import webbrowser
- api = __parse_object(object)
-
- version = 'master' if 'master' in Qgis.QGIS_VERSION.lower() else re.findall(r'^\d.[0-9]*', Qgis.QGIS_VERSION)[0]
-
- if not api:
- webbrowser.open(f"https://qgis.org/pyqgis/{version}")
- elif api[0] == 'qgis':
- webbrowser.open(f"https://qgis.org/pyqgis/{version}/{api[1]}/{api[2]}.html")
- elif api[0] == 'qt':
- qtversion = '.'.join(qVersion().split(".")[:2])
- webbrowser.open(f"https://doc.qt.io/qt-{qtversion}/{api[2].lower()}.html")
+ return _help(object, api=Qgis.DocumentationApi.PyQgis)
"""
]
@@ -224,9 +250,9 @@ def execCommandImpl(self, cmd, show_input=True):
if cmd == "?":
self.shell.console_widget.shell_output.insertHelp()
elif cmd == '_pyqgis':
- webbrowser.open("https://qgis.org/pyqgis/{}".format(version))
+ self.shell.showApi(Qgis.DocumentationApi.PyQgis)
elif cmd == '_api':
- webbrowser.open("https://qgis.org/api/{}".format('' if version == 'master' else version))
+ self.shell.showApi(Qgis.DocumentationApi.CppQgis)
elif cmd == '_cookbook':
webbrowser.open(
"https://docs.qgis.org/{}/en/docs/pyqgis_developer_cookbook/".format(
@@ -458,3 +484,21 @@ def runFile(self, filename, override_file_name: Optional[str] = None):
self._interpreter.execCommandImpl("del __file__", False)
self._interpreter.execCommandImpl("sys.path.remove({0})".format(
QgsProcessingUtils.stringToPythonLiteral(dirname)), False)
+
+ def showApiDocumentation(self, text, force_search=False):
+ self._interpreter.execCommandImpl(f'_help({repr(text)}, api=Qgis.DocumentationApi.PyQgis, force_search={force_search})', show_input=False)
+
+ def showApi(self, api: Qgis.DocumentationApi):
+ self._interpreter.execCommandImpl(f'_help(api=Qgis.DocumentationApi.{api.name})', show_input=False)
+
+ def populateContextMenu(self, menu):
+
+ word = self.selectedText() or self.wordAtPoint(self.mapFromGlobal(QCursor.pos()))
+ if word:
+ context_help_action = QAction(
+ QgsApplication.getThemeIcon("mActionHelpContents.svg"),
+ QCoreApplication.translate("PythonConsole", "Context Help"),
+ menu)
+ context_help_action.triggered.connect(partial(self.showApiDocumentation, word, force_search=True))
+ context_help_action.setShortcut('F1')
+ menu.addAction(context_help_action)
diff --git a/python/console/console_settings.py b/python/console/console_settings.py
index a34e0adc259d..dafa29931eee 100644
--- a/python/console/console_settings.py
+++ b/python/console/console_settings.py
@@ -25,7 +25,7 @@
from qgis.PyQt.QtWidgets import QWidget, QFileDialog, QMessageBox, QTableWidgetItem, QHBoxLayout
from qgis.PyQt.QtGui import QIcon, QDesktopServices
-from qgis.core import QgsSettings, QgsApplication, QgsSettingsTree
+from qgis.core import QgsSettings, QgsApplication, QgsSettingsTree, Qgis
from qgis.gui import QgsOptionsPageWidget, QgsOptionsWidgetFactory
from .console_compile_apis import PrepareAPIDialog
@@ -76,6 +76,10 @@ def __init__(self, parent):
self.parent = parent
self.setupUi(self)
+ # Populate the documentation Browser combobox
+ self.contextHelpBrowser.addItem(QCoreApplication.translate("PythonConsole", "Embedded Webview (developer tools)"), Qgis.DocumentationBrowser.DeveloperToolsPanel)
+ self.contextHelpBrowser.addItem(QCoreApplication.translate("PythonConsole", "Default System Web Browser"), Qgis.DocumentationBrowser.SystemWebBrowser)
+
self.autopep8Level.setClearValue(1)
self.maxLineLength.setClearValue(80)
@@ -211,14 +215,17 @@ def saveSettings(self):
settings.setValue("pythonConsole/formatOnSave", self.formatOnSave.isChecked())
- pythonSettingsTreeNode = QgsSettingsTree.node("gui").childNode("code-editor").childNode("python")
+ codeEditorTreeNode = QgsSettingsTree.node("gui").childNode("code-editor")
+ pythonSettingsTreeNode = codeEditorTreeNode.childNode("python")
pythonSettingsTreeNode.childSetting("sort-imports").setValue(self.sortImports.isChecked())
pythonSettingsTreeNode.childSetting("formatter").setValue(self.formatter.currentText())
pythonSettingsTreeNode.childSetting("autopep8-level").setValue(self.autopep8Level.value())
pythonSettingsTreeNode.childSetting("black-normalize-quotes").setValue(self.blackNormalizeQuotes.isChecked())
pythonSettingsTreeNode.childSetting("max-line-length").setValue(self.maxLineLength.value())
- pythonSettingsTreeNode.childSetting('external-editor').setValue(
- self.externalEditor.text())
+ pythonSettingsTreeNode.childSetting('external-editor').setValue(self.externalEditor.text())
+ pythonSettingsTreeNode.childSetting('context-help-browser').setVariantValue(self.contextHelpBrowser.currentData().name)
+
+ codeEditorTreeNode.childSetting('context-help-hover').setValue(self.contextHelpHover.isChecked())
def restoreSettings(self):
settings = QgsSettings()
@@ -244,7 +251,8 @@ def restoreSettings(self):
self.autoSurround.setChecked(settings.value("pythonConsole/autoSurround", True, type=bool))
self.autoInsertImport.setChecked(settings.value("pythonConsole/autoInsertImport", False, type=bool))
- pythonSettingsTreeNode = QgsSettingsTree.node("gui").childNode("code-editor").childNode("python")
+ codeEditorTreeNode = QgsSettingsTree.node("gui").childNode("code-editor")
+ pythonSettingsTreeNode = codeEditorTreeNode.childNode("python")
self.formatOnSave.setChecked(settings.value("pythonConsole/formatOnSave", False, type=bool))
self.sortImports.setChecked(pythonSettingsTreeNode.childSetting("sort-imports").value())
@@ -253,6 +261,15 @@ def restoreSettings(self):
self.blackNormalizeQuotes.setChecked(pythonSettingsTreeNode.childSetting("black-normalize-quotes").value())
self.maxLineLength.setValue(pythonSettingsTreeNode.childSetting("max-line-length").value())
+ browserName = pythonSettingsTreeNode.childSetting('context-help-browser').valueAsVariant()
+ try:
+ browser = Qgis.DocumentationBrowser[browserName]
+ except KeyError:
+ browser = Qgis.DocumentationBrowser.DeveloperToolsPanel
+
+ self.contextHelpBrowser.setCurrentIndex(self.contextHelpBrowser.findData(browser))
+ self.contextHelpHover.setChecked(codeEditorTreeNode.childSetting('context-help-hover').value())
+
if settings.value("pythonConsole/autoCompleteSource") == 'fromDoc':
self.autoCompFromDoc.setChecked(True)
elif settings.value("pythonConsole/autoCompleteSource") == 'fromAPI':
diff --git a/python/console/console_settings.ui b/python/console/console_settings.ui
index 70c26cbc6715..a95715b34490 100644
--- a/python/console/console_settings.ui
+++ b/python/console/console_settings.ui
@@ -6,8 +6,8 @@
0
0
- 809
- 974
+ 753
+ 556
@@ -24,6 +24,7 @@
+ 50
false
@@ -52,9 +53,9 @@
0
- -115
- 795
- 1089
+ 0
+ 739
+ 1240
@@ -70,8 +71,95 @@
0
- -
-
+
-
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ Run and Debug
+
+
+ true
+
+
+ true
+
+
+
+ 2
+
+
-
+
+
+ Auto-save script before running
+
+
+
+ -
+
+
+ Enable Object Inspector (switching between tabs may be slow)
+
+
+ false
+
+
+
+
+
+
+ -
+
+
+ Typing
+
+
+ false
+
+
+ true
+
+
+
+ 2
+
+
-
+
+
+ Automatic parentheses insertion
+
+
+
+ -
+
+
+ Automatically surround selection when typing quotes or brackets
+
+
+
+ -
+
+
+ Automatic insertion of the 'import' string on 'from xxx'
+
+
+
+
+
+
+ -
+
Formatting
@@ -176,6 +264,35 @@
+ -
+
+
+ External Editor
+
+
+
-
+
+
+ <html><head/><body><p>Command to launch an external Python code editor. If empty, the default system editor will be used.</p><p>Use the token <span style=" font-style:italic;"><file></span> to insert the filename, <span style=" font-style:italic;"><line></span> to insert line number, and <span style=" font-style:italic;"><col></span> to insert the column number. For example:<br/><span style=" font-family:'Noto Sans Mono';">kate -l <line> -c <col> "<file>"</span></p></body></html>
+
+
+ true
+
+
+ Qt::TextBrowserInteraction
+
+
+
+ -
+
+
+ Default
+
+
+
+
+
+
-
@@ -264,7 +381,7 @@
- -
+
-
APIs
@@ -466,58 +583,10 @@
- -
-
-
- Run and Debug
-
-
- true
-
-
- true
-
-
-
- 2
-
-
-
-
-
- Auto-save script before running
-
-
-
- -
-
-
- Enable Object Inspector (switching between tabs may be slow)
-
-
- false
-
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
- -
-
+
-
+
- Typing
+ Contextual Help (F1)
false
@@ -525,57 +594,24 @@
true
-
+
2
-
-
+
- Automatic parentheses insertion
+ Open in
- -
-
-
- Automatically surround selection when typing quotes or brackets
-
-
-
- -
-
-
- Automatic insertion of the 'import' string on 'from xxx'
-
-
+
-
+
-
-
-
- -
-
-
- External Editor
-
-
-
-
-
+
-
+
- <html><head/><body><p>Command to launch an external Python code editor. If empty, the default system editor will be used.</p><p>Use the token <span style=" font-style:italic;"><file></span> to insert the filename, <span style=" font-style:italic;"><line></span> to insert line number, and <span style=" font-style:italic;"><col></span> to insert the column number. For example:<br/><span style=" font-family:'Noto Sans Mono';">kate -l <line> -c <col> "<file>"</span></p></body></html>
-
-
- true
-
-
- Qt::TextBrowserInteraction
-
-
-
- -
-
-
- Default
+ F1 works on hovered words
@@ -608,7 +644,6 @@
groupBoxAutoCompletion
- scrollArea
autoCompThreshold
autoCompFromDoc
autoCompFromAPI
@@ -616,6 +651,7 @@
autoCloseBracket
autoSurround
autoInsertImport
+ contextHelpBrowser
formatOnSave
sortImports
maxLineLength
@@ -631,6 +667,8 @@
groupBoxPreparedAPI
compileAPIs
lineEdit
+ scrollArea
+ externalEditor
diff --git a/python/core/auto_additions/qgis.py b/python/core/auto_additions/qgis.py
index 6a2d3f4d36a2..99bbecf3f9e1 100644
--- a/python/core/auto_additions/qgis.py
+++ b/python/core/auto_additions/qgis.py
@@ -10794,6 +10794,36 @@
"""
# --
Qgis.ColorModel.baseClass = Qgis
+# monkey patching scoped based enum
+Qgis.DocumentationApi.PyQgis.__doc__ = "PyQgis API documentation"
+Qgis.DocumentationApi.PyQgisSearch.__doc__ = "Search in PyQgis API documentation"
+Qgis.DocumentationApi.CppQgis.__doc__ = "C++ QGIS API documentation"
+Qgis.DocumentationApi.Qt.__doc__ = "Qt API documentation"
+Qgis.DocumentationApi.__doc__ = """Documentation API
+
+.. versionadded:: 3.42
+
+* ``PyQgis``: PyQgis API documentation
+* ``PyQgisSearch``: Search in PyQgis API documentation
+* ``CppQgis``: C++ QGIS API documentation
+* ``Qt``: Qt API documentation
+
+"""
+# --
+Qgis.DocumentationApi.baseClass = Qgis
+# monkey patching scoped based enum
+Qgis.DocumentationBrowser.DeveloperToolsPanel.__doc__ = "Embedded webview in the DevTools panel"
+Qgis.DocumentationBrowser.SystemWebBrowser.__doc__ = "Default system web browser"
+Qgis.DocumentationBrowser.__doc__ = """Documentation API browser
+
+.. versionadded:: 3.42
+
+* ``DeveloperToolsPanel``: Embedded webview in the DevTools panel
+* ``SystemWebBrowser``: Default system web browser
+
+"""
+# --
+Qgis.DocumentationBrowser.baseClass = Qgis
from enum import Enum
diff --git a/python/core/auto_generated/qgis.sip.in b/python/core/auto_generated/qgis.sip.in
index fc9c1c4223fd..b6687de410d2 100644
--- a/python/core/auto_generated/qgis.sip.in
+++ b/python/core/auto_generated/qgis.sip.in
@@ -3173,6 +3173,20 @@ The development version
Cmyk,
};
+ enum class DocumentationApi
+ {
+ PyQgis,
+ PyQgisSearch,
+ CppQgis,
+ Qt,
+ };
+
+ enum class DocumentationBrowser
+ {
+ DeveloperToolsPanel,
+ SystemWebBrowser,
+ };
+
static const double DEFAULT_SEARCH_RADIUS_MM;
static const float DEFAULT_MAPTOPIXEL_THRESHOLD;
diff --git a/python/gui/auto_additions/qgscodeeditor.py b/python/gui/auto_additions/qgscodeeditor.py
index 56a526d62071..7c4121878534 100644
--- a/python/gui/auto_additions/qgscodeeditor.py
+++ b/python/gui/auto_additions/qgscodeeditor.py
@@ -56,13 +56,14 @@
QgsCodeEditor.Flags.baseClass = QgsCodeEditor
Flags = QgsCodeEditor # dirty hack since SIP seems to introduce the flags in module
try:
- QgsCodeEditor.__attribute_docs__ = {'SEARCH_RESULT_INDICATOR': 'Indicator index for search results', 'sessionHistoryCleared': 'Emitted when the history of commands run in the current session is cleared.\n\n.. versionadded:: 3.30\n', 'persistentHistoryCleared': 'Emitted when the persistent history of commands run in the editor is cleared.\n\n.. versionadded:: 3.30\n'}
+ QgsCodeEditor.__attribute_docs__ = {'SEARCH_RESULT_INDICATOR': 'Indicator index for search results', 'sessionHistoryCleared': 'Emitted when the history of commands run in the current session is cleared.\n\n.. versionadded:: 3.30\n', 'persistentHistoryCleared': 'Emitted when the persistent history of commands run in the editor is cleared.\n\n.. versionadded:: 3.30\n', 'helpRequested': 'Emitted when documentation was requested for the specified ``word``.\n\n.. versionadded:: 3.42\n'}
QgsCodeEditor.languageToString = staticmethod(QgsCodeEditor.languageToString)
QgsCodeEditor.defaultColor = staticmethod(QgsCodeEditor.defaultColor)
QgsCodeEditor.color = staticmethod(QgsCodeEditor.color)
QgsCodeEditor.setColor = staticmethod(QgsCodeEditor.setColor)
QgsCodeEditor.getMonospaceFont = staticmethod(QgsCodeEditor.getMonospaceFont)
QgsCodeEditor.isFixedPitch = staticmethod(QgsCodeEditor.isFixedPitch)
+ QgsCodeEditor.__signal_arguments__ = {'helpRequested': ['word: str']}
QgsCodeEditor.__group__ = ['codeeditors']
except NameError:
pass
diff --git a/python/gui/auto_additions/qgsgui.py b/python/gui/auto_additions/qgsgui.py
index 62086f05bb2d..23b0a1136f99 100644
--- a/python/gui/auto_additions/qgsgui.py
+++ b/python/gui/auto_additions/qgsgui.py
@@ -33,5 +33,6 @@
QgsGui.higFlags = staticmethod(QgsGui.higFlags)
QgsGui.sampleColor = staticmethod(QgsGui.sampleColor)
QgsGui.findScreenAt = staticmethod(QgsGui.findScreenAt)
+ QgsGui.hasWebEngine = staticmethod(QgsGui.hasWebEngine)
except NameError:
pass
diff --git a/python/gui/auto_generated/codeeditors/qgscodeeditor.sip.in b/python/gui/auto_generated/codeeditors/qgscodeeditor.sip.in
index 2fe8d0f9c15c..f7312489bddd 100644
--- a/python/gui/auto_generated/codeeditors/qgscodeeditor.sip.in
+++ b/python/gui/auto_generated/codeeditors/qgscodeeditor.sip.in
@@ -519,6 +519,14 @@ Emitted when the persistent history of commands run in the editor is cleared.
.. versionadded:: 3.30
%End
+
+ void helpRequested( const QString &word );
+%Docstring
+Emitted when documentation was requested for the specified ``word``.
+
+.. versionadded:: 3.42
+%End
+
protected:
static bool isFixedPitch( const QFont &font );
diff --git a/python/gui/auto_generated/codeeditors/qgscodeeditorpython.sip.in b/python/gui/auto_generated/codeeditors/qgscodeeditorpython.sip.in
index 6ff2e3befbbb..e42a32690ce3 100644
--- a/python/gui/auto_generated/codeeditors/qgscodeeditorpython.sip.in
+++ b/python/gui/auto_generated/codeeditors/qgscodeeditorpython.sip.in
@@ -94,6 +94,13 @@ Updates the editor capabilities.
Searches the selected text in the official PyQGIS online documentation.
.. versionadded:: 3.16
+%End
+
+ virtual void showApiDocumentation( const QString &item );
+%Docstring
+Displays the given text in the official APIs (PyQGIS, C++ QGIS or Qt) documentation.
+
+.. versionadded:: 3.42
%End
virtual void toggleComment();
diff --git a/python/gui/auto_generated/qgisinterface.sip.in b/python/gui/auto_generated/qgisinterface.sip.in
index d78806ba761b..04d4e289c185 100644
--- a/python/gui/auto_generated/qgisinterface.sip.in
+++ b/python/gui/auto_generated/qgisinterface.sip.in
@@ -1490,6 +1490,18 @@ Unregister a previously registered tool factory from the development/debugging t
.. seealso:: :py:func:`registerDevToolWidgetFactory`
.. versionadded:: 3.14
+%End
+
+ virtual void showApiDocumentation( Qgis::DocumentationApi api = Qgis::DocumentationApi::PyQgis, Qgis::DocumentationBrowser browser = Qgis::DocumentationBrowser::DeveloperToolsPanel, const QString &object = QString(), const QString &module = QString() ) = 0;
+%Docstring
+Show a page of the API documentation
+
+:param api: Which API to display
+:param browser: Web browser used to display the API documentation
+:param object: object to show in the documentation
+:param module: used only if api = :py:class:`Qgis`.DocumentationApi.PyQgis
+
+.. versionadded:: 3.42
%End
virtual void registerApplicationExitBlocker( QgsApplicationExitBlockerInterface *blocker ) = 0;
diff --git a/python/gui/auto_generated/qgsgui.sip.in b/python/gui/auto_generated/qgsgui.sip.in
index c63fc6258cfc..f454cd348c20 100644
--- a/python/gui/auto_generated/qgsgui.sip.in
+++ b/python/gui/auto_generated/qgsgui.sip.in
@@ -251,6 +251,13 @@ Returns the screen at the given global ``point`` (pixel).
+ static bool hasWebEngine();
+%Docstring
+Checks whether QWebEngineView is available to display HTML content.
+
+.. versionadded:: 3.42
+%End
+
signals:
diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt
index 0124ddae0253..10105ca8fd6c 100644
--- a/src/app/CMakeLists.txt
+++ b/src/app/CMakeLists.txt
@@ -174,6 +174,7 @@ set(QGIS_APP_SRCS
devtools/networklogger/qgsnetworkloggerwidgetfactory.cpp
devtools/profiler/qgsprofilerpanelwidget.cpp
devtools/profiler/qgsprofilerwidgetfactory.cpp
+ devtools/documentation/qgsdocumentationpanelwidget.cpp
devtools/querylogger/qgsappquerylogger.cpp
devtools/querylogger/qgsdatabasequeryloggernode.cpp
devtools/querylogger/qgsqueryloggerpanelwidget.cpp
diff --git a/src/app/devtools/documentation/qgsdocumentationpanelwidget.cpp b/src/app/devtools/documentation/qgsdocumentationpanelwidget.cpp
new file mode 100644
index 000000000000..a638dade0e7d
--- /dev/null
+++ b/src/app/devtools/documentation/qgsdocumentationpanelwidget.cpp
@@ -0,0 +1,56 @@
+/***************************************************************************
+ qgsdocumentationpanelwidget.cpp
+ -------------------------
+ begin : October 2024
+ copyright : (C) 2024 by Yoann Quenach de Quivillic
+ email : yoann dot quenach 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. *
+ * *
+ ***************************************************************************/
+
+#include "qgsdocumentationpanelwidget.h"
+#include "moc_qgsdocumentationpanelwidget.cpp"
+#include "qgisapp.h"
+#include
+
+#ifdef HAVE_WEBENGINE
+#include
+#else
+#include "qgswebview.h"
+#endif
+
+
+//
+// QgsDocumentationPanelWidget
+//
+
+QgsDocumentationPanelWidget::QgsDocumentationPanelWidget( QWidget *parent )
+ : QgsDevToolWidget( parent )
+{
+ setupUi( this );
+#ifdef HAVE_WEBENGINE
+ mWebView = new QWebEngineView( this );
+#else
+ mWebView = new QgsWebView( this );
+#endif
+
+ mWebViewContainer->layout()->addWidget( mWebView );
+
+ connect( mPyQgisHomeButton, &QToolButton::clicked, this, [] {QgisApp::instance()->showApiDocumentation( Qgis::DocumentationApi::PyQgis, Qgis::DocumentationBrowser::DeveloperToolsPanel );} );
+ connect( mQtHomeButton, &QToolButton::clicked, this, [] {QgisApp::instance()->showApiDocumentation( Qgis::DocumentationApi::Qt, Qgis::DocumentationBrowser::DeveloperToolsPanel );} );
+ connect( mOpenUrlButton, &QToolButton::clicked, this, [this] {QgisApp::instance()->openURL( mWebView->url().toString(), false );} );
+
+}
+
+void QgsDocumentationPanelWidget::showUrl( const QUrl &url )
+{
+ if ( mWebView->url() != url )
+ {
+ mWebView->load( url );
+ }
+}
diff --git a/src/app/devtools/documentation/qgsdocumentationpanelwidget.h b/src/app/devtools/documentation/qgsdocumentationpanelwidget.h
new file mode 100644
index 000000000000..75038ad8cb64
--- /dev/null
+++ b/src/app/devtools/documentation/qgsdocumentationpanelwidget.h
@@ -0,0 +1,61 @@
+/***************************************************************************
+ qgsdocumentationpanelwidget.h
+ -------------------------
+ begin : October 2024
+ copyright : (C) 2024 by Yoann Quenach de Quivillic
+ email : yoann dot quenach 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. *
+ * *
+ ***************************************************************************/
+#ifndef QGSDOCUMENTATIONPANELWIDGET_H
+#define QGSDOCUMENTATIONPANELWIDGET_H
+
+#include "qgsdevtoolwidget.h"
+#include "qgsconfig.h"
+#include "ui_qgsdocumentationpanelbase.h"
+
+#ifdef HAVE_WEBENGINE
+class QWebEngineView;
+#else
+class QgsWebView;
+#endif
+
+/**
+ * \ingroup app
+ * \class QgsDocumentationPanelWidget
+ * \brief A panel widget showing the API documentation.
+ *
+ * \since QGIS 3.42
+ */
+class QgsDocumentationPanelWidget : public QgsDevToolWidget, private Ui::QgsDocumentationPanelBase
+{
+ Q_OBJECT
+
+ public:
+
+ /**
+ * Constructor for QgsDocumentationPanelWidget.
+ */
+ QgsDocumentationPanelWidget( QWidget *parent );
+
+
+ void showUrl( const QUrl &url );
+
+ private:
+
+#ifdef HAVE_WEBENGINE
+ QWebEngineView *mWebView = nullptr;
+#else
+ QgsWebView *mWebView = nullptr;
+#endif
+
+
+};
+
+
+#endif // QGSDOCUMENTATIONPANELWIDGET_H
diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp
index a4a5d942c682..51ecbabd1137 100644
--- a/src/app/qgisapp.cpp
+++ b/src/app/qgisapp.cpp
@@ -12964,25 +12964,12 @@ void QgisApp::helpContents()
void QgisApp::apiDocumentation()
{
- if ( QFileInfo::exists( QgsApplication::pkgDataPath() + "/doc/api/index.html" ) )
- {
- openURL( QStringLiteral( "api/index.html" ) );
- }
- else
- {
- QgsSettings settings;
- QString QgisApiUrl = settings.value( QStringLiteral( "qgis/QgisApiUrl" ),
- QStringLiteral( "https://qgis.org/api/" ) ).toString();
- openURL( QgisApiUrl, false );
- }
+ showApiDocumentation( Qgis::DocumentationApi::CppQgis, Qgis::DocumentationBrowser::SystemWebBrowser );
}
void QgisApp::pyQgisApiDocumentation()
{
- QgsSettings settings;
- QString PyQgisApiUrl = settings.value( QStringLiteral( "qgis/PyQgisApiUrl" ),
- QStringLiteral( "https://qgis.org/pyqgis/" ) ).toString();
- openURL( PyQgisApiUrl, false );
+ showApiDocumentation( Qgis::DocumentationApi::PyQgis, Qgis::DocumentationBrowser::SystemWebBrowser );
}
void QgisApp::reportaBug()
@@ -13126,6 +13113,12 @@ void QgisApp::unregisterDevToolFactory( QgsDevToolWidgetFactory *factory )
mDevToolFactories.removeAll( factory );
}
+
+void QgisApp::showApiDocumentation( Qgis::DocumentationApi api, Qgis::DocumentationBrowser browser, const QString &object, const QString &module )
+{
+ mDevToolsWidget->showApiDocumentation( api, browser, object, module );
+}
+
void QgisApp::registerApplicationExitBlocker( QgsApplicationExitBlockerInterface *blocker )
{
mApplicationExitBlockers << blocker;
diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h
index 7514bb96ccde..34b8e29d21b7 100644
--- a/src/app/qgisapp.h
+++ b/src/app/qgisapp.h
@@ -798,6 +798,9 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
//! Unregister a previously registered dev tool factory
void unregisterDevToolFactory( QgsDevToolWidgetFactory *factory );
+ //! Show a page of the API documentation
+ void showApiDocumentation( Qgis::DocumentationApi api, Qgis::DocumentationBrowser browser, const QString &object = QString(), const QString &module = QString() );
+
/**
* Register a new application exit blocker, which can be used to prevent the QGIS application
* from exiting while a plugin or script has unsaved changes.
@@ -1415,6 +1418,9 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
*/
void activateDeactivateLayerRelatedActions( QgsMapLayer *layer );
+ //! Open a url in the users configured browser
+ void openURL( QString url, bool useQgisDocDirectory = true );
+
protected:
void showEvent( QShowEvent *event ) override;
@@ -1778,8 +1784,6 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
void supportProviders();
//! Open the QGIS homepage in users browser
void helpQgisHomePage();
- //! Open a url in the users configured browser
- void openURL( QString url, bool useQgisDocDirectory = true );
//! Check qgis version against the qgis version server
void checkQgisVersion();
//!Invoke the custom projection dialog
@@ -2737,6 +2741,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
QgsScopedDevToolWidgetFactory mStartupProfilerWidgetFactory;
QgsAppQueryLogger *mQueryLogger = nullptr;
QgsScopedDevToolWidgetFactory mQueryLoggerWidgetFactory;
+ QgsScopedDevToolWidgetFactory mDocumentationWidgetFactory;
std::vector< QgsScopedOptionsWidgetFactory > mOptionWidgetFactories;
diff --git a/src/app/qgisappinterface.cpp b/src/app/qgisappinterface.cpp
index 62fb66c1fc39..ca870fc2040e 100644
--- a/src/app/qgisappinterface.cpp
+++ b/src/app/qgisappinterface.cpp
@@ -641,6 +641,12 @@ void QgisAppInterface::unregisterDevToolWidgetFactory( QgsDevToolWidgetFactory *
qgis->unregisterDevToolFactory( factory );
}
+void QgisAppInterface::showApiDocumentation( Qgis::DocumentationApi api, Qgis::DocumentationBrowser browser, const QString &object, const QString &module )
+{
+ qgis->showApiDocumentation( api, browser, object, module );
+}
+
+
void QgisAppInterface::registerApplicationExitBlocker( QgsApplicationExitBlockerInterface *blocker )
{
qgis->registerApplicationExitBlocker( blocker );
diff --git a/src/app/qgisappinterface.h b/src/app/qgisappinterface.h
index d1494ed9fbce..b67407b5d6d9 100644
--- a/src/app/qgisappinterface.h
+++ b/src/app/qgisappinterface.h
@@ -157,6 +157,7 @@ class APP_EXPORT QgisAppInterface : public QgisInterface
void unregisterProjectPropertiesWidgetFactory( QgsOptionsWidgetFactory *factory ) override;
void registerDevToolWidgetFactory( QgsDevToolWidgetFactory *factory ) override;
void unregisterDevToolWidgetFactory( QgsDevToolWidgetFactory *factory ) override;
+ void showApiDocumentation( Qgis::DocumentationApi api, Qgis::DocumentationBrowser browser, const QString &object, const QString &module ) override;
void registerApplicationExitBlocker( QgsApplicationExitBlockerInterface *blocker ) override;
void unregisterApplicationExitBlocker( QgsApplicationExitBlockerInterface *blocker ) override;
void registerMapToolHandler( QgsAbstractMapToolHandler *handler ) override;
diff --git a/src/app/qgsdevtoolspanelwidget.cpp b/src/app/qgsdevtoolspanelwidget.cpp
index 427e82299a10..202b33777b71 100644
--- a/src/app/qgsdevtoolspanelwidget.cpp
+++ b/src/app/qgsdevtoolspanelwidget.cpp
@@ -19,6 +19,9 @@
#include "qgsdevtoolwidget.h"
#include "qgspanelwidgetstack.h"
#include "qgssettingsentryimpl.h"
+#include "qgsapplication.h"
+#include "qgsdockwidget.h"
+#include "devtools/documentation/qgsdocumentationpanelwidget.h"
const QgsSettingsEntryString *QgsDevToolsPanelWidget::settingLastActiveTab = new QgsSettingsEntryString( QStringLiteral( "last-active-tab" ), QgsDevToolsPanelWidget::sTreeDevTools, QString(), QStringLiteral( "Last visible tab in developer tools panel" ) );
@@ -31,6 +34,11 @@ QgsDevToolsPanelWidget::QgsDevToolsPanelWidget( const QListsetIconSize( QgisApp::instance()->iconSize( false ) );
mOptionsListWidget->setMaximumWidth( static_cast< int >( mOptionsListWidget->iconSize().width() * 1.18 ) );
+
+ // Add embedded documentation
+ mDocumentationPanel = new QgsDocumentationPanelWidget( this );
+ addToolWidget( mDocumentationPanel ) ;
+
for ( QgsDevToolWidgetFactory *factory : factories )
addToolFactory( factory );
@@ -44,6 +52,21 @@ QgsDevToolsPanelWidget::QgsDevToolsPanelWidget( const QListaddWidget( widget );
+
+ QListWidgetItem *item = new QListWidgetItem( widget->windowIcon(), QString() );
+ item->setToolTip( widget->windowTitle() );
+ item->setData( Qt::UserRole, widget->objectName() );
+ mOptionsListWidget->addItem( item );
+ if ( mOptionsListWidget->count() == 1 )
+ {
+ setCurrentTool( 0 );
+ }
+}
+
+
void QgsDevToolsPanelWidget::addToolFactory( QgsDevToolWidgetFactory *factory )
{
if ( QgsDevToolWidget *toolWidget = factory->createWidget( this ) )
@@ -104,3 +127,107 @@ void QgsDevToolsPanelWidget::setCurrentTool( int row )
whileBlocking( mOptionsListWidget )->setCurrentRow( row );
mStackedWidget->setCurrentIndex( row );
}
+
+void QgsDevToolsPanelWidget::showApiDocumentation(
+ Qgis::DocumentationApi api, Qgis::DocumentationBrowser browser, const QString &object, const QString &module
+)
+{
+ bool useQgisDocDirectory = false;
+ QString baseUrl;
+ QString version;
+
+ if ( api == Qgis::DocumentationApi::Qt )
+ {
+ version = QString( qVersion() ).split( '.' ).mid( 0, 2 ).join( '.' );
+ baseUrl = QString( "https://doc.qt.io/qt-%1/" ).arg( version );
+ }
+ else
+ {
+ if ( Qgis::version().toLower().contains( QStringLiteral( "master" ) ) )
+ {
+ version = QStringLiteral( "master" );
+ }
+ else
+ {
+ version = QString( Qgis::version() ).split( '.' ).mid( 0, 2 ).join( '.' );
+ }
+
+ if ( api == Qgis::DocumentationApi::PyQgis || api == Qgis::DocumentationApi::PyQgisSearch )
+ {
+ QgsSettings settings;
+ baseUrl = settings.value( QStringLiteral( "qgis/PyQgisApiUrl" ),
+ QString( "https://qgis.org/pyqgis/%1/" ).arg( version ) ).toString();
+ }
+ else
+ {
+ if ( QFileInfo::exists( QgsApplication::pkgDataPath() + "/doc/api/index.html" ) )
+ {
+ useQgisDocDirectory = true;
+ baseUrl = "api/";
+ }
+ else
+ {
+ QgsSettings settings;
+ baseUrl = settings.value( QStringLiteral( "qgis/QgisApiUrl" ),
+ QString( "https://qgis.org/api/%1/" ).arg( version ) ).toString();
+ }
+ }
+ }
+
+
+ QString url;
+ if ( object.isEmpty() )
+ {
+ url = baseUrl == "api/" ? baseUrl + "index.html" : baseUrl;
+ }
+ else
+ {
+ switch ( api )
+ {
+ case Qgis::DocumentationApi::PyQgis:
+ url = baseUrl + QString( "%1/%2.html" ).arg( module, object );
+ break;
+ case Qgis::DocumentationApi::PyQgisSearch:
+ url = baseUrl + QString( "search.html?q=%2" ).arg( object );
+ break;
+ case Qgis::DocumentationApi::CppQgis:
+ url = baseUrl + QString( "class%1.html" ).arg( object );
+ break;
+ case Qgis::DocumentationApi::Qt:
+ url = baseUrl + QString( "%1.html" ).arg( object.toLower() );
+ break;
+ }
+ }
+#ifndef HAVE_WEBENGINE
+ // QWebView does not support the search function from the PyQGIS documentation homepage
+ if ( api == Qgis::DocumentationApi::PyQgisSearch )
+ {
+ browser = Qgis::DocumentationBrowser::SystemWebBrowser;
+ }
+#endif
+
+ switch ( browser )
+ {
+ case Qgis::DocumentationBrowser::SystemWebBrowser:
+ QgisApp::instance()->openURL( url, useQgisDocDirectory );
+ break;
+ case Qgis::DocumentationBrowser::DeveloperToolsPanel:
+ if ( useQgisDocDirectory )
+ {
+ url = "file://" + QgsApplication::pkgDataPath() + "/doc/" + url;
+ }
+ if ( QgsDockWidget *dock = QgisApp::instance()->findChild< QgsDockWidget * >( "DevTools" ) )
+ {
+ dock->setUserVisible( true );
+ }
+ showUrl( QUrl( url ) );
+ break;
+ }
+
+}
+
+void QgsDevToolsPanelWidget::showUrl( const QUrl &url )
+{
+ setActiveTab( mDocumentationPanel->objectName() );
+ mDocumentationPanel->showUrl( url );
+}
diff --git a/src/app/qgsdevtoolspanelwidget.h b/src/app/qgsdevtoolspanelwidget.h
index 13e46402817f..4b482fd45433 100644
--- a/src/app/qgsdevtoolspanelwidget.h
+++ b/src/app/qgsdevtoolspanelwidget.h
@@ -20,7 +20,8 @@
#include "qgssettingstree.h"
class QgsDevToolWidgetFactory;
-
+class QgsDevToolWidget;
+class QgsDocumentationPanelWidget;
class APP_EXPORT QgsDevToolsPanelWidget : public QWidget, private Ui::QgsDevToolsWidgetBase
{
Q_OBJECT
@@ -32,12 +33,22 @@ class APP_EXPORT QgsDevToolsPanelWidget : public QWidget, private Ui::QgsDevTool
QgsDevToolsPanelWidget( const QList &factories, QWidget *parent = nullptr );
~QgsDevToolsPanelWidget() override;
+ void addToolWidget( QgsDevToolWidget *widget );
void addToolFactory( QgsDevToolWidgetFactory *factory );
void removeToolFactory( QgsDevToolWidgetFactory *factory );
void setActiveTab( const QString &title );
+ void showApiDocumentation(
+ Qgis::DocumentationApi api = Qgis::DocumentationApi::PyQgis,
+ Qgis::DocumentationBrowser browser = Qgis::DocumentationBrowser::DeveloperToolsPanel,
+ const QString &object = QString(),
+ const QString &module = QString()
+ );
+
+ void showUrl( const QUrl &url );
+
private slots:
void setCurrentTool( int row );
@@ -45,6 +56,7 @@ class APP_EXPORT QgsDevToolsPanelWidget : public QWidget, private Ui::QgsDevTool
private:
QMap< QgsDevToolWidgetFactory *, int> mFactoryPages;
+ QgsDocumentationPanelWidget *mDocumentationPanel = nullptr;
};
#endif // QGSDEVTOOLSPANELWIDGET_H
diff --git a/src/core/qgis.h b/src/core/qgis.h
index 70f7e59b7905..63eb997fda78 100644
--- a/src/core/qgis.h
+++ b/src/core/qgis.h
@@ -5579,6 +5579,32 @@ class CORE_EXPORT Qgis
};
Q_ENUM( ColorModel )
+ /**
+ * Documentation API
+ *
+ * \since QGIS 3.42
+ */
+ enum class DocumentationApi : int
+ {
+ PyQgis, //!< PyQgis API documentation
+ PyQgisSearch, //!< Search in PyQgis API documentation
+ CppQgis, //!< C++ QGIS API documentation
+ Qt, //!< Qt API documentation
+ };
+ Q_ENUM( DocumentationApi )
+
+ /**
+ * Documentation API browser
+ *
+ * \since QGIS 3.42
+ */
+ enum class DocumentationBrowser : int
+ {
+ DeveloperToolsPanel, //!< Embedded webview in the DevTools panel
+ SystemWebBrowser, //!< Default system web browser
+ };
+ Q_ENUM( DocumentationBrowser )
+
/**
* Identify search radius in mm
*/
diff --git a/src/core/qgswebview.h b/src/core/qgswebview.h
index ce98e823e891..cdfdc5454e31 100644
--- a/src/core/qgswebview.h
+++ b/src/core/qgswebview.h
@@ -93,6 +93,11 @@ class CORE_EXPORT QgsWebView : public QTextBrowser
setSource( url );
}
+ QUrl url() const
+ {
+ return source();
+ }
+
QWebPage *page() const
{
return mPage;
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 2c6d41bb70fc..9e9aa88fa51e 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -1824,6 +1824,13 @@ if (BUILD_WITH_QT6)
)
endif()
+if (WITH_QTWEBENGINE)
+ find_package(${QT_VERSION_BASE} COMPONENTS WebEngineWidgets REQUIRED)
+ target_link_libraries(qgis_gui
+ ${QT_VERSION_BASE}::WebEngineWidgets
+ )
+endif()
+
if (WITH_QTGAMEPAD)
target_link_libraries(qgis_gui
${QT_VERSION_BASE}::Gamepad
diff --git a/src/gui/codeeditors/qgscodeeditor.cpp b/src/gui/codeeditors/qgscodeeditor.cpp
index 076524549360..bd83094f8250 100644
--- a/src/gui/codeeditors/qgscodeeditor.cpp
+++ b/src/gui/codeeditors/qgscodeeditor.cpp
@@ -24,6 +24,7 @@
#include "qgscodeeditorhistorydialog.h"
#include "qgsstringutils.h"
#include "qgsfontutils.h"
+#include "qgssettingsentryimpl.h"
#include
#include
@@ -38,6 +39,12 @@
#include
#include "Qsci/qscilexer.h"
+///@cond PRIVATE
+const QgsSettingsEntryBool *QgsCodeEditor::settingContextHelpHover = new QgsSettingsEntryBool( QStringLiteral( "context-help-hover" ), sTreeCodeEditor, false, QStringLiteral( "Whether the context help should works on hovered words" ) );
+///@endcond PRIVATE
+
+
+
QMap< QgsCodeEditorColorScheme::ColorRole, QString > QgsCodeEditor::sColorRoleToSettingsKey
{
{QgsCodeEditorColorScheme::ColorRole::Default, QStringLiteral( "defaultFontColor" ) },
@@ -193,6 +200,30 @@ void QgsCodeEditor::keyPressEvent( QKeyEvent *event )
return;
}
+ if ( event->key() == Qt::Key_F1 )
+ {
+
+ // Check if some text is selected
+ QString text = selectedText();
+
+ // Check if mouse is hovering over a word
+ if ( text.isEmpty() && settingContextHelpHover->value() )
+ {
+ text = wordAtPoint( mapFromGlobal( QCursor::pos() ) );
+ }
+
+ // Otherwise, check if there is a word at the current text cursor position
+ if ( text.isEmpty() )
+ {
+ int line, index;
+ getCursorPosition( &line, &index );
+ text = wordAtLineIndex( line, index );
+ }
+ emit helpRequested( text ) ;
+ return;
+ }
+
+
if ( mMode == QgsCodeEditor::Mode::CommandInput )
{
switch ( event->key() )
diff --git a/src/gui/codeeditors/qgscodeeditor.h b/src/gui/codeeditors/qgscodeeditor.h
index 94ebd4153fe8..21008cbd85d6 100644
--- a/src/gui/codeeditors/qgscodeeditor.h
+++ b/src/gui/codeeditors/qgscodeeditor.h
@@ -33,6 +33,7 @@
class QgsFilterLineEdit;
class QToolButton;
class QCheckBox;
+class QgsSettingsEntryBool;
SIP_IF_MODULE( HAVE_QSCI_SIP )
@@ -107,6 +108,7 @@ class GUI_EXPORT QgsCodeEditor : public QsciScintilla
#ifndef SIP_RUN
static inline QgsSettingsTreeNode *sTreeCodeEditor = QgsSettingsTree::sTreeGui->createChildNode( QStringLiteral( "code-editor" ) );
+ static const QgsSettingsEntryBool *settingContextHelpHover;
#endif
/**
@@ -551,6 +553,14 @@ class GUI_EXPORT QgsCodeEditor : public QsciScintilla
*/
void persistentHistoryCleared();
+
+ /**
+ * Emitted when documentation was requested for the specified \a word.
+ *
+ * \since QGIS 3.42
+ */
+ void helpRequested( const QString &word );
+
protected:
/**
diff --git a/src/gui/codeeditors/qgscodeeditorpython.cpp b/src/gui/codeeditors/qgscodeeditorpython.cpp
index 65c167c566e6..078026aeb287 100644
--- a/src/gui/codeeditors/qgscodeeditorpython.cpp
+++ b/src/gui/codeeditors/qgscodeeditorpython.cpp
@@ -22,6 +22,7 @@
#include "qgspythonrunner.h"
#include "qgsprocessingutils.h"
#include "qgssettingsentryimpl.h"
+#include "qgssettingsentryenumflag.h"
#include "qgssettings.h"
#include
#include
@@ -52,6 +53,7 @@ const QgsSettingsEntryBool *QgsCodeEditorPython::settingSortImports = new QgsSet
const QgsSettingsEntryInteger *QgsCodeEditorPython::settingAutopep8Level = new QgsSettingsEntryInteger( QStringLiteral( "autopep8-level" ), sTreePythonCodeEditor, 1, QStringLiteral( "Autopep8 aggressive level" ) );
const QgsSettingsEntryBool *QgsCodeEditorPython::settingBlackNormalizeQuotes = new QgsSettingsEntryBool( QStringLiteral( "black-normalize-quotes" ), sTreePythonCodeEditor, true, QStringLiteral( "Whether quotes should be normalized when auto-formatting code using black" ) );
const QgsSettingsEntryString *QgsCodeEditorPython::settingExternalPythonEditorCommand = new QgsSettingsEntryString( QStringLiteral( "external-editor" ), sTreePythonCodeEditor, QString(), QStringLiteral( "Command to launch an external Python code editor. Use the token to insert the filename, to insert line number, and to insert the column number." ) );
+const QgsSettingsEntryEnumFlag< Qgis::DocumentationBrowser > *QgsCodeEditorPython::settingContextHelpBrowser = new QgsSettingsEntryEnumFlag< Qgis::DocumentationBrowser >( QStringLiteral( "context-help-browser" ), sTreePythonCodeEditor, Qgis::DocumentationBrowser::DeveloperToolsPanel, QStringLiteral( "Web browser used to display the api documentation" ) );
///@endcond PRIVATE
@@ -73,6 +75,8 @@ QgsCodeEditorPython::QgsCodeEditorPython( QWidget *parent, const QList
QgsCodeEditorPython::initializeLexer();
+ connect( this, &QgsCodeEditorPython::helpRequested, this, &QgsCodeEditorPython::showApiDocumentation );
+
updateCapabilities();
}
@@ -517,12 +521,24 @@ void QgsCodeEditorPython::populateContextMenu( QMenu *menu )
{
QgsCodeEditor::populateContextMenu( menu );
+ QString text = selectedText();
+ if ( text.isEmpty() )
+ {
+ text = wordAtPoint( mapFromGlobal( QCursor::pos() ) );
+ }
+ if ( text.isEmpty() )
+ {
+ return;
+ }
+
QAction *pyQgisHelpAction = new QAction(
QgsApplication::getThemeIcon( QStringLiteral( "console/iconHelpConsole.svg" ) ),
tr( "Search Selection in PyQGIS Documentation" ),
menu );
+
pyQgisHelpAction->setEnabled( hasSelectedText() );
- connect( pyQgisHelpAction, &QAction::triggered, this, &QgsCodeEditorPython::searchSelectedTextInPyQGISDocs );
+ pyQgisHelpAction->setShortcut( QStringLiteral( "F1" ) );
+ connect( pyQgisHelpAction, &QAction::triggered, this, [text, this] {showApiDocumentation( text );} );
menu->addSeparator();
menu->addAction( pyQgisHelpAction );
@@ -707,13 +723,32 @@ bool QgsCodeEditorPython::checkSyntax()
void QgsCodeEditorPython::searchSelectedTextInPyQGISDocs()
{
- if ( !hasSelectedText() )
- return;
+ showApiDocumentation( selectedText() );
+}
- QString text = selectedText();
- text = text.replace( QLatin1String( ">>> " ), QString() ).replace( QLatin1String( "... " ), QString() ).trimmed(); // removing prompts
- const QString version = QString( Qgis::version() ).split( '.' ).mid( 0, 2 ).join( '.' );
- QDesktopServices::openUrl( QUrl( QStringLiteral( "https://qgis.org/pyqgis/%1/search.html?q=%2" ).arg( version, text ) ) );
+void QgsCodeEditorPython::showApiDocumentation( const QString &text )
+{
+ QString searchText = text;
+ searchText = searchText.replace( QLatin1String( ">>> " ), QString() ).replace( QLatin1String( "... " ), QString() ).trimmed(); // removing prompts
+
+ QRegularExpression qtExpression( "^Q[A-Z][a-zA-Z]" );
+
+ if ( qtExpression.match( searchText ).hasMatch() )
+ {
+ const QString qtVersion = QString( qVersion() ).split( '.' ).mid( 0, 2 ).join( '.' );
+ QString baseUrl = QString( "https://doc.qt.io/qt-%1" ).arg( qtVersion );
+ QDesktopServices::openUrl( QUrl( QStringLiteral( "%1/%2.html" ).arg( baseUrl, searchText.toLower() ) ) );
+ return;
+ }
+ const QString qgisVersion = QString( Qgis::version() ).split( '.' ).mid( 0, 2 ).join( '.' );
+ if ( searchText.isEmpty() )
+ {
+ QDesktopServices::openUrl( QUrl( QStringLiteral( "https://qgis.org/pyqgis/%1/" ).arg( qgisVersion ) ) );
+ }
+ else
+ {
+ QDesktopServices::openUrl( QUrl( QStringLiteral( "https://qgis.org/pyqgis/%1/search.html?q=%2" ).arg( qgisVersion, searchText ) ) );
+ }
}
void QgsCodeEditorPython::toggleComment()
diff --git a/src/gui/codeeditors/qgscodeeditorpython.h b/src/gui/codeeditors/qgscodeeditorpython.h
index 6e7813422982..f0c9efc6bc9e 100644
--- a/src/gui/codeeditors/qgscodeeditorpython.h
+++ b/src/gui/codeeditors/qgscodeeditorpython.h
@@ -23,6 +23,7 @@
class QgsSettingsEntryInteger;
class QgsSettingsEntryBool;
+template class QgsSettingsEntryEnumFlag;
SIP_IF_MODULE( HAVE_QSCI_SIP )
@@ -62,6 +63,7 @@ class GUI_EXPORT QgsCodeEditorPython : public QgsCodeEditor
static const QgsSettingsEntryInteger *settingAutopep8Level;
static const QgsSettingsEntryBool *settingBlackNormalizeQuotes;
static const QgsSettingsEntryString *settingExternalPythonEditorCommand;
+ static const QgsSettingsEntryEnumFlag< Qgis::DocumentationBrowser > *settingContextHelpBrowser;
///@endcond PRIVATE
#endif
@@ -129,6 +131,13 @@ class GUI_EXPORT QgsCodeEditorPython : public QgsCodeEditor
*/
void searchSelectedTextInPyQGISDocs();
+ /**
+ * Displays the given text in the official APIs (PyQGIS, C++ QGIS or Qt) documentation.
+ *
+ * \since QGIS 3.42
+ */
+ virtual void showApiDocumentation( const QString &item );
+
/**
* Toggle comment for the selected text.
*
diff --git a/src/gui/qgisinterface.h b/src/gui/qgisinterface.h
index 8563f6f94d5b..4c454e6c3ba2 100644
--- a/src/gui/qgisinterface.h
+++ b/src/gui/qgisinterface.h
@@ -1280,6 +1280,16 @@ class GUI_EXPORT QgisInterface : public QObject
*/
virtual void unregisterDevToolWidgetFactory( QgsDevToolWidgetFactory *factory ) = 0;
+ /**
+ * Show a page of the API documentation
+ * \param api Which API to display
+ * \param browser Web browser used to display the API documentation
+ * \param object object to show in the documentation
+ * \param module used only if api = Qgis::DocumentationApi::PyQgis
+ * \since QGIS 3.42
+ */
+ virtual void showApiDocumentation( Qgis::DocumentationApi api = Qgis::DocumentationApi::PyQgis, Qgis::DocumentationBrowser browser = Qgis::DocumentationBrowser::DeveloperToolsPanel, const QString &object = QString(), const QString &module = QString() ) = 0;
+
/**
* Register a new application exit blocker, which can be used to prevent the QGIS application
* from exiting while a plugin or script has unsaved changes.
diff --git a/src/gui/qgsgui.cpp b/src/gui/qgsgui.cpp
index 315d3b374aaa..e78c9cedef5d 100644
--- a/src/gui/qgsgui.cpp
+++ b/src/gui/qgsgui.cpp
@@ -527,6 +527,15 @@ void QgsGui::initCalloutWidgets()
} );
}
+bool QgsGui::hasWebEngine()
+{
+#ifdef HAVE_WEBENGINE
+ return true;
+#else
+ return false;
+#endif
+}
+
///@cond PRIVATE
void QgsGui::emitOptionsChanged()
{
diff --git a/src/gui/qgsgui.h b/src/gui/qgsgui.h
index 50d0a85eef2f..f8a9402e72d7 100644
--- a/src/gui/qgsgui.h
+++ b/src/gui/qgsgui.h
@@ -318,6 +318,13 @@ class GUI_EXPORT QgsGui : public QObject
*/
static void initCalloutWidgets() SIP_SKIP;
+ /**
+ * Checks whether QWebEngineView is available to display HTML content.
+ *
+ * \since QGIS 3.42
+ */
+ static bool hasWebEngine();
+
///@cond PRIVATE
void emitOptionsChanged() SIP_SKIP;
///@endcond
diff --git a/src/ui/qgsdocumentationpanelbase.ui b/src/ui/qgsdocumentationpanelbase.ui
new file mode 100644
index 000000000000..37f35dd0abb3
--- /dev/null
+++ b/src/ui/qgsdocumentationpanelbase.ui
@@ -0,0 +1,136 @@
+
+
+ QgsDocumentationPanelBase
+
+
+
+ 0
+ 0
+ 428
+ 538
+
+
+
+ API Documentation
+
+
+
+ :/images/themes/default/mActionHelpContents.svg:/images/themes/default/mActionHelpContents.svg
+
+
+ -
+
+
-
+
+
+ PyQGIS API Documentation
+
+
+
+ :/images/icons/qgis_icon.svg:/images/icons/qgis_icon.svg
+
+
+
+ 24
+ 24
+
+
+
+ true
+
+
+
+ -
+
+
+ Qt API Documentation
+
+
+
+ :/images/themes/default/mIconQt.svg:/images/themes/default/mIconQt.svg
+
+
+
+ 24
+ 24
+
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Open in Web Browser
+
+
+
+ :/images/themes/default/mIconWms.svg:/images/themes/default/mIconWms.svg
+
+
+
+ 24
+ 24
+
+
+
+ true
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+
+
+
+
+ QgsPanelWidget
+ QWidget
+
+ 1
+
+
+
+
+
+
+