diff options
author | Eike Ziller <eike.ziller@qt.io> | 2018-10-01 12:29:21 +0200 |
---|---|---|
committer | Eike Ziller <eike.ziller@qt.io> | 2018-10-01 12:29:21 +0200 |
commit | ada8aaffed20dfbe6bc6df8c21c85b4138957490 (patch) | |
tree | d738ea0a24c1013e589e69240d6fc91ae13ed999 | |
parent | 55732e19082a566f644e08bd28b9cff6161dcb7e (diff) | |
parent | 3412dc563119221bd730c1487937519df0761792 (diff) |
Merge remote-tracking branch 'origin/4.8'
Change-Id: Ic4e84c9aaa8cf605f7561d1e6ef3fc4b3414e729
26 files changed, 175 insertions, 134 deletions
@@ -1,9 +1,10 @@ # Python Extensions for Qt Creator This plugin for Qt Creator makes it possible to extend the Qt Creator by -writing Python extensions. These extensions are simply Python packages, -so they are directories containing a `__init__.py` and any other Python -files necessary. They can be shared as zip archives, installed and managed +writing Python extensions. These extensions are simply Python modules or +packages, so they are either single Python files or directories containing a +`__init__.py` and any other Python files necessary. +They can be shared as zip archives, installed and managed through the included extension manager (which is a Python extension itself) and thus allow the Qt Creator to be easily extended with custom functionality. @@ -129,9 +130,10 @@ The following process allows the plugin to work: 2. When Qt Creator is executed, the C++ plugin searches the standard Qt Creator plugin directories for a directory named `python`, the first directory found is the Python extension directory. - Each python package in this directory represents it's own Python extension. - Each package is loaded with `import`, which executes the initialization - code in its `__init__.py`. + Each python module and package in this directory represents it's own Python + extension. Each extension is loaded with `import`, which executes the + module's initialization code (for extensions in the form of Python packages + that is the code in `__init__.py`). 3. Now all Python extensions have registered their actions / menus / etc., which can be triggered from the Qt Creator interface. diff --git a/docs/extensions.md b/docs/extensions.md index 7152ef6..57a6f15 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -6,33 +6,28 @@ for Qt Creator. ## Importing Qt Creator specific modules Currently, the bindings are available from Python under the following names: ```Python -from PythonExtension import QtCreator # Imports the generated module for the typesystem -from PythonExtension import PluginInstance # Imports the plugin instance +from QtCreator import Utils # Imports Qt Creator's Utils helper library +from QtCreator import Core # Imports Qt Creator's Core plugin API +from QtCreator import PythonExtensions # Imports Python extension specific helper functions ``` -## `PythonExtension.PluginInstance` +## `QtCreator.PythonExtensions` This is the Python binding for the extension manager that works on the C++ side. It provides the -following functions, that can (and should) be used from Python: +following functions, that can be used from Python: ```Python -from PythonExtension import PluginInstance as inst +from QtCreator import PythonExtensions # Returns a PySide QDir which points to the extension directory -inst.extensionDir() +PythonExtensions.extensionDir() # Returns a list with the names of all Python extensions -# If loaded only is supplied as True, only extensions that -# where loaded successfully are returned -inst.extensionList(loadedOnly = False) - -# Mark an extension as loaded -# This should normally not be used and exists mainly -# for use by the extension manager -inst.flagAsLoaded("name_of_extension") +# Each extension has properties 'name' and 'loaded' +PythonExtensions.extensionList() # Returns the path of the custom location to # where missing dependencies should be pip installed -inst.pythonPackagePath() +PythonExtensions.pythonPackagePath() ``` @@ -48,7 +43,7 @@ add a new action container, that holds a sub-menu. The following code snippet illustrates how to add a new menu. ```Python -from PythonExtension import QtCreator +from QtCreator import Core def hello(): print("Hello World.") @@ -56,24 +51,24 @@ def hello(): # By convention, the menuId starts with "Python" menuId = "Python.SmallMenu.Menu" -menu = QtCreator.Core.ActionManager.createMenu(menuId) +menu = Core.ActionManager.createMenu(menuId) menu.menu().setTitle("My menu") menu.menu().addAction("My action", hello) -# Add our new menu to the "Tools" menu in QtCreator -QtCreator.Core.ActionManager.actionContainer("QtCreator.Menu.Tools").addMenu(menu) +# Add our new menu to the "Tools" menu in Qt Creator +Core.ActionManager.actionContainer("QtCreator.Menu.Tools").addMenu(menu) ``` ### Adding a new action directly The following code snippet illustrates how to add a new action to an existing action container. ```Python -from PythonExtension import QtCreator +from QtCreator import Core def hello(): print("Hello World.") # Add a new action to the "Tools" menu -menu = QtCreator.Core.ActionManager.actionContainer("QtCreator.Menu.Tools") +menu = Core.ActionManager.actionContainer("QtCreator.Menu.Tools") menu.menu().addAction("My action", hello) ``` diff --git a/examples/examples.pro b/examples/examples.pro index 57b1764..34b5843 100644 --- a/examples/examples.pro +++ b/examples/examples.pro @@ -7,7 +7,6 @@ include(../plugins/pythonextensions/qtcreator.pri) include($$IDE_SOURCE_TREE/qtcreator.pri) inst_examples.files = \ - macroexpander \ projects \ requirerequests \ smallmenu \ @@ -18,7 +17,8 @@ inst_examples.CONFIG += no_default_install directory INSTALLS += inst_examples inst_examplefiles.files = \ - examples_common.py + examples_common.py \ + macroexpander.py inst_examplefiles.path = $$IDE_PLUGIN_PATH/python inst_examplefiles.CONFIG += no_default_install diff --git a/examples/examples_common.py b/examples/examples_common.py index db7f669..0bb93d4 100644 --- a/examples/examples_common.py +++ b/examples/examples_common.py @@ -38,15 +38,15 @@ ## ############################################################################# -from PythonExtension import QtCreator +from QtCreator import Core def ensureExamplesMenu(): menuId = 'PythonExtensions.Examples' - menu = QtCreator.Core.ActionManager.actionContainer(menuId) + menu = Core.ActionManager.actionContainer(menuId) if not menu: - menu = QtCreator.Core.ActionManager.createMenu(menuId) + menu = Core.ActionManager.createMenu(menuId) menu.menu().setTitle("Python Extensions Examples") - toolsMenu = QtCreator.Core.ActionManager.actionContainer("QtCreator.Menu.Tools") + toolsMenu = Core.ActionManager.actionContainer("QtCreator.Menu.Tools") toolsMenu.addMenu(menu) return menu diff --git a/examples/macroexpander/__init__.py b/examples/macroexpander.py index 20e10a3..17c30cc 100644 --- a/examples/macroexpander/__init__.py +++ b/examples/macroexpander.py @@ -45,22 +45,23 @@ import examples_common from PySide2.QtCore import QObject, SIGNAL from PySide2.QtWidgets import QInputDialog, QMessageBox, QAction -from PythonExtension import QtCreator +from QtCreator import Core +from QtCreator import Utils # Register our macro (it can be used as %{Python:exp}) -QtCreator.Utils.MacroExpander().registerPrefix(b"Python", "Evaluate Python expressions.", lambda x: eval(x)) +Utils.MacroExpander().registerPrefix(b"Python", "Evaluate Python expressions.", lambda x: eval(x)) # Add a small menu item, that let's us test the macro def act(): - text = QInputDialog.getMultiLineText(QtCreator.Core.ICore.dialogParent(), + text = QInputDialog.getMultiLineText(Core.ICore.dialogParent(), "Input Text", "Input your text, including some macros", "3 + 3 = %{Python:3+3}") - text = QtCreator.Utils.MacroExpander().expand(text[0]) - QMessageBox.information(QtCreator.Core.ICore.dialogParent(), "Result", text) + text = Utils.MacroExpander().expand(text[0]) + QMessageBox.information(Core.ICore.dialogParent(), "Result", text) # Add this to the "Tools" menu action = QAction("Test Macro Expander...") -command = QtCreator.Core.ActionManager.registerAction(action, 'PythonExtensions.Examples.MacroExpander') +command = Core.ActionManager.registerAction(action, 'PythonExtensions.Examples.MacroExpander') QObject.connect(action, SIGNAL('triggered()'), act) examples_common.addExampleItem(command) diff --git a/examples/projects/__init__.py b/examples/projects/__init__.py index c7919fc..a618e5b 100644 --- a/examples/projects/__init__.py +++ b/examples/projects/__init__.py @@ -42,16 +42,16 @@ # project. It also includes a dependency on a plugin # that can be disabled. +from . import view import examples_common -import view -from PythonExtension import QtCreator +from QtCreator import Core from PySide2.QtCore import QObject, SIGNAL from PySide2.QtWidgets import QAction # When importing optional bindings, we can warn the user in case things go south try: - from PythonExtension.QtCreator import ProjectExplorer + from QtCreator import ProjectExplorer except ImportError: view.error("The extension \"projects\" could not be loaded, since it depends on a disabled plugin.") raise Exception("Dependencies missing") @@ -65,7 +65,7 @@ def showProjectPath(): view.error("Please open a project") action = QAction("Show Project Directory") -command = QtCreator.Core.ActionManager.registerAction(action, 'PythonExtensions.Examples.ShowProjectDirectory') +command = Core.ActionManager.registerAction(action, 'PythonExtensions.Examples.ShowProjectDirectory') QObject.connect(action, SIGNAL('triggered()'), showProjectPath) examples_common.addExampleItem(command) diff --git a/examples/projects/view.py b/examples/projects/view.py index 31dbbe6..df1eeb0 100644 --- a/examples/projects/view.py +++ b/examples/projects/view.py @@ -41,10 +41,10 @@ # This contains all the display logic from PySide2 import QtWidgets -from PythonExtension import QtCreator +from QtCreator import Core def error(text, title="Error"): - QtWidgets.QMessageBox.critical(QtCreator.Core.ICore.instance().dialogParent(), title, text) + QtWidgets.QMessageBox.critical(Core.ICore.instance().dialogParent(), title, text) def show(text, title="Result"): - QtWidgets.QMessageBox.information(QtCreator.Core.ICore.instance().dialogParent(), title, text) + QtWidgets.QMessageBox.information(Core.ICore.instance().dialogParent(), title, text) diff --git a/examples/requirerequests/__init__.py b/examples/requirerequests/__init__.py index acbf9e6..3fb14c7 100644 --- a/examples/requirerequests/__init__.py +++ b/examples/requirerequests/__init__.py @@ -44,7 +44,7 @@ import examples_common -from PythonExtension import QtCreator +from QtCreator import Core from PySide2.QtCore import QObject, SIGNAL from PySide2.QtWidgets import QAction, QMessageBox @@ -52,14 +52,14 @@ import requests def load(url): r = requests.get(url) - box = QMessageBox(QtCreator.Core.ICore.dialogParent()) + box = QMessageBox(Core.ICore.dialogParent()) box.setWindowTitle("Request results") box.setText("The request status is {}".format(r.status_code)) box.setDetailedText(r.text) box.exec_() action = QAction("Load from the web...") -command = QtCreator.Core.ActionManager.registerAction(action, 'PythonExtensions.Examples.LoadFromWeb') +command = Core.ActionManager.registerAction(action, 'PythonExtensions.Examples.LoadFromWeb') QObject.connect(action, SIGNAL('triggered()'), lambda: load("https://www.qt.io/")) examples_common.addExampleItem(command) diff --git a/examples/smallmenu/__init__.py b/examples/smallmenu/__init__.py index 5dcfef9..f26b637 100644 --- a/examples/smallmenu/__init__.py +++ b/examples/smallmenu/__init__.py @@ -41,27 +41,27 @@ # This example illustrates the use of action # containers to create new menus -from actions import hello, goodbye +from .actions import hello, goodbye import examples_common -from PythonExtension import QtCreator +from QtCreator import Core from PySide2.QtCore import QObject, SIGNAL from PySide2.QtWidgets import QAction # Create submenu and add it to examples menu menuId = "PythonExtensions.Examples.SmallMenu" -menu = QtCreator.Core.ActionManager.createMenu(menuId) +menu = Core.ActionManager.createMenu(menuId) menu.menu().setTitle("Small Menu") examplesMenu = examples_common.ensureExamplesMenu() examplesMenu.addMenu(menu) # Add actions to our new menu helloAction = QAction("Say Hello") -command = QtCreator.Core.ActionManager.registerAction(helloAction, 'PythonExtensions.Examples.SayHello') +command = Core.ActionManager.registerAction(helloAction, 'PythonExtensions.Examples.SayHello') QObject.connect(helloAction, SIGNAL('triggered()'), hello) menu.addAction(command) goodbyeAction = QAction("Say Goodbye") -command = QtCreator.Core.ActionManager.registerAction(goodbyeAction, 'PythonExtensions.Examples.SayGoodbye') +command = Core.ActionManager.registerAction(goodbyeAction, 'PythonExtensions.Examples.SayGoodbye') QObject.connect(goodbyeAction, SIGNAL('triggered()'), goodbye) menu.addAction(command) diff --git a/examples/transform/__init__.py b/examples/transform/__init__.py index c58f4e6..f2647dc 100644 --- a/examples/transform/__init__.py +++ b/examples/transform/__init__.py @@ -42,11 +42,11 @@ # user supplied function that executes on # a set of files specified by the user -import actions +from . import actions +from . import ui import examples_common -import ui -from PythonExtension import QtCreator +from QtCreator import Core from PySide2.QtCore import QObject, SIGNAL from PySide2.QtWidgets import QAction @@ -56,7 +56,7 @@ def transform(): actions.run(code[0]) action = QAction("Transform files...") -command = QtCreator.Core.ActionManager.registerAction(action, 'PythonExtensions.Examples.TransformFiles') +command = Core.ActionManager.registerAction(action, 'PythonExtensions.Examples.TransformFiles') QObject.connect(action, SIGNAL('triggered()'), transform) examples_common.addExampleItem(command) diff --git a/examples/transform/actions.py b/examples/transform/actions.py index de54d94..e459ab2 100644 --- a/examples/transform/actions.py +++ b/examples/transform/actions.py @@ -38,13 +38,14 @@ ## ############################################################################# -from PythonExtension import QtCreator -import ui +from . import ui + +from QtCreator import Core # Apply a function to each file def run(code): try: - files = QtCreator.Core.DocumentManager.getOpenFileNames("") + files = Core.DocumentManager.getOpenFileNames("") if len(files) == 0: ui.error("No files were selected.") return diff --git a/examples/transform/ui.py b/examples/transform/ui.py index 21892ef..cc5b7f1 100644 --- a/examples/transform/ui.py +++ b/examples/transform/ui.py @@ -38,7 +38,7 @@ ## ############################################################################# -from PythonExtension import QtCreator +from QtCreator import Core from PySide2 import QtWidgets template = """# Parameters: @@ -53,7 +53,7 @@ filebody def getCode(): return QtWidgets.QInputDialog.getMultiLineText( - QtCreator.Core.ICore.dialogParent(), + Core.ICore.dialogParent(), "Input transformation expression", "Transform expression:", template diff --git a/optional/projectexplorer/binding.cpp b/optional/projectexplorer/binding.cpp index 325027b..ae1bbc7 100644 --- a/optional/projectexplorer/binding.cpp +++ b/optional/projectexplorer/binding.cpp @@ -59,7 +59,7 @@ void bind() // Bind module into interpreter bool pythonError = PyErr_Occurred() != nullptr; if (pythonInitialized && !pythonError) { - PyUtil::bindSubPyObject("PythonExtension.QtCreator", "ProjectExplorer", (void *)SbkQtCreatorBindingProjectExplorerModuleObject); + PyUtil::bindSubPyObject("QtCreator", "ProjectExplorer", (void *)SbkQtCreatorBindingProjectExplorerModuleObject); } else { if (pythonError) PyErr_Print(); diff --git a/optional/projectexplorer/projectexplorer.pro b/optional/projectexplorer/projectexplorer.pro index a60ac99..b998d93 100644 --- a/optional/projectexplorer/projectexplorer.pro +++ b/optional/projectexplorer/projectexplorer.pro @@ -18,6 +18,7 @@ QTC_PLUGIN_RECOMMENDS += \ include(../binding/binding.pri) INCLUDEPATH *= $$IDE_SOURCE_TREE/src/plugins/projectexplorer +win32: DEFINES += NOMINMAX # Shiboken binding generation setup diff --git a/optional/projectexplorer/typesystem_projectexplorer.xml b/optional/projectexplorer/typesystem_projectexplorer.xml index b663619..0e0eeff 100644 --- a/optional/projectexplorer/typesystem_projectexplorer.xml +++ b/optional/projectexplorer/typesystem_projectexplorer.xml @@ -60,7 +60,9 @@ <object-type name="ProjectTree"/> <object-type name="Project"> <enum-type name="ModelRoles"/> - <enum-type name="RestoreResult"/> + <!-- protected methods that return enum classes fail on Windows, remove for now --> + <!--<enum-type name="RestoreResult"/>--> + <modify-function signature="fromMap(const QVariantMap&,QString*)" remove="all"/> </object-type> </namespace-type> diff --git a/optional/texteditor/binding.cpp b/optional/texteditor/binding.cpp index d2d5b83..6b9007f 100644 --- a/optional/texteditor/binding.cpp +++ b/optional/texteditor/binding.cpp @@ -59,7 +59,7 @@ void bind() // Bind module into interpreter bool pythonError = PyErr_Occurred() != nullptr; if (pythonInitialized && !pythonError) { - PyUtil::bindSubPyObject("PythonExtension.QtCreator", "TextEditor", (void *)SbkQtCreatorBindingTextEditorModuleObject); + PyUtil::bindSubPyObject("QtCreator", "TextEditor", (void *)SbkQtCreatorBindingTextEditorModuleObject); } else { if (pythonError) PyErr_Print(); diff --git a/plugins/pythonextensions/bindingheaders/pythonextensions_extension.h b/plugins/pythonextensions/bindingheaders/pythonextensions_extension.h new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/plugins/pythonextensions/bindingheaders/pythonextensions_extension.h diff --git a/plugins/pythonextensions/pythonextensions.pro b/plugins/pythonextensions/pythonextensions.pro index c8fe73d..334e029 100644 --- a/plugins/pythonextensions/pythonextensions.pro +++ b/plugins/pythonextensions/pythonextensions.pro @@ -24,7 +24,7 @@ include($$IDE_SOURCE_TREE/src/qtcreatorplugin.pri) # Qt Creator Core bindings WRAPPED_HEADER = $$PWD/wrappedclasses.h -WRAPPER_DIR = $$OUT_PWD/PythonExtension/QtCreator +WRAPPER_DIR = $$OUT_PWD/QtCreator TYPESYSTEM_FILE = typesystem_qtcreator.xml TYPESYSTEM_NAME = qtcreator SHIBOKEN_QT = core gui widgets @@ -36,6 +36,7 @@ WRAPPED_CLASSES = \ bindingheaders/pythonextensions.h \ bindingheaders/pythonextensions_internal.h \ bindingheaders/pythonextensions_internal_pythonextensionsplugin.h \ + bindingheaders/pythonextensions_extension.h \ bindingheaders/core.h \ bindingheaders/core_actioncontainer.h \ bindingheaders/core_actionmanager.h \ diff --git a/plugins/pythonextensions/pythonextensionsplugin.cpp b/plugins/pythonextensions/pythonextensionsplugin.cpp index 3547a59..59b32a1 100644 --- a/plugins/pythonextensions/pythonextensionsplugin.cpp +++ b/plugins/pythonextensions/pythonextensionsplugin.cpp @@ -39,6 +39,8 @@ #include <extensionsystem/pluginmanager.h> #include <extensionsystem/pluginspec.h> +#include <utils/algorithm.h> + #include <QDir> #include <QIODevice> #include <QFile> @@ -131,23 +133,39 @@ QDir PythonExtensionsPlugin::extensionDir() return extension_dir; } -QStringList PythonExtensionsPlugin::extensionList(const bool loadedOnly) +static QVector<Extension> getExtensionList(const QDir &directory) { - if (loadedOnly) - return m_loadedExtensions; - - QDir extension_dir = extensionDir(); - if (!extension_dir.exists()) - return QStringList(); + if (!directory.exists()) + return {}; + + QStringList entries = directory.entryList(QDir::AllDirs | QDir::NoDotAndDotDot); + entries.removeAll("site-packages"); + entries.removeAll("__pycache__"); + const QVector<Extension> packageExtensions + = Utils::transform<QVector>(entries, [](const QString &entry) { + return Extension({entry, false}); + }); + const QStringList fileEntries = directory.entryList({"*.py"}, QDir::Files); + const QVector<Extension> fileExtensions + = Utils::transform<QVector>(fileEntries, [](const QString &entry) { + return Extension({entry.left(entry.size() - 3), false}); + }); + return packageExtensions + fileExtensions; +} - QStringList extension_list = extension_dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot); - extension_list.removeOne("site-packages"); - return extension_list; +QVector<Extension> PythonExtensionsPlugin::extensionList() +{ + return extensionListRef(); } -void PythonExtensionsPlugin::flagAsLoaded(const QString &extension) +QVector<Extension> &PythonExtensionsPlugin::extensionListRef() { - m_loadedExtensions << extension; + static bool initialized = false; + if (!initialized) { + m_extensions = getExtensionList(extensionDir()); + initialized = true; + } + return m_extensions; } QString PythonExtensionsPlugin::pythonPackagePath() @@ -162,18 +180,21 @@ QString PythonExtensionsPlugin::pythonPackagePath() void PythonExtensionsPlugin::initializePythonBindings() { // Add our custom module directory - if (extensionDir().exists()) { + if (extensionDir().exists()) PyUtil::addToSysPath(extensionDir().path().toStdString()); - PyUtil::addToSysPath(pythonPackagePath().toStdString()); - } + // Add directory for local python packages that are installed as requirements of extensions + // Need to create it first if it doesn't exist, otherwise python will ignore the path + if (!QFile::exists(pythonPackagePath())) + QDir().mkpath(pythonPackagePath()); + PyUtil::addToSysPath(pythonPackagePath().toStdString()); // Initialize the Python context and register global Qt Creator variable - if (!PyUtil::bindShibokenModuleObject("PythonExtension", "QtCreator")) { + if (!PyUtil::bindCoreModules()) { qWarning() << "Python bindings could not be initialized"; Core::MessageManager::write(Constants::MESSAGE_MANAGER_PREFIX + tr("Python bindings could not be initialized")); return; } // Bind the plugin instance - PyUtil::bindObject("PythonExtension", "PluginInstance", PyUtil::PythonExtensionsPluginType, this); + PyUtil::bindObject("QtCreator", "PythonExtensions", PyUtil::PythonExtensionsPluginType, this); } void PythonExtensionsPlugin::initializeOptionalBindings() @@ -208,16 +229,18 @@ void PythonExtensionsPlugin::installRequirements() if (!extension_dir.exists()) return; - QStringList extension_list = extensionList(); - for (const QString &extension : extension_list) { - QString extension_requirements(extension_dir.absolutePath() + "/" + extension + "/requirements.txt"); - if (QFileInfo::exists(extension_requirements) && !QFileInfo::exists(extension_requirements + ".installed")) { - if (!PyUtil::pipInstallRequirements( - extension_requirements.toStdString(), - pythonPackagePath().toStdString() - )) { - qWarning() << "Failed to install requirements for extension" << extension; - Core::MessageManager::write(Constants::MESSAGE_MANAGER_PREFIX + tr("Failed to install requirements for extension ") + extension); + QVector<Extension> extension_list = extensionListRef(); + for (const Extension &extension : extension_list) { + QString extension_requirements(extension_dir.absolutePath() + "/" + extension.name + + "/requirements.txt"); + if (QFileInfo::exists(extension_requirements) + && !QFileInfo::exists(extension_requirements + ".installed")) { + if (!PyUtil::pipInstallRequirements(extension_requirements.toStdString(), + pythonPackagePath().toStdString())) { + qWarning() << "Failed to install requirements for extension" << extension.name; + Core::MessageManager::write(Constants::MESSAGE_MANAGER_PREFIX + + tr("Failed to install requirements for extension ") + + extension.name); } } } @@ -235,22 +258,25 @@ void PythonExtensionsPlugin::initializePythonExtensions() qDebug() << "Found Python extension directory at location" << extension_dir.absolutePath(); - QStringList extension_list = extensionList(); + QVector<Extension> &extension_list = extensionListRef(); qDebug() << "Number of Python extensions found:" << extension_list.size(); + int loadedCount = 0; // Run the extension initialization code - for (const QString &extension : extension_list) { - qDebug() << "Trying to initialize extension" << extension; - if (!PyUtil::runScript("import " + extension.toStdString())) { - qWarning() << "Failed to initialize extension" << extension; - Core::MessageManager::write(Constants::MESSAGE_MANAGER_PREFIX + tr("Failed to initialize extension ") + extension); + for (Extension &extension : extension_list) { + qDebug() << "Trying to initialize extension" << extension.name; + if (PyUtil::runScript("import " + extension.name.toStdString())) { + extension.loaded = true; + ++loadedCount; } else { - m_loadedExtensions << extension; + qWarning() << "Failed to initialize extension" << extension.name; + Core::MessageManager::write(Constants::MESSAGE_MANAGER_PREFIX + + tr("Failed to initialize extension ") + extension.name); } } - qDebug() << "Number of Python extensions loaded:" << m_loadedExtensions.size(); + qDebug() << "Number of Python extensions loaded:" << loadedCount; } } // namespace Internal diff --git a/plugins/pythonextensions/pythonextensionsplugin.h b/plugins/pythonextensions/pythonextensionsplugin.h index 29010d4..1a4bce3 100644 --- a/plugins/pythonextensions/pythonextensionsplugin.h +++ b/plugins/pythonextensions/pythonextensionsplugin.h @@ -33,6 +33,13 @@ #include <QStringList> namespace PythonExtensions { + +class Extension { +public: + QString name; + bool loaded; +}; + namespace Internal { class PythonExtensionsPlugin : public ExtensionSystem::IPlugin @@ -50,15 +57,16 @@ public: ShutdownFlag aboutToShutdown() final; QDir extensionDir(); - QStringList extensionList(const bool loadedOnly = false); - void flagAsLoaded(const QString &extension); + QVector<Extension> extensionList(); QString pythonPackagePath(); + private: - QStringList m_loadedExtensions; void initializePythonBindings(); void initializeOptionalBindings(); void installRequirements(); void initializePythonExtensions(); + QVector<Extension> &extensionListRef(); + QVector<Extension> m_extensions; }; // Util functions diff --git a/plugins/pythonextensions/pyutil.cpp b/plugins/pythonextensions/pyutil.cpp index b563c55..b992e36 100644 --- a/plugins/pythonextensions/pyutil.cpp +++ b/plugins/pythonextensions/pyutil.cpp @@ -56,10 +56,10 @@ extern "C" void initQtCreator(); // These variables store all Python types exported by QtCreators bindings, // as well as the types exported for QtWidgets. -extern PyTypeObject **SbkPythonExtension_QtCreatorTypes; +extern PyTypeObject **SbkQtCreatorTypes; // This variable stores the Python module generated by Shiboken -extern PyObject *SbkPythonExtension_QtCreatorModuleObject; +extern PyObject *SbkQtCreatorModuleObject; namespace PyUtil { @@ -157,7 +157,7 @@ bool bindObject(const QString &moduleName, const QString &name, int index, void return false; // Generate the type - PyTypeObject *typeObject = SbkPythonExtension_QtCreatorTypes[index]; + PyTypeObject *typeObject = SbkQtCreatorTypes[index]; PyObject *po = Shiboken::Conversions::pointerToPython(reinterpret_cast<SbkObjectType *>(typeObject), o); if (!po) { @@ -169,9 +169,10 @@ bool bindObject(const QString &moduleName, const QString &name, int index, void return bindPyObject(moduleName, name, po); } -bool bindShibokenModuleObject(const QString &moduleName, const QString &name) +bool bindCoreModules() { - return bindPyObject(moduleName, name, SbkPythonExtension_QtCreatorModuleObject); + return bindSubPyObject("QtCreator", "Utils", SbkQtCreatorModuleObject) + && bindSubPyObject("QtCreator", "Core", SbkQtCreatorModuleObject); } bool bindPyObject(const QString &moduleName, const QString &name, void *obj) diff --git a/plugins/pythonextensions/pyutil.h b/plugins/pythonextensions/pyutil.h index 62a1622..ca58f6e 100644 --- a/plugins/pythonextensions/pyutil.h +++ b/plugins/pythonextensions/pyutil.h @@ -37,7 +37,7 @@ namespace PyUtil { // Note: If modifying the bindings, check these types still align enum QtCreatorTypes { - PythonExtensionsPluginType = 33, // SBK_PYTHONEXTENSIONS_INTERNAL_PYTHONEXTENSIONSPLUGIN_IDX + PythonExtensionsPluginType = 34, // SBK_PYTHONEXTENSIONS_INTERNAL_PYTHONEXTENSIONSPLUGIN_IDX }; enum State @@ -53,7 +53,7 @@ PYTHONEXTENSIONSSHARED_EXPORT bool createModule(const std::string &moduleName); bool bindObject(const QString &moduleName, const QString &name, int index, void *o); -bool bindShibokenModuleObject(const QString &moduleName, const QString &name); +bool bindCoreModules(); PYTHONEXTENSIONSSHARED_EXPORT bool bindPyObject(const QString &moduleName, const QString &name, void *obj); diff --git a/plugins/pythonextensions/typesystem_qtcreator.xml b/plugins/pythonextensions/typesystem_qtcreator.xml index 736fe44..69f051e 100644 --- a/plugins/pythonextensions/typesystem_qtcreator.xml +++ b/plugins/pythonextensions/typesystem_qtcreator.xml @@ -41,12 +41,13 @@ --> <!-- Typesystem for Qt Creator Python host plugin --> -<typesystem package="PythonExtension.QtCreator"> +<typesystem package="QtCreator"> <!-- Load PySide QtWidgets typesystem (is this correct? yup) --> <load-typesystem name="typesystem_widgets.xml" generate="no"/> <namespace-type name="PythonExtensions"> + <value-type name="Extension"/> <namespace-type name="Internal"> <object-type name="PythonExtensionsPlugin"/> </namespace-type> diff --git a/python/extensionmanager/__init__.py b/python/extensionmanager/__init__.py index 7932118..f6caa37 100644 --- a/python/extensionmanager/__init__.py +++ b/python/extensionmanager/__init__.py @@ -40,21 +40,22 @@ # Entry point of the Python extension -from PythonExtension import QtCreator +from .list import * + +from QtCreator import Core from PySide2.QtCore import * from PySide2.QtWidgets import * -from list import * # Actions def showExtensionList(): - dialog = ListView(QtCreator.Core.ICore.dialogParent()) + dialog = ListView(Core.ICore.dialogParent()) dialog.exec_() pythonExtensionsAction = QAction("About Python Extensions...") pythonExtensionsAction.setMenuRole(QAction.ApplicationSpecificRole) -pythonExtensionsCommand = QtCreator.Core.ActionManager.registerAction(pythonExtensionsAction, - "PythonExtensions.About") +pythonExtensionsCommand = Core.ActionManager.registerAction(pythonExtensionsAction, + "PythonExtensions.About") QObject.connect(pythonExtensionsAction, SIGNAL("triggered()"), showExtensionList) -helpMenu = QtCreator.Core.ActionManager.actionContainer("QtCreator.Menu.Help") +helpMenu = Core.ActionManager.actionContainer("QtCreator.Menu.Help") helpMenu.addAction(pythonExtensionsCommand, "QtCreator.Group.Help.About") diff --git a/python/extensionmanager/actions.py b/python/extensionmanager/actions.py index 29d64cc..48e5ae4 100644 --- a/python/extensionmanager/actions.py +++ b/python/extensionmanager/actions.py @@ -41,7 +41,7 @@ # Functions for installing and deleting extensions import sys, zipfile -from PythonExtension import PluginInstance as instance +from QtCreator import PythonExtensions from send2trash import send2trash def install(path): @@ -59,14 +59,14 @@ def install(path): return "The .zip file is malformed." # Make sure the extension manager does not overwrite # extensions that are already installed - for ext in instance.extensionList(): - if extName == ext: - return "The extension \"{}\" is already installed. Uninstall it before trying again.".format(ext) - extZip.extractall(instance.extensionDir().absolutePath()) + for ext in PythonExtensions.extensionList(): + if extName == ext.name: + return "The extension \"{}\" is already installed. Uninstall it before trying again.".format(extName) + extZip.extractall(PythonExtensions.extensionDir().absolutePath()) extZip.close() except Exception as e: return str(e) return True def uninstall(ext): - send2trash(instance.extensionDir().absolutePath() + "/" + ext) + send2trash(PythonExtensions.extensionDir().absolutePath() + "/" + ext) diff --git a/python/extensionmanager/list.py b/python/extensionmanager/list.py index 2996be3..c71b4d4 100644 --- a/python/extensionmanager/list.py +++ b/python/extensionmanager/list.py @@ -40,9 +40,10 @@ # Ui for extension list view +from . import actions + from PySide2 import QtCore, QtWidgets, QtGui -from PythonExtension import PluginInstance as instance -import actions +from QtCreator import PythonExtensions class ExtensionList(QtWidgets.QListWidget): def __init__(self): @@ -55,13 +56,13 @@ class ExtensionList(QtWidgets.QListWidget): def loadExtensionList(self): i = 0 - for ext in instance.extensionList(): + for ext in PythonExtensions.extensionList(): item = QtWidgets.QListWidgetItem(self) - if not ext in instance.extensionList(True): - item.setText(ext + " [did not load]") + if not ext.loaded: + item.setText(ext.name + " [did not load]") item.setIcon(QtGui.QIcon.fromTheme("dialog-error")) else: - item.setText(ext) + item.setText(ext.name) item.setIcon(QtGui.QIcon.fromTheme("applications-development")) if i % 2 == 1: item.setBackground(QtGui.QBrush(QtGui.QColor.fromRgb(240, 240, 240))) @@ -78,7 +79,7 @@ class ListView(QtWidgets.QDialog): self.layout = QtWidgets.QVBoxLayout(self) self.label = QtWidgets.QLabel() - self.label.setText("Manage Python extensions installed to \"{0}\".".format(instance.extensionDir().absolutePath())) + self.label.setText("Manage Python extensions installed to \"{0}\".".format(PythonExtensions.extensionDir().absolutePath())) self.label.setWordWrap(True) self.layout.addWidget(self.label) @@ -115,7 +116,7 @@ class ListView(QtWidgets.QDialog): selected = self.list.selectedIndexes() if len(selected) >= 1: selected = selected[0].row() - ext = instance.extensionList()[selected] + ext = PythonExtensions.extensionList()[selected] if ext == "extensionmanager": QtWidgets.QMessageBox.warning(self, "Can not Uninstall", "The Extension Manager can not uninstall itself.") else: @@ -144,7 +145,7 @@ class ListView(QtWidgets.QDialog): "/", "Qt Creator Python Extensions (*.zip)" ) - oldExtensions = list(instance.extensionList()) + oldExtensions = list(PythonExtensions.extensionList()) result = actions.install(fileName[0]) if result == True: QtWidgets.QMessageBox.information( |