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/gui/auto_additions/qgscodeeditor.py b/python/PyQt6/gui/auto_additions/qgscodeeditor.py
index 9ca000f68875..0449f760090a 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 whent the F1 key is pressed while hovering over a 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_generated/codeeditors/qgscodeeditor.sip.in b/python/PyQt6/gui/auto_generated/codeeditors/qgscodeeditor.sip.in
index 30eccb100b18..605ec9b40620 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( QString word );
+%Docstring
+Emitted whent the F1 key is pressed while hovering over a 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..1130559bd339 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
+Searches 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..a3ec357aeca3 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( const QString &api = QStringLiteral( "pyqgis" ), bool embedded = true, const QString &object = QString(), const QString &module = QString() ) = 0;
+%Docstring
+Show a page of the API documentation
+
+:param api: "pyqgis" or "qgis" or "qt" or "pyqgis-search"
+:param embedded: If ``True``, the documentation will be opened in the embedded devtools webview. Otherwise, use system web browser
+:param object: object to show in the documentation
+:param module: used only if api = "pyqgis"
+
+.. versionadded:: 3.42
%End
virtual void registerApplicationExitBlocker( QgsApplicationExitBlockerInterface *blocker ) = 0;
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..1c6068112c8c 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,40 @@ def __parse_object(object=None):
module = match[1]
obj = match[2]
return 'qt', module, obj
+""",
+ r"""
+def _help(object=None, api="pyqgis", embedded=True, 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.
+ '''
+
+ if not object:
+ return iface.showApiDocumentation(api, embedded=embedded)
+
+ if isinstance(object, str):
+ try:
+ object = eval(object)
+ except (SyntaxError, NameError):
+ if embedded and not force_search:
+ return iface.showApiDocumentation(api, embedded=True)
+ else:
+ return iface.showApiDocumentation("pyqgis-search", object=object, embedded=False)
+
+ obj_info = __parse_object(object)
+ if not obj_info:
+ if force_search or isinstance(object, str) and not embedded:
+ return iface.showApiDocumentation("pyqgis-search", object=object, embedded=False)
+ else:
+ return iface.showApiDocumentation(api, embedded=embedded)
+
+ obj_type, module, class_name = obj_info
+ if obj_type == "qt":
+ api = "qt"
+
+ iface.showApiDocumentation(api, embedded=embedded, object=class_name, module=module)
+
""",
r"""
def _api(object=None):
@@ -109,18 +145,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")
""",
r"""
def _pyqgis(object=None):
@@ -129,18 +154,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="pyqgis")
"""
]
@@ -224,9 +238,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("pyqgis")
elif cmd == '_api':
- webbrowser.open("https://qgis.org/api/{}".format('' if version == 'master' else version))
+ self.shell.showApi("qgis")
elif cmd == '_cookbook':
webbrowser.open(
"https://docs.qgis.org/{}/en/docs/pyqgis_developer_cookbook/".format(
@@ -458,3 +472,28 @@ 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):
+
+ pythonSettingsTreeNode = QgsSettingsTree.node("gui").childNode("code-editor").childNode("python")
+
+ embedded = pythonSettingsTreeNode.childSetting('context-help-embedded').value()
+
+ self._interpreter.execCommandImpl(f'_help({repr(text)}, api="pyqgis", embedded={embedded}, force_search={force_search})', show_input=False)
+
+ def showApi(self, api):
+ pythonSettingsTreeNode = QgsSettingsTree.node("gui").childNode("code-editor").childNode("python")
+ embedded = pythonSettingsTreeNode.childSetting('context-help-embedded').value()
+ self._interpreter.execCommandImpl(f'_help(api="{api}", embedded={embedded})', 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..39ecc7e14b74 100644
--- a/python/console/console_settings.py
+++ b/python/console/console_settings.py
@@ -211,14 +211,16 @@ 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-embedded').setValue(self.contextHelpBrowser.currentIndex() == 0)
+ codeEditorTreeNode.childSetting('context-help-hover').setValue(self.contextHelpHover.isChecked())
def restoreSettings(self):
settings = QgsSettings()
@@ -244,7 +246,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())
@@ -252,6 +255,8 @@ def restoreSettings(self):
self.autopep8Level.setValue(pythonSettingsTreeNode.childSetting("autopep8-level").value())
self.blackNormalizeQuotes.setChecked(pythonSettingsTreeNode.childSetting("black-normalize-quotes").value())
self.maxLineLength.setValue(pythonSettingsTreeNode.childSetting("max-line-length").value())
+ self.contextHelpBrowser.setCurrentIndex(0 if pythonSettingsTreeNode.childSetting('context-help-embedded').value() else 1)
+ self.contextHelpHover.setChecked(codeEditorTreeNode.childSetting('context-help-hover').value())
if settings.value("pythonConsole/autoCompleteSource") == 'fromDoc':
self.autoCompFromDoc.setChecked(True)
diff --git a/python/console/console_settings.ui b/python/console/console_settings.ui
index 70c26cbc6715..c8e1c3c81e23 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,35 @@
true
-
+
2
-
-
+
- Automatic parentheses insertion
+ Open in
- -
-
-
- Automatically surround selection when typing quotes or brackets
-
-
-
- -
-
-
- Automatic insertion of the 'import' string on 'from xxx'
-
+
-
+
+
-
+
+ Embedded Webview (developer tools)
+
+
+ -
+
+ Default system web browser
+
+
-
-
-
- -
-
-
- 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 +655,6 @@
groupBoxAutoCompletion
- scrollArea
autoCompThreshold
autoCompFromDoc
autoCompFromAPI
@@ -616,6 +662,7 @@
autoCloseBracket
autoSurround
autoInsertImport
+ contextHelpBrowser
formatOnSave
sortImports
maxLineLength
@@ -631,6 +678,8 @@
groupBoxPreparedAPI
compileAPIs
lineEdit
+ scrollArea
+ externalEditor
diff --git a/python/gui/auto_additions/qgscodeeditor.py b/python/gui/auto_additions/qgscodeeditor.py
index 56a526d62071..d6374e103a41 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 whent the F1 key is pressed while hovering over a 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_generated/codeeditors/qgscodeeditor.sip.in b/python/gui/auto_generated/codeeditors/qgscodeeditor.sip.in
index 2fe8d0f9c15c..47847693be0e 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( QString word );
+%Docstring
+Emitted whent the F1 key is pressed while hovering over a 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..1130559bd339 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
+Searches 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..a3ec357aeca3 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( const QString &api = QStringLiteral( "pyqgis" ), bool embedded = true, const QString &object = QString(), const QString &module = QString() ) = 0;
+%Docstring
+Show a page of the API documentation
+
+:param api: "pyqgis" or "qgis" or "qt" or "pyqgis-search"
+:param embedded: If ``True``, the documentation will be opened in the embedded devtools webview. Otherwise, use system web browser
+:param object: object to show in the documentation
+:param module: used only if api = "pyqgis"
+
+.. versionadded:: 3.42
%End
virtual void registerApplicationExitBlocker( QgsApplicationExitBlockerInterface *blocker ) = 0;
diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt
index dab19c127f3a..ed6ddcc8533d 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..0fe86ddb81a2
--- /dev/null
+++ b/src/app/devtools/documentation/qgsdocumentationpanelwidget.cpp
@@ -0,0 +1,42 @@
+/***************************************************************************
+ 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 "qgswebview.h"
+#include "qgisapp.h"
+#include
+
+//
+// QgsDocumentationPanelWidget
+//
+
+QgsDocumentationPanelWidget::QgsDocumentationPanelWidget( QWidget *parent )
+ : QgsDevToolWidget( parent )
+{
+ setupUi( this );
+
+ connect( mPyQgisHomeButton, &QToolButton::clicked, this, [] {QgisApp::instance()->showApiDocumentation( QStringLiteral( "pyqgis" ), true );} );
+ connect( mQtHomeButton, &QToolButton::clicked, this, [] {QgisApp::instance()->showApiDocumentation( QStringLiteral( "qt" ), true );} );
+ 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..e0288928a229
--- /dev/null
+++ b/src/app/devtools/documentation/qgsdocumentationpanelwidget.h
@@ -0,0 +1,45 @@
+/***************************************************************************
+ 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 "ui_qgsdocumentationpanelbase.h"
+
+/**
+ * \ingroup app
+ * \class QgsDocumentationPanelWidget
+ * \brief A panel widget showing profiled startup times for debugging.
+ *
+ * \since QGIS 3.14
+ */
+class QgsDocumentationPanelWidget : public QgsDevToolWidget, private Ui::QgsDocumentationPanelBase
+{
+ Q_OBJECT
+
+ public:
+
+ /**
+ * Constructor for QgsDocumentationPanelWidget.
+ */
+ QgsDocumentationPanelWidget( QWidget *parent );
+
+
+ void showUrl( const QUrl &url );
+
+};
+
+
+#endif // QGSDOCUMENTATIONPANELWIDGET_H
diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp
index 128f26ec4df9..a05839079c88 100644
--- a/src/app/qgisapp.cpp
+++ b/src/app/qgisapp.cpp
@@ -12963,25 +12963,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", false );
}
void QgisApp::pyQgisApiDocumentation()
{
- QgsSettings settings;
- QString PyQgisApiUrl = settings.value( QStringLiteral( "qgis/PyQgisApiUrl" ),
- QStringLiteral( "https://qgis.org/pyqgis/" ) ).toString();
- openURL( PyQgisApiUrl, false );
+ showApiDocumentation( "pyqgis", false );
}
void QgisApp::reportaBug()
@@ -13125,6 +13112,96 @@ void QgisApp::unregisterDevToolFactory( QgsDevToolWidgetFactory *factory )
mDevToolFactories.removeAll( factory );
}
+
+void QgisApp::showApiDocumentation( const QString &api, bool embedded, const QString &object, const QString &module )
+{
+ bool useQgisDocDirectory = false;
+ QString baseUrl;
+ QString version;
+
+ if ( api == "qt" )
+ {
+ version = QString( qVersion() ).split( '.' ).mid( 0, 2 ).join( '.' );
+ baseUrl = QString( "https://doc.qt.io/qt-%1/" ).arg( version );
+ }
+ else if ( api.contains( "qgis" ) )
+ {
+ if ( Qgis::version().toLower().contains( QStringLiteral( "master" ) ) )
+ {
+ version = QStringLiteral( "master" );
+ }
+ else
+ {
+ version = QString( Qgis::version() ).split( '.' ).mid( 0, 2 ).join( '.' );
+ }
+
+ if ( api.contains( "pyqgis" ) )
+ {
+ 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();
+ }
+ }
+ }
+ else
+ {
+ messageBar()->pushWarning( tr( "Unknown API" ), api );
+ return;
+ }
+
+ QString url;
+ if ( object.isEmpty() )
+ {
+ url = baseUrl == "api/" ? baseUrl + "index.html" : baseUrl;
+ }
+ else
+ {
+ if ( api == QStringLiteral( "pyqgis" ) )
+ {
+ url = baseUrl + QString( "%1/%2.html" ).arg( module, object );
+ }
+ else if ( api == QStringLiteral( "pyqgis-search" ) )
+ {
+ url = baseUrl + QString( "search.html?q=%2" ).arg( object );
+ }
+ else if ( api == QStringLiteral( "qgis" ) )
+ {
+ url = baseUrl + QString( "class%1.html" ).arg( object );
+ }
+ else // Qt
+ {
+ url = baseUrl + QString( "%1.html" ).arg( object.toLower() );
+ }
+ }
+
+ if ( embedded )
+ {
+ if ( useQgisDocDirectory )
+ {
+ url = "file://" + QgsApplication::pkgDataPath() + "/doc/" + url;
+ }
+ mDevToolsDock->show();
+ mDevToolsWidget->showUrl( QUrl( url ) );
+ }
+ else
+ {
+ openURL( url, useQgisDocDirectory );
+ }
+}
+
void QgisApp::registerApplicationExitBlocker( QgsApplicationExitBlockerInterface *blocker )
{
mApplicationExitBlockers << blocker;
diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h
index 7514bb96ccde..38aa74ca3698 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( const QString &api, bool embedded, 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 2e9a1d4d925a..5699c60067a7 100644
--- a/src/app/qgisappinterface.cpp
+++ b/src/app/qgisappinterface.cpp
@@ -640,6 +640,12 @@ void QgisAppInterface::unregisterDevToolWidgetFactory( QgsDevToolWidgetFactory *
qgis->unregisterDevToolFactory( factory );
}
+void QgisAppInterface::showApiDocumentation( const QString &api, bool embedded, const QString &object, const QString &module )
+{
+ qgis->showApiDocumentation( api, embedded, object, module );
+}
+
+
void QgisAppInterface::registerApplicationExitBlocker( QgsApplicationExitBlockerInterface *blocker )
{
qgis->registerApplicationExitBlocker( blocker );
diff --git a/src/app/qgisappinterface.h b/src/app/qgisappinterface.h
index d1494ed9fbce..8237d5e9c595 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( const QString &api, bool embedded, 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 2199cdf3e378..35181cc2340c 100644
--- a/src/app/qgsdevtoolspanelwidget.cpp
+++ b/src/app/qgsdevtoolspanelwidget.cpp
@@ -18,6 +18,7 @@
#include "qgsdevtoolwidget.h"
#include "qgspanelwidgetstack.h"
#include "qgssettingsentryimpl.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" ) );
@@ -30,6 +31,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 );
@@ -43,6 +49,21 @@ QgsDevToolsPanelWidget::QgsDevToolsPanelWidget( const QListaddWidget( widget );
+
+ QListWidgetItem *item = new QListWidgetItem( widget->windowIcon(), QString() );
+ item->setToolTip( widget->windowTitle() );
+ item->setData( Qt::UserRole, widget->windowTitle() );
+ mOptionsListWidget->addItem( item );
+ if ( mOptionsListWidget->count() == 1 )
+ {
+ setCurrentTool( 0 );
+ }
+}
+
+
void QgsDevToolsPanelWidget::addToolFactory( QgsDevToolWidgetFactory *factory )
{
if ( QgsDevToolWidget *toolWidget = factory->createWidget( this ) )
@@ -103,3 +124,9 @@ void QgsDevToolsPanelWidget::setCurrentTool( int row )
whileBlocking( mOptionsListWidget )->setCurrentRow( row );
mStackedWidget->setCurrentIndex( row );
}
+
+void QgsDevToolsPanelWidget::showUrl( const QUrl &url )
+{
+ whileBlocking( mOptionsListWidget )->setCurrentRow( 0 );
+ mDocumentationPanel->showUrl( url );
+}
diff --git a/src/app/qgsdevtoolspanelwidget.h b/src/app/qgsdevtoolspanelwidget.h
index 13e46402817f..d35f4677bcf0 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,15 @@ 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 showUrl( const QUrl &url );
+
private slots:
void setCurrentTool( int row );
@@ -45,6 +49,7 @@ class APP_EXPORT QgsDevToolsPanelWidget : public QWidget, private Ui::QgsDevTool
private:
QMap< QgsDevToolWidgetFactory *, int> mFactoryPages;
+ QgsDocumentationPanelWidget *mDocumentationPanel;
};
#endif // QGSDEVTOOLSPANELWIDGET_H
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/codeeditors/qgscodeeditor.cpp b/src/gui/codeeditors/qgscodeeditor.cpp
index b37baa753e7a..b4407949d46a 100644
--- a/src/gui/codeeditors/qgscodeeditor.cpp
+++ b/src/gui/codeeditors/qgscodeeditor.cpp
@@ -23,6 +23,7 @@
#include "qgscodeeditorhistorydialog.h"
#include "qgsstringutils.h"
#include "qgsfontutils.h"
+#include "qgssettingsentryimpl.h"
#include
#include
@@ -37,6 +38,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" ) },
@@ -192,6 +199,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..7337ba5705e3 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 whent the F1 key is pressed while hovering over a word
+ *
+ * \since QGIS 3.42
+ */
+ void helpRequested( QString word );
+
protected:
/**
diff --git a/src/gui/codeeditors/qgscodeeditorpython.cpp b/src/gui/codeeditors/qgscodeeditorpython.cpp
index 7f3ac0b19eb2..bb7789f4787d 100644
--- a/src/gui/codeeditors/qgscodeeditorpython.cpp
+++ b/src/gui/codeeditors/qgscodeeditorpython.cpp
@@ -51,6 +51,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 QgsSettingsEntryBool *QgsCodeEditorPython::settingContextHelpEmbedded = new QgsSettingsEntryBool( QStringLiteral( "context-help-embedded" ), sTreePythonCodeEditor, true, QStringLiteral( "Whether the context help should be displayed in an embedded webview in the devtools panel" ) );
///@endcond PRIVATE
@@ -72,6 +73,8 @@ QgsCodeEditorPython::QgsCodeEditorPython( QWidget *parent, const QList
QgsCodeEditorPython::initializeLexer();
+ connect( this, &QgsCodeEditorPython::helpRequested, this, &QgsCodeEditorPython::showApiDocumentation );
+
updateCapabilities();
}
@@ -516,12 +519,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 );
@@ -706,13 +721,25 @@ 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( '.' );
+ 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..fc125410c6c2 100644
--- a/src/gui/codeeditors/qgscodeeditorpython.h
+++ b/src/gui/codeeditors/qgscodeeditorpython.h
@@ -62,6 +62,7 @@ class GUI_EXPORT QgsCodeEditorPython : public QgsCodeEditor
static const QgsSettingsEntryInteger *settingAutopep8Level;
static const QgsSettingsEntryBool *settingBlackNormalizeQuotes;
static const QgsSettingsEntryString *settingExternalPythonEditorCommand;
+ static const QgsSettingsEntryBool *settingContextHelpEmbedded;
///@endcond PRIVATE
#endif
@@ -129,6 +130,13 @@ class GUI_EXPORT QgsCodeEditorPython : public QgsCodeEditor
*/
void searchSelectedTextInPyQGISDocs();
+ /**
+ * Searches 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..c2603b627d4f 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 "pyqgis" or "qgis" or "qt" or "pyqgis-search"
+ * \param embedded If TRUE, the documentation will be opened in the embedded devtools webview. Otherwise, use system web browser
+ * \param object object to show in the documentation
+ * \param module used only if api = "pyqgis"
+ * \since QGIS 3.42
+ */
+ virtual void showApiDocumentation( const QString &api = QStringLiteral( "pyqgis" ), bool embedded = true, 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/ui/qgsdocumentationpanelbase.ui b/src/ui/qgsdocumentationpanelbase.ui
new file mode 100644
index 000000000000..288d4dee34d1
--- /dev/null
+++ b/src/ui/qgsdocumentationpanelbase.ui
@@ -0,0 +1,119 @@
+
+
+ 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
+
+
+
+
+ -
+
+
+ Qt API Documentation
+
+
+
+ :/images/themes/default/mIconQt.svg:/images/themes/default/mIconQt.svg
+
+
+
+ 24
+ 24
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Open in Web Browser
+
+
+
+ :/images/themes/default/mIconWms.svg:/images/themes/default/mIconWms.svg
+
+
+
+ 24
+ 24
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+
+
+ QgsPanelWidget
+ QWidget
+
+ 1
+
+
+ QgsWebView
+ QWidget
+
+ 1
+
+
+
+
+
+
+