初识 Qt Creator 插件开发

本文通过几个示例,来学习如何开发 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 提供的一些函数。

当插件描述文件读取、并且所有依赖都满足之后,插件将开始进行加载。这一步分为三个阶段:

  1. 所有插件的库文件按照依赖树从根到叶子的顺序进行加载。
  2. 按照依赖树从根到叶子的顺序依次调用每个插件的 IPlugin::initialize() 函数。
  3. 按照依赖树从叶子到根的顺序依次调用每个插件的 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 就可以在 工具 菜单中找到我们的插件。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容

  • 1.Qt概述 1.1 什么是Qt Qt是一个跨平台的C++图形用户界面应用程序框架。它为应用程序开发者提供建立艺术...
    你的社交帐号昵阅读 8,690评论 0 10
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,092评论 1 32
  • Qt是属于一个跨平台的GUI开发软件,支持的平台有Unix、Linux、Windows/WinCE、IOS等。 Q...
    一叶之界阅读 8,150评论 0 17
  • 一池塘的荷花多少天的时候会开满一半? 你以为是第15天,其实是在第29天的时候,当地30天来临时,满池的荷花都开了...
    多则惑少则得阅读 162评论 0 0
  • 又一年了,新年快乐哇!你会更好的!我会在未来中做到独挡一面的!想做最最真诚的自己!赵钶的歌曲《有人》听的我感慨万千...
    茯苓_fuling阅读 38评论 0 1