📜 ⬆️ ⬇️

GalaPlugin - JS / QML plugin for QtCreator

After reading the post Using the QtCreator + 2 plugin mode panel , I had an idea to try to create a plugin that can extend QtCreator’s functionality using JavaScript and QML. There was a project GalaPlugin .

Here is a small demo of what happened.



How it works


The idea for this plug-in is quite simple - when initializing, in the bool initialize(const QStringList &arguments, QString *errorString) function bool initialize(const QStringList &arguments, QString *errorString) we are looking for special “script” plug-ins in the plug-in folder. These are JavaScript files with the * .gala extension, which can contain the following constructs:
')
  1. Mandatory function initialize() , which is called immediately after the script is loaded.
  2. An optional function extensionsInitialized() , which will be called in the corresponding function of the main plugin (when all the plugins are loaded).
  3. An optional aboutToShutdown() function that will be called before QtCreator is closed.
  4. An optional integer variable galaPluginOrder , which is used to reorder script plugins when loaded.
  5. An optional boolean variable galaPluginDisable , with which you can ignore script plugins.
  6. An optional boolean variable galaPluginTrace that allows you to log all galaPluginTrace calls (useful for debugging).

Here is an example of the script saveAllBttn.gala plugin, which adds the “Save All” button to the mode panel
SaveAllBttn.gala
 var galaPluginOrder = 1; var galaPluginTrace = true; function saveAllAction() { var docs = editorManager.documents(); for (var i = 0; i < docs.length; ++i) { var doc = docs[i]; if (doc.isModified()) { doc.save("", false); } } } function createSaveAllButton() { var bttn = galaAPI.createQObject("QPushButton", modeManager); bttn.flat = true; bttn.text = "Save All"; bttn.focusPolicy = 0; bttn.styleSheet = "QPushButton {color: white; }"; // disable button minimum width bttn.sizePolicy = galaAPI.sizePolicy(13, 0, 1); bttn.clicked.connect(saveAllAction); return bttn; } function initialize() { modeManager.addWidget(createSaveAllButton()); galaAPI.debug("Success initialize"); } function extensionsInitialized() { galaAPI.debug("Success extensionsInitialized"); } function aboutToShutdown() { galaAPI.debug("Success aboutToShutdown"); } 


In addition, 4 more script plugins are available:

To use the QtCreator API from scripting, I created wrappers around the necessary classes (such as Core::ICore , Core::Command , Core::ActionManager and others). The process of creating wrappers is almost mechanical: we create a class that inherits QObject, passes and stores in it a pointer to a class from the QtCreator API, and re-calls all public methods of the original class in a wrapper in the public slots section.

Here is a small example:
 class GModeManager : public GWrapper { Q_OBJECT public: GModeManager(QJSEngine* jsEngine) : GWrapper(jsEngine), m_owner(qobject_cast<Core::ModeManager*>(Core::ModeManager::instance())) { Q_ASSERT(m_owner); } ~GModeManager() {} Core::ModeManager* owner1() { return m_owner; } public slots: QJSValue owner() { return m_jsEngine->toScriptValue(m_owner); } QJSValue currentMode() { return m_jsEngine->toScriptValue(m_owner->currentMode()); } QJSValue mode(QString id) { return m_jsEngine->toScriptValue(m_owner->mode(str2id(id))); } void addAction(QAction *action, int priority) { m_owner->addAction(action, priority); } void addProjectSelector(QAction *action) { m_owner->addProjectSelector(action); } void addWidget(QWidget *widget) { m_owner->addWidget(widget); } void activateMode(QString id) { m_owner->activateMode(str2id(id)); } void setFocusToCurrentMode() { m_owner->setFocusToCurrentMode(); } bool isModeSelectorVisible() { return m_owner->isModeSelectorVisible(); } void setModeSelectorVisible(bool visible) { m_owner->setModeSelectorVisible(visible); } private: Core::ModeManager* m_owner; }; 


Small complexity with the functions returning the list of pointers to objects: they need to be packaged in JavaScript Array values. Here is an example of implementing the editorManager.documents() function:

 QJSValue documents() { QList<Core::IDocument *> documents = m_owner->documentModel()->openedDocuments(); QJSValue array = m_jsEngine->newArray(documents.size()); for (quint32 i = 0; i < (quint32)documents.size(); ++i) { array.setProperty(i, m_jsEngine->newQObject(new GDocument(m_jsEngine, documents[i]))); } return array; } 


Currently, the following global objects can be used in the JavaScript / QML environment:

  1. core - represents Core::ICore::instance()
  2. messageManager - represents Core::MessageManager::instance()
  3. actionManager - represents Core::ActionManager::instance()
  4. editorManager - represents Core::EditorManager:instance()
  5. modeManager - represents Core::ModeManager::instance()
  6. galaAPI - serves as an access point to auxiliary useful functions.

During the writing of the plugin, I encountered the following problems.
If the slot returns a pointer to a QObject inherited object, then the JavaScript environment takes ownership of the object. This is useful if the slot creates a wrapper and returns it to the JS code. for example

 GCommand *command(QString id) { return new GCommand(m_jsEngine, m_owner->command(str2id(id))); } 

If the slot returns an internal QtCreator object, then the JS environment should not own it. In such cases, you need to return not a pointer to the object, but QJSValue, in which and wrap the pointer.

 //  QMenu* QJSValue menu() const { return m_jsEngine->toScriptValue(static_cast<QObject*>(m_owner->menu())); } 


Another problem with “forwarding” signals in JS is the default parameters and the same method names.

When some foo method is called from JS, a method with that name is searched for in the object's meta-system and the first one found is called. There is no comparison by the number (and, especially, types) of parameters. At the same time, if the signal has default parameters, moc generates several meta-methods. For example, for the signal void foo(int a, int b = 0, int c = 1); three meta methods will be generated
 void foo(int a); void foo(int a, int b); void foo(int a, int b, int c); 

And in that order - first the shortest version. Thus, the default settings in JS cannot be used and it is necessary to transfer all the parameters manually. And to make methods with identical names unique.

Conclusion


This plugin allows you to extend the functionality of QtCreator very easily. I see the main use of script plug-ins in creating visual elements on the mode pane, creating toolbars and menu items for frequently used or specific commands. The ability to embed QML objects provides ample opportunities. You can easily create a QML view that will monitor any web service, whether it is weather, currency exchange rate, new articles on your favorite resource, an account in sports competitions, build status, etc. I am not a QML professional, but many interesting examples can be found online.

If you have an interesting idea or, even better, you have implemented an interesting script plugin, please share it with the community. I will gladly add it to my project.

I also ask for help in compiling the plugin for different platforms. With Windows trouble, as easy to build, as under Linux does not work. Under MacOS there is no possibility to collect at all.

PS for those who read to the end, another video:

Source: https://habr.com/ru/post/223143/


All Articles