aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTilman Roeder <tilman.roder@qt.io>2018-08-03 09:38:07 +0200
committerTilman Roeder <tilman.roder@qt.io>2018-08-15 10:10:16 +0000
commit13e02b9aaea19ac21251d152a8fa69336ae76ebd (patch)
treea6320449c18251033d3a2557afaed6a8fcafbfc9
parentefea0c2e4a2966d88f65cdab90f841f7905dee14 (diff)
Initial commit
This is a quite large commit containing: * The main extension that runs and initializes Python * Some (example) bindings * An initial build script for the main extension * Optional binding and examples of how to create them * An initial build script for the optional bindings * A simple extension manager written in Python * A few example Python extensions * Some documentation (both in the code and as markdown files) * A collection of helpful python scripts * A small collection of unit tests * A TODO list For any additional details the code / docs should be consulted. Change-Id: I3937886cfefa2f64d5a78013889a8e097eec8261 Reviewed-by: Eike Ziller <eike.ziller@qt.io>
-rw-r--r--.gitignore16
-rw-r--r--README.md129
-rw-r--r--TODO.md30
-rw-r--r--docs/README.md17
-rw-r--r--docs/extensions.md131
-rw-r--r--docs/plugin.md164
-rw-r--r--docs/tools.md43
-rw-r--r--examples/README.md17
-rw-r--r--examples/macroexpander/main.py61
-rw-r--r--examples/numpysetup/main.py60
-rw-r--r--examples/numpysetup/setup.py62
-rw-r--r--examples/package.py64
-rw-r--r--examples/projects/main.py63
-rw-r--r--examples/projects/view.py50
-rw-r--r--examples/requirerequests/main.py58
-rw-r--r--examples/requirerequests/requirements.txt5
-rw-r--r--examples/smallmenu/actions.py54
-rw-r--r--examples/smallmenu/main.py56
-rw-r--r--examples/transform/actions.py61
-rw-r--r--examples/transform/main.py55
-rw-r--r--examples/transform/ui.py72
-rw-r--r--optional/README.md34
-rw-r--r--optional/projectexplorer/binding.cpp69
-rw-r--r--optional/projectexplorer/binding_custom.pri57
-rw-r--r--optional/projectexplorer/bindingheaders/projectexplorer.h0
-rw-r--r--optional/projectexplorer/bindingheaders/projectexplorer_buildconfiguration.h0
-rw-r--r--optional/projectexplorer/bindingheaders/projectexplorer_ibuildconfigurationfactory.h0
-rw-r--r--optional/projectexplorer/bindingheaders/projectexplorer_project.h0
-rw-r--r--optional/projectexplorer/bindingheaders/projectexplorer_projectconfiguration.h0
-rw-r--r--optional/projectexplorer/bindingheaders/projectexplorer_projecttree.h0
-rw-r--r--optional/projectexplorer/bindingheaders/projectexplorer_runconfiguration.h0
-rw-r--r--optional/projectexplorer/bindingheaders/projectexplorer_statefulprojectconfiguration.h0
-rw-r--r--optional/projectexplorer/bindingheaders/utils.h0
-rw-r--r--optional/projectexplorer/bindingheaders/utils_filename.h0
-rw-r--r--optional/projectexplorer/typesystem_projectexplorer.xml72
-rw-r--r--optional/projectexplorer/wrappedclasses.h37
-rw-r--r--optional/setup.py139
-rw-r--r--optional/template/binding.h30
-rw-r--r--optional/template/binding.pro87
-rw-r--r--optional/texteditor/binding.cpp70
-rw-r--r--optional/texteditor/binding_custom.pri51
-rw-r--r--optional/texteditor/bindingheaders/texteditor.h0
-rw-r--r--optional/texteditor/typesystem_texteditor.xml75
-rw-r--r--optional/texteditor/wrappedclasses.h37
-rw-r--r--plugins/pythonextensions/PythonExtensions.json.in18
-rw-r--r--plugins/pythonextensions/bindingheaders/core.h0
-rw-r--r--plugins/pythonextensions/bindingheaders/core_actioncontainer.h0
-rw-r--r--plugins/pythonextensions/bindingheaders/core_actionmanager.h0
-rw-r--r--plugins/pythonextensions/bindingheaders/core_command.h0
-rw-r--r--plugins/pythonextensions/bindingheaders/core_constants.h0
-rw-r--r--plugins/pythonextensions/bindingheaders/core_context.h0
-rw-r--r--plugins/pythonextensions/bindingheaders/core_documentmanager.h0
-rw-r--r--plugins/pythonextensions/bindingheaders/core_documentmodel.h0
-rw-r--r--plugins/pythonextensions/bindingheaders/core_editormanager.h0
-rw-r--r--plugins/pythonextensions/bindingheaders/core_fileutils.h33
-rw-r--r--plugins/pythonextensions/bindingheaders/core_icontext.h0
-rw-r--r--plugins/pythonextensions/bindingheaders/core_icore.h0
-rw-r--r--plugins/pythonextensions/bindingheaders/core_id.h0
-rw-r--r--plugins/pythonextensions/bindingheaders/core_idocument.h0
-rw-r--r--plugins/pythonextensions/bindingheaders/core_ieditor.h0
-rw-r--r--plugins/pythonextensions/bindingheaders/core_messagemanager.h0
-rw-r--r--plugins/pythonextensions/bindingheaders/extensionsystem.h0
-rw-r--r--plugins/pythonextensions/bindingheaders/extensionsystem_iplugin.h0
-rw-r--r--plugins/pythonextensions/bindingheaders/pythonextensions.h0
-rw-r--r--plugins/pythonextensions/bindingheaders/pythonextensions_internal.h0
-rw-r--r--plugins/pythonextensions/bindingheaders/pythonextensions_internal_pythonextensionsplugin.h0
-rw-r--r--plugins/pythonextensions/bindingheaders/utils.h0
-rw-r--r--plugins/pythonextensions/bindingheaders/utils_filename.h0
-rw-r--r--plugins/pythonextensions/bindingheaders/utils_macroexpander.h0
-rw-r--r--plugins/pythonextensions/glue/macroexpander_glue.cpp66
-rw-r--r--plugins/pythonextensions/pyside2.pri42
-rw-r--r--plugins/pythonextensions/pythonextensions.pro126
-rw-r--r--plugins/pythonextensions/pythonextensions_dependencies.pri12
-rw-r--r--plugins/pythonextensions/pythonextensions_global.h34
-rw-r--r--plugins/pythonextensions/pythonextensionsplugin.cpp295
-rw-r--r--plugins/pythonextensions/pythonextensionsplugin.h74
-rw-r--r--plugins/pythonextensions/pyutil.cpp271
-rw-r--r--plugins/pythonextensions/pyutil.h72
-rw-r--r--plugins/pythonextensions/qtcreator.pri15
-rw-r--r--plugins/pythonextensions/typesystem_qtcreator.xml237
-rw-r--r--plugins/pythonextensions/wrappedclasses.h50
-rw-r--r--python/extensionmanager/actions.py72
-rw-r--r--python/extensionmanager/list.py162
-rw-r--r--python/extensionmanager/main.py52
-rw-r--r--python/extensionmanager/requirements.txt2
-rw-r--r--pythonextensions.pro9
l---------tests/package.py1
-rw-r--r--tests/testextension/main.py141
-rw-r--r--tests/testextension/requirements.txt5
-rw-r--r--tests/testextension/setup.py48
-rw-r--r--tools/build.py109
-rw-r--r--tools/license.py110
-rw-r--r--tools/pyside2_config.py302
-rw-r--r--tools/sanity.py88
94 files changed, 4472 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b815636
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+# Ignore build output
+.moc
+.obj
+QtCreatorPython
+PythonExtension
+.qmake.stash
+Makefile
+*.log
+plugins/pythonextensions/PythonExtensions.json
+
+# Ignore optional build directories
+optional/build_*
+
+# Ignore packaged examples and tests
+examples/*.zip
+tests/*.zip
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..517d07d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,129 @@
+# Python Extensions for QtCreator
+
+This plugin for QtCreator makes it possible to extend the QtCreator by writing Python extensions.
+These extensions consist of a directory containing a `main.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 QtCreator to be easily extended with
+custom functionality.
+
+**WARNING:** This is a first draft / proof of concept and only offers very limited functionality.
+There will still be many bugs and so far the project has only been tested on Linux.
+
+
+## What's included
+This repository contains the following:
+
+ * The code of the C++ plugin that executes the Python extensions
+ * An extension manager written in Python that is installed alongside the C++ plugin and allows to
+ install new Python extensions, as well as manage existing ones.
+ * A few example plugins in the `examples` folder. These can be installed manually, see the
+ separate `examples/README.md` for more information.
+ * An incomplete documentation of the C++ plugin, as well as an incomplete documentation for Python
+ extension authors.
+
+## Installation instructions
+Note that this process has so far only be tested on Linux. The plugin itself has only been tested
+with Python 3.5 and 2.7.12 and QtCreator 4.7.82.
+
+### Installing dependencies
+Before you can compile and install the PythonExtensions plugin, you need to install [PySide2](https://pyside.org)
+for the version of Python you plan to use. This is necessary, as the plugin uses the system installed
+Python (or the Python installed in your virtual env, if you have one set up) and it's packages. You
+will also need to build your own version of QtCreator and require a Qt installation. (This
+project has so far only been tested with Qt5.11.0.)
+
+To obtain the required Qt version, you can either build Qt yourself, or use the
+[installer provided on the Qt website](https://www.qt.io/download).
+
+To install PySide2 please refer to their [installation instructions](http://wiki.qt.io/Qt_for_Python/GettingStarted).
+When installing PySide2, make sure [Shiboken](https://doc.qt.io/qtforpython/shiboken2/contents.html)
+is installed alongside, as it will be necessary for compiling the plugin. (Shiboken is the binding
+generator used by and originally developed for PySide2.)
+
+**WARNING:** This project depends on two recent patches to Shiboken, one of which has not yet passed
+code review. These patches are:
+
+ * [Change-Id: Ib72b14cc704c04ae3b4197fd2af718276e3fe788](https://codereview.qt-project.org/#/c/234966/4)
+ (merged)
+ * [Change-Id: Iaaa38b66b5d3aabc0fb8f995f964cd7aef2a11da](https://codereview.qt-project.org/#/c/235072/)
+ (review in progress)
+
+Make sure your version of Shiboken has both of these patches. Otherwise, you will encounter errors
+when parsing / compiling this project. To get these patches, you will probably need to build
+Shiboken from source.
+
+To build QtCreator, which is necessary for building the plugin, please refer to their
+[build instructions](https://doc-snapshots.qt.io/qtcreator-extending/getting-and-building.html).
+
+### Building the C++ plugin
+Once all dependencies are installed, you can go ahead and build the plugin itself. To do this (in
+the best case) all you will have to do is run the following commands in the `plugins/pythonextensions` directory:
+```
+$ path/to/your/qmake
+$ make
+```
+After this, the plugin should be installed into the QtCreator version you built in the previous
+steps. If this worked, you can now go ahead and check out the `examples` folder and play with the
+provided example Python extensions.
+
+Notice that depending on your configuration, you may need to add Clang to your library paths for
+Shiboken to run. Achieve this by running the following before building the plugin:
+```
+export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/clang/lib
+```
+
+You may need to expose your QtCreator sources like this:
+```
+export QTC_SOURCE=/path/to/your/qt-creator
+export QTC_BUILD=/path/to/your/qt-creator
+```
+
+**NOTICE:** This plugin generates a few debug messages and potential warnings. Any Python extension
+that is installed might also write output to stdout. To see these messages, you can execute
+QtCreator from your terminal by running the executable there.
+
+### What to do if it didn't work?
+There are several things you might want to try:
+
+ * Read through the build output and make sure all dependencies are set up correctly
+ - A problem I encountered here is Shiboken not finding Clang. If this happens, try running:
+ ```
+ $ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/your/libclang/lib
+ ```
+ - Another problem that can occur if you have multiple Qt versions installed, is PySide not finding
+ the correct version. In this case try adding the line
+ ```
+ set(CMAKE_PREFIX_PATH "/path/to/your/qt5/5.11.0/gcc_64")
+ ```
+ in the file `sources/pyside2/CMakeLists.txt` in the PySide project.
+ * Have a look at `pythonextensions.pro` and change the hard-coded fall-back paths you find there to
+ match the paths of your setup.
+
+If none of the above suggestions fix your problem, I am afraid you will have to find a solution for
+yourself. If that happens and you find a fix, please contact the developers, so that it can be included in
+this list (or in the build system).
+
+## How it works
+In a nutshell, this project generates Python bindings for the QtCreator using Shiboken and then
+executes python scripts it finds in a bespoke directory.
+
+The following process allows the plugin to work:
+
+ 1. At compile time, Shiboken generates a huge amount of C++ code that allows a few classes from the
+ Core plugin and utils library to be accessed from Python.
+ 2. When QtCreator is executed, the C++ plugin searches the standard QtCreator plugin directories
+ for a directory named `python`, the first directory found is the Python extension directory.
+ - Now, each subdirectory represents it's own Python extension. For each subdirectory the
+ C++ plugin checks whether it contains a `setup.py`. If it does, this setup script is
+ executed.
+ - After all the setup scripts have been executed, each subdirectory is checked for a file named
+ `main.py`. This file is the extensions entry point and is executed by the C++ plugin.
+ 3. Now all Python extensions have registered their actions / menus / etc., which can be triggered
+ from the QtCreator interface.
+
+When executed, the Python extensions can import any modules / packages installed for the system
+Python or found in their own directory. While the C++ plugin takes some precautions to isolate the
+Python extensions when executed, they are still all run in the same Python instance, which means that
+they are not completely isolated. However, so far this did not turn out to be a problem.
+
+For a more detailed description, please refer to the documentation in `docs`.
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..80b1a08
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,30 @@
+# To do list for the project
+
+## Bindings
+- [ ] Complete Core bindings (most of the necessary xml is there, but commented)
+- [ ] Complete Utils bindings
+- [ ] Maybe add more bindings from QtCreators libs (?)
+
+## Optional Bindings
+- [ ] Find a way to include the non-optional bindings (see `optional/texteditor/typesystem_texteditor.xml` for description of problem)
+- [ ] Complete ProjectExplorer bindings
+- [ ] Complete TextEditor bindings
+
+## Build System
+- [ ] Fix having to `export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/clang/lib`
+- [ ] Automatically build optional bindings alongside main plugin (?)
+- [ ] Fix optional bindings build system
+
+## Examples
+- [ ] Make a text editor example (once the text editor binding does anything)
+
+## Documentation
+- [ ] Write more docs for the optional binding (`optional/README.md`)
+
+## Unit Tests
+- [ ] Add unit tests for PyUtils (C++)
+- [ ] Add unit tests for Bindings themselves (in Python unit test extension)
+
+## Other
+- [ ] Fix excessive line widths (run `tools/sanity.py` to find them)
+- [ ] Maybe make tools executable (?) (adding `#!/usr/bin/env python`)
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..22fbe0e
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,17 @@
+# Documentation for Python Extensions
+
+This is the documentation for the QtCreator Python Extensions plugin.
+
+## Contents
+ 1. [Writing Python Extensions](./extensions.md)
+ 2. [The C++ PythonExtensionsPlugin](./plugin.md)
+ 3. [Build and Development Tools](./tools.md)
+
+## General overview
+
+Please refer to the section \`How it works' in the main [README.md](../README.md) file.
+
+## A Note regarding Python versions
+In principle, the C++ plugin supports both Python 2 and 3 (at least for the versions tested, i.e.
+2.7 and 3.5). However, the extensions themselves might only support one version of Python. This is
+currently the case for the extension manager.
diff --git a/docs/extensions.md b/docs/extensions.md
new file mode 100644
index 0000000..eda9c4c
--- /dev/null
+++ b/docs/extensions.md
@@ -0,0 +1,131 @@
+# Documentation for Python Extension Authors
+
+This document is intended to provide an introduction to writing Python extensions
+for the QtCreator.
+
+## Importing QtCreator 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
+```
+
+## `PythonExtension.PluginInstance`
+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:
+
+```Python
+from PythonExtension import PluginInstance as inst
+
+# Returns a PySide QDir which points to the extension directory
+inst.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")
+
+# Returns the path of the custom location to
+# where missing dependencies should be pip installed
+inst.pythonPackagePath()
+```
+
+
+# QtCreator bindings
+Generally, the parts of QtCreator that are exposed have an interface that is nearly
+identical to the C++ interface.
+
+## Working with Menus
+You can add new items to the menus of QtCreator. You can either add an action directly, or
+add a new action container, that holds a sub-menu.
+
+### Adding a sub menu
+The following code snippet illustrates how to add a new menu.
+
+```Python
+from PythonExtension import QtCreator
+
+def hello():
+ print("Hello World.")
+
+# By convention, the menuId starts with "Python"
+menuId = "Python.SmallMenu.Menu"
+
+menu = QtCreator.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)
+```
+
+### 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
+
+def hello():
+ print("Hello World.")
+
+# Add a new action to the "Tools" menu
+menu = QtCreator.Core.ActionManager.actionContainer("QtCreator.Menu.Tools")
+menu.menu().addAction("My action", hello)
+```
+
+### Menu Id list
+Currently, the following menu ids are available in QtCreator.
+
+```Python
+"QtCreator.Menu.File"
+"QtCreator.Menu.File.RecentFiles"
+"QtCreator.Menu.Edit"
+"QtCreator.Menu.Edit.Advanced"
+"QtCreator.Menu.Tools"
+"QtCreator.Menu.Tools.External"
+"QtCreator.Menu.Window"
+"QtCreator.Menu.Window.Panes"
+"QtCreator.Menu.Window.ModeStyles"
+"QtCreator.Menu.Window.Views"
+"QtCreator.Menu.Help"
+```
+
+
+# The embedded Python interpreter
+
+## Python modules and `sys.path`
+When importing modules, the following important locations will be checked (in this order):
+
+ 1. The folder of the extension itself (files and folders in your extension)
+ 2. The system Python path entries (anything you `pip install`ed globally)
+ 3. The QtCreator specific Python module directory
+ - Note: This is where you should install any dependencies missing
+ if you want to use non-standard Python packages / modules
+ - This last path is accessible with `PluginInstance.pythonPackagePath()`
+
+Any changes you make to sys.path and any modules you import, will be cleared after your script
+finished executing.
+
+## Reserved variable names
+Names that look like
+```Python
+qt_creator_{SOME_NAME}_symbol_mchawrioklpilnjajqkfl
+```
+are reserved for the ExtensionManager. Unless you know exactly what you are doing, you should never
+touch any of these variables.
+
+## Installing dependencies
+There are two ways you can install dependencies. If you need very fine-grained control over how
+extension looks like before it can be run, you can supply a `setup.py` script, which is run
+separately before your extension is started. An example of such a script can be found in the example
+extension `examples/numpysetup`.
+
+If you only want to install dependencies, you can also include a standard Python `requirements.txt`
+file in your extension folder. **Note that this file is pip installed _once_.** After the initial
+pip install, a new file called `requirements.txt.installed` is created in the extensions folder. To
+run the install again, this file simply has to be deleted. **Be careful to remove this file, when
+distributing your extension in a .zip file.**
diff --git a/docs/plugin.md b/docs/plugin.md
new file mode 100644
index 0000000..e94633b
--- /dev/null
+++ b/docs/plugin.md
@@ -0,0 +1,164 @@
+# C++ Plugin Documentation
+
+This document is intended for people who want to understand, maintain or
+extend the C++ part of this plugin.
+
+**NOTE:** The plugins C++ sources are located at `plugins/pythonextensions`.
+
+## Files
+The following files exist as part of the C++ plugin:
+
+ * `pyutil.[h|cpp]`
+ - Contains utilities for working with the embedded Python interpreter
+ - All the direct interaction with Shiboken / Python happens
+ in the .cpp file to prevent a known error
+ - These files were originally based of the ['scriptableapplication'](http://code.qt.io/cgit/pyside/pyside-setup.git/tree/examples/scriptableapplication)
+ example from the PySide project
+ * `pythonextensionsplugin.[h|cpp]`
+ - Contains the IPlugin class and manages the life-cycle of Python
+ extensions
+ - This name is the standard for QtCreator plugins
+ * `pythonextensions_global.h`
+ - standard plugin file
+ * `typesystem_qtcreator.xml`
+ - Typesystem for Shiboken
+ - This file describes the classes and types that are bound to
+ Python
+ * `wrappedclasses.h`
+ - This file contains the headers that Shiboken will parse when generating
+ the bindings
+ * `glue` (directory)
+ - This directory contains any glue code needed for the bindings
+ - Currently it looks like this will mainly be needed to overcome the missing
+ support for C++ function pointers in Shiboken
+
+## Buildsystem
+The following is part of the buildsystem. The buildsystem has so far only been
+tested on Linux.
+
+ * `pythonextensions.pro`
+ - QMake project file
+ - The projects dependencies on the QtCreator side are handled in
+ the file `pythonextensions_dependencies.pri`
+ - The current build system emerged in part from the standard plugin build
+ setup, combined with some .pro code from the 'scriptableapplication' and
+ a lot of bits and pices added during the initial development phase (mainly
+ through trial and error)
+ * `pyside2.pri`
+ - This file is originally based of the 'scriptableapplication'
+ example from the PySide project
+ - It is responsible for detecting the PySide2 configuration
+ * `tools/pyside2_config.py`
+ - This script is a copy of a script found in the PySide2 examples
+ - It detects the PySide2 configuration and is called by the .pri file
+ - **Note:** This script will be officially included as a PySide util in the future.
+
+### Goals of the buildsystem
+The buildsystem aims to achieve two things
+
+ 1. Build the C++ plugin, linking against any relevant libraries
+ 2. Install the generated shared object, as well as copy the
+ `/python` directory into the QtCreator plugin directory. This
+ directory contains the included extension manager, which is implemented
+ as a Python extension
+
+## General architecture of the plugin
+During initialization, the plugin does several things:
+
+ 1. Initialize the Python bindings (this happens during the plugin's `initialize`
+ call)
+ 1. initialize the embedded Python
+ 2. bind the module object generated by Shiboken
+ - Note that this requires a patch for Shiboken, that
+ exposes this generated object in a way that allows
+ the `PyUtils` functions to access it
+ 3. bind the PythonExtensionsPlugin instance itself
+
+ 2. Run the `setup.py` script for every extension that provides it (this happens
+ during the `delayedInitialize` call)
+ - This is run here to not block the QtCreator ui while loading the extensions
+ - It is separated from the main extension runtime, to provide a facility to
+ extensions that depend on Python modules that are not part of the standard
+ Python library (for an example, see `examples/numpysetup` or the extension
+ loader)
+ - This script fails without an explicit warning in the plugin, however, all
+ Python errors are printed to stdout, so they can be inspected there during
+ extension development
+ - Note that the extension manager attempts to run an extensions `main.py`
+ script after installing it, but does not consider the `setup.py` script.
+ If `main.py` than fails to run, the extension manager advises the user to
+ restart QtCreator
+ - There is currently no facility for scripts to detect if they have loaded
+ before their setup was called
+ - Because the setup is run every time, the setup script needs to detect if
+ e.g. attempting to install dependencies is necessary (e.g. trying to import
+ them)
+
+ 3. Attempt to run the `main.py` script for every extension (this happens
+ during the `delayedInitialize` call)
+ - An extension that does not provide a `main.py` is considered to have failed
+ loading
+ - An extension for which the `main.py` fails to run is considered to have not
+ loaded
+ - Provided `main.py` succeeds, the plugin is
+ considered to have loaded (even if the setup script failed)
+
+The extension manager provides functions to find the extension directory, list
+all installed extensions and list all loaded extensions. (The loaded extensions
+are tracked by the PythonExtensionPlugin instance.) For details on the signatures
+of these functions, see `pythonextensionsplugin.h`.
+
+The plugin directories that are searched for the extension directory (currently
+`/python`) are determined using `ExtensionSystem::PluginManager::pluginPaths()`.
+
+## Important points / workarounds for problems regarding the various dependencies
+
+When executing Python code, be sure that Python was initialized (this is typically
+done by the provided helper functions). Usually, any extension code should be
+executed using `runScriptWithPath()` (never use `runScript()`, as it does not
+provide any facilities for separating extensions).
+
+The util function `runScriptWithPath()` runs a supplied script in a way that
+allows to import Python packages / modules from the provided path. It also
+isolates the script by cleaning the module namespace (and removing all
+modifications to sys.path) after the script finishes execution. Currently this is
+achieved by wrapping the supplied script with some special Python code, that
+implements these precautions. Because of this, no variables which have
+`mchawrioklpilnjajqkfl` as part of their name should be used in extension
+scripts. This function is used for both executing `setup.py` and `main.py` and
+should be used whenever executing user supplied scripts.
+
+The Python initialization needs to manually link the Python shared library due
+to a bug in CPython. This is done in `init()` (`PyUtils.cpp`) and has some
+facilities for detecting the required version and linking against the correct
+shared object. However, this needs to be more extensively tested and will probably
+have to be amended with several special cases for different platforms / versions.
+
+In the `PyUtil.cpp` file, the macros `signals` and `slots` are undefined due to
+compatibility reasons with the CPython headers. Due to this incompatibility, any
+QtCreator code and Shiboken code needs to be well separated. (These symbols need
+to be undefined **BEFORE** including anything Python related.)
+
+The glue code for the MacroExpander functions `registerVariable()` and `registerPrefix()`
+stores a reference to a Python object that can be called from other functions of the
+MacroExpander. For this to work, there may not be an unmatched call to `PyEval_SaveThread()`
+in any of the bindings for these functions. To achieve this, all those functions are modified
+using a code injection in the `typesystem_qtcreator.xml`.
+
+### My build suddenly starts seg-faulting for no apparent reason
+**DON'T PANIC**, did you change the bindings? Check if the value of
+```
+SBK_PYTHONEXTENSIONS_INTERNAL_PYTHONEXTENSIONSPLUGIN_IDX
+```
+changed. (See pyutil.h and the `..._python.h` in the generated bindings.)
+
+## Thinks that could be broken on other platforms
+
+This is _not_ an exhaustive list, _nor_ is it backed by any actual experience with
+building this project on non-linux operating systems. However, the following things
+are quite hacky and might not work on other systems:
+
+ * Different Python versions might not work
+ * Wired Python setups might trip it up (e.g. on macOS)
+ - Especially watch out for the manually linked Python in pyutils.cpp
+ * The pip install stuff might not select the correct version of Python on every machine
diff --git a/docs/tools.md b/docs/tools.md
new file mode 100644
index 0000000..e7e6612
--- /dev/null
+++ b/docs/tools.md
@@ -0,0 +1,43 @@
+# Documentation regarding included tools / utilities
+
+This project includes a few Python scripts, included as utilities for convenience. All of these are
+relatively small and self-contained. They should also all include usage instructions in comments at
+the beginning of the scripts. Some of them are scattered around the project. These include:
+
+ * [The optional bindings build script](../optional/setup.py)
+ - This script is intended to help building the optional bindings, by bundling files needed by
+ all optional binding libraries in a `template` directory.
+ - **Available options are:**
+ - --qmake=/path/to/your/qmake *required*
+ - --only=binding_dir *optional, only build binding_dir*
+ - clean *optional, ignores all options and removes the build directories*
+
+ * [The example packaging script](../examples/package.py)
+ - This script creates .zip files for use with the included extension manager form the example
+ extension sources.
+ - **Available options are:**
+ - *run with no arguments in `examples` directory to create extension .zip files*
+ - clean *remove generated .zip files*
+ - **Note:** There is a symbolic link to this script in the `tests` directory
+
+The other scripts are in the [tools folder](../tools/), they are:
+
+ * [The license script](../tools/license.py)
+ - This script adds the Qt license to all relevant source files.
+ - Takes no options and should be run in root directory with `$ python ./tools/license.py`.
+
+ * [PySide build helper script](../tools/pyside2_config.py)
+ - This is taken verbatim from the pyside-setup project and is used to discover the pyside
+ installation / setup during builds. Should not be used manually.
+
+ * [Sanity helper script](../tools/sanity.py)
+ - This script automatically corrects some annoying nags for the Qt Gerrit sanity check.
+ - Takes no options and should be run in root directory with `$ python ./tools/sanity.py`.
+
+ * [Build helper script](../tools/build.py)
+ - This script executes all the build scripts in this project in the correct order
+ - Mainly for lazy people and only usable if everything is setup correctly
+ - **Available options are:**
+ - --qmake=/path/to/your/qmake *required*
+ - --skip *optional, only build main c++ plugin*
+ - --user *optional, build to user directory*
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 0000000..f94ef22
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,17 @@
+# Python Extension Examples
+
+This directory contains examples of what Python extensions might look like. Each extension is in its
+own directory, where you can inspect its source code.
+
+
+## How to install
+If you want to install some of these extensions to play around with them, you can use the
+`package.py` utility. Run `$ python package.py` in your terminal, to generate extension packages
+(.zip files) of all the extensions in this directory. The generated extension packages can then be
+installed into your QtCreator using the extension manager that comes with the plugin. (Find the
+extension manager in the help menu of your QtCreator.)
+
+To clean this directory from generated packages, simply run `$ python package.py clean` in your
+terminal. Note that for your own Python extensions, it is sufficient to distribute .zip
+files, which you can easily generate manually. **Remember to include a top-level directory** when
+making your own extension packages.
diff --git a/examples/macroexpander/main.py b/examples/macroexpander/main.py
new file mode 100644
index 0000000..bd64028
--- /dev/null
+++ b/examples/macroexpander/main.py
@@ -0,0 +1,61 @@
+#############################################################################
+##
+## Copyright (C) 2018 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Python Extensions Plugin for QtCreator.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+# This example demonstrates how to use the macro expander
+# API to register a macro that can evaluate Python expressions
+
+from PySide2.QtWidgets import QInputDialog, QMessageBox
+from PythonExtension import QtCreator
+
+# Register our macro (it can be used as %{Python:exp})
+QtCreator.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(),
+ "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)
+
+# Add this to the "Tools" menu
+menu = QtCreator.Core.ActionManager.actionContainer("QtCreator.Menu.Tools")
+menu.menu().addAction("Test MacroExpander...", act)
diff --git a/examples/numpysetup/main.py b/examples/numpysetup/main.py
new file mode 100644
index 0000000..cf67157
--- /dev/null
+++ b/examples/numpysetup/main.py
@@ -0,0 +1,60 @@
+#############################################################################
+##
+## Copyright (C) 2018 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Python Extensions Plugin for QtCreator.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+# This example illustrates the use of external
+# Python modules
+
+import numpy as np
+from PythonExtension import QtCreator
+from PySide2.QtWidgets import QMessageBox
+
+def dotProduct():
+ a = np.array([1,2,3], float)
+ b = np.array([4,5,6], float)
+ return np.dot(a, b)
+
+def show():
+ box = QMessageBox()
+ box.setText("[1,2,3] * [4,5,6] = {}".format(dotProduct()))
+ box.exec_()
+
+# Add this to the "Window" menu
+menu = QtCreator.Core.ActionManager.actionContainer("QtCreator.Menu.Window")
+menu.menu().addAction("Show dot product", show)
diff --git a/examples/numpysetup/setup.py b/examples/numpysetup/setup.py
new file mode 100644
index 0000000..901bbe4
--- /dev/null
+++ b/examples/numpysetup/setup.py
@@ -0,0 +1,62 @@
+#############################################################################
+##
+## Copyright (C) 2018 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Python Extensions Plugin for QtCreator.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+# Setup script that installs required modules.
+# If you simply want to make sure a Python
+# module is installed, you can use this script
+# directly.
+# The modules are installed locally for QtCreator
+# and are not accessible from the system Python.
+
+import importlib, subprocess, sys
+from PythonExtension import PluginInstance as instance
+
+def setup(modules=None):
+ def install(module):
+ cmd = "{0} -m pip install -I -t {1} {2}".format(sys.executable, instance.pythonPackagePath(), module)
+ subprocess.call(cmd.split())
+ for module in modules:
+ try:
+ importlib.import_module(module)
+ except ImportError:
+ install(module)
+
+if __name__ == "__main__":
+ setup(["numpy"])
diff --git a/examples/package.py b/examples/package.py
new file mode 100644
index 0000000..08f6077
--- /dev/null
+++ b/examples/package.py
@@ -0,0 +1,64 @@
+#############################################################################
+##
+## Copyright (C) 2018 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Python Extensions Plugin for QtCreator.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+# This script is just here for convenience. If you are writing
+# your own extension, manually creating a zip archive is sufficient.
+
+# Usage: $ python package.py
+# (pass argument 'clean' to remove generated package.zip files)
+
+import os, sys, shutil
+
+def package():
+ for entry in os.scandir("./"):
+ if entry.is_dir():
+ shutil.make_archive(entry.path.split("/")[-1], "zip", "./", entry.path)
+
+def clean():
+ for entry in os.scandir("./"):
+ if entry.is_file() and entry.name.split(".")[-1] == "zip":
+ os.remove(entry.path)
+
+
+if __name__ == "__main__":
+ if "clean" in sys.argv:
+ clean()
+ else:
+ package()
diff --git a/examples/projects/main.py b/examples/projects/main.py
new file mode 100644
index 0000000..daac366
--- /dev/null
+++ b/examples/projects/main.py
@@ -0,0 +1,63 @@
+#############################################################################
+##
+## Copyright (C) 2018 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Python Extensions Plugin for QtCreator.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+# This example demonstrates how to work with the current
+# project. It also includes a dependency on a plugin
+# that can be disabled.
+
+import sys, view
+
+# When importing optional bindings, we can warn the user in case things go south
+try:
+ from PythonExtension.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")
+
+# Now we can assume, that ProjectExplorer was imported
+def showProjectPath():
+ current_project = ProjectExplorer.ProjectTree.instance().currentProject()
+ if current_project:
+ view.show(current_project.projectDirectory().toString())
+ else:
+ view.error("Please open a project")
+
+helpMenu = QtCreator.Core.ActionManager.actionContainer("QtCreator.Menu.Window")
+helpMenu.menu().addAction("Show Project Directory", showProjectPath)
diff --git a/examples/projects/view.py b/examples/projects/view.py
new file mode 100644
index 0000000..9052d1f
--- /dev/null
+++ b/examples/projects/view.py
@@ -0,0 +1,50 @@
+#############################################################################
+##
+## Copyright (C) 2018 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Python Extensions Plugin for QtCreator.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+# This contains all the display logic
+
+from PySide2 import QtWidgets
+from PythonExtension import QtCreator
+
+def error(text, title="Error"):
+ QtWidgets.QMessageBox.critical(QtCreator.Core.ICore.instance().dialogParent(), title, text)
+
+def show(text, title="Result"):
+ QtWidgets.QMessageBox.information(QtCreator.Core.ICore.instance().dialogParent(), title, text)
diff --git a/examples/requirerequests/main.py b/examples/requirerequests/main.py
new file mode 100644
index 0000000..9ca4ee9
--- /dev/null
+++ b/examples/requirerequests/main.py
@@ -0,0 +1,58 @@
+#############################################################################
+##
+## Copyright (C) 2018 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Python Extensions Plugin for QtCreator.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+# This is intended to illustrate the
+# use of requirements.txt files in
+# extensions.
+
+import requests
+from PythonExtension import QtCreator
+from PySide2 import QtWidgets
+
+def load(url):
+ r = requests.get(url)
+ box = QtWidgets.QMessageBox(QtCreator.Core.ICore.dialogParent())
+ box.setWindowTitle("Request results")
+ box.setText("The request status is {}".format(r.status_code))
+ box.setDetailedText(r.text)
+ box.exec_()
+
+helpMenu = QtCreator.Core.ActionManager.actionContainer("QtCreator.Menu.Window")
+helpMenu.menu().addAction("Load from the web...", lambda: load("https://www.qt.io/"))
diff --git a/examples/requirerequests/requirements.txt b/examples/requirerequests/requirements.txt
new file mode 100644
index 0000000..eaae000
--- /dev/null
+++ b/examples/requirerequests/requirements.txt
@@ -0,0 +1,5 @@
+# This illustrates that requirement.txt files
+# can be used instead of a setup.py, for simply
+# installing missing packages
+
+requests
diff --git a/examples/smallmenu/actions.py b/examples/smallmenu/actions.py
new file mode 100644
index 0000000..6a9815b
--- /dev/null
+++ b/examples/smallmenu/actions.py
@@ -0,0 +1,54 @@
+#############################################################################
+##
+## Copyright (C) 2018 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Python Extensions Plugin for QtCreator.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+# Menu actions
+
+from PySide2.QtWidgets import QMessageBox
+
+def hello():
+ msg("Hello there")
+
+def goodbye():
+ msg("Goodbye for now")
+
+def msg(text):
+ box = QMessageBox()
+ box.setText(text)
+ box.exec_()
diff --git a/examples/smallmenu/main.py b/examples/smallmenu/main.py
new file mode 100644
index 0000000..b2753c1
--- /dev/null
+++ b/examples/smallmenu/main.py
@@ -0,0 +1,56 @@
+#############################################################################
+##
+## Copyright (C) 2018 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Python Extensions Plugin for QtCreator.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+# This example illustrates the use of action
+# containers to create new menus
+
+from PythonExtension import QtCreator
+from actions import hello, goodbye
+
+# By convention, the menuId starts with "Python"
+menuId = "Python.SmallMenu.Menu"
+
+menu = QtCreator.Core.ActionManager.createMenu(menuId)
+menu.menu().setTitle("Small menu")
+menu.menu().addAction("Say hello", hello)
+menu.menu().addAction("Say goodbye", goodbye)
+
+# Add our new menu to the "Tools" menu in QtCreator
+QtCreator.Core.ActionManager.actionContainer("QtCreator.Menu.Tools").addMenu(menu)
diff --git a/examples/transform/actions.py b/examples/transform/actions.py
new file mode 100644
index 0000000..a74bb0b
--- /dev/null
+++ b/examples/transform/actions.py
@@ -0,0 +1,61 @@
+#############################################################################
+##
+## Copyright (C) 2018 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Python Extensions Plugin for QtCreator.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+from PythonExtension import QtCreator
+import ui
+
+# Apply a function to each file
+def run(code):
+ try:
+ files = QtCreator.Core.DocumentManager.getOpenFileNames("")
+ if len(files) == 0:
+ ui.error("No files were selected.")
+ return
+ for filename in files:
+ s = open(filename, "r")
+ filebody = s.read()
+ s.close()
+ filebody = eval(code)
+ s = open(filename, "w")
+ s.write(filebody)
+ s.close()
+ ui.success()
+ except Exception as e:
+ ui.error(str(e))
diff --git a/examples/transform/main.py b/examples/transform/main.py
new file mode 100644
index 0000000..b0d47f1
--- /dev/null
+++ b/examples/transform/main.py
@@ -0,0 +1,55 @@
+#############################################################################
+##
+## Copyright (C) 2018 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Python Extensions Plugin for QtCreator.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+# This example extension allows you to run a
+# user supplied function that executes on
+# a set of files specified by the user
+
+from PythonExtension import QtCreator
+import actions, ui
+
+def transform():
+ code = ui.getCode()
+ if code[1]:
+ actions.run(code[0])
+
+# Add our new action to the "Tools" menu
+menu = QtCreator.Core.ActionManager.actionContainer("QtCreator.Menu.Tools")
+menu.menu().addAction("Transform files...", transform)
diff --git a/examples/transform/ui.py b/examples/transform/ui.py
new file mode 100644
index 0000000..441fa69
--- /dev/null
+++ b/examples/transform/ui.py
@@ -0,0 +1,72 @@
+#############################################################################
+##
+## Copyright (C) 2018 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Python Extensions Plugin for QtCreator.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+from PythonExtension import QtCreator
+from PySide2 import QtWidgets
+
+template = """# Parameters:
+# filename (string)
+# filebody (string)
+# Writing:
+# Your expression is evaluated, the
+# result is written to the file.
+
+filebody
+"""
+
+def getCode():
+ return QtWidgets.QInputDialog.getMultiLineText(
+ QtCreator.Core.ICore.dialogParent(),
+ "Input transformation expression",
+ "Transform expression:",
+ template
+ )
+
+def success():
+ box = QtWidgets.QMessageBox()
+ box.setText("Success!")
+ box.exec_()
+
+def error(log = ""):
+ box = QtWidgets.QMessageBox()
+ box.setText("There was a problem running your script.")
+ if log != "":
+ box.setDetailedText(log)
+ box.exec_()
diff --git a/optional/README.md b/optional/README.md
new file mode 100644
index 0000000..2c5af5d
--- /dev/null
+++ b/optional/README.md
@@ -0,0 +1,34 @@
+# Optional Bindings
+
+This directory contains all optional bindings for QtCreator
+Modules that are bundled with this plugin.
+
+Optional bindings come in the form of dynamically loaded libraries,
+each binding has its own folder. The template directory contains files
+that are shared for each binding.
+
+## Documentation
+
+### Building
+If your environment is setup correctly (i.e. you can build the Python Extensions plugin), all that
+you should need to do is run:
+
+```
+python setup.py --qmake=/path/to/your/qmake
+```
+
+and the optional bindings should build and be installed. To clean the build files after a build,
+just run:
+
+```
+python setup.py clean
+```
+
+For problems and possible solutions, please refer to the main `README.md`.
+
+### Writing optional binding libraries
+**NOTICE:** Please refer to the `pyutil.h` header file for anything that is not explained
+here.
+
+Each library project here must include a `binding.cpp` file which must implement
+a function with the signature `void bind();`.
diff --git a/optional/projectexplorer/binding.cpp b/optional/projectexplorer/binding.cpp
new file mode 100644
index 0000000..a544beb
--- /dev/null
+++ b/optional/projectexplorer/binding.cpp
@@ -0,0 +1,69 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Python Extensions Plugin for QtCreator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "binding.h"
+
+#include <QtCore/QDebug>
+#include <QtCore/QString>
+
+#undef signals
+#undef slots
+
+#include <sbkpython.h>
+#include <sbkconverter.h>
+#include <sbkmodule.h>
+
+#if PY_MAJOR_VERSION >= 3
+extern "C" PyObject *PyInit_QtCreatorBindingProjectExplorer();
+#else
+extern "C" void initQtCreatorBindingProjectExplorer();
+#endif
+
+extern PyObject *SbkQtCreatorBindingProjectExplorerModuleObject;
+
+namespace PyUtil {
+ extern bool bindSubPyObject(const QString &moduleName, const QString &name, void *obj);
+}
+
+void bind()
+{
+ // Init module
+ #if PY_MAJOR_VERSION >= 3
+ const bool pythonInitialized = PyInit_QtCreatorBindingProjectExplorer() != nullptr;
+ #else
+ const bool pythonInitialized = true;
+ initQtCreatorBindingProjectExplorer();
+ #endif
+ // Bind module into interpreter
+ bool pythonError = PyErr_Occurred() != nullptr;
+ if (pythonInitialized && !pythonError) {
+ PyUtil::bindSubPyObject("PythonExtension.QtCreator", "ProjectExplorer", (void *)SbkQtCreatorBindingProjectExplorerModuleObject);
+ } else {
+ if (pythonError)
+ PyErr_Print();
+ qDebug() << "There was a problem initializing the ProjectExplorer bindings.";
+
+ }
+}
diff --git a/optional/projectexplorer/binding_custom.pri b/optional/projectexplorer/binding_custom.pri
new file mode 100644
index 0000000..b9e3faf
--- /dev/null
+++ b/optional/projectexplorer/binding_custom.pri
@@ -0,0 +1,57 @@
+# Additional sources
+
+SOURCES += \
+ # optional
+
+HEADERS += \
+ # optional
+
+
+# Declare dependencies and name
+
+# This has to be PythonBinding{PluginName}
+QTC_PLUGIN_NAME = PythonBindingProjectExplorer
+QTC_LIB_DEPENDS += \
+ extensionsystem \
+ utils
+
+QTC_PLUGIN_DEPENDS += \
+ coreplugin \
+ projectexplorer \
+ pythonextensions
+
+QTC_PLUGIN_RECOMMENDS += \
+ # optional plugin dependencies. nothing here at this time
+
+
+# Shiboken binding generation setup
+
+WRAPPED_HEADER = wrappedclasses.h
+WRAPPER_DIR = $$OUT_PWD/QtCreatorBindingProjectExplorer
+TYPESYSTEM_FILE = typesystem_projectexplorer.xml
+
+TYPESYSTEM_NAME = qtcreatorbindingprojectexplorer
+
+## Include additional QtCreator paths
+QT_INCLUDEPATHS += \
+ -I"$$IDE_SOURCE_TREE/src/plugins/projectexplorer"
+
+INCLUDEPATH += \
+ $$IDE_SOURCE_TREE/src/plugins/projectexplorer \
+ $$IDE_SOURCE_TREE/src/libs/utils
+
+## These headers are needed so the generated wrappers are added to the
+## build. Right now they are empty files, however there might be a more elegant
+## option.
+WRAPPED_CLASSES = \
+ bindingheaders/projectexplorer.h \
+ bindingheaders/projectexplorer_buildconfiguration.h \
+ bindingheaders/projectexplorer_projectconfiguration.h \
+ bindingheaders/projectexplorer_statefulprojectconfiguration.h \
+ # bindingheaders/projectexplorer_ibuildconfigurationfactory.h \
+ bindingheaders/projectexplorer_runconfiguration.h \
+ bindingheaders/projectexplorer_project.h \
+ bindingheaders/projectexplorer_projecttree.h \
+ bindingheaders/utils.h \
+ bindingheaders/utils_filename.h \
+# Sentinel line
diff --git a/optional/projectexplorer/bindingheaders/projectexplorer.h b/optional/projectexplorer/bindingheaders/projectexplorer.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/optional/projectexplorer/bindingheaders/projectexplorer.h
diff --git a/optional/projectexplorer/bindingheaders/projectexplorer_buildconfiguration.h b/optional/projectexplorer/bindingheaders/projectexplorer_buildconfiguration.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/optional/projectexplorer/bindingheaders/projectexplorer_buildconfiguration.h
diff --git a/optional/projectexplorer/bindingheaders/projectexplorer_ibuildconfigurationfactory.h b/optional/projectexplorer/bindingheaders/projectexplorer_ibuildconfigurationfactory.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/optional/projectexplorer/bindingheaders/projectexplorer_ibuildconfigurationfactory.h
diff --git a/optional/projectexplorer/bindingheaders/projectexplorer_project.h b/optional/projectexplorer/bindingheaders/projectexplorer_project.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/optional/projectexplorer/bindingheaders/projectexplorer_project.h
diff --git a/optional/projectexplorer/bindingheaders/projectexplorer_projectconfiguration.h b/optional/projectexplorer/bindingheaders/projectexplorer_projectconfiguration.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/optional/projectexplorer/bindingheaders/projectexplorer_projectconfiguration.h
diff --git a/optional/projectexplorer/bindingheaders/projectexplorer_projecttree.h b/optional/projectexplorer/bindingheaders/projectexplorer_projecttree.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/optional/projectexplorer/bindingheaders/projectexplorer_projecttree.h
diff --git a/optional/projectexplorer/bindingheaders/projectexplorer_runconfiguration.h b/optional/projectexplorer/bindingheaders/projectexplorer_runconfiguration.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/optional/projectexplorer/bindingheaders/projectexplorer_runconfiguration.h
diff --git a/optional/projectexplorer/bindingheaders/projectexplorer_statefulprojectconfiguration.h b/optional/projectexplorer/bindingheaders/projectexplorer_statefulprojectconfiguration.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/optional/projectexplorer/bindingheaders/projectexplorer_statefulprojectconfiguration.h
diff --git a/optional/projectexplorer/bindingheaders/utils.h b/optional/projectexplorer/bindingheaders/utils.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/optional/projectexplorer/bindingheaders/utils.h
diff --git a/optional/projectexplorer/bindingheaders/utils_filename.h b/optional/projectexplorer/bindingheaders/utils_filename.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/optional/projectexplorer/bindingheaders/utils_filename.h
diff --git a/optional/projectexplorer/typesystem_projectexplorer.xml b/optional/projectexplorer/typesystem_projectexplorer.xml
new file mode 100644
index 0000000..b663619
--- /dev/null
+++ b/optional/projectexplorer/typesystem_projectexplorer.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0"?>
+<!--
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Python Extensions Plugin for QtCreator.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+-->
+
+<!-- Typesystem for Qt Creator Python host plugin -->
+<typesystem package="QtCreatorBindingProjectExplorer">
+
+ <!-- Load PySide QtWidgets typesystem (is this correct? yup) -->
+ <load-typesystem name="typesystem_widgets.xml" generate="no"/>
+ <!-- Currently there are problems with getting this to work ... -->
+ <!-- <load-typesystem name="../../plugins/pythonextensions/typesystem_qtcreator.xml" generate="no"/> -->
+
+ <namespace-type name="ProjectExplorer">
+ <object-type name="BuildConfiguration">
+ <enum-type name="BuildType"/>
+ </object-type>
+ <object-type name="ProjectConfiguration"/>
+ <object-type name="RunConfiguration">
+ <enum-type name="ConfigurationState"/>
+ </object-type>
+ <object-type name="StatefulProjectConfiguration"/>
+ <object-type name="ProjectTree"/>
+ <object-type name="Project">
+ <enum-type name="ModelRoles"/>
+ <enum-type name="RestoreResult"/>
+ </object-type>
+ </namespace-type>
+
+ <namespace-type name="Utils">
+ <value-type name="FileName"/>
+ <modify-function signature="operator&lt;&lt;(QTextStream&amp;,Utils::FileName)" remove="all"/>
+ </namespace-type>
+
+</typesystem>
diff --git a/optional/projectexplorer/wrappedclasses.h b/optional/projectexplorer/wrappedclasses.h
new file mode 100644
index 0000000..2d043bb
--- /dev/null
+++ b/optional/projectexplorer/wrappedclasses.h
@@ -0,0 +1,37 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Python Extensions Plugin for QtCreator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#ifndef WRAPPEDCLASSES_H
+#define WRAPPEDCLASSES_H
+
+#include <QAbstractSocket>
+
+#include <projectexplorer/buildconfiguration.h>
+#include <projectexplorer/projectconfiguration.h>
+#include <projectexplorer/runconfiguration.h>
+#include <projectexplorer/project.h>
+#include <projectexplorer/projecttree.h>
+
+#endif // header end
diff --git a/optional/setup.py b/optional/setup.py
new file mode 100644
index 0000000..c0f6f43
--- /dev/null
+++ b/optional/setup.py
@@ -0,0 +1,139 @@
+#############################################################################
+##
+## Copyright (C) 2018 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Python Extensions Plugin for QtCreator.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+# Assemble the binding projects and build them
+
+# You need to specify the correct qmake when running:
+# $ python setup.py --qmake=/path/to/qmake
+# (defaults to "qmake")
+
+# You can also specify a binding to build:
+# $ python setup.py --only=bindingname
+# (default is to build all)
+
+# You can clean the build directories with
+# $ python setup.py clean
+
+import os, shutil, subprocess, sys
+
+
+def qmake():
+ for arg in sys.argv:
+ if arg.split("=")[0] == "--qmake":
+ return arg.split("=")[-1]
+ return "qmake"
+
+def only():
+ for arg in sys.argv:
+ if arg.split("=")[0] == "--only":
+ return arg.split("=")[-1]
+ return False
+
+def copytree(src, dst, symlinks=False, ignore=None):
+ for item in os.listdir(src):
+ s = os.path.join(src, item)
+ d = os.path.join(dst, item)
+ if os.path.isdir(s):
+ shutil.copytree(s, d, symlinks, ignore)
+ else:
+ shutil.copy2(s, d)
+
+def generate_build_deps():
+ if os.path.exists("build_deps"):
+ shutil.rmtree("build_deps")
+ # Plugin dependencies .pri file
+ os.makedirs("build_deps/pythonextensions")
+ shutil.copy2(
+ "../plugins/pythonextensions/pythonextensions_dependencies.pri",
+ "build_deps/pythonextensions/pythonextensions_dependencies.pri"
+ )
+
+def generate_build_dir(binding_name):
+ build_dir = "build_{}".format(binding_name)
+ if os.path.exists(build_dir):
+ shutil.rmtree(build_dir)
+ os.makedirs(build_dir)
+ copytree("template", build_dir)
+ copytree(binding_name, build_dir)
+
+def run_build(binding_name):
+ build_dir = "build_{}".format(binding_name)
+ if os.path.exists(build_dir):
+ try:
+ subprocess.check_call(qmake(), cwd=build_dir)
+ subprocess.check_call("make", cwd=build_dir)
+ except Exception as e:
+ print("Got exception when building {}:".format(binding_name), e)
+ return False
+ return True
+ return False
+
+def clean_build_dirs():
+ for build_dir in os.scandir():
+ if build_dir.is_dir() and build_dir.name.split("_")[0] == "build":
+ shutil.rmtree(build_dir.name)
+ print("Removing {}".format(build_dir.name))
+
+def main():
+ generate_build_deps()
+ if only():
+ generate_build_dir(only())
+ if run_build(only()):
+ print("Built {}".format(only()))
+ else:
+ print("Error building {}".format(only()))
+ print("Skipping other builds")
+ return
+ final_message = "Summary:"
+ for binding in os.scandir():
+ if binding.is_dir() and binding.name != "template" and binding.name.split("_")[0] != "build":
+ generate_build_dir(binding.name)
+ if run_build(binding.name):
+ final_message += "\nBuilt {}".format(binding.name)
+ else:
+ final_message += "\nError building {}".format(binding.name)
+ print(final_message)
+
+
+if __name__ == "__main__":
+ if "clean" in sys.argv:
+ clean_build_dirs()
+ else:
+ main()
diff --git a/optional/template/binding.h b/optional/template/binding.h
new file mode 100644
index 0000000..9b397a1
--- /dev/null
+++ b/optional/template/binding.h
@@ -0,0 +1,30 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Python Extensions Plugin for QtCreator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QtGlobal>
+
+extern "C" Q_DECL_EXPORT void bind();
diff --git a/optional/template/binding.pro b/optional/template/binding.pro
new file mode 100644
index 0000000..57be22b
--- /dev/null
+++ b/optional/template/binding.pro
@@ -0,0 +1,87 @@
+# NOTE: This is not yet properly tested on general systems
+# and will (if at all) probably only work on a linux environment
+
+PYTHON = python
+DEFINES += PYTHONEXTENSIONS_LIBRARY
+
+# PythonExtensions files
+
+SOURCES += \
+ binding.cpp
+
+HEADERS += \
+ binding.h
+
+
+# Qt Creator linking
+
+# Shared QtCreator sources and build destination
+# (these are shared with the main plugin)
+include(../../plugins/pythonextensions/qtcreator.pri)
+
+## Include Qt and QtCreator paths
+QT_INCLUDEPATHS = -I"$$[QT_INSTALL_HEADERS]" -I"$$[QT_INSTALL_HEADERS]/QtCore" \
+ -I"$$[QT_INSTALL_HEADERS]/QtGui" -I"$$[QT_INSTALL_HEADERS]/QtWidgets" \
+ -I"$$[QT_INSTALL_HEADERS]/QtNetwork" \
+ -I"$$IDE_SOURCE_TREE/src/plugins" \
+ -I"$$IDE_SOURCE_TREE/src/libs"
+
+# Custom Buildsystem setup per binding
+include(binding_custom.pri)
+
+# Add the build dependencies directory, which will contain dependencies
+# related to the python extensions plugin
+QTC_PLUGIN_DIRS += $${WRAPPER_DIR}/../../build_deps/
+include($$IDE_SOURCE_TREE/src/qtcreatorplugin.pri)
+
+# Shiboken stuff
+
+# This setup is currently only tested on Linux
+
+include(../../plugins/pythonextensions/pyside2.pri)
+
+SHIBOKEN_OPTIONS = --generator-set=shiboken --enable-parent-ctor-heuristic \
+ --enable-pyside-extensions --enable-return-value-heuristic --use-isnull-as-nb_nonzero \
+ $$QT_INCLUDEPATHS -I$$PWD -T$$PWD -T$$PYSIDE2/typesystems --output-directory=$$OUT_PWD
+
+## Prepare the shiboken tool
+QT_TOOL.shiboken.binary = $$system_path($$PYSIDE2/shiboken2)
+qtPrepareTool(SHIBOKEN, shiboken)
+
+## Shiboken run that adds the module wrapper to GENERATED_SOURCES
+shiboken.output = $$WRAPPER_DIR/$${TYPESYSTEM_NAME}_module_wrapper.cpp
+shiboken.commands = $$SHIBOKEN $$SHIBOKEN_OPTIONS $$PWD/$$WRAPPED_HEADER ${QMAKE_FILE_IN}
+shiboken.input = TYPESYSTEM_FILE
+shiboken.dependency_type = TYPE_C
+shiboken.variable_out = GENERATED_SOURCES
+
+module_wrapper_dummy_command.output = $$WRAPPER_DIR/${QMAKE_FILE_BASE}_wrapper.cpp
+module_wrapper_dummy_command.commands = echo ${QMAKE_FILE_IN}
+module_wrapper_dummy_command.depends = $$WRAPPER_DIR/$${TYPESYSTEM_NAME}_module_wrapper.cpp
+module_wrapper_dummy_command.input = WRAPPED_CLASSES
+module_wrapper_dummy_command.dependency_type = TYPE_C
+module_wrapper_dummy_command.variable_out = GENERATED_SOURCES
+
+## Get the path component to the active config build folder
+defineReplace(getOutDir) {
+ out_dir = $$OUT_PWD
+ CONFIG(release, debug|release): out_dir = $$out_dir/release
+ else:out_dir = $$out_dir/debug
+ return($$out_dir)
+}
+
+QMAKE_EXTRA_COMPILERS += shiboken module_wrapper_dummy_command
+
+# Include paths for Shiboken generated files
+INCLUDEPATH += $$WRAPPER_DIR \
+ $$WRAPPER_DIR/../../../plugins/pythonextensions \
+ $$WRAPPER_DIR/../../../plugins/pythonextensions/PythonExtension/QtCreator \
+ "$$IDE_SOURCE_TREE/src/plugins/coreplugin" \
+ "$$IDE_SOURCE_TREE/src/plugins/coreplugin/actionmanager" \
+ "$$IDE_SOURCE_TREE/src/plugins/coreplugin/editormanager" \
+ "$$IDE_SOURCE_TREE/src/libs/extensionsystem" \
+ "$$IDE_SOURCE_TREE/src/libs/utils"
+
+for(i, PYSIDE2_INCLUDE) {
+ INCLUDEPATH += $$i/QtWidgets $$i/QtGui $$i/QtCore $$i/QtNetwork
+}
diff --git a/optional/texteditor/binding.cpp b/optional/texteditor/binding.cpp
new file mode 100644
index 0000000..d4f4bf5
--- /dev/null
+++ b/optional/texteditor/binding.cpp
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Python Extensions Plugin for QtCreator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "binding.h"
+
+#include <QtCore/QDebug>
+#include <QtCore/QString>
+
+#undef signals
+#undef slots
+
+#include <sbkpython.h>
+#include <sbkconverter.h>
+#include <sbkmodule.h>
+
+#if PY_MAJOR_VERSION >= 3
+extern "C" PyObject *PyInit_QtCreatorBindingTextEditor();
+#else
+extern "C" void initQtCreatorBindingTextEditor();
+#endif
+
+extern PyObject *SbkQtCreatorBindingTextEditorModuleObject;
+
+namespace PyUtil {
+ extern bool bindSubPyObject(const QString &moduleName, const QString &name, void *obj);
+}
+
+void bind()
+{
+ // Init module
+ #if PY_MAJOR_VERSION >= 3
+ const bool pythonInitialized = PyInit_QtCreatorBindingTextEditor() != nullptr;
+ #else
+ const bool pythonInitialized = true;
+ initQtCreatorBindingTextEditor();
+ #endif
+ // Bind module into interpreter
+ bool pythonError = PyErr_Occurred() != nullptr;
+ if (pythonInitialized && !pythonError) {
+ PyUtil::bindSubPyObject("PythonExtension.QtCreator", "TextEditor", (void *)SbkQtCreatorBindingTextEditorModuleObject);
+ } else {
+ if (pythonError)
+ PyErr_Print();
+ qDebug() << pythonInitialized << pythonError;
+ qDebug() << "There was a problem initializing the TextEditor bindings.";
+
+ }
+}
diff --git a/optional/texteditor/binding_custom.pri b/optional/texteditor/binding_custom.pri
new file mode 100644
index 0000000..361da10
--- /dev/null
+++ b/optional/texteditor/binding_custom.pri
@@ -0,0 +1,51 @@
+# Additional sources
+
+SOURCES += \
+ # optional
+
+HEADERS += \
+ # optional
+
+
+# Declare dependencies and name
+
+# This has to be PythonBinding{PluginName}
+QTC_PLUGIN_NAME = PythonBindingTextEditor
+QTC_LIB_DEPENDS += \
+ extensionsystem \
+ utils
+
+QTC_PLUGIN_DEPENDS += \
+ coreplugin \
+ texteditor \
+ pythonextensions
+
+QTC_PLUGIN_RECOMMENDS += \
+ # optional plugin dependencies. nothing here at this time
+
+
+# Shiboken binding generation setup
+
+WRAPPED_HEADER = wrappedclasses.h
+WRAPPER_DIR = $$OUT_PWD/QtCreatorBindingTextEditor
+TYPESYSTEM_FILE = typesystem_texteditor.xml
+
+TYPESYSTEM_NAME = qtcreatorbindingtexteditor
+
+## Include additional QtCreator paths
+QT_INCLUDEPATHS += \
+ -I"$$IDE_SOURCE_TREE/src/plugins/texteditor"
+
+INCLUDEPATH += \
+ $$IDE_SOURCE_TREE/src/plugins/coreplugin \
+ $$IDE_SOURCE_TREE/src/plugins/coreplugin/editormanager \
+ $$IDE_SOURCE_TREE/src/plugins/coreplugin/find \
+ $$IDE_SOURCE_TREE/src/plugins/texteditor \
+ $$IDE_SOURCE_TREE/src/libs/utils
+
+## These headers are needed so the generated wrappers are added to the
+## build. Right now they are empty files, however there might be a more elegant
+## option.
+WRAPPED_CLASSES = \
+ bindingheaders/texteditor.h \
+# Sentinel line
diff --git a/optional/texteditor/bindingheaders/texteditor.h b/optional/texteditor/bindingheaders/texteditor.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/optional/texteditor/bindingheaders/texteditor.h
diff --git a/optional/texteditor/typesystem_texteditor.xml b/optional/texteditor/typesystem_texteditor.xml
new file mode 100644
index 0000000..ef10df0
--- /dev/null
+++ b/optional/texteditor/typesystem_texteditor.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0"?>
+<!--
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Python Extensions Plugin for QtCreator.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+-->
+
+<!-- Typesystem for Qt Creator Python host plugin -->
+<typesystem package="QtCreatorBindingTextEditor">
+
+ <!-- Load PySide QtWidgets typesystem (is this correct? yup) -->
+ <load-typesystem name="typesystem_widgets.xml" generate="no"/>
+ <!-- Currently there are problems with getting this to work (see below) -->
+ <!-- <load-typesystem name="../../plugins/pythonextensions/typesystem_qtcreator.xml" generate="no"/> -->
+ <!--
+ Here is the deal: This will compile just fine, but WON'T LOAD.
+ The reason for this is known:
+ -> The generated binding initializer will try to load the Python Module 'QtCreatorPython'
+ -> This module does not exists, because we expose everything as members of 'PythonExtension'
+ -> Now one might try to simply rename QtCreatorPython to PythonExtension.QtCreator, which would
+ generate an import statement like `Shiboken::Module::import("PythonExtension.QtCreator")`
+ -> Problem: That does not work either, because QtCreator is actually a member in PythonExtension,
+ which is a module and not a package.
+ -> So yeah, essentially it seems like this is something that would not be a problem in a module
+ for CPython, but there seems to be no facility to circumvent this when embedding CPython.
+ There is also no way in Shibken to fix this and no easy fix for that.
+ ==> The easiest way forward is to just include the parts that are needed within each binding in
+ the typesystem. Problems that might arise:
+ 1) Big size of bindings due to repetition of code (pretty sure)
+ 2) Interoperability issues between bindings (conjecture)
+ -->
+
+ <namespace-type name="TextEditor">
+ <enum-type name="TextPositionOperation"/>
+ <!-- <object-type name="BaseTextEditor">
+ <modify-function signature="position(TextEditor::TextPositionOperation,int)const" remove="all"/>
+ </object-type> -->
+ </namespace-type>
+
+</typesystem>
diff --git a/optional/texteditor/wrappedclasses.h b/optional/texteditor/wrappedclasses.h
new file mode 100644
index 0000000..260f2ac
--- /dev/null
+++ b/optional/texteditor/wrappedclasses.h
@@ -0,0 +1,37 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Python Extensions Plugin for QtCreator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#ifndef WRAPPEDCLASSES_H
+#define WRAPPEDCLASSES_H
+
+#include <QList>
+
+#include <texteditor/texteditor.h>
+
+#include <coreplugin/editormanager/ieditor.h>
+
+#include <utils/filesearch.h>
+
+#endif // header end
diff --git a/plugins/pythonextensions/PythonExtensions.json.in b/plugins/pythonextensions/PythonExtensions.json.in
new file mode 100644
index 0000000..e6bf147
--- /dev/null
+++ b/plugins/pythonextensions/PythonExtensions.json.in
@@ -0,0 +1,18 @@
+{
+ \"Name\" : \"PythonExtensions\",
+ \"Version\" : \"$$QTCREATOR_VERSION\",
+ \"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\",
+ \"Vendor\" : \"The Qt Company Ltd\",
+ \"Copyright\" : \"(C) $$QTCREATOR_COPYRIGHT_YEAR The Qt Company Ltd\",
+ \"License\" : [ \"Commercial Usage\",
+ \"\",
+ \"Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt Commercial License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company.\",
+ \"\",
+ \"GNU General Public License Usage\",
+ \"\",
+ \"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html.\"
+ ],
+ \"Description\" : \"This plugin manages and runs Python extensions.\",
+ \"Url\" : \"https://www.qt.io\",
+ $$dependencyList
+}
diff --git a/plugins/pythonextensions/bindingheaders/core.h b/plugins/pythonextensions/bindingheaders/core.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/pythonextensions/bindingheaders/core.h
diff --git a/plugins/pythonextensions/bindingheaders/core_actioncontainer.h b/plugins/pythonextensions/bindingheaders/core_actioncontainer.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/pythonextensions/bindingheaders/core_actioncontainer.h
diff --git a/plugins/pythonextensions/bindingheaders/core_actionmanager.h b/plugins/pythonextensions/bindingheaders/core_actionmanager.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/pythonextensions/bindingheaders/core_actionmanager.h
diff --git a/plugins/pythonextensions/bindingheaders/core_command.h b/plugins/pythonextensions/bindingheaders/core_command.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/pythonextensions/bindingheaders/core_command.h
diff --git a/plugins/pythonextensions/bindingheaders/core_constants.h b/plugins/pythonextensions/bindingheaders/core_constants.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/pythonextensions/bindingheaders/core_constants.h
diff --git a/plugins/pythonextensions/bindingheaders/core_context.h b/plugins/pythonextensions/bindingheaders/core_context.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/pythonextensions/bindingheaders/core_context.h
diff --git a/plugins/pythonextensions/bindingheaders/core_documentmanager.h b/plugins/pythonextensions/bindingheaders/core_documentmanager.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/pythonextensions/bindingheaders/core_documentmanager.h
diff --git a/plugins/pythonextensions/bindingheaders/core_documentmodel.h b/plugins/pythonextensions/bindingheaders/core_documentmodel.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/pythonextensions/bindingheaders/core_documentmodel.h
diff --git a/plugins/pythonextensions/bindingheaders/core_editormanager.h b/plugins/pythonextensions/bindingheaders/core_editormanager.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/pythonextensions/bindingheaders/core_editormanager.h
diff --git a/plugins/pythonextensions/bindingheaders/core_fileutils.h b/plugins/pythonextensions/bindingheaders/core_fileutils.h
new file mode 100644
index 0000000..b726b41
--- /dev/null
+++ b/plugins/pythonextensions/bindingheaders/core_fileutils.h
@@ -0,0 +1,33 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Python Extensions Plugin for QtCreator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#ifdef SBK_QTCREATORPYTHON_PYTHON_H
+#ifndef BINDINGHEADERS_FILE_UTILS_H
+#define BINDINGHEADERS_FILE_UTILS_H
+
+#include <coreplugin/fileutils.h>
+
+#endif
+#endif
diff --git a/plugins/pythonextensions/bindingheaders/core_icontext.h b/plugins/pythonextensions/bindingheaders/core_icontext.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/pythonextensions/bindingheaders/core_icontext.h
diff --git a/plugins/pythonextensions/bindingheaders/core_icore.h b/plugins/pythonextensions/bindingheaders/core_icore.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/pythonextensions/bindingheaders/core_icore.h
diff --git a/plugins/pythonextensions/bindingheaders/core_id.h b/plugins/pythonextensions/bindingheaders/core_id.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/pythonextensions/bindingheaders/core_id.h
diff --git a/plugins/pythonextensions/bindingheaders/core_idocument.h b/plugins/pythonextensions/bindingheaders/core_idocument.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/pythonextensions/bindingheaders/core_idocument.h
diff --git a/plugins/pythonextensions/bindingheaders/core_ieditor.h b/plugins/pythonextensions/bindingheaders/core_ieditor.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/pythonextensions/bindingheaders/core_ieditor.h
diff --git a/plugins/pythonextensions/bindingheaders/core_messagemanager.h b/plugins/pythonextensions/bindingheaders/core_messagemanager.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/pythonextensions/bindingheaders/core_messagemanager.h
diff --git a/plugins/pythonextensions/bindingheaders/extensionsystem.h b/plugins/pythonextensions/bindingheaders/extensionsystem.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/pythonextensions/bindingheaders/extensionsystem.h
diff --git a/plugins/pythonextensions/bindingheaders/extensionsystem_iplugin.h b/plugins/pythonextensions/bindingheaders/extensionsystem_iplugin.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/pythonextensions/bindingheaders/extensionsystem_iplugin.h
diff --git a/plugins/pythonextensions/bindingheaders/pythonextensions.h b/plugins/pythonextensions/bindingheaders/pythonextensions.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/pythonextensions/bindingheaders/pythonextensions.h
diff --git a/plugins/pythonextensions/bindingheaders/pythonextensions_internal.h b/plugins/pythonextensions/bindingheaders/pythonextensions_internal.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/pythonextensions/bindingheaders/pythonextensions_internal.h
diff --git a/plugins/pythonextensions/bindingheaders/pythonextensions_internal_pythonextensionsplugin.h b/plugins/pythonextensions/bindingheaders/pythonextensions_internal_pythonextensionsplugin.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/pythonextensions/bindingheaders/pythonextensions_internal_pythonextensionsplugin.h
diff --git a/plugins/pythonextensions/bindingheaders/utils.h b/plugins/pythonextensions/bindingheaders/utils.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/pythonextensions/bindingheaders/utils.h
diff --git a/plugins/pythonextensions/bindingheaders/utils_filename.h b/plugins/pythonextensions/bindingheaders/utils_filename.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/pythonextensions/bindingheaders/utils_filename.h
diff --git a/plugins/pythonextensions/bindingheaders/utils_macroexpander.h b/plugins/pythonextensions/bindingheaders/utils_macroexpander.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/pythonextensions/bindingheaders/utils_macroexpander.h
diff --git a/plugins/pythonextensions/glue/macroexpander_glue.cpp b/plugins/pythonextensions/glue/macroexpander_glue.cpp
new file mode 100644
index 0000000..606bf81
--- /dev/null
+++ b/plugins/pythonextensions/glue/macroexpander_glue.cpp
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Python Extensions Plugin for QtCreator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+void registerPythonVariable(const QByteArray &variable, const QString &description, PyObject *func)
+{
+ if (!PyCallable_Check(func)) {
+ PyErr_BadArgument();
+ return;
+ }
+ Py_XINCREF(func);
+
+ globalMacroExpander()->registerVariable(variable, description,
+ [=]() -> QString {
+ PyObject *ret = PyObject_Repr(PyObject_CallFunction(func, NULL));
+ #if PY_MAJOR_VERSION >= 3
+ QString value = QString::fromUtf8(PyUnicode_AsUTF8(ret));
+ #else
+ QString value = QString::fromUtf8(PyString_AsString(ret));
+ #endif
+ return value;
+ }
+ );
+}
+
+void registerPythonPrefixVariable(const QByteArray &variable, const QString &description, PyObject *func)
+{
+ if (!PyCallable_Check(func)) {
+ PyErr_BadArgument();
+ return;
+ }
+ Py_XINCREF(func);
+
+ globalMacroExpander()->registerPrefix(variable, description,
+ [=](QString passed_string) -> QString {
+ PyObject *ret = PyObject_Repr(PyObject_CallFunction(func, "s", passed_string.toStdString().c_str()));
+ #if PY_MAJOR_VERSION >= 3
+ QString value = QString::fromUtf8(PyUnicode_AsUTF8(ret));
+ #else
+ QString value = QString::fromUtf8(PyString_AsString(ret));
+ #endif
+ return value;
+ }
+ );
+}
diff --git a/plugins/pythonextensions/pyside2.pri b/plugins/pythonextensions/pyside2.pri
new file mode 100644
index 0000000..3e6cc23
--- /dev/null
+++ b/plugins/pythonextensions/pyside2.pri
@@ -0,0 +1,42 @@
+# Discover PySide2 configuration
+
+PYSIDE_CONFIG = ../../tools/pyside2_config.py
+
+PYSIDE2 = $$system($$PYTHON $$PYSIDE_CONFIG --pyside2)
+isEmpty(PYSIDE2): error(Unable to locate the PySide2 package location)
+
+PYTHON_INCLUDE = $$system($$PYTHON $$PYSIDE_CONFIG --python-include)
+isEmpty(PYTHON_INCLUDE): error(Unable to locate the Python include headers directory)
+
+PYTHON_LFLAGS = $$system($$PYTHON $$PYSIDE_CONFIG --python-link)
+isEmpty(PYTHON_LFLAGS): error(Unable to locate the Python library for linking)
+
+PYSIDE2_INCLUDE = $$system($$PYTHON $$PYSIDE_CONFIG --pyside2-include)
+isEmpty(PYSIDE2_INCLUDE): error(Unable to locate the PySide2 include headers directory)
+
+PYSIDE2_LFLAGS = $$system($$PYTHON $$PYSIDE_CONFIG --pyside2-link)
+isEmpty(PYSIDE2_LFLAGS): error(Unable to locate the PySide2 libraries for linking)
+
+PYSIDE2_SHARED_LIBRARIES = $$system($$PYTHON $$PYSIDE_CONFIG --pyside2-shared-libraries)
+isEmpty(PYSIDE2_SHARED_LIBRARIES): error(Unable to locate the used PySide2 shared libraries)
+
+INCLUDEPATH += "$$PYTHON_INCLUDE" $$PYSIDE2_INCLUDE
+LIBS += $$PYTHON_LFLAGS $$PYSIDE2_LFLAGS
+!build_pass:message(INCLUDEPATH is $$INCLUDEPATH)
+!build_pass:message(LIBS are $$LIBS)
+
+!build_pass:message(Using $$PYSIDE2)
+
+!win32 {
+ QMAKE_RPATHDIR += $$PYSIDE2
+}
+
+# Needed to fix Python dynamic linking problems
+# see pyutil.cpp
+LIBS += -ldl
+
+# Suppress non-relevant warnings from
+# Shiboken generated code
+QMAKE_CXXFLAGS += \
+ -Wno-missing-field-initializers \
+ -Wno-unused-parameter
diff --git a/plugins/pythonextensions/pythonextensions.pro b/plugins/pythonextensions/pythonextensions.pro
new file mode 100644
index 0000000..d3c5119
--- /dev/null
+++ b/plugins/pythonextensions/pythonextensions.pro
@@ -0,0 +1,126 @@
+# NOTE: This is not yet properly tested on general systems
+# and will (if at all) probably only work on a linux environment
+
+PYTHON = python
+DEFINES += PYTHONEXTENSIONS_LIBRARY
+
+# PythonExtensions files
+
+SOURCES += \
+ pythonextensionsplugin.cpp \
+ pyutil.cpp
+
+HEADERS += \
+ pythonextensionsplugin.h \
+ pythonextensions_global.h \
+ pyutil.h
+
+
+# Qt Creator linking
+
+# Shared QtCreator sources and build destination
+# (these are shared by this and the optional bindings)
+include(qtcreator.pri)
+
+# Declare dependencies
+include(pythonextensions_dependencies.pri)
+
+include($$IDE_SOURCE_TREE/src/qtcreatorplugin.pri)
+
+# Install extension manager extension during first build
+copyextensions.commands = $(COPY_DIR) $$PWD/../../python $$DESTDIR/
+first.depends = $(first) copyextensions
+export(first.depends)
+export(copyextensions.commands)
+QMAKE_EXTRA_TARGETS += first copyextensions
+
+# Shiboken stuff
+
+# This setup is currently only tested on Linux
+
+WRAPPED_HEADER = wrappedclasses.h
+WRAPPER_DIR = $$OUT_PWD/PythonExtension/QtCreator
+TYPESYSTEM_FILE = typesystem_qtcreator.xml
+
+include(pyside2.pri)
+
+## Include Qt and QtCreator paths
+QT_INCLUDEPATHS = -I"$$[QT_INSTALL_HEADERS]" -I"$$[QT_INSTALL_HEADERS]/QtCore" \
+ -I"$$[QT_INSTALL_HEADERS]/QtGui" -I"$$[QT_INSTALL_HEADERS]/QtWidgets" \
+ -I"$$IDE_SOURCE_TREE/src/plugins" \
+ -I"$$IDE_SOURCE_TREE/src/plugins/coreplugin" \
+ -I"$$IDE_SOURCE_TREE/src/libs"
+
+SHIBOKEN_OPTIONS = --generator-set=shiboken --enable-parent-ctor-heuristic \
+ --enable-pyside-extensions --enable-return-value-heuristic --use-isnull-as-nb_nonzero \
+ $$QT_INCLUDEPATHS -I$$PWD -T$$PWD -T$$PYSIDE2/typesystems --output-directory=$$OUT_PWD
+
+## Prepare the shiboken tool
+QT_TOOL.shiboken.binary = $$system_path($$PYSIDE2/shiboken2)
+qtPrepareTool(SHIBOKEN, shiboken)
+
+## Shiboken run that adds the module wrapper to GENERATED_SOURCES
+shiboken.output = $$WRAPPER_DIR/qtcreator_module_wrapper.cpp
+shiboken.commands = $$SHIBOKEN $$SHIBOKEN_OPTIONS $$PWD/wrappedclasses.h ${QMAKE_FILE_IN}
+shiboken.input = TYPESYSTEM_FILE
+shiboken.dependency_type = TYPE_C
+shiboken.variable_out = GENERATED_SOURCES
+
+## These headers are needed so the generated wrappers are added to the
+## build. Right now they are empty files, however there might be a more elegant
+## option.
+WRAPPED_CLASSES = \
+ bindingheaders/pythonextensions.h \
+ bindingheaders/pythonextensions_internal.h \
+ bindingheaders/pythonextensions_internal_pythonextensionsplugin.h \
+ bindingheaders/core.h \
+ bindingheaders/core_actioncontainer.h \
+ bindingheaders/core_actionmanager.h \
+ bindingheaders/core_command.h \
+ bindingheaders/core_constants.h \
+ bindingheaders/core_icontext.h \
+ bindingheaders/core_icore.h \
+ bindingheaders/core_id.h \
+ bindingheaders/core_context.h \
+ bindingheaders/core_editormanager.h \
+ bindingheaders/core_ieditor.h \
+ bindingheaders/core_idocument.h \
+ bindingheaders/core_documentmanager.h \
+ bindingheaders/core_documentmodel.h \
+ bindingheaders/core_fileutils.h \
+ bindingheaders/core_messagemanager.h \
+ bindingheaders/utils.h \
+ bindingheaders/utils_macroexpander.h \
+ bindingheaders/utils_filename.h \
+ bindingheaders/extensionsystem.h \
+ bindingheaders/extensionsystem_iplugin.h
+# Sentinel line
+
+module_wrapper_dummy_command.output = $$WRAPPER_DIR/${QMAKE_FILE_BASE}_wrapper.cpp
+module_wrapper_dummy_command.commands = echo ${QMAKE_FILE_IN}
+module_wrapper_dummy_command.depends = $$WRAPPER_DIR/qtcreator_module_wrapper.cpp
+module_wrapper_dummy_command.input = WRAPPED_CLASSES
+module_wrapper_dummy_command.dependency_type = TYPE_C
+module_wrapper_dummy_command.variable_out = GENERATED_SOURCES
+
+## Get the path component to the active config build folder
+defineReplace(getOutDir) {
+ out_dir = $$OUT_PWD
+ CONFIG(release, debug|release): out_dir = $$out_dir/release
+ else:out_dir = $$out_dir/debug
+ return($$out_dir)
+}
+
+QMAKE_EXTRA_COMPILERS += shiboken module_wrapper_dummy_command
+
+# TODO: Fix some more of these hardcoded include paths
+INCLUDEPATH += $$WRAPPER_DIR \
+ "$$IDE_SOURCE_TREE/src/plugins/coreplugin" \
+ "$$IDE_SOURCE_TREE/src/plugins/coreplugin/actionmanager" \
+ "$$IDE_SOURCE_TREE/src/plugins/coreplugin/editormanager" \
+ "$$IDE_SOURCE_TREE/src/libs/extensionsystem" \
+ "$$IDE_SOURCE_TREE/src/libs/utils"
+
+for(i, PYSIDE2_INCLUDE) {
+ INCLUDEPATH += $$i/QtWidgets $$i/QtGui $$i/QtCore
+}
diff --git a/plugins/pythonextensions/pythonextensions_dependencies.pri b/plugins/pythonextensions/pythonextensions_dependencies.pri
new file mode 100644
index 0000000..4a0b56e
--- /dev/null
+++ b/plugins/pythonextensions/pythonextensions_dependencies.pri
@@ -0,0 +1,12 @@
+# Declare dependencies
+
+QTC_PLUGIN_NAME = PythonExtensions
+QTC_LIB_DEPENDS += \
+ extensionsystem \
+ utils
+
+QTC_PLUGIN_DEPENDS += \
+ coreplugin
+
+QTC_PLUGIN_RECOMMENDS += \
+ # optional plugin dependencies. nothing here at this time
diff --git a/plugins/pythonextensions/pythonextensions_global.h b/plugins/pythonextensions/pythonextensions_global.h
new file mode 100644
index 0000000..6f0dab7
--- /dev/null
+++ b/plugins/pythonextensions/pythonextensions_global.h
@@ -0,0 +1,34 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Python Extensions Plugin for QtCreator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QtGlobal>
+
+#if defined(PYTHONEXTENSIONS_LIBRARY)
+# define PYTHONEXTENSIONSSHARED_EXPORT Q_DECL_EXPORT
+#else
+# define PYTHONEXTENSIONSSHARED_EXPORT Q_DECL_IMPORT
+#endif
diff --git a/plugins/pythonextensions/pythonextensionsplugin.cpp b/plugins/pythonextensions/pythonextensionsplugin.cpp
new file mode 100644
index 0000000..078a78c
--- /dev/null
+++ b/plugins/pythonextensions/pythonextensionsplugin.cpp
@@ -0,0 +1,295 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Python Extensions Plugin for QtCreator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "pythonextensionsplugin.h"
+
+#include "pyutil.h"
+
+#include <coreplugin/icore.h>
+#include <coreplugin/icontext.h>
+#include <coreplugin/actionmanager/actionmanager.h>
+#include <coreplugin/actionmanager/command.h>
+#include <coreplugin/actionmanager/actioncontainer.h>
+#include <coreplugin/coreconstants.h>
+#include <coreplugin/editormanager/editormanager.h>
+#include <coreplugin/messagemanager.h>
+
+#include <extensionsystem/pluginmanager.h>
+#include <extensionsystem/pluginspec.h>
+
+#include <QDir>
+#include <QIODevice>
+#include <QFile>
+#include <QDir>
+#include <QTextStream>
+#include <QString>
+#include <QStringList>
+#include <QLibrary>
+
+
+namespace PythonExtensions {
+namespace Constants {
+
+const char EXTENSIONS_DIR[] = "/python";
+const char PY_PACKAGES_DIR[] = "/site-packages";
+
+const char PY_BINDING_LIB[] = "/libPythonBinding";
+
+const char MESSAGE_MANAGER_PREFIX[] = "Python Extensions: ";
+
+} // namespace Constants
+namespace Internal {
+
+PythonExtensionsPlugin::PythonExtensionsPlugin()
+{
+ // Empty
+}
+
+PythonExtensionsPlugin::~PythonExtensionsPlugin()
+{
+ // Unregister objects from the plugin manager's object pool
+ // Delete members
+}
+
+bool PythonExtensionsPlugin::initialize(const QStringList &arguments, QString *errorString)
+{
+ // Register objects in the plugin manager's object pool
+ // Load settings
+ // Add actions to menus
+ // Connect to other plugins' signals
+ // In the initialize function, a plugin can be sure that the plugins it
+ // depends on have initialized their members.
+
+ Q_UNUSED(arguments)
+ Q_UNUSED(errorString)
+
+ initializePythonBindings();
+
+ // Python extensions are loaded after C++ plugins for now (plan: later flag can be set)
+
+ return true;
+}
+
+void PythonExtensionsPlugin::extensionsInitialized()
+{
+ // Retrieve objects from the plugin manager's object pool
+ // In the extensionsInitialized function, a plugin can be sure that all
+ // plugins that depend on it are completely initialized.
+}
+
+bool PythonExtensionsPlugin::delayedInitialize()
+{
+ // Initialize optional bindings
+ initializeOptionalBindings();
+ // Pip install any requirements known for the script
+ installRequirements();
+ // Run the setup for each extension that requires it
+ setupPythonExtensions();
+ // Python plugins are initialized here, to avoid blocking on startup
+ initializePythonExtensions();
+ return true;
+}
+
+ExtensionSystem::IPlugin::ShutdownFlag PythonExtensionsPlugin::aboutToShutdown()
+{
+ // Save settings
+ // Disconnect from signals that are not needed during shutdown
+ // Hide UI (if you add UI that is not in the main window directly)
+ return SynchronousShutdown;
+}
+
+QDir PythonExtensionsPlugin::extensionDir()
+{
+ // Search python directory in plugin paths
+ QDir extension_dir;
+ for (const QString &path : ExtensionSystem::PluginManager::pluginPaths()) {
+ extension_dir = QDir(path + Constants::EXTENSIONS_DIR);
+ if (extension_dir.exists())
+ break;
+ }
+ // Can be checked for validity with .exists()
+ return extension_dir;
+}
+
+QStringList PythonExtensionsPlugin::extensionList(const bool loadedOnly)
+{
+ if (loadedOnly)
+ return m_loadedExtensions;
+
+ QDir extension_dir = extensionDir();
+ if (!extension_dir.exists())
+ return QStringList();
+
+ QStringList extension_list = extension_dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
+ extension_list.removeOne("site-packages");
+ return extension_list;
+}
+
+void PythonExtensionsPlugin::flagAsLoaded(const QString &extension)
+{
+ m_loadedExtensions << extension;
+}
+
+QString PythonExtensionsPlugin::pythonPackagePath()
+{
+ if (extensionDir().exists()) {
+ return extensionDir().absolutePath() + Constants::PY_PACKAGES_DIR;
+ } else {
+ return QString();
+ }
+}
+
+void PythonExtensionsPlugin::initializePythonBindings()
+{
+ // Add our custom module directory
+ if (extensionDir().exists())
+ PyUtil::addToSysPath(pythonPackagePath().toStdString());
+ // Initialize the Python context and register global QtCreator variable
+ if (!PyUtil::bindShibokenModuleObject("PythonExtension", "QtCreator")) {
+ 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);
+}
+
+void PythonExtensionsPlugin::initializeOptionalBindings()
+{
+ // Try to load optional bindings for all loaded plugins
+ // If a plugin has optional bindings, they occur in the form of
+ // a shared object which has the name libPythonBinding{PluginName}
+ // and exposes a symbol called `bind' which is a void function taking
+ // no arguments. This function is responsible for binding the
+ // object using the exposed PyUtil api and for reporting any errors
+ // etc. to the stderr / stdout.
+ // Examples of projects for such libraries exist within this repository.
+ for (int i = 0; i < ExtensionSystem::PluginManager::loadQueue().size(); i++) {
+ // Check each plugin directory for the library (first found is used)
+ QString name = ExtensionSystem::PluginManager::loadQueue()[i]->name();
+ for (const QString &path : ExtensionSystem::PluginManager::pluginPaths()) {
+ QLibrary bindingLib(path + Constants::PY_BINDING_LIB + name);
+ QFunctionPointer bind = bindingLib.resolve("bind");
+ if (bind) {
+ qDebug() << "Initializing optional bindings for plugin" << name;
+ bind();
+ break;
+ }
+ }
+ }
+}
+
+void PythonExtensionsPlugin::installRequirements()
+{
+ // Pip install any requirements.txt file found
+ QDir extension_dir = extensionDir();
+ 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);
+ }
+ }
+ }
+}
+
+void PythonExtensionsPlugin::setupPythonExtensions()
+{
+ // Run the setup.py file for all extensions that provide it.
+ // later, there might be a way to determine if the setup needs
+ // to run.
+ QDir extension_dir = extensionDir();
+ if (!extension_dir.exists())
+ return;
+
+ QStringList extension_list = extensionList();
+ for (const QString &extension : extension_list) {
+ QFile extension_setup(extension_dir.absolutePath() + "/" + extension + "/setup.py");
+ if (extension_setup.open(QIODevice::ReadOnly)) {
+ QTextStream in(&extension_setup);
+ QString setup_code = in.readAll();
+ if (!PyUtil::runScriptWithPath(
+ setup_code.toStdString(),
+ QString(extension_dir.absolutePath() + "/" + extension).toStdString()
+ )) {
+ qWarning() << "Failed to setup extension" << extension;
+ Core::MessageManager::write(Constants::MESSAGE_MANAGER_PREFIX + tr("Failed to setup extension ") + extension);
+ }
+ }
+ }
+}
+
+void PythonExtensionsPlugin::initializePythonExtensions()
+{
+ // Search python directory in plugin paths
+ QDir extension_dir = extensionDir();
+ if (!extension_dir.exists()) {
+ qWarning() << "Python extension directory not found";
+ Core::MessageManager::write(Constants::MESSAGE_MANAGER_PREFIX + tr("Python extension directory not found"));
+ return;
+ }
+
+ qDebug() << "Found Python extension directory at location" << extension_dir.absolutePath();
+
+ QStringList extension_list = extensionList();
+
+ qDebug() << "Number of Python extensions found:" << extension_list.size();
+
+ // Run the extension initialization code
+ for (const QString &extension : extension_list) {
+ qDebug() << "Trying to initialize extension" << extension;
+
+ QFile extension_main(extension_dir.absolutePath() + "/" + extension + "/main.py");
+ if (extension_main.open(QIODevice::ReadOnly)) {
+ QTextStream in(&extension_main);
+ QString extension_code = in.readAll();
+ if (!PyUtil::runScriptWithPath(
+ extension_code.toStdString(),
+ QString(extension_dir.absolutePath() + "/" + extension).toStdString()
+ )) {
+ qWarning() << "Failed to initialize extension" << extension;
+ Core::MessageManager::write(Constants::MESSAGE_MANAGER_PREFIX + tr("Failed to initialize extension ") + extension);
+ } else {
+ m_loadedExtensions << extension;
+ }
+ } else {
+ qWarning() << "Failed to load main.py for extension" << extension;
+ Core::MessageManager::write(Constants::MESSAGE_MANAGER_PREFIX + tr("Failed to load main.py for extension ") + extension);
+ }
+ }
+
+ qDebug() << "Number of Python extensions loaded:" << m_loadedExtensions.size();
+}
+
+} // namespace Internal
+} // namespace PythonExtensions
diff --git a/plugins/pythonextensions/pythonextensionsplugin.h b/plugins/pythonextensions/pythonextensionsplugin.h
new file mode 100644
index 0000000..a920911
--- /dev/null
+++ b/plugins/pythonextensions/pythonextensionsplugin.h
@@ -0,0 +1,74 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Python Extensions Plugin for QtCreator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "pythonextensions_global.h"
+
+#include <extensionsystem/iplugin.h>
+
+#include <QDir>
+#include <QStringList>
+
+namespace PythonExtensions {
+namespace Internal {
+
+class PythonExtensionsPlugin : public ExtensionSystem::IPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "PythonExtensions.json")
+
+public:
+ PythonExtensionsPlugin();
+ ~PythonExtensionsPlugin();
+
+ bool initialize(const QStringList &arguments, QString *errorString) final;
+ void extensionsInitialized() final;
+ bool delayedInitialize() final;
+ ShutdownFlag aboutToShutdown() final;
+
+ QDir extensionDir();
+ QStringList extensionList(const bool loadedOnly = false);
+ void flagAsLoaded(const QString &extension);
+ QString pythonPackagePath();
+private:
+ QStringList m_loadedExtensions;
+ void initializePythonBindings();
+ void initializeOptionalBindings();
+ void installRequirements();
+ void setupPythonExtensions();
+ void initializePythonExtensions();
+};
+
+// Util functions
+// TODO: Are any needed? Until now there were no problems with object ownership
+
+} // namespace Internal
+} // namespace PythonExtensions
+
+
+// Fix for binding build, caused by multiple QtCreator headers
+// having the same name, for some build configurations.
+#include "bindingheaders/core_fileutils.h"
diff --git a/plugins/pythonextensions/pyutil.cpp b/plugins/pythonextensions/pyutil.cpp
new file mode 100644
index 0000000..56f7c03
--- /dev/null
+++ b/plugins/pythonextensions/pyutil.cpp
@@ -0,0 +1,271 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Python Extensions Plugin for QtCreator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "pyutil.h"
+
+#include <dlfcn.h> // dlopen
+
+#include <QtCore/QByteArray>
+#include <QtCore/QCoreApplication>
+#include <QtCore/QDebug>
+#include <QtCore/QStringList>
+#include <QtCore/QTemporaryFile>
+#include <QtCore/QDir>
+
+// These are used in python and cause compile-time errors
+// if still defined.
+#undef signals
+#undef slots
+
+#include <sbkpython.h>
+#include <sbkconverter.h>
+#include <sbkmodule.h>
+
+// Python has no concept of private variables and there
+// is no way to declare a namespace or scope that will be
+// inaccessible from the user script.
+// To avoid naming collisions with the setup and tear down
+// scripts that attempt to separate different extensions on
+// the level of user code (or at least make them appear separated),
+// this macro mangles the names of variables used.
+// Use as:
+// "... some Python code ..." privatName("my_var_name") "... more code ..."
+#define privateName(name) "qt_creator_" name "_symbol_mchawrioklpilnjajqkfl"
+
+// Setup and utility functions for QtCreator bindings
+// from typesystem.xml
+
+#if PY_MAJOR_VERSION >= 3
+extern "C" PyObject *PyInit_QtCreator();
+#else
+extern "C" void initQtCreator();
+#endif
+
+// These variables store all Python types exported by QtCreators bindings,
+// as well as the types exported for QtWidgets.
+extern PyTypeObject **SbkPythonExtension_QtCreatorTypes;
+
+// This variable stores the Python module generated by Shiboken
+extern PyObject *SbkPythonExtension_QtCreatorModuleObject;
+
+namespace PyUtil {
+
+static State state = PythonUninitialized;
+
+static void cleanup()
+{
+ if (state > PythonUninitialized) {
+ Py_Finalize();
+ state = PythonUninitialized;
+ }
+}
+
+State init()
+{
+ if (state > PythonUninitialized)
+ return state;
+
+ // If there is an active python virtual environment, use that environment's packages location.
+ QByteArray virtualEnvPath = qgetenv("VIRTUAL_ENV");
+ if (!virtualEnvPath.isEmpty())
+ qputenv("PYTHONHOME", virtualEnvPath);
+
+ // Python's shared libraries don't work properly if included from other
+ // shared libraries. See https://mail.python.org/pipermail/new-bugs-announce/2008-November/003322.html
+ #if PY_MAJOR_VERSION >= 3
+ std::string version = "libpython"+std::to_string(PY_MAJOR_VERSION)+"."+std::to_string(PY_MINOR_VERSION)+"m.so";
+ #else
+ std::string version = "libpython"+std::to_string(PY_MAJOR_VERSION)+"."+std::to_string(PY_MINOR_VERSION)+".so";
+ #endif
+ dlopen(version.c_str(), RTLD_LAZY | RTLD_GLOBAL);
+
+ Py_Initialize();
+ qAddPostRoutine(cleanup);
+ state = PythonInitialized;
+
+ #if PY_MAJOR_VERSION >= 3
+ const bool pythonInitialized = PyInit_QtCreator() != nullptr;
+ #else
+ const bool pythonInitialized = true;
+ initQtCreator();
+ #endif
+ const bool pyErrorOccurred = PyErr_Occurred() != nullptr;
+ if (pythonInitialized && !pyErrorOccurred) {
+ state = QtCreatorModuleLoaded;
+ } else {
+ if (pyErrorOccurred)
+ PyErr_Print();
+ qWarning("Failed to initialize the QtCreator module.");
+ }
+
+ // The Python interpreter eats SIGINT, which is not what we want.
+ // This stops it from happening.
+ // See https://mail.python.org/pipermail/cplusplus-sig/2012-December/016858.html
+ if (PyRun_SimpleString(std::string(
+ "import signal as " privateName("signal") "\n"
+ "" privateName("signal") ".signal(" privateName("signal") ".SIGINT, " privateName("signal") ".SIG_DFL)"
+ ).c_str()) == -1) {
+ if (PyErr_Occurred())
+ PyErr_Print();
+ qWarning("Failed to prevent SIGINT capture.");
+ }
+
+ return state;
+}
+
+bool createModule(const std::string &moduleName)
+{
+ if (init() != QtCreatorModuleLoaded)
+ return false;
+
+ PyObject *module = PyImport_AddModule(moduleName.c_str());
+ if (!module) {
+ if (PyErr_Occurred())
+ PyErr_Print();
+ qWarning() << __FUNCTION__ << "Failed to create module";
+ return false;
+ }
+
+ return true;
+}
+
+bool bindObject(const QString &moduleName, const QString &name, int index, void *o)
+{
+ if (init() != QtCreatorModuleLoaded)
+ return false;
+
+ // Generate the type
+ PyTypeObject *typeObject = SbkPythonExtension_QtCreatorTypes[index];
+
+ PyObject *po = Shiboken::Conversions::pointerToPython(reinterpret_cast<SbkObjectType *>(typeObject), o);
+ if (!po) {
+ qWarning() << __FUNCTION__ << "Failed to create wrapper for" << o;
+ return false;
+ }
+ Py_INCREF(po);
+
+ return bindPyObject(moduleName, name, po);
+}
+
+bool bindShibokenModuleObject(const QString &moduleName, const QString &name)
+{
+ return bindPyObject(moduleName, name, SbkPythonExtension_QtCreatorModuleObject);
+}
+
+bool bindPyObject(const QString &moduleName, const QString &name, void *obj)
+{
+ if (init() != QtCreatorModuleLoaded)
+ return false;
+
+ PyObject *module = PyImport_AddModule(moduleName.toLocal8Bit().constData());
+ if (!module) {
+ if (PyErr_Occurred())
+ PyErr_Print();
+ qWarning() << __FUNCTION__ << "Failed to locate module" << moduleName;
+ return false;
+ }
+
+ if (PyModule_AddObject(module, name.toLocal8Bit().constData(), (PyObject *)obj) < 0) {
+ if (PyErr_Occurred())
+ PyErr_Print();
+ qWarning() << __FUNCTION__ << "Failed to add object" << name << "to" << moduleName;
+ return false;
+ }
+
+ return true;
+}
+
+bool bindSubPyObject(const QString &moduleName, const QString &name, void *obj)
+{
+ PyObject *moduleDict = PyModule_GetDict((PyObject *)obj);
+ if (!moduleDict) {
+ if (PyErr_Occurred())
+ PyErr_Print();
+ qWarning("Could not obtain module dict");
+ return false;
+ }
+ PyObject *moduleItem = PyDict_GetItemString(moduleDict, name.toLocal8Bit().constData());
+ if (!moduleDict) {
+ if (PyErr_Occurred())
+ PyErr_Print();
+ qWarning() << "Could not obtain module item" << name;
+ return false;
+ }
+ return bindPyObject(moduleName, name, (void *)moduleItem);
+}
+
+bool runScript(const std::string &script)
+{
+ if (init() == PythonUninitialized)
+ return false;
+
+ if (PyRun_SimpleString(script.c_str()) == -1) {
+ if (PyErr_Occurred())
+ PyErr_Print();
+ return false;
+ }
+
+ return true;
+}
+
+bool runScriptWithPath(const std::string &script, const std::string &path)
+{
+ // I couldn't find a direct c api, but this should cause no variable name
+ // collisions. It also cleans the imported modules after the script finishes
+ const std::string s =
+"import sys as " privateName("sys") "\n"
+"" privateName("path") " = list(" privateName("sys") ".path)\n"
+"" privateName("sys") ".path.insert(0, \"" + path + "\")\n"
+"" privateName("loaded_modules") " = list(" privateName("sys") ".modules.keys())\n"
+"" + script + "\n"
+"" privateName("sys") ".path = " privateName("path") "\n"
+"for m in list(" privateName("sys") ".modules):\n"
+" if m not in " privateName("loaded_modules") ":\n"
+" del(" privateName("sys") ".modules[m])\n";
+ return runScript(s);
+}
+
+bool addToSysPath(const std::string &path)
+{
+ // Add a path to Pythons sys.path
+ // Used for installing dependencies into custom
+ // directory
+ const std::string s =
+"import sys as " privateName("sys") "\n"
+"" privateName("sys") ".path.append(\"" + path + "\")";
+ return runScript(s);
+}
+
+bool pipInstallRequirements(const std::string &requirements, const std::string &target)
+{
+ // Run a requirements.txt file with pip
+ const std::string s =
+"import subprocess, sys\n"
+"subprocess.check_call(\"{} -m pip install -t " + target + " -r " + requirements + "\".format(sys.executable).split())\n"
+"open(\"" + requirements + "\".replace(\"requirements.txt\",\"requirements.txt.installed\"),\"a\").close()\n";
+ return runScriptWithPath(s, "");
+}
+
+} // namespace PyUtil
diff --git a/plugins/pythonextensions/pyutil.h b/plugins/pythonextensions/pyutil.h
new file mode 100644
index 0000000..908427d
--- /dev/null
+++ b/plugins/pythonextensions/pyutil.h
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Python Extensions Plugin for QtCreator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#ifndef PYUTIL_H
+#define PYUTIL_H
+
+#include "pythonextensions_global.h"
+#include <string>
+
+class QObject;
+class QString;
+
+namespace PyUtil {
+
+// Note: If modifying the bindings, check these types still align
+enum QtCreatorTypes
+{
+ PythonExtensionsPluginType = 33, // SBK_PYTHONEXTENSIONS_INTERNAL_PYTHONEXTENSIONSPLUGIN_IDX
+};
+
+enum State
+{
+ PythonUninitialized,
+ PythonInitialized,
+ QtCreatorModuleLoaded,
+};
+
+State init();
+
+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);
+
+PYTHONEXTENSIONSSHARED_EXPORT bool bindPyObject(const QString &moduleName, const QString &name, void *obj);
+
+PYTHONEXTENSIONSSHARED_EXPORT bool bindSubPyObject(const QString &moduleName, const QString &name, void *obj);
+
+PYTHONEXTENSIONSSHARED_EXPORT bool runScript(const std::string &script);
+
+PYTHONEXTENSIONSSHARED_EXPORT bool runScriptWithPath(const std::string &script, const std::string &path);
+
+PYTHONEXTENSIONSSHARED_EXPORT bool addToSysPath(const std::string &path);
+
+PYTHONEXTENSIONSSHARED_EXPORT bool pipInstallRequirements(const std::string &requirements, const std::string &target);
+
+} // namespace PyUtil
+
+#endif
diff --git a/plugins/pythonextensions/qtcreator.pri b/plugins/pythonextensions/qtcreator.pri
new file mode 100644
index 0000000..cb58e20
--- /dev/null
+++ b/plugins/pythonextensions/qtcreator.pri
@@ -0,0 +1,15 @@
+# QtCreator specific build settings
+# (shared by plugin and optional bindings)
+
+## Either set the IDE_SOURCE_TREE when running qmake,
+## or set the QTC_SOURCE environment variable, to override the default setting
+isEmpty(IDE_SOURCE_TREE): IDE_SOURCE_TREE = $$(QTC_SOURCE)
+
+## Either set the IDE_BUILD_TREE when running qmake,
+## or set the QTC_BUILD environment variable, to override the default setting
+isEmpty(IDE_BUILD_TREE): IDE_BUILD_TREE = $$(QTC_BUILD)
+
+# KEEP this line and DON'T edit it!
+# (if you NEED to change it, have a look at tools/build.py)
+# USE_USER_DESTDIR = yes
+# END KEEP
diff --git a/plugins/pythonextensions/typesystem_qtcreator.xml b/plugins/pythonextensions/typesystem_qtcreator.xml
new file mode 100644
index 0000000..9a1376d
--- /dev/null
+++ b/plugins/pythonextensions/typesystem_qtcreator.xml
@@ -0,0 +1,237 @@
+<?xml version="1.0"?>
+<!--
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Python Extensions Plugin for QtCreator.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+-->
+
+<!-- Typesystem for Qt Creator Python host plugin -->
+<typesystem package="PythonExtension.QtCreator">
+
+ <!-- Load PySide QtWidgets typesystem (is this correct? yup) -->
+ <load-typesystem name="typesystem_widgets.xml" generate="no"/>
+
+ <namespace-type name="PythonExtensions">
+ <namespace-type name="Internal">
+ <object-type name="PythonExtensionsPlugin"/>
+ </namespace-type>
+ </namespace-type>
+
+ <namespace-type name="Core">
+ <object-type name="ActionContainer">
+ <enum-type name="OnAllDisabledBehavior"/>
+ <modify-function signature="addMenu(Core::ActionContainer *, Core::ActionContainer *, Core::Id)"/>
+ <modify-function signature="addMenu(Core::ActionContainer *, Core::Id)"/>
+ <modify-function signature="addSeparator(const Core::Context &amp;, Core::Id, QAction **)" remove="all"/>
+ <modify-function signature="addSeparator(Core::Id)" remove="all"/>
+ </object-type>
+ <object-type name="ActionManager"/>
+ <!-- <object-type name="BaseFileFilter"/> -->
+ <!-- <object-type name="BaseFileWizard"/> -->
+ <!-- <object-type name="BaseFileWizardFactory"/> -->
+ <!-- <object-type name="BaseTextDocument"/> -->
+ <!-- <object-type name="BaseTextFind"/> -->
+ <!-- Causes Seg-Fault (still?) -->
+ <object-type name="Command">
+ <enum-type name="CommandAttribute"/>
+ </object-type>
+ <!-- <object-type name="CommandButton"/> -->
+ <!-- <object-type name="CommandLocator"/> -->
+ <!-- <object-type name="CommandMappings"/> -->
+ <value-type name="Context"/>
+ <!-- <object-type name="DesignMode"/> -->
+ <!-- <object-type name="DiffService"/> -->
+ <object-type name="DocumentManager">
+ <enum-type name="ResolveMode"/>
+ </object-type>
+ <object-type name="DocumentModel"/>
+ <object-type name="EditorManager">
+ <!-- <enum-type name="OpenEditorFlags"/> -->
+ </object-type>
+ <!-- <object-type name="EditorManagerPlaceHolder"/> -->
+ <!-- <object-type name="EditorToolBar"/> -->
+ <!-- <object-type name="ExternalToolManager"/> -->
+ <object-type name="FileUtils"/>
+ <!-- <object-type name="Find"/> -->
+ <!-- <object-type name="FindToolBarPlaceHolder"/> -->
+ <!-- <object-type name="FutureProgress"/> -->
+ <!-- <object-type name="GenerateFile"/> -->
+ <!-- <object-type name="HelpManager"/> -->
+ <!-- <object-type name="Highlight"/> -->
+ <!-- <object-type name="HighlightScrollBarController"/> -->
+ <object-type name="IContext"/>
+ <object-type name="ICore">
+ <enum-type name="ContextPriority"/>
+ <enum-type name="OpenFilesFlags"/>
+ </object-type>
+ <object-type name="IDocument">
+ <enum-type name="OpenResult"/>
+ <enum-type name="ReloadSetting"/>
+ <enum-type name="ChangeTrigger"/>
+ <enum-type name="ChangeType"/>
+ <enum-type name="ReloadBehavior"/>
+ <enum-type name="ReloadFlag"/>
+ </object-type>
+ <!-- <object-type name="IDocumentFactory"/> -->
+ <object-type name="IEditor"/>
+ <!-- <object-type name="IEditorFactory"/> -->
+ <!-- <object-type name="IExternalEditor"/> -->
+ <!-- <object-type name="IFeatureProvider"/> -->
+ <!-- <object-type name="IFindFilter"/> -->
+ <!-- <object-type name="IFindSupport"/> -->
+ <!-- <object-type name="ILocatorFilter"/> -->
+ <!-- <object-type name="IMode"/> -->
+ <!-- <object-type name="INavigationWidgetFactory"/> -->
+ <!-- <object-type name="IOptionsPage"/> -->
+ <!-- <object-type name="IOptionsPageProvider"/> -->
+ <!-- <object-type name="IOutputPane"/> -->
+ <!-- <object-type name="IVersionControl"/> -->
+ <!-- <object-type name="IWelcomePage"/> -->
+ <!-- <object-type name="IWizardFactory"/> -->
+ <value-type name="Id">
+ <modify-function signature="operator&gt;&gt;(QDataStream&amp;, Core::Id&amp;)" remove="all"/>
+ <modify-function signature="operator&lt;&lt;(QDataStream&amp;, Core::Id)" remove="all"/>
+ </value-type>
+ <!-- <object-type name="InfoBar"/> -->
+ <!-- <object-type name="InfoBarDisplay"/> -->
+ <!-- <object-type name="InfoBarEntry"/> -->
+ <!-- <object-type name="ItemViewFind"/> -->
+ <!-- <object-type name="JsExpander"/> -->
+ <!-- <object-type name="LocatorFilterEntry"/> -->
+ <!-- <object-type name="LocatorManager"/> -->
+ <!-- <object-type name="ModeManager"/> -->
+ <object-type name="MessageManager">
+ <enum-type name="PrintToOutputPaneFlag"/>
+ </object-type>
+ <!-- <object-type name="NavigationView"/> -->
+ <!-- <object-type name="NavigationWidget"/> -->
+ <!-- <object-type name="NavigationWidgetPlaceHolder"/> -->
+ <!-- <object-type name="NonResizingSplitter"/> -->
+ <!-- <object-type name="OpenDocumentsTreeView"/> -->
+ <!-- <object-type name="OutputPanePlaceHolder"/> -->
+ <!-- <object-type name="OutputWindow"/> -->
+ <!-- <object-type name="PatchTool"/> -->
+ <!-- <object-type name="ProgressManager"/> -->
+ <!-- <object-type name="ProgressTimer"/> -->
+ <!-- <object-type name="PromptOverwriteDialog"/> -->
+ <!-- <object-type name="ReadOnlyFilesDialog"/> -->
+ <!-- <object-type name="RightPanePlaceHolder"/> -->
+ <!-- <object-type name="RightPaneWidget"/> -->
+ <!-- <object-type name="SearchResult"/> -->
+ <!-- <object-type name="SearchResultItem"/> -->
+ <!-- <object-type name="SearchResultWindow"/> -->
+ <!-- <object-type name="SettingsDatabase"/> -->
+ <!-- <object-type name="ShellCommand"/> -->
+ <!-- <object-type name="SideBar"/> -->
+ <!-- <object-type name="SideBarItem"/> -->
+ <!-- <object-type name="StatusBarManager"/> -->
+ <!-- <object-type name="VariableChoser"/> -->
+ <!-- <object-type name="VcsManager"/> -->
+ <!-- <object-type name="WelcomePageButton"/> -->
+ <!-- <object-type name="WelcomePageFrame"/> -->
+ <!-- <object-type name="WizardDialogParameters"/> -->
+
+ <enum-type name="FindFlag"/>
+ <enum-type name="MakeWritableResult"/>
+ <enum-type name="Side"/>
+
+ <!-- <function signature="highlightAll(const QString &amp;, FindFlags)"/> -->
+ <!-- <function signature="qHash(Id)"/> -->
+
+ <namespace-type name="Constants"/>
+ </namespace-type>
+
+ <namespace-type name="Utils">
+ <object-type name="MacroExpander">
+ <inject-code class="native" position="beginning" file="glue/macroexpander_glue.cpp"/>
+
+ <!-- This requires to specifically disable threads, so that the python state is not empty when executing the expansion -->
+ <!-- However, since Shiboken does not support disabling threads directly, we need to rewrite the call to C++ -->
+ <modify-function signature="expand(const QString) const" allow-thread="no">
+ <inject-code class="target" position="beginning">
+ // Same as generated, no PyEval_SaveThread
+ %RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(%1);
+ %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
+ </inject-code>
+ </modify-function>
+ <modify-function signature="expand(const QByteArray) const" allow-thread="no">
+ <inject-code class="target" position="beginning">
+ // Same as generated, no PyEval_SaveThread
+ %RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(%1);
+ %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
+ </inject-code>
+ </modify-function>
+ <modify-function signature="expandProcessArgs(const QString) const" allow-thread="no">
+ <inject-code class="target" position="beginning">
+ // Same as generated, no PyEval_SaveThread
+ %RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(%1);
+ %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
+ </inject-code>
+ </modify-function>
+ <modify-function signature="value(const QByteArray, bool *found) const" allow-thread="no">
+ <inject-code class="target" position="beginning">
+ // Same as generated, no PyEval_SaveThread
+ %RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(%1);
+ %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
+ </inject-code>
+ </modify-function>
+
+ <!-- Not Pythonic, just call .value(b"Macro").decode("utf-8") -->
+ <modify-function signature="resolveMacro(const QString, QString) const" remove="all"/>
+
+ <add-function signature="registerVariable(const QByteArray&amp;,const QString&amp;,PyObject*)">
+ <inject-code>
+ registerPythonVariable(%1, %2, %3);
+ </inject-code>
+ </add-function>
+ <add-function signature="registerPrefix(const QByteArray&amp;,const QString&amp;,PyObject*)">
+ <inject-code>
+ registerPythonPrefixVariable(%1, %2, %3);
+ </inject-code>
+ </add-function>
+ </object-type>
+ <value-type name="FileName"/>
+ <modify-function signature="operator&lt;&lt;(QTextStream&amp;,Utils::FileName)" remove="all"/>
+ <enum-type name="OsType"/>
+ </namespace-type>
+
+ <namespace-type name="ExtensionSystem">
+ <object-type name="IPlugin"/>
+ </namespace-type>
+
+</typesystem>
diff --git a/plugins/pythonextensions/wrappedclasses.h b/plugins/pythonextensions/wrappedclasses.h
new file mode 100644
index 0000000..ebc9910
--- /dev/null
+++ b/plugins/pythonextensions/wrappedclasses.h
@@ -0,0 +1,50 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Python Extensions Plugin for QtCreator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#ifndef WRAPPEDCLASSES_H
+#define WRAPPEDCLASSES_H
+
+#include "pythonextensionsplugin.h"
+
+#include <coreplugin/icore.h>
+#include <coreplugin/icontext.h>
+#include <coreplugin/editormanager/ieditor.h>
+#include <coreplugin/idocument.h>
+#include <coreplugin/editormanager/editormanager.h>
+#include <coreplugin/actionmanager/actionmanager.h>
+#include <coreplugin/actionmanager/command.h>
+#include <coreplugin/actionmanager/actioncontainer.h>
+#include <coreplugin/coreconstants.h>
+#include <coreplugin/id.h>
+#include <coreplugin/documentmanager.h>
+#include <coreplugin/fileutils.h>
+#include <coreplugin/messagemanager.h>
+
+#include <utils/macroexpander.h>
+#include <utils/fileutils.h>
+
+#include <extensionsystem/iplugin.h>
+
+#endif // header end
diff --git a/python/extensionmanager/actions.py b/python/extensionmanager/actions.py
new file mode 100644
index 0000000..0efaae4
--- /dev/null
+++ b/python/extensionmanager/actions.py
@@ -0,0 +1,72 @@
+#############################################################################
+##
+## Copyright (C) 2018 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Python Extensions Plugin for QtCreator.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+# Functions for installing and deleting extensions
+
+import sys, zipfile
+from PythonExtension import PluginInstance as instance
+from send2trash import send2trash
+
+def install(path):
+ try:
+ extZip = zipfile.ZipFile(path, "r")
+ # Verify all files are in the same directory
+ # NOTE: This is vulnerable to things like ../
+ if len(extZip.namelist()) < 1:
+ extZip.close()
+ return "The .zip file is empty."
+ extName = extZip.namelist()[0].split("/")[0]
+ for path in extZip.namelist():
+ if extName != path.split("/")[0] or len(extName) < 1:
+ extZip.close()
+ 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())
+ extZip.close()
+ except Exception as e:
+ return str(e)
+ return True
+
+def uninstall(ext):
+ send2trash(instance.extensionDir().absolutePath() + "/" + ext)
diff --git a/python/extensionmanager/list.py b/python/extensionmanager/list.py
new file mode 100644
index 0000000..9850964
--- /dev/null
+++ b/python/extensionmanager/list.py
@@ -0,0 +1,162 @@
+#############################################################################
+##
+## Copyright (C) 2018 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Python Extensions Plugin for QtCreator.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+# Ui for extension list view
+
+from PySide2 import QtCore, QtWidgets, QtGui
+from PythonExtension import PluginInstance as instance
+import actions
+
+class ExtensionList(QtWidgets.QListWidget):
+ def __init__(self):
+ super(ExtensionList, self).__init__()
+
+ self.itemClicked.connect(self.showInfo)
+
+ def showInfo(self, item):
+ return
+
+ def loadExtensionList(self):
+ i = 0
+ for ext in instance.extensionList():
+ item = QtWidgets.QListWidgetItem(self)
+ if not ext in instance.extensionList(True):
+ item.setText(ext + " [did not load]")
+ item.setIcon(QtGui.QIcon.fromTheme("dialog-error"))
+ else:
+ item.setText(ext)
+ item.setIcon(QtGui.QIcon.fromTheme("applications-development"))
+ if i % 2 == 1:
+ item.setBackground(QtGui.QBrush(QtGui.QColor.fromRgb(240, 240, 240)))
+ i += 1
+
+
+# View that lists all extensions
+class ListView(QtWidgets.QDialog):
+ def __init__(self, parent):
+ super(ListView, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
+
+ self.setWindowTitle("Installed Python Extensions")
+
+ self.layout = QtWidgets.QVBoxLayout(self)
+
+ self.label = QtWidgets.QLabel()
+ self.label.setText("Manage Python extensions installed to \"{0}\".".format(instance.extensionDir().absolutePath()))
+ self.label.setWordWrap(True)
+ self.layout.addWidget(self.label)
+
+ self.list = ExtensionList()
+ self.layout.addWidget(self.list)
+
+ self.buttonDone = QtWidgets.QPushButton("Close")
+ self.buttonDone.setDefault(True)
+ self.buttonDone.clicked.connect(self.close)
+
+ self.buttonDelete = QtWidgets.QPushButton("Uninstall")
+ self.buttonDelete.setAutoDefault(False)
+ self.buttonDelete.clicked.connect(self.actionDelete)
+
+ self.buttonAdd = QtWidgets.QPushButton("Install")
+ self.buttonAdd.setAutoDefault(False)
+ self.buttonAdd.clicked.connect(self.actionAdd)
+
+ self.buttonBox = QtWidgets.QDialogButtonBox(QtCore.Qt.Horizontal)
+ self.buttonBox.addButton(self.buttonDone, QtWidgets.QDialogButtonBox.AcceptRole)
+ self.buttonBox.addButton(self.buttonDelete, QtWidgets.QDialogButtonBox.ActionRole)
+ self.buttonBox.addButton(self.buttonAdd, QtWidgets.QDialogButtonBox.ActionRole)
+ self.layout.addWidget(self.buttonBox)
+
+ self.reloadExtensionList()
+
+ self.resize(400, 350)
+
+ def reloadExtensionList(self):
+ self.list.clear()
+ self.list.loadExtensionList()
+
+ def actionDelete(self):
+ selected = self.list.selectedIndexes()
+ if len(selected) >= 1:
+ selected = selected[0].row()
+ ext = instance.extensionList()[selected]
+ if ext == "extensionmanager":
+ QtWidgets.QMessageBox.warning(self, "Can not Uninstall", "The Extension Manager can not uninstall itself.")
+ else:
+ ret = QtWidgets.QMessageBox.warning(
+ self,
+ "Uninstall \"" + ext + "\"?",
+ "You are about to uninstall \"" + ext + "\". This action can not be undone. Do you want to proceed?",
+ QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Yes,
+ QtWidgets.QMessageBox.Cancel
+ )
+ if ret == QtWidgets.QMessageBox.Yes:
+ actions.uninstall(ext)
+ self.reloadExtensionList()
+ QtWidgets.QMessageBox.information(
+ self,
+ "Removed \"" + ext + "\"",
+ "The extension was uninstalled. Please restart QtCreator to apply this change."
+ )
+ else:
+ QtWidgets.QMessageBox.warning(self, "Select an Extension", "Please select an extension to uninstall.")
+
+ def actionAdd(self):
+ fileName = QtWidgets.QFileDialog.getOpenFileName(
+ self,
+ "Select Extension",
+ "/",
+ "QtCreator Python Extensions (*.zip)"
+ )
+ oldExtensions = list(instance.extensionList())
+ result = actions.install(fileName[0])
+ if result == True:
+ QtWidgets.QMessageBox.information(
+ self,
+ "Extension Installed",
+ "The extension from \"" + fileName[0] + "\" was installed successfully. Please restart QtCreator to apply this change."
+ )
+ self.reloadExtensionList()
+ else:
+ box = QtWidgets.QMessageBox(self)
+ box.setWindowTitle("Could not Install Extension")
+ box.setText("There was a problem installing your extension.")
+ box.setDetailedText(str(result))
+ box.setIcon(QtWidgets.QMessageBox.Warning)
+ box.exec_()
diff --git a/python/extensionmanager/main.py b/python/extensionmanager/main.py
new file mode 100644
index 0000000..6f22f0a
--- /dev/null
+++ b/python/extensionmanager/main.py
@@ -0,0 +1,52 @@
+#############################################################################
+##
+## Copyright (C) 2018 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Python Extensions Plugin for QtCreator.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+# Entry point of the Python extension
+
+from PythonExtension import QtCreator
+from list import *
+
+# Actions
+def showExtensionList():
+ dialog = ListView(QtCreator.Core.ICore.dialogParent())
+ dialog.exec_()
+
+helpMenu = QtCreator.Core.ActionManager.actionContainer("QtCreator.Menu.Help")
+helpMenu.menu().addAction("About Python Extensions...", showExtensionList)
diff --git a/python/extensionmanager/requirements.txt b/python/extensionmanager/requirements.txt
new file mode 100644
index 0000000..97ffc1a
--- /dev/null
+++ b/python/extensionmanager/requirements.txt
@@ -0,0 +1,2 @@
+# Needed packages
+Send2Trash
diff --git a/pythonextensions.pro b/pythonextensions.pro
new file mode 100644
index 0000000..2230178
--- /dev/null
+++ b/pythonextensions.pro
@@ -0,0 +1,9 @@
+TEMPLATE = subdirs
+
+SUBDIRS += \
+ plugins/pythonextensions
+
+# Note: This does not yet include the
+# optional bindings. They have to
+# be built using their own setup.py,
+# or using the tools/build.py
diff --git a/tests/package.py b/tests/package.py
new file mode 120000
index 0000000..48dd31f
--- /dev/null
+++ b/tests/package.py
@@ -0,0 +1 @@
+../examples/package.py \ No newline at end of file
diff --git a/tests/testextension/main.py b/tests/testextension/main.py
new file mode 100644
index 0000000..a1773d8
--- /dev/null
+++ b/tests/testextension/main.py
@@ -0,0 +1,141 @@
+#############################################################################
+##
+## Copyright (C) 2018 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Python Extensions Plugin for QtCreator.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+# Unit tests for the Python Extensions Plugins
+# behavior from the view of an extension.
+# To use simply install as an extension and run
+# QtCreator. (Outputs to terminal)
+
+import unittest
+import os, sys, io
+
+# NOTE: Done here, because the unit-test stuff imports math
+math_imported = "math" in sys.modules
+
+# Test Cases
+
+class TestRequirements(unittest.TestCase):
+
+ def test_installed(self):
+ """
+ Test if numpy was installed as expected
+ """
+ try:
+ import numpy
+ except ImportError:
+ self.assertTrue(False, msg="Install failed.")
+
+ def test_marked(self):
+ """
+ Test whether the extensions requirements were
+ marked as installed
+ """
+ self.assertTrue(os.path.exists("{}/requirements.txt.installed".format(sys.path[0])), msg="Install was not marked.")
+
+class TestPath(unittest.TestCase):
+
+ def test_clean(self):
+ """
+ The sys.path should be sanitized after every script
+ is run. This also applies to setup.py
+ """
+ self.assertFalse("test/path" in sys.path, msg="Sys path not sanitized.")
+
+class TestModules(unittest.TestCase):
+
+ def test_clean(self):
+ """
+ The imported modules should be cleaned after every
+ script. This also applies to setup.py
+ """
+ self.assertFalse(math_imported, msg="Modules not sanitized.")
+
+ def test_exists_builtin(self):
+ """
+ Test if the bindings for Core, Utils, ExtensionSystem, and PluginInstance
+ exist. These are the bindings that currently ship with the main C++ plugin
+ and are non-optional.
+ """
+ try:
+ from PythonExtension import QtCreator
+ except ImportError:
+ self.assertTrue(False, msg="QtCreator binding module missing.")
+ self.assertTrue("Core" in dir(QtCreator), msg="Core module missing.")
+ self.assertTrue("Utils" in dir(QtCreator), msg="Utils module missing.")
+ self.assertTrue("ExtensionSystem" in dir(QtCreator), msg="ExtensionSystem module missing.")
+
+ def text_exists_optional(self):
+ """
+ These tests may fail, even if everything is setup correctly, depending on
+ which QtCreator plugins are enabled.
+ """
+ try:
+ from PythonExtension.QtCreator import PluginInstance
+ from PythonExtension.QtCreator import ProjectExplorer
+ from PythonExtension.QtCreator import TextEditor
+ except ImportError:
+ self.assertTrue(False, msg="Optional binding modules missing.")
+
+
+# Run Unit Tests
+
+def add_tests(test_case, suite):
+ for case in dir(test_case):
+ if case[:5] == "test_":
+ suite.addTest(test_case(case))
+
+def suite():
+ suite = unittest.TestSuite()
+ add_tests(TestRequirements, suite)
+ add_tests(TestPath, suite)
+ add_tests(TestModules, suite)
+ return suite
+
+out = io.StringIO()
+runner = unittest.TextTestRunner(out)
+runner.run(suite())
+
+print(out.getvalue())
+
+# Make test output available from ui
+
+from PythonExtension import QtCreator
+
+QtCreator.Core.MessageManager.write("Python Extensions: Unit test results:\n{}".format(out.getvalue()))
diff --git a/tests/testextension/requirements.txt b/tests/testextension/requirements.txt
new file mode 100644
index 0000000..869dda8
--- /dev/null
+++ b/tests/testextension/requirements.txt
@@ -0,0 +1,5 @@
+# A simple requirements.txt, more complex things
+# are not required, as pip is expected to be maintained
+# and tested.
+
+numpy
diff --git a/tests/testextension/setup.py b/tests/testextension/setup.py
new file mode 100644
index 0000000..1e57d9b
--- /dev/null
+++ b/tests/testextension/setup.py
@@ -0,0 +1,48 @@
+#############################################################################
+##
+## Copyright (C) 2018 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Python Extensions Plugin for QtCreator.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+# Setup for the tests
+
+# This should be sanitized after setup.py halts
+import sys
+sys.path.append("test/path")
+
+# This should not be available in main.py
+import math
diff --git a/tools/build.py b/tools/build.py
new file mode 100644
index 0000000..4491025
--- /dev/null
+++ b/tools/build.py
@@ -0,0 +1,109 @@
+#############################################################################
+##
+## Copyright (C) 2018 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Python Extensions Plugin for QtCreator.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+# This is intended as a convenience for building the main C++
+# plugin, as well as the optional bindings using one command.
+# This exists mainly because I am lazy and should probably not
+# really be a part of the build-system going forward.
+
+# Usage:
+# $ python build.py --qmake=/path/to/qmake
+
+# Options:
+# --user
+# (optional, sets USE_USER_DESTDIR)
+# --skip
+# (optional, skip optional builds)
+
+import subprocess, sys
+
+
+def user():
+ return "--user" in sys.argv
+
+def skip():
+ return "--skip" in sys.argv
+
+def qmake():
+ for arg in sys.argv:
+ if arg.split("=")[0] == "--qmake":
+ return arg.split("=")[-1]
+ return "qmake"
+
+def clean():
+ return "clean" in sys.argv
+
+def read_pri():
+ f = open("plugins/pythonextensions/qtcreator.pri", "r")
+ body = f.read(-1)
+ f.close()
+ return body
+
+def write_pri(body):
+ f = open("plugins/pythonextensions/qtcreator.pri", "w")
+ f.write(body)
+ f.close()
+
+def build_plugin():
+ subprocess.check_call(qmake(), cwd="plugins/pythonextensions")
+ subprocess.check_call("make", cwd="plugins/pythonextensions")
+
+def build_optional():
+ subprocess.check_call(["python", "setup.py", "--qmake={}".format(qmake())], cwd="optional")
+
+def main():
+ if user():
+ write_pri(read_pri().replace("# USE_USER_DESTDIR = yes", "USE_USER_DESTDIR = yes"))
+ build_plugin()
+ if not skip():
+ build_optional()
+ if user():
+ write_pri(read_pri().replace("USE_USER_DESTDIR = yes", "# USE_USER_DESTDIR = yes"))
+
+def main_clean():
+ subprocess.check_call(["make", "clean"], cwd="plugins/pythonextensions")
+ subprocess.check_call(["python", "setup.py", "clean"], cwd="optional")
+
+
+if __name__ == "__main__":
+ if clean():
+ main_clean()
+ else:
+ main()
diff --git a/tools/license.py b/tools/license.py
new file mode 100644
index 0000000..9fdbc60
--- /dev/null
+++ b/tools/license.py
@@ -0,0 +1,110 @@
+# Add Qt license to all .cpp / .h
+# Usage: python license.py (run in project root directory)
+
+cpp = """/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Python Extensions Plugin for QtCreator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+"""
+
+py = """#############################################################################
+##
+## Copyright (C) 2018 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Python Extensions Plugin for QtCreator.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+"""
+
+import os, re
+
+
+def add_license(file):
+ if "license.py" in file:
+ print("Skipped {}".format(file))
+ return
+ license = ""
+ f_type = file.split(".")[-1]
+ if f_type == "h" or f_type == "cpp":
+ license = cpp
+ elif f_type == "py":
+ license = py
+ else:
+ return
+ f = open(file, "r")
+ body = f.read(-1)
+ f.close()
+ if re.match("^\\s*$", body) is not None:
+ print("Skipped empty file {}".format(file))
+ elif "Copyright (C) 2016 The Qt Company Ltd." in body:
+ body = body.replace("Copyright (C) 2016 The Qt Company Ltd.", "Copyright (C) 2018 The Qt Company Ltd.")
+ f = open(file, "w")
+ f.write(body)
+ f.close()
+ print("Fixed {}".format(file))
+ elif license not in body and "Copyright (C) 2018 The Qt Company Ltd." not in body:
+ body = license + body
+ f = open(file, "w")
+ f.write(body)
+ f.close()
+ print("Added license to {}".format(file))
+ else:
+ print("Skipped {}".format(file))
+
+for path in os.walk("."):
+ for filename in path[2]:
+ add_license(path[0] + "/" + filename)
diff --git a/tools/pyside2_config.py b/tools/pyside2_config.py
new file mode 100644
index 0000000..dc69d08
--- /dev/null
+++ b/tools/pyside2_config.py
@@ -0,0 +1,302 @@
+#############################################################################
+##
+## Copyright (C) 2018 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Python Extensions Plugin for QtCreator.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+import os, glob, re, sys, imp
+from distutils import sysconfig
+
+usage = """
+Utility to determine include/link options of PySide2 and Python for qmake
+
+Usage: pyside2_config.py [option]
+Options:
+ --python-include Print Python include path
+ --python-link Print Python link flags
+ --pyside2 Print PySide2 location
+ --pyside2-include Print PySide2 include paths
+ --pyside2-link Print PySide2 link flags
+ --pyside2-shared-libraries Print paths of PySide2 shared libraries (.so's, .dylib's, .dll's)
+ -a Print all
+ --help/-h Print this help
+"""
+
+def cleanPath(path):
+ return path if sys.platform != 'win32' else path.replace('\\', '/')
+
+def sharedLibrarySuffix():
+ if sys.platform == 'win32':
+ return 'lib'
+ elif sys.platform == 'darwin':
+ return 'dylib'
+ # Linux
+ else:
+ return 'so.*'
+
+def sharedLibraryGlobPattern():
+ glob = '*.' + sharedLibrarySuffix()
+ return glob if sys.platform == 'win32' else 'lib' + glob
+
+def filterPySide2SharedLibraries(list, only_shiboken=False):
+ def predicate(item):
+ basename = os.path.basename(item)
+ if 'shiboken' in basename or ('pyside2' in basename and not only_shiboken):
+ return True
+ return False
+ result = [item for item in list if predicate(item)]
+ return result
+
+# Return qmake link option for a library file name
+def linkOption(lib):
+ # On Linux:
+ # Since we cannot include symlinks with wheel packages
+ # we are using an absolute path for the libpyside and libshiboken
+ # libraries when compiling the project
+ baseName = os.path.basename(lib)
+ link = ' -l'
+ if sys.platform in ['linux', 'linux2']: # Linux: 'libfoo.so' -> '/absolute/path/libfoo.so'
+ link = lib
+ elif sys.platform in ['darwin']: # Darwin: 'libfoo.so' -> '-lfoo'
+ link += os.path.splitext(baseName[3:])[0]
+ else: # Windows: 'libfoo.dll' -> 'libfoo.dll'
+ link += os.path.splitext(baseName)[0]
+ return link
+
+# Locate PySide2 via package path
+def findPySide2():
+ for p in sys.path:
+ if 'site-' in p:
+ pyside2 = os.path.join(p, 'PySide2')
+ if os.path.exists(pyside2):
+ return cleanPath(os.path.realpath(pyside2))
+ return None
+
+# Return version as "3.5"
+def pythonVersion():
+ return str(sys.version_info[0]) + '.' + str(sys.version_info[1])
+
+def pythonInclude():
+ return sysconfig.get_python_inc()
+
+def pythonLinkQmake():
+ flags = pythonLinkData()
+ if sys.platform == 'win32':
+ libdir = flags['libdir']
+ # This will add the "~1" shortcut for directories that
+ # contain white spaces
+ # e.g.: "Program Files" to "Progra~1"
+ for d in libdir.split("\\"):
+ if " " in d:
+ libdir = libdir.replace(d, d.split(" ")[0][:-1]+"~1")
+ return '-L{} -l{}'.format(libdir, flags['lib'])
+ elif sys.platform == 'darwin':
+ return '-L{} -l{}'.format(flags['libdir'], flags['lib'])
+
+ else:
+ # Linux and anything else
+ return '-L{} -l{}'.format(flags['libdir'], flags['lib'])
+
+def pythonLinkCmake():
+ flags = pythonLinkData()
+ libdir = flags['libdir']
+ lib = re.sub(r'.dll$', '.lib', flags['lib'])
+ return '{};{}'.format(libdir, lib)
+
+def pythonLinkData():
+ # @TODO Fix to work with static builds of Python
+ libdir = sysconfig.get_config_var('LIBDIR')
+ if libdir is None:
+ libdir = os.path.abspath(os.path.join(
+ sysconfig.get_config_var('LIBDEST'), "..", "libs"))
+ version = pythonVersion()
+ version_no_dots = version.replace('.', '')
+
+ flags = {}
+ flags['libdir'] = libdir
+ if sys.platform == 'win32':
+ suffix = '_d' if any([tup[0].endswith('_d.pyd') for tup in imp.get_suffixes()]) else ''
+ flags['lib'] = 'python{}{}'.format(version_no_dots, suffix)
+
+ elif sys.platform == 'darwin':
+ flags['lib'] = 'python{}'.format(version)
+
+ # Linux and anything else
+ else:
+ if sys.version_info[0] < 3:
+ suffix = '_d' if any([tup[0].endswith('_d.so') for tup in imp.get_suffixes()]) else ''
+ flags['lib'] = 'python{}{}'.format(version, suffix)
+ else:
+ flags['lib'] = 'python{}{}'.format(version, sys.abiflags)
+
+ return flags
+
+def pyside2Include(only_shiboken=False):
+ pySide2 = findPySide2()
+ if pySide2 is None:
+ return None
+
+ includes = "{0}/include/shiboken2".format(pySide2)
+ if not only_shiboken:
+ includes = includes + " {0}/include/PySide2".format(pySide2)
+
+ return includes
+
+def pyside2Link():
+ pySide2 = findPySide2()
+ if pySide2 is None:
+ return None
+ link = "-L{}".format(pySide2)
+ glob_result = glob.glob(os.path.join(pySide2, sharedLibraryGlobPattern()))
+ for lib in filterPySide2SharedLibraries(glob_result):
+ link += ' '
+ link += linkOption(lib)
+ return link
+
+def pyside2SharedLibrariesData(only_shiboken=False):
+ pySide2 = findPySide2()
+ if pySide2 is None:
+ return None
+
+ glob_result = glob.glob(os.path.join(pySide2, sharedLibraryGlobPattern()))
+ filtered_libs = filterPySide2SharedLibraries(glob_result, only_shiboken)
+ libs = []
+ if sys.platform == 'win32':
+ for lib in filtered_libs:
+ libs.append(os.path.realpath(lib))
+ else:
+ for lib in filtered_libs:
+ libs.append(lib)
+ return libs
+
+def pyside2SharedLibraries():
+ libs = pyside2SharedLibrariesData()
+ if libs is None:
+ return None
+
+ if sys.platform == 'win32':
+ if not libs:
+ return ''
+ dlls = ''
+ for lib in libs:
+ dll = os.path.splitext(lib)[0] + '.dll'
+ dlls += dll + ' '
+
+ return dlls
+ else:
+ libs_string = ''
+ for lib in libs:
+ libs_string += lib + ' '
+ return libs_string
+
+def pyside2SharedLibrariesCmake(only_shiboken=False):
+ libs = pyside2SharedLibrariesData(only_shiboken)
+ result = ';'.join(libs)
+ return result
+
+option = sys.argv[1] if len(sys.argv) == 2 else '-a'
+if option == '-h' or option == '--help':
+ print(usage)
+ sys.exit(0)
+
+generic_error = (' Did you forget to activate your virtualenv? Or perhaps'
+ ' you forgot to build / install PySide2 into your currently active Python'
+ ' environment?')
+pyside2_error = 'Unable to locate PySide2.' + generic_error
+pyside2_libs_error = 'Unable to locate the PySide2 shared libraries.' + generic_error
+python_link_error = 'Unable to locate the Python library for linking.'
+
+if option == '--pyside2' or option == '-a':
+ pySide2 = findPySide2()
+ if pySide2 is None:
+ sys.exit(pyside2_error)
+ print(pySide2)
+
+if option == '--pyside2-link' or option == '-a':
+ l = pyside2Link()
+ if l is None:
+ sys.exit(pyside2_error)
+
+ print(l)
+
+if option == '--shiboken-include' or option == '-a':
+ i = pyside2Include(only_shiboken=True)
+ if i is None:
+ sys.exit(pyside2_error)
+ print(i)
+
+if option == '--pyside2-include' or option == '-a':
+ i = pyside2Include()
+ if i is None:
+ sys.exit(pyside2_error)
+ print(i)
+
+if option == '--python-include' or option == '-a':
+ i = pythonInclude()
+ if i is None:
+ sys.exit('Unable to locate the Python include headers directory.')
+ print(i)
+
+if option == '--python-link' or option == '-a':
+ l = pythonLinkQmake()
+ if l is None:
+ sys.exit(python_link_error)
+ print(l)
+
+if option == '--python-link-cmake' or option == '-a':
+ l = pythonLinkCmake()
+ if l is None:
+ sys.exit(python_link_error)
+ print(l)
+
+if option == '--pyside2-shared-libraries' or option == '-a':
+ l = pyside2SharedLibraries()
+ if l is None:
+ sys.exit(pyside2_libs_error)
+ print(l)
+
+if option == '--pyside2-shared-libraries-cmake' or option == '-a':
+ l = pyside2SharedLibrariesCmake()
+ if l is None:
+ sys.exit(pyside2_libs_error)
+ print(l)
+
+if option == '--shiboken-shared-libraries-cmake' or option == '-a':
+ l = pyside2SharedLibrariesCmake(only_shiboken=True)
+ if l is None:
+ sys.exit(pyside2_libs_error)
+ print(l)
diff --git a/tools/sanity.py b/tools/sanity.py
new file mode 100644
index 0000000..94215e8
--- /dev/null
+++ b/tools/sanity.py
@@ -0,0 +1,88 @@
+#############################################################################
+##
+## Copyright (C) 2018 The Qt Company Ltd.
+## Contact: http://www.qt.io/licensing/
+##
+## This file is part of the Python Extensions Plugin for QtCreator.
+##
+## $QT_BEGIN_LICENSE:BSD$
+## You may use this file under the terms of the BSD license as follows:
+##
+## "Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are
+## met:
+## * Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## * Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in
+## the documentation and/or other materials provided with the
+## distribution.
+## * Neither the name of The Qt Company Ltd nor the names of its
+## contributors may be used to endorse or promote products derived
+## from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+# This one fixes things for the sanity bot
+# Usage: Run with no options from project root
+
+import os
+
+
+total_notices = 0
+
+files_nl = ["cpp", "h", "py", "xml", "md", "pro", "pri", "txt", "gitignore"]
+
+def fix_nl(file):
+ f = open(file, "r")
+ body = f.read(-1)
+ f.close()
+ if len(body) > 0 and body[-1] != "\n":
+ body += "\n"
+ f = open(file, "w")
+ f.write(body)
+ f.close()
+ print("Fixed {}".format(file))
+ else:
+ print("Skipped {}".format(file))
+
+files_width = ["cpp", "h"]
+max_line_widths = {"cpp": 100, "h": 100}
+
+def notice_line_width(file):
+ f = open(file, "r")
+ lines = f.read(-1).split("\n")
+ f.close()
+ global total_notices
+ for i in range(len(lines)):
+ if len(lines[i]) > max_line_widths[file_type(file)]:
+ print("Notice: Max line width exceeded in file '{0}', line {1}".format(file, i))
+ total_notices += 1
+
+def file_type(file):
+ return file.split(".")[-1]
+
+for path in os.walk("."):
+ for filename in path[2]:
+ if file_type(filename) in files_nl:
+ fix_nl(path[0] + "/" + filename)
+ if file_type(filename) in files_width:
+ notice_line_width(path[0] + "/" + filename)
+
+if total_notices > 0:
+ print("({} notices)".format(total_notices))