美文网首页C语言&嵌入式Qt 慢慢学
初识 Qt Creator 插件开发

初识 Qt Creator 插件开发

作者: AbeirL | 来源:发表于2019-12-13 14:38 被阅读0次

    本文通过几个示例,来学习如何开发 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 就可以在 工具 菜单中找到我们的插件。

    相关文章

      网友评论

        本文标题:初识 Qt Creator 插件开发

        本文链接:https://www.haomeiwen.com/subject/fhatnctx.html