From 62e20676ee02fd171a10aa76a4b1eb1b2b6ef0a4 Mon Sep 17 00:00:00 2001 From: Yoann Quenach de Quivillic Date: Thu, 3 Oct 2024 22:39:48 +0200 Subject: [PATCH] Remove python parameter and add context menu actions --- .../gui/auto_generated/qgisinterface.sip.in | 9 ++- python/console/console_editor.py | 19 +++--- python/console/console_output.py | 22 ++++--- python/console/console_sci.py | 62 +++++++++++++------ .../gui/auto_generated/qgisinterface.sip.in | 9 ++- src/app/qgisapp.cpp | 51 +++++++-------- src/app/qgisapp.h | 2 +- src/app/qgisappinterface.cpp | 4 +- src/app/qgisappinterface.h | 2 +- src/gui/qgisinterface.h | 8 ++- 10 files changed, 116 insertions(+), 72 deletions(-) diff --git a/python/PyQt6/gui/auto_generated/qgisinterface.sip.in b/python/PyQt6/gui/auto_generated/qgisinterface.sip.in index dbadcf1e0234..a3ec357aeca3 100644 --- a/python/PyQt6/gui/auto_generated/qgisinterface.sip.in +++ b/python/PyQt6/gui/auto_generated/qgisinterface.sip.in @@ -1492,9 +1492,14 @@ Unregister a previously registered tool factory from the development/debugging t .. versionadded:: 3.14 %End - virtual void showApiDocumentation( const QString &api = QStringLiteral( "qgis" ), bool python = true, bool embedded = true, const QString &module = QString(), const QString &object = QString() ) = 0; + 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 in the embedded Dev tool webview +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 diff --git a/python/console/console_editor.py b/python/console/console_editor.py index aa3f3d368473..b1929e1478ed 100644 --- a/python/console/console_editor.py +++ b/python/console/console_editor.py @@ -123,7 +123,8 @@ def __init__(self, self.modificationChanged.connect(self.editor_tab.modified) self.modificationAttempted.connect(self.fileReadOnly) - self.helpRequested.connect(self.console_widget.shell.help) + def showApiDocumentation(self, text): + self.console_widget.shell.showApiDocumentation(text) def set_code_editor_widget(self, widget: QgsCodeEditorWidget): self.code_editor_widget = widget @@ -156,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"), @@ -248,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) @@ -260,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 e184ebfdd4ef..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 @@ -171,8 +172,6 @@ def __init__(self, self.selectAllShortcut.setContext(Qt.ShortcutContext.WidgetWithChildrenShortcut) self.selectAllShortcut.activated.connect(self.selectAll) - self.helpRequested.connect(self.shell_editor.help) - def on_app_exit(self): """ Prepares the console for a graceful close @@ -241,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( @@ -273,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) @@ -315,3 +316,6 @@ def enteredSelected(self): 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 e53d7e2a47e8..5f1087ce604a 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,8 +34,8 @@ 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, @@ -104,31 +105,37 @@ def __parse_object(object=None): return 'qt', module, obj """, r""" -def _help(object=None, api="c++", embedded=True): +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 object is None: - if api == "c++": - iface.showApiDocumentation(python=True) - else: - iface.showApiDocumentation() - return + if not object: + return iface.showApiDocumentation(api, embedded=embedded) if isinstance(object, str): try: object = eval(object) except (SyntaxError, NameError): - return + 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: - return + 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 - iface.showApiDocumentation(obj_type, python= not api=="c++", embedded=embedded, module=module, object=class_name) + if obj_type == "qt": + api = "qt" + + iface.showApiDocumentation(api, embedded=embedded, object=class_name, module=module) """, r""" @@ -138,7 +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. ''' - return _help(object, api="c++") + return _help(object, api="qgis") """, r""" def _pyqgis(object=None): @@ -231,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( @@ -329,8 +336,6 @@ def __init__(self, console_widget: PythonConsoleWidget): self.sessionHistoryCleared.connect(self.on_session_history_cleared) self.persistentHistoryCleared.connect(self.on_persistent_history_cleared) - self.helpRequested.connect(self.help) - def _setMinimumHeight(self): font = self.lexer().defaultFont(0) fm = QFontMetrics(font) @@ -468,11 +473,28 @@ def runFile(self, filename, override_file_name: Optional[str] = None): self._interpreter.execCommandImpl("sys.path.remove({0})".format( QgsProcessingUtils.stringToPythonLiteral(dirname)), False) - def help(self, name): + def showApiDocumentation(self, text, force_search=False): pythonSettingsTreeNode = QgsSettingsTree.node("gui").childNode("code-editor").childNode("python") embedded = pythonSettingsTreeNode.childSetting('context-help-embedded').value() - api = "pyqgis" if pythonSettingsTreeNode.childSetting('context-help-pyqgis').value() else "c++" + api = "pyqgis" if pythonSettingsTreeNode.childSetting('context-help-pyqgis').value() else "qgis" + + self._interpreter.execCommandImpl(f'_help({repr(text)}, api="{api}", embedded={embedded}, force_search={force_search})', show_input=False) - self._interpreter.execCommandImpl(f'_help("{name}", api="{api}", embedded={embedded})', 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/gui/auto_generated/qgisinterface.sip.in b/python/gui/auto_generated/qgisinterface.sip.in index dbadcf1e0234..a3ec357aeca3 100644 --- a/python/gui/auto_generated/qgisinterface.sip.in +++ b/python/gui/auto_generated/qgisinterface.sip.in @@ -1492,9 +1492,14 @@ Unregister a previously registered tool factory from the development/debugging t .. versionadded:: 3.14 %End - virtual void showApiDocumentation( const QString &api = QStringLiteral( "qgis" ), bool python = true, bool embedded = true, const QString &module = QString(), const QString &object = QString() ) = 0; + 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 in the embedded Dev tool webview +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 diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index cedbd7ba4977..82d9884f9c8e 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -12940,12 +12940,12 @@ void QgisApp::helpContents() void QgisApp::apiDocumentation() { - showApiDocumentation( "qgis", false, false ); + showApiDocumentation( "qgis", false ); } void QgisApp::pyQgisApiDocumentation() { - showApiDocumentation( "qgis", true, false ); + showApiDocumentation( "pyqgis", false ); } void QgisApp::reportaBug() @@ -13090,46 +13090,46 @@ void QgisApp::unregisterDevToolFactory( QgsDevToolWidgetFactory *factory ) } -void QgisApp::showApiDocumentation( const QString &api, bool python, bool embedded, const QString &module, const QString &object ) +void QgisApp::showApiDocumentation( const QString &api, bool embedded, const QString &object, const QString &module ) { bool useQgisDocDirectory = false; QString baseUrl; + QString version; + if ( api == "qt" ) { - const QStringList parts = QString( qVersion() ).split( "." ); - baseUrl = QString( "https://doc.qt.io/qt-%1.%2/" ).arg( parts[0], parts[1] ); + version = QString( qVersion() ).split( '.' ).mid( 0, 2 ).join( '.' ); + baseUrl = QString( "https://doc.qt.io/qt-%1/" ).arg( version ); } - else if ( api == "qgis" ) + else if ( api.contains( "qgis" ) ) { - // Get the current QGIS version - QString version; if ( Qgis::version().toLower().contains( QStringLiteral( "master" ) ) ) { version = QStringLiteral( "master" ); } else { - version = Qgis::version().left( 4 ); + version = QString( Qgis::version() ).split( '.' ).mid( 0, 2 ).join( '.' ); } - if ( python ) + if ( api.contains( "pyqgis" ) ) { QgsSettings settings; baseUrl = settings.value( QStringLiteral( "qgis/PyQgisApiUrl" ), - QString( "https://qgis.org/pyqgis/%1" ).arg( version ) ).toString(); + QString( "https://qgis.org/pyqgis/%1/" ).arg( version ) ).toString(); } else { if ( QFileInfo::exists( QgsApplication::pkgDataPath() + "/doc/api/index.html" ) ) { useQgisDocDirectory = true; - baseUrl = "api/index.html"; + baseUrl = "api/"; } else { QgsSettings settings; baseUrl = settings.value( QStringLiteral( "qgis/QgisApiUrl" ), - QString( "https://qgis.org/api/%1" ).arg( version ) ).toString(); + QString( "https://qgis.org/api/%1/" ).arg( version ) ).toString(); } } } @@ -13142,24 +13142,25 @@ void QgisApp::showApiDocumentation( const QString &api, bool python, bool embedd QString url; if ( object.isEmpty() ) { - url = baseUrl; + url = baseUrl == "api/" ? baseUrl + "index.html" : baseUrl; } else { - if ( api == QStringLiteral( "qgis" ) ) + if ( api == QStringLiteral( "pyqgis" ) ) { - if ( python ) // pyQGIS - { - url = baseUrl + QString( "/%1/%2.html" ).arg( module, object ); - } - else - { - url = baseUrl + QString( "/class%1.html" ).arg( object ); - } + 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() ); + url = baseUrl + QString( "%1.html" ).arg( object.toLower() ); } } @@ -13174,7 +13175,7 @@ void QgisApp::showApiDocumentation( const QString &api, bool python, bool embedd } else { - openURL( baseUrl, useQgisDocDirectory ); + openURL( url, useQgisDocDirectory ); } } diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index d4b08e880400..6438ebdc15ae 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -799,7 +799,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow void unregisterDevToolFactory( QgsDevToolWidgetFactory *factory ); //! Show a page of the API documentation - void showApiDocumentation( const QString &api, bool python, bool embedded, const QString &module = QString(), const QString &object = QString() ); + 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 diff --git a/src/app/qgisappinterface.cpp b/src/app/qgisappinterface.cpp index fa8812f500bf..5699c60067a7 100644 --- a/src/app/qgisappinterface.cpp +++ b/src/app/qgisappinterface.cpp @@ -640,9 +640,9 @@ void QgisAppInterface::unregisterDevToolWidgetFactory( QgsDevToolWidgetFactory * qgis->unregisterDevToolFactory( factory ); } -void QgisAppInterface::showApiDocumentation( const QString &api, bool python, bool embedded, const QString &module, const QString &object ) +void QgisAppInterface::showApiDocumentation( const QString &api, bool embedded, const QString &object, const QString &module ) { - qgis->showApiDocumentation( api, python, embedded, module, object ); + qgis->showApiDocumentation( api, embedded, object, module ); } diff --git a/src/app/qgisappinterface.h b/src/app/qgisappinterface.h index e41f8652520e..8237d5e9c595 100644 --- a/src/app/qgisappinterface.h +++ b/src/app/qgisappinterface.h @@ -157,7 +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 python, bool embedded, const QString &module, const QString &object ) 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/gui/qgisinterface.h b/src/gui/qgisinterface.h index f74ebb547d09..c2603b627d4f 100644 --- a/src/gui/qgisinterface.h +++ b/src/gui/qgisinterface.h @@ -1281,10 +1281,14 @@ class GUI_EXPORT QgisInterface : public QObject virtual void unregisterDevToolWidgetFactory( QgsDevToolWidgetFactory *factory ) = 0; /** - * Show a page of the API documentation in the embedded Dev tool webview + * 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( "qgis" ), bool python = true, bool embedded = true, const QString &module = QString(), const QString &object = QString() ) = 0; + 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