本文通過幾個示例,來學(xué)習(xí)如何開發(fā) Qt Creator 插件。
首先,先確定幾個目錄的位置:
- Qt 安裝目錄 -
/usr/software/Qt5.13.1 - Qt Creator 安裝目錄 -
/data/qt-creator-opensource-src-4.10.1
目錄的位置不是固定的,可以按照自己的習(xí)慣選擇安裝時的目錄。
準(zhǔn)備工作
源碼下載
注意,安裝 qt 時,請最大安裝,因?yàn)榫幾g qt creator 需要依賴所有的 qt 庫,非最大安裝會出現(xiàn)編譯錯誤
首先,需要安裝 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/
編譯
注意,編譯時間較長,請耐心等待
運(yùn)行一下命令執(zhí)行編譯
# 進(jìn)入 qt creator 安裝目錄
$ cd /data/qt-creator-opensource-src-4.10.1
# 運(yùn)行 qmake
$ /usr/software/Qt5.12.5/5.12.5/gcc_64/bin/qmake -r
# 開始編譯
$ make -j8
這里執(zhí)行 make -j8,表示使用8核運(yùn)行編譯,視編譯機(jī)器的cpu數(shù)決定 -jN 參數(shù)中N的值,最大與cpu核心數(shù)相等,可縮短編譯時間
編譯完成后會在 qt creator 安裝目錄中的 bin 目錄中生成可執(zhí)行文件,運(yùn)行 ./bin/qtcreator 啟動ide。
創(chuàng)建插件項(xiàng)目
在 Qt Creator 中選擇 文件 > 新建文件或項(xiàng)目 > Library > Qt Creator 插件,之后根據(jù)項(xiàng)目向?qū)瓿刹寮?xiàng)目的創(chuàng)建。
注意,向?qū)У谌?插件信息 頁面中的 Qt Creator源文件 和 Qt Creator構(gòu)建 處需要選擇 Qt Creaotr 安裝目錄,這里選擇 /data/qt-creator-opensource-src-4.10.1

項(xiàng)目創(chuàng)建完成后,目錄結(jié)構(gòu)如下:


