Qt Creatorのプラグインの作り方を調べた

  • 15
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

こんにちは

@kenya888 です。日本Qtユーザー会ではどちらかというとユーザー会のサーバーインフラとか、その辺を見ています。
Qt Advent Calendarも佳境というか、ゴールまであと少し。毎日楽しい記事が投稿されていて、クリスマスが待ち遠しいというよりクリスマスが来てしまうことが残念に感じてしまうくらいです。個人的に今年はあまりQtで色々遊んでみたかったけどできなかったという反省がありますが、今年やりたかったことだったQt Creatorをプラグインで拡張する方法を調べて書いてみることにしました。コードは1行も書いてませんのであしからず。

Qt Creatorはだいたいプラグインでできている

Qt Creatorは、Qtでプログラムを作成するための強力なIDEです。とても便利な機能がたくさんありますが、実はそのほとんどがプラグインとして実装されていて、Qt Creator本体自身の機能は主にプラグインローダーです。よく紹介されるのですが、手元のstripされたQt Creatorのバイナリの大きさはこんなかんじです。(まぁ実体はCoreプラグインだから詭弁とも言えますが...)

kenya888@mba /opt/qt-creator/bin $ ls -lh qtcreator
-rwxr-xr-x 1 kenya888 wheel 101K 12月 21 07:01 qtcreator

実際にはpluginsディレクトリにあるたくさんのプラグインが機能を提供しているというわけですね。そして、このプラグインは豊富なAPIが提供されていますので、もちろん自分でオレオレプラグインを作成して、Qt Creatorを拡張できます。夢が広がりますね。広がりませんか?

試してみよう

というわけで、早速どんなもんか試してみましょう。Qt Creatorのプラグイン開発はもちろんQt Creatorで行えます。

Qt Creatorをビルドしておく

事前に、Qt Creatorのソースを取得して、ビルドしておきます。今回は以下の環境でビルドしました。大体Qt 5.4/Qt Creator 3.3が出たすぐ後くらいです。この記事の公開日の前日くらいにビルドしました...

  • Gentoo Linux x86_64
  • Kernel 3.17.7
  • Qt 5.4 (5.4 branch)
  • Qt Creator (master branch)

ちなみに、日本Qtユーザー会ではgit.qt-users.jpでQtとQt Creatorのgitリポジトリをgitoriousからミラーしていますので、ぜひお使いくださいませ。

あとで聞かれるので、Qt Creatorのソースディレクトリとビルドディレクトリを決めておきます。今回はこんな風になっています。

ソースディレクトリ: /home/kenya888/devel/qt-creator
ビルドディレクトリ: /home/kenya888/devel/qtcbuild

ちなみに、開発のために実行しているQt Creatorは /opt/qt-creator の下にインストールしています。

Qt Creatorプラグイン用のプロジェクトを作成する

Qt Creatorで、新しいプロジェクトを作成します。テンプレートとして、'Library'->'Qt Creatorプラグイン'を選択します。

001.png

プロジェクト名とパス、使用するキットを選択したあと、プラグイン情報を記述します。

006.png

プラグイン名やベンダ名他情報を記入するのですが、ここに'Qt Creatorソース'と'Qt Creatorビルド' ディレクトリの入力を求められます。(ここでいいのかという気もしますけど)ソースディレクトリはプラグイン作成のために必要なヘッダ情報などを参照するために、ビルドディレクトリは実際にプラグインがデプロイ時にここのpluginsディレクトリにインストールされるようになっています。

あとはウィザードを完了すれば、Qt Creatorプラグインを作成するためのプロジェクトが出来上がります。

009.png

左側の'プロジェクト'ボタンをクリックして、'実行時の設定'を開くと、'カスタム実行ファイル'にある'実行ファイル'の項目が空なので、ここにビルドディレクトリのqtcreatorのバイナリを指定しておきましょう。

011.png

実行してみる

ここでコードを一切変更せず、とりあえず実行してみると、プラグインがビルドされて、別のQt Creatorが起動します。まずは'ヘルプ'->'プラグインについて...'を確認して、プラグインが登録され、有効化されていることを確認しましょう。

014.png

「ユーティリティ」というカテゴリに属していて、チェックが入っていますね。

次に動作を確認します。'ツール' -> 'exp1' -> 'exp1 action Meta+Ctrl+Alt+A' というメニューを実行すると、メッセージボックスが表示されます。

