本文通过几个示例,来学习如何开发 Qt Creator 插件。
首先,先确定几个目录的位置:
- Qt 安装目录 -
/usr/software/Qt5.13.1
- Qt Creator 安装目录 -
/data/qt-creator-opensource-src-4.10.1
目录的位置不是固定的,可以按照自己的习惯选择安装时的目录。
准备工作
源码下载
注意,安装 qt 时,请最大安装,因为编译 qt creator 需要依赖所有的 qt 库,非最大安装会出现编译错误
首先,需要安装 qt,建议下载离线安装包,本示例使用 Qt 5.13.1
http://download.qt.io/official_releases/qt/5.13/5.13.1/qt-opensource-linux-x64-5.13.1.run
或者访问下载目录选择需要的版本下载
http://download.qt.io/official_releases/qt/
还需下载 qt creator 源码,本示例使用 Qt Creator 4.10.1
http://download.qt.io/official_releases/qtcreator/4.10/4.10.1/qt-creator-opensource-src-4.10.1.tar.gz
或者访问下载目录选择需要的版本下载
http://download.qt.io/official_releases/qtcreator/
编译
注意,编译时间较长,请耐心等待
运行一下命令执行编译
# 进入 qt creator 安装目录
$ cd /data/qt-creator-opensource-src-4.10.1
# 运行 qmake
$ /usr/software/Qt5.12.5/5.12.5/gcc_64/bin/qmake -r
# 开始编译
$ make -j8
这里执行 make -j8
,表示使用8核运行编译,视编译机器的cpu数决定 -jN
参数中N的值,最大与cpu核心数相等,可缩短编译时间
编译完成后会在 qt creator 安装目录中的 bin 目录中生成可执行文件,运行 ./bin/qtcreator
启动ide。
创建插件项目
在 Qt Creator 中选择 文件 > 新建文件或项目 > Library > Qt Creator 插件,之后根据项目向导完成插件项目的创建。
注意,向导第三步 插件信息 页面中的 Qt Creator源文件 和 Qt Creator构建 处需要选择 Qt Creaotr 安装目录,这里选择 /data/qt-creator-opensource-src-4.10.1
项目创建完成后,目录结构如下:
详解
接下来将分析主要的项目文件
Demo1.json.in
该定义了插件的信息,插件编译时会生成名为 Demo1.json 的文件。
这里需要注意一下,修改
Demo1.json.in
文件,将该文件的中字符串key和value前后的双引号"
替换成\"
,否则插件可能无法运行。
{
\"Name\" : \"Demo1\",
\"Version\" : \"0.0.1\",
\"CompatVersion\" : \"0.0.1\",
\"Vendor\" : \"abeir\",
\"Copyright\" : \"(C) SyberOS\",
\"License\" : \"Put your license information here\",
\"Description\" : \"Put a short description of your plugin here\",
\"Url\" : \"http://www.syberos.com\",
$$dependencyList
}
demo1.pro
DEFINES += DEMO1_LIBRARY
SOURCES += demo1plugin.cpp
HEADERS += demo1plugin.h demo1_global.h demo1constants.h
isEmpty(IDE_SOURCE_TREE): IDE_SOURCE_TREE = $$(QTC_SOURCE)
isEmpty(IDE_SOURCE_TREE): IDE_SOURCE_TREE = "/data/qt-creator-opensource-src-4.10.1"
isEmpty(IDE_BUILD_TREE): IDE_BUILD_TREE = $$(QTC_BUILD)
isEmpty(IDE_BUILD_TREE): IDE_BUILD_TREE = "/data/qt-creator-opensource-src-4.10.1"
QTC_PLUGIN_NAME = Demo1
QTC_LIB_DEPENDS += # nothing here at this time
QTC_PLUGIN_DEPENDS += coreplugin
QTC_PLUGIN_RECOMMENDS += # optional plugin dependencies. nothing here at this time
include($$IDE_SOURCE_TREE/src/qtcreatorplugin.pri)
QTC_SOURCE 和 QTC_BUILD 变量指明 Qt Creator 的源码目录和构建目录。可以通过设置环境变量的方式修改至其他路径。由于之前创建插件项目时,指定了源码目录和构建目录,在这里都指向了 /data/qt-creator-opensource-src-4.10.1
。
QTC_LIB_DEPENDS 指定依赖的库。
QTC_PLUGIN_DEPENDS 指定依赖的其他插件。
demo1plugin.h
#ifndef DEMO1_H
#define DEMO1_H
#include "demo1_global.h"
#include <extensionsystem/iplugin.h>
namespace Demo1 {
namespace Internal {
class Demo1Plugin : public ExtensionSystem::IPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Demo1.json")
public:
Demo1Plugin();
~Demo1Plugin() override;
bool initialize(const QStringList &arguments, QString *errorString) override;
void extensionsInitialized() override;
ShutdownFlag aboutToShutdown() override;
private:
void triggerAction();
};
} // namespace Internal
} // namespace Demo1
#endif // DEMO1_H
IPlugin 类是插件的基类,所有的插件都需要继承该类,并实现其中的虚函数。
IPlugin 提供了一系列函数,用于插件加载到一定阶段时进行回调。下面我们按照插件加载的顺序介绍 IPlugin 提供的一些函数。
当插件描述文件读取、并且所有依赖都满足之后,插件将开始进行加载。这一步分为三个阶段:
- 所有插件的库文件按照依赖树从根到叶子的顺序进行加载。
- 按照依赖树从根到叶子的顺序依次调用每个插件的 IPlugin::initialize() 函数。
- 按照依赖树从叶子到根的顺序依次调用每个插件的 IPlugin::extensionsInitialized() 函数。
virtual bool initialize(const QStringList &arguments, QString *errorString) = 0;
该函数会在插件加载完成,并且创建了插件对象之后调用。该函数返回值是bool类型,当插件初始化成功,返回 true;否则,由 errorString 参数将错误信息返出。当前插件的 initialize() 函数会在所有依赖的插件都调用了 initialize() 函数之后被调用 。如果插件需要共享一些对象,就应该将这些共享对象放在这个函数中。
virtual void extensionsInitialized() = 0;
该函数在 initialize() 函数调用完毕、并且所依赖插件的 extensionsInitialized() 函数调用完毕之后被调用。当运行到这一阶段时,插件所依赖的其它插件都已经初始化完毕。这也暗示着,该插件所依赖的各个插件提供的可被共享的对象都已经创建完毕,可以正常使用了。
virtual bool delayedInitialize() { return false; }
该函数会在 extensionsInitialized() 函数调用完成,并且所依赖插件的 delayedInitialize() 函数也调用完成之后才被调用。delayedInitialize() 函数会在程序运行之后才被调用,并且距离程序启动有几个毫秒的间隔。为避免不必要的延迟,插件对该函数的实现应该尽快返回。该函数的意义在于,有些插件可能需要进行一些重要的启动工作;这些工作虽然不必在启动时直接完成,但也应该在程序启动之后的较短时间内完成。该函数默认返回false,即不需要延迟初始化。
virtual ShutdownFlag aboutToShutdown() { return SynchronousShutdown; }
signals:
void asynchronousShutdownFinished();
aboutToShutdown() 函数应该用于与其它插件断开连接、隐藏所有 UI、优化关闭操作,会以插件初始化的相反顺序调用,即先调用当前插件的 aboutToShutdown() 函数,再依次调用依赖插件的。如果插件需要延迟真正的关闭,例如,需要等待外部进程执行完毕,以便自己完全关闭,则应该返回 AsynchronousShutdown,这么做的话会进入主事件循环,等待所有返回了 AsynchronousShutdown 的插件都发出了 asynchronousShutdownFinished() 信号之后,再执行相关操作。该函数默认返回 SynchronousShutdown,即不等待其它插件关闭。
下面,我们再回过头来看看 demo1plugin.h
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Demo1.json")
Q_PLUGIN_METADATA 这个宏用于声明插件的元数据。当实例化插件对象时,这些元数据会作为该对象的一部分。这个宏需要声明一个 IID 属性,用于标识对象实现的接口;还需要一个文件的引用FILE属性,该文件包含了插件的元数据。
使用这个宏的类必须有无参数的构造函数。在这里,Qt Creator 规定其插件的 IID 必须是 org.qt-project.Qt.QtCreatorPlugin。FILE指向了 Demo1.json 文件,但是项目里只有 Demo1.json.in,这是由于编译时,会使用 Demo1.json.in 生成 Demo1.json 文件,我们可以在编译目录下找到该文件。
demo1plugin.cpp
#include "demo1plugin.h"
#include "demo1constants.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 <QAction>
#include <QMessageBox>
#include <QMainWindow>
#include <QMenu>
namespace Demo1 {
namespace Internal {
Demo1Plugin::Demo1Plugin()
{
}
Demo1Plugin::~Demo1Plugin()
{
}
bool Demo1Plugin::initialize(const QStringList &arguments, QString *errorString)
{
Q_UNUSED(arguments)
Q_UNUSED(errorString)
auto action = new QAction(tr("Demo1 Action"), this);
Core::Command *cmd = Core::ActionManager::registerAction(action, Constants::ACTION_ID,
Core::Context(Core::Constants::C_GLOBAL));
cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+Alt+Meta+A")));
connect(action, &QAction::triggered, this, &Demo1Plugin::triggerAction);
Core::ActionContainer *menu = Core::ActionManager::createMenu(Constants::MENU_ID);
menu->menu()->setTitle(tr("Demo1"));
menu->addAction(cmd);
Core::ActionManager::actionContainer(Core::Constants::M_TOOLS)->addMenu(menu);
return true;
}
void Demo1Plugin::extensionsInitialized()
{
}
ExtensionSystem::IPlugin::ShutdownFlag Demo1Plugin::aboutToShutdown()
{
return SynchronousShutdown;
}
void Demo1Plugin::triggerAction()
{
QMessageBox::information(Core::ICore::mainWindow(),
tr("Action Triggered"),
tr("This is an action from Demo1."));
}
} // namespace Internal
} // namespace Demo1
接下来分析一下 demo1plugin.cpp 都做了些什么。
auto action = new QAction(tr("Demo1 Action"), this);
Core::Command *cmd = Core::ActionManager::registerAction(action, Constants::ACTION_ID,
Core::Context(Core::Constants::C_GLOBAL));
cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+Alt+Meta+A")));
connect(action, &QAction::triggered, this, &Demo1Plugin::triggerAction);
这段代码定义了一个动作 Demo1 Action,并为该动作设置了快捷键 Ctrl+Alt+Meta+A,最后再将 triggered 信号绑定至本类中的 triggerAction 槽函数上。triggered 信号是在用户触发了动作,比如,单击或使用快捷键时发送。
Core::ActionContainer *menu = Core::ActionManager::createMenu(Constants::MENU_ID);
menu->menu()->setTitle(tr("Demo1"));
menu->addAction(cmd);
Core::ActionManager::actionContainer(Core::Constants::M_TOOLS)->addMenu(menu);
这段代码创建了一个菜单,并设置了菜单名为 Demo1,最后再将菜单添加到 Qt Creator 的 工具 菜单中。
Qt Creator 提供了一些主菜单允许我们将自己创建的菜单添加进去:
- Core::Constants::M_FILE - 文件
- Core::Constants::M_EDIT - 编辑
- Core::Constants::M_EDIT_ADVANCED - 编辑 > Advanced
- Core::Constants::M_TOOLS - 工具
- Core::Constants::M_TOOLS_EXTERNAL - 工具 > 外部
- Core::Constants::M_WINDOW - 控件
- Core::Constants::M_WINDOW_MODESTYLES - 控件 > Mode Selector Style
- Core::Constants::M_WINDOW_VIEWS - 控件 > 视图
- Core::Constants::M_HELP - 帮助
void Demo1Plugin::triggerAction()
{
QMessageBox::information(Core::ICore::mainWindow(),
tr("Action Triggered"),
tr("This is an action from Demo1."));
}
这段代码是当我们点击 工具 > Demo1 > Demo1 Action 时,触发一个弹出框。
最后,运行 构建项目,Demo1 插件将被编译成名为 libDemo1.so
的文件并安装至 /data/qt-creator-opensource-src-4.10.1/lib/qtcreator/plugins
运行
$ /data/qt-creator-opensource-src-4.10.1/bin/qtcreator
启动 Qt Creator 就可以在 工具 菜单中找到我们的插件。