詳解
接下來將分析主要的項(xiàng)目文件
Demo1.json.in
該定義了插件的信息,插件編譯時會生成名為 Demo1.json 的文件。
這里需要注意一下,修改
Demo1.json.in文件,將該文件的中字符串key和value前后的雙引號"替換成\",否則插件可能無法運(yùn)行。
{
\"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 的源碼目錄和構(gòu)建目錄。可以通過設(shè)置環(huán)境變量的方式修改至其他路徑。由于之前創(chuàng)建插件項(xiàng)目時,指定了源碼目錄和構(gòu)建目錄,在這里都指向了 /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 類是插件的基類,所有的插件都需要繼承該類,并實(shí)現(xiàn)其中的虛函數(shù)。
IPlugin 提供了一系列函數(shù),用于插件加載到一定階段時進(jìn)行回調(diào)。下面我們按照插件加載的順序介紹 IPlugin 提供的一些函數(shù)。
當(dāng)插件描述文件讀取、并且所有依賴都滿足之后,插件將開始進(jìn)行加載。這一步分為三個階段:
- 所有插件的庫文件按照依賴樹從根到葉子的順序進(jìn)行加載。
- 按照依賴樹從根到葉子的順序依次調(diào)用每個插件的 IPlugin::initialize() 函數(shù)。
- 按照依賴樹從葉子到根的順序依次調(diào)用每個插件的 IPlugin::extensionsInitialized() 函數(shù)。
virtual bool initialize(const QStringList &arguments, QString *errorString) = 0;
該函數(shù)會在插件加載完成,并且創(chuàng)建了插件對象之后調(diào)用。該函數(shù)返回值是bool類型,當(dāng)插件初始化成功,返回 true;否則,由 errorString 參數(shù)將錯誤信息返出。當(dāng)前插件的 initialize() 函數(shù)會在所有依賴的插件都調(diào)用了 initialize() 函數(shù)之后被調(diào)用 。如果插件需要共享一些對象,就應(yīng)該將這些共享對象放在這個函數(shù)中。
virtual void extensionsInitialized() = 0;
該函數(shù)在 initialize() 函數(shù)調(diào)用完畢、并且所依賴插件的 extensionsInitialized() 函數(shù)調(diào)用完畢之后被調(diào)用。當(dāng)運(yùn)行到這一階段時,插件所依賴的其它插件都已經(jīng)初始化完畢。這也暗示著,該插件所依賴的各個插件提供的可被共享的對象都已經(jīng)創(chuàng)建完畢,可以正常使用了。
virtual bool delayedInitialize() { return false; }
該函數(shù)會在 extensionsInitialized() 函數(shù)調(diào)用完成,并且所依賴插件的 delayedInitialize() 函數(shù)也調(diào)用完成之后才被調(diào)用。delayedInitialize() 函數(shù)會在程序運(yùn)行之后才被調(diào)用,并且距離程序啟動有幾個毫秒的間隔。為避免不必要的延遲,插件對該函數(shù)的實(shí)現(xiàn)應(yīng)該盡快返回。該函數(shù)的意義在于,有些插件可能需要進(jìn)行一些重要的啟動工作;這些工作雖然不必在啟動時直接完成,但也應(yīng)該在程序啟動之后的較短時間內(nèi)完成。該函數(shù)默認(rèn)返回false,即不需要延遲初始化。
virtual ShutdownFlag aboutToShutdown() { return SynchronousShutdown; }
signals:
void asynchronousShutdownFinished();
aboutToShutdown() 函數(shù)應(yīng)該用于與其它插件斷開連接、隱藏所有 UI、優(yōu)化關(guān)閉操作,會以插件初始化的相反順序調(diào)用,即先調(diào)用當(dāng)前插件的 aboutToShutdown() 函數(shù),再依次調(diào)用依賴插件的。如果插件需要延遲真正的關(guān)閉,例如,需要等待外部進(jìn)程執(zhí)行完畢,以便自己完全關(guān)閉,則應(yīng)該返回 AsynchronousShutdown,這么做的話會進(jìn)入主事件循環(huán),等待所有返回了 AsynchronousShutdown 的插件都發(fā)出了 asynchronousShutdownFinished() 信號之后,再執(zhí)行相關(guān)操作。該函數(shù)默認(rèn)返回 SynchronousShutdown,即不等待其它插件關(guān)閉。
下面,我們再回過頭來看看 demo1plugin.h
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Demo1.json")
Q_PLUGIN_METADATA 這個宏用于聲明插件的元數(shù)據(jù)。當(dāng)實(shí)例化插件對象時,這些元數(shù)據(jù)會作為該對象的一部分。這個宏需要聲明一個 IID 屬性,用于標(biāo)識對象實(shí)現(xiàn)的接口;還需要一個文件的引用FILE屬性,該文件包含了插件的元數(shù)據(jù)。
使用這個宏的類必須有無參數(shù)的構(gòu)造函數(shù)。在這里,Qt Creator 規(guī)定其插件的 IID 必須是 org.qt-project.Qt.QtCreatorPlugin。FILE指向了 Demo1.json 文件,但是項(xiàng)目里只有 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,并為該動作設(shè)置了快捷鍵 Ctrl+Alt+Meta+A,最后再將 triggered 信號綁定至本類中的 triggerAction 槽函數(shù)上。triggered 信號是在用戶觸發(fā)了動作,比如,單擊或使用快捷鍵時發(fā)送。
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);
這段代碼創(chuàng)建了一個菜單,并設(shè)置了菜單名為 Demo1,最后再將菜單添加到 Qt Creator 的 工具 菜單中。
Qt Creator 提供了一些主菜單允許我們將自己創(chuàng)建的菜單添加進(jìn)去:
- 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."));
}
這段代碼是當(dāng)我們點(diǎn)擊 工具 > Demo1 > Demo1 Action 時,觸發(fā)一個彈出框。
最后,運(yùn)行 構(gòu)建項(xiàng)目,Demo1 插件將被編譯成名為 libDemo1.so 的文件并安裝至 /data/qt-creator-opensource-src-4.10.1/lib/qtcreator/plugins
運(yùn)行
$ /data/qt-creator-opensource-src-4.10.1/bin/qtcreator
啟動 Qt Creator 就可以在 工具 菜單中找到我們的插件。