Вот небольшая демка того, что получилось.
Как это работает
Идея для этого плагина достаточно проста — при инициализации, в функции bool initialize(const QStringList &arguments, QString *errorString) мы ищем в папке плагина специальные «скриптовые» плагины. Это JavaScript файлы с расширением *.gala, в которых могут быть следующие конструкции:
- Обязательная функция
initialize(), которая вызывается сразу после загрузки скрипта. - Опциональная функция
extensionsInitialized(), которая будет вызвана в соответствующей функции основного плагина (когда все плагины загружены). - Опциональная функция
aboutToShutdown(), которая будет вызвана перед закрытием QtCreator’а. - Опциональная целочисленная переменная
galaPluginOrder, которая используется для переупорядочивания скриптовых плагинов при загрузке. - Опциональная булева переменная
galaPluginDisable, с помощью которой можно игнорировать скриптовые плагины. - Опциональная булева переменная
galaPluginTrace, которая позволяет логировать все вызовы оберток (полезно при отладке).
Вот пример скриптового плагина 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"); }
Кроме того доступны еще 4 скриптовых плагина:
- CloseAllBttn — добавляет на панель режимов кнопку закрытия всех документов
- CloseAllToolMenu — добавляет пункт меню Tools->MyPlugin->Close All
- Clock — добавляет анимированные цифровые часы на панель режимов
- RelaxTracker — добавляет специальный QML объект, который через определенные промежутки времени меняет зеленый прямоугольник на красный, с моргающей надписью «Break» (идея из оригинальной статьи)
- Weather — добавляет краткую информацию по погоде на панель режимов
Что бы воспользоваться QtCreator API из скриптинга, я создал обёртки вокруг необходимых классов (таких как Core::ICore, Core::Command, Core::ActionManager и других). Процесс создания обёрток почти механический: создаем класс наследник QObject, передаём и сохраняем в нём указатель на класс из QtCreator API, и все public методы исходного класса перевызываем в обёртке в секции public slots.
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; };
Небольшая сложность с функциями, возвращающими список указателей на объекты: их нужно упаковывать в JavaScript Array значения. Вот пример реализации функции editorManager.documents():
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; }
На данный момент в окружении JavaScript/QML можно пользоваться следующими глобальными объектами:
- core — представляет
Core::ICore::instance() - messageManager — представляет
Core::MessageManager::instance() - actionManager — представляет
Core::ActionManager::instance() - editorManager — представляет
Core::EditorManager:instance() - modeManager — представляет
Core::ModeManager::instance() - galaAPI — служит точкой доступа к вспомогательным полезным функциям
В ходе написания плагина я столкнулся со следующими проблемами.
Если слот возвращает указатель на QObject наследованный объект, то JavaScript окружение берёт владение этим объектом на себя. Это полезно, если слот создает обёртку и возвращает её в JS код. Например
GCommand *command(QString id) { return new GCommand(m_jsEngine, m_owner->command(str2id(id))); }
Если же слот возвращает внутренний объект QtCreator’а, то владеть им JS окружение не должно. В таких случаях нужно возвращать не указатель на объект, а QJSValue, в которое и завернуть указатель.
// возвращает QMenu* QJSValue menu() const { return m_jsEngine->toScriptValue(static_cast<QObject*>(m_owner->menu())); }
Еще одна проблема при «пробросе» сигналов в JS заключается в параметрах по умолчанию и одинаковых именах методов.
Когда некоторый метод foo вызывается из JS, то в мета-системе объекта ищется метод с таким именем и вызывается первый найденный. Никакого сопоставления по количеству (и, тем более, типам) параметров нет. При этом, если сигнал имеет параметры по умолчанию, moc генерирует несколько мета-методов. Например, для сигнала void foo(int a, int b = 0, int c = 1); будут сгенерированы три мета-метода
void foo(int a); void foo(int a, int b); void foo(int a, int b, int c);
Причём именно в таком порядке — сначала самая короткая версия. Таким образом параметры по умолчанию в JS использовать не получается и необходимо передавать все параметры вручную. А методы с одинаковыми именами делать уникальными.
Заключение
Данный плагин позволяет расширять функциональность QtCreator’а очень легко. Я вижу основное использование скриптовых плагинов в создании визуальных элементов на пенели режимов, создание тулбаров и пунктов меню для часто используемых или специфичных команд. Возможность встраивать QML объекты даёт широкие возможности. Можно легко создать QML view, который будет следить за каким-либо web сервисом, будь то погода, курс валюты, новые статьи на любимом ресурсе, счёт в спортивных соревнованиях, статус сборки и т.п. Я не профессионал по QML, но в сети можно найти много интересных примеров.
Если у вас появилась интересная идея или, еще лучше, вы реализовали интересный скриптовый плагин, прошу поделиться с сообществом. Я с удовольствием добавлю его в свой проект.
Так же прошу помощи в компиляции плагина под разные платформы. С Windows беда, так же легко собирать, как и под Linux не получается. Под MacOS вообще нет возможности собирать.
P.S. для тех, кто прочитал до конца, ещё одно видео:
ссылка на оригинал статьи http://habrahabr.ru/post/223143/
Добавить комментарий