012.png

013.png

もう完成です。すごいですね(違
中身についてちょっと勉強してみましょう。

プラグインの動作

プラグインのライフサイクル

まず、Qt Creatorのプラグインのライフサイクルについて、Plugin Life Cycleにある説明を読んでみます。起動時の動作の部分を簡単に訳すと

  1. Qt Creatorを起動すると、パスに存在するダイナミックライブラリを検索して読み込む。この時メタデータライブラリと、'org.qt-project.Qt.QtCreatorPlugin'のIIDがないライブラリは無視
  2. プラグインごとにExtensionSystem::PluginSpecを作成
  3. プラグインを'Read'ステートにセット
  4. プラグインの依存性と互換性をチェック
  5. プラグインを'Resolved'ステートにセット
  6. すべてのプラグインをソートしてロードキューに入れる
  7. プラグインをロードして、IPluginインスタンスを作成する。プラグインのコンストラクタが実行される
  8. プラグインを'loaded'ステートにセット
  9. ロードキューの順番で、各プラグインの initialize() を実行する。このタイミングでプラグインのexportされたインターフェースがすべて使用可能になっている必要がある。
  10. プラグインを'Initialized'ステートにセット
  11. ロードキューの順序と逆順でextensionsInitialized() を実行
  12. プラグインを'Running'ステートにセット

これでプラグインのロードとセットアップ、実行が完了し、Qt CreatorのGUIが起動します。
シャットダウン時の動きもリンク先に書いてありますので、見てみてください。

さっきのサンプルではどんなことをやっているか

先ほどのサンプルを見てみましょう。サンプルのプラグインは、「メニューバーの'ツール'メニューに'exp1'メニューを追加して、'exp1 action'アクションを選択するとメッセージボックスを表示する」だけのプラグインでした。

exp1constants.h
#ifndef EXP1CONSTANTS_H
#define EXP1CONSTANTS_H

namespace exp1 {
namespace Constants {

// このプラグインで使う文字リテラル
const char ACTION_ID[] = "exp1.Action";
const char MENU_ID[] = "exp1.Menu";

} // namespace exp1
} // namespace Constants

#endif // EXP1CONSTANTS_H
exp1plugin.h
#ifndef EXP1_H
#define EXP1_H

#include "exp1_global.h"

#include <extensionsystem/iplugin.h>

namespace exp1 {
namespace Internal {

class exp1Plugin : public ExtensionSystem::IPlugin
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "exp1.json") // プラグインのメタデータを作成するマクロ

public:
    exp1Plugin();
    ~exp1Plugin();

    bool initialize(const QStringList &arguments, QString *errorString);
    void extensionsInitialized();
    ShutdownFlag aboutToShutdown();

private slots:
    void triggerAction();
};

} // namespace Internal
} // namespace exp1

#endif // EXP1_H

Q_PLUGIN_METADATAマクロで、IIDと、メタデータ記述用のファイルを指定しています。先ほどライフサイクルの説明でふれたように、IIDは"org.qt-project.Qt.QtCreatorPlugin"でないとQtCreatorのプラグインとして認識してもらえないのでしたね。FILEの"exp1.json"はプラグインのビルド時に作成されますが、元となる"exp1.json.in"ファイルがプロジェクトディレクトリの直下に作成されています。ベンダーの名前やライセンス名などなど、プロジェクト作成のウィザードで指定したものが入っていることが分かりますね。プラグインのメタデータについての説明も読んでみましょう。

exp1.json.in
{
    \"Name\" : \"exp1\",
    \"Version\" : \"0.0.1\",
    \"CompatVersion\" : \"0.0.1\",
    \"Vendor\" : \"vendor_exp1\",
    \"Copyright\" : \"copyright_exp1\",
    \"License\" : \"LGPLv2\",
    \"Description\" : \"example plugin\",
    \"Url\" : \"http://example.com\",
    $$dependencyList
}

プラグインで処理をさせるために、サンプルではCore PluginのAPIを使って、メニューやアクションの登録をしています。

exp1plugin.cpp
#include "exp1plugin.h"
#include "exp1constants.h"

// Qt CreatorのCoreプラグインの機能を使用
#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>

#include <QtPlugin>

using namespace exp1::Internal;

exp1Plugin::exp1Plugin()
{
    // Create your members
}

exp1Plugin::~exp1Plugin()
{
    // Unregister objects from the plugin manager's object pool
    // Delete members
}

bool exp1Plugin::initialize(const QStringList &arguments, QString *errorString)
{
    // Register objects in the plugin manager's object pool
    // Load settings
    // Add actions to menus
    // Connect to other plugins' signals
    // In the initialize function, a plugin can be sure that the plugins it
    // depends on have initialized their members.

    // コンパイラにパラメータを本文内で使用しないことを明示的に伝えるQ_UNUSEDマクロ
    Q_UNUSED(arguments)
    Q_UNUSED(errorString)

    QAction *action = new QAction(tr("exp1 action"), this); // QAction

    // Qt CreatorのCore::ActionManagerを使用して、Globalコンテキスト(いつでも有効)なアクションとしてCore::Command クラスオブジェクトを作成。
    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, SIGNAL(triggered()), this, SLOT(triggerAction())); // actionのtriggered()シグナルをexp1PluginクラスのtriggerAction()スロットに接続

    Core::ActionContainer *menu = Core::ActionManager::createMenu(Constants::MENU_ID); // メニューの作成
    menu->menu()->setTitle(tr("exp1")); // メニューのタイトルを設定
    menu->addAction(cmd); // アクションの登録
    Core::ActionManager::actionContainer(Core::Constants::M_TOOLS)->addMenu(menu); "ツール'メニューへメニューを登録

    return true; // サンプルだから常にtrue
}

void exp1Plugin::extensionsInitialized()
{
    // Retrieve objects from the plugin manager's object pool
    // In the extensionsInitialized function, a plugin can be sure that all
    // plugins that depend on it are completely initialized.
}

ExtensionSystem::IPlugin::ShutdownFlag exp1Plugin::aboutToShutdown()
{
    // Save settings
    // Disconnect from signals that are not needed during shutdown
    // Hide UI (if you add UI that is not in the main window directly)
    return SynchronousShutdown;
}

// triggerAction() スロットの処理
void exp1Plugin::triggerAction()
{
    // メッセージボックスの表示
    QMessageBox::information(Core::ICore::mainWindow(),
                             tr("Action triggered"),
                             tr("This is an action from exp1."));
}

こんな感じで、使いたい機能を追加していくと、自分の好きな処理をQt Creatorにやらせることができますね。

いろいろなプラグインを作るには

先ほど、プラグインをビルドして実行した時に、'プラグインについて'メニューを確認したと思います。その時、exp1プラグインが'ユーティリティ'というカテゴリにあったと思います。では、他のカテゴリのプラグインが作りたい時はどうしたらよいのでしょうか。

今のところQt Creatorに関するドキュメントで、今回の記事で見てきた導入部分とAPIリファレンスとの間を埋めてくれるようなドキュメントはほぼないといってよく、最小限のサンプルコードのようなものもありません。

ただし、Qt Creatorはそもそもすべてプラグインなので、Qt Creatorに付属しているプラグインのソースコードを読むと、きっと書き方が分かってくると思います。

私もまだ読んでませんが、これから読んでみようと思うもの

  • remotelinux (特定のデバイスをビルド/実行環境にする場合)
  • genericprojectmanager (プロジェクト作成用のウィザードとかmakeステップの定義)
  • android (外部NDKとの連携とか、エミュレータとの連携とか)

本当は作りたいプラグインがあったんですが、時間がなく間に合わなかったので冬休みの自由研究にしようかと... ^^;

おわりに

まだQtをやったことないみなさんも来年はQtやってみませんか?明日からでもいいですよ?

さて明日は日本が誇るQt Ambassadorのひとり @IoriAYANE さんの投稿です。楽しみですねー。日本には世界で認められたQt ChampionとQt Ambassadorがいるので、日本のQtコミュニティは安泰です。楽しみにしましょう!

参考文献

Extending Qt Creator Manual
http://doc-snapshot.qt-project.org/qtcreator-extending/extending-index.html

Qt Creator API Reference
http://doc.qt.digia.com/qtcreator-extending/qtcreator-api.html

Qt Creatorを拡張する -SlideShare-
http://www.slideshare.net/takumiasaki/qt-creator

この投稿は Qt Advent Calendar 201421日目の記事です。