GalaPlugin - JS / QML plugin for QtCreator
After reading the post Using the QtCreator + 2 modes panel of the plugin , I had the idea to try to create a plugin that can extend the functionality of QtCreator using JavaScript and QML. The GalaPlugin project has appeared .
Here is a small demo of what happened.
The idea for this plugin is quite simple - upon initialization, in a function,
Here is an example SaveAllBttn.gala script plugin that adds a Save All button to the mode bar
In addition, 4 more script plugins are available:
To take advantage of QtCreator API scripting, I created a wrapper around the required classes (such as
A little complexity with functions that return a list of pointers to objects: they need to be packed in JavaScript Array values. Here is an example implementation of a function
Currently, in the JavaScript / QML environment, you can use the following global objects:
While writing 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 this object. This is useful if the slot creates a wrapper and returns it to the JS code. for instance
If the slot returns an internal QtCreator object, then the environment should not own it. In such cases, you need to return not a pointer to the object, but a QJSValue, into which you wrap the pointer.
Another problem with signal forwarding in JS is the default parameters and the same method names.
When a method
And it is in this order - first the shortest version. Thus, it is not possible to use the default parameters in JS and you must pass all the parameters manually. And make methods with the same name unique.
This plugin allows you to extend the functionality of QtCreator'a very easily. I see the main use of script plug-ins in creating visual elements on the mode panel, creating toolbars and menu items for frequently used or specific commands. The ability to embed QML objects provides great opportunities. You can easily create a QML view that will monitor any web service, be it weather, exchange rates, new articles on your favorite resource, account in sports competitions, build status, etc. I am not a QML professional, but there are many interesting examples on the net.
If you have an interesting idea or, even better, you have implemented an interesting script plugin, please share with the community. I will be happy to add it to my project.
I also ask for help in compiling the plugin for different platforms. With Windows, the trouble is as easy to build as it does with Linux. Under MacOS, there is no way to build at all.
PS for those who read to the end, another video:
Here is a small demo of what happened.
How it works
The idea for this plugin is quite simple - upon initialization, in a function,
bool initialize(const QStringList &arguments, QString *errorString)
we look for special “script” plugins in the plugin folder. These are JavaScript files with the * .gala extension, which may contain the following constructs:- Mandatory function
initialize()
that is called immediately after loading the script. - An optional function
extensionsInitialized()
that will be called in the corresponding function of the main plugin (when all plugins are loaded). - An optional function
aboutToShutdown()
that will be called before closing QtCreator. - An optional integer variable
galaPluginOrder
that is used to reorder script plugins at boot time. - An optional boolean
galaPluginDisable
that can be used to ignore script plugins. - An optional Boolean variable
galaPluginTrace
that allows you to log all wrapper calls (useful for debugging).
Here is an example SaveAllBttn.gala script plugin that adds a Save All button to the mode bar
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:
- CloseAllBttn - adds a close button for all documents to the mode bar
- CloseAllToolMenu - Adds the menu item Tools-> MyPlugin-> Close All
- Clock - adds an animated digital clock to the mode bar
- RelaxTracker - adds a special QML object that, at certain intervals, changes the green rectangle to red, with the blinking inscription “Break” (idea from the original article)
- Weather - adds brief weather information to the mode bar
To take advantage of QtCreator API scripting, I created a wrapper around the required classes (such as
Core::ICore
, Core::Command
, Core::ActionManager
and others). The process of creating wrappers is almost mechanical: we create a QObject descendant class, pass and store the pointer to the class from the QtCreator API in it, and re-call all public methods of the source class in the 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::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;
};
A little complexity with functions that return a list of pointers to objects: they need to be packed in JavaScript Array values. Here is an example implementation of a function
editorManager.documents()
:QJSValue documents()
{
QList 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, in the JavaScript / QML environment, you can use the following global objects:
- core - represents
Core::ICore::instance()
- messageManager - represents
Core::MessageManager::instance()
- actionManager - represents
Core::ActionManager::instance()
- editorManager - presents
Core::EditorManager:instance()
- modeManager - represents
Core::ModeManager::instance()
- galaAPI - serves as an access point to auxiliary useful functions
While writing 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 this object. This is useful if the slot creates a wrapper and returns it to the JS code. for instance
GCommand *command(QString id) { return new GCommand(m_jsEngine, m_owner->command(str2id(id))); }
If the slot returns an internal QtCreator object, then the environment should not own it. In such cases, you need to return not a pointer to the object, but a QJSValue, into which you wrap the pointer.
// возвращает QMenu*
QJSValue menu() const { return m_jsEngine->toScriptValue(static_cast(m_owner->menu())); }
Another problem with signal forwarding in JS is the default parameters and the same method names.
When a method
foo
is called from JS, a method with that name is searched in the meta-system of the object and the first one is called. There is no comparison in the number (and, especially, types) of parameters. In this case, if the signal has default parameters, moc generates several meta-methods. For example, void foo(int a, int b = 0, int c = 1);
three meta methods will be generated for a signalvoid foo(int a);
void foo(int a, int b);
void foo(int a, int b, int c);
And it is in this order - first the shortest version. Thus, it is not possible to use the default parameters in JS and you must pass all the parameters manually. And make methods with the same name unique.
Conclusion
This plugin allows you to extend the functionality of QtCreator'a very easily. I see the main use of script plug-ins in creating visual elements on the mode panel, creating toolbars and menu items for frequently used or specific commands. The ability to embed QML objects provides great opportunities. You can easily create a QML view that will monitor any web service, be it weather, exchange rates, new articles on your favorite resource, account in sports competitions, build status, etc. I am not a QML professional, but there are many interesting examples on the net.
If you have an interesting idea or, even better, you have implemented an interesting script plugin, please share with the community. I will be happy to add it to my project.
I also ask for help in compiling the plugin for different platforms. With Windows, the trouble is as easy to build as it does with Linux. Under MacOS, there is no way to build at all.
PS for those who read to the end, another video: