QtのCustom Widget Pluginを斬る！

# 1. 初めに

クリスマスなので？~~リア充~~QtのCustom Widget Pluginをばっさり斬ってみたいと思います。

## 1.1. 開発環境
OSはWindowsとLinuxの２種類を扱います。（macOSは手元に環境がない為、対象外とさせていただきます。ごめんなさい。）
Qt SDK及びQt Creatorは、インストーラ版(MinGW版(Windows)/gcc版(Linux))と自分でコードをビルドした版の２種類。
２×２の組み合わせで４種類を対象とします。
Qt5.9.1とQt5.9.2が元ネタのQt Versionです。

# 2. Custom Widget Pluginって何？
Custom Widget PluginはQt Designer上において、QWidgetのサブクラスの操作をカスタマイズする機能です。
QWidgetのサブクラスをQt Designerで操作する機能は、Qtに２つ用意されています。
(1)格上げ(Proceed)する機能
(2)Custom Widget Plugin
の２つです。
この内、(1)格上げ(Proceed)する機能については、いろいろなサイトや書籍で扱っていますが、なぜか本稿の対象である(2)Custom Widget Pluginについては、Webサイトで検索してもほとんど情報が出てきません。そこで、本稿で取り上げてみることにしました。

Custom Widget Pluginは、QWidgetのサブクラスに、Extension機能を加えた動的ライブラリです。
このライブラリをQt Creator/Qt Designerが認識する場所(後述)に格納することによって、Qt Creator/Qt Designerが認識します。
Pluginが認識すると、Qt Designerのウィジェットボックスに「Input Widets」や「Display Widgets」と同レベルのグループ(グループ名は自分で設定可能)が表示され、この中に、自分で作成したオリジナルウィジェットが表示されます。もちろん中央部にドラッグできます。
※スクリーンショットが貼れればよかったのですが、環境整えるのが間に合いませんでしたorz

## 2.1. 特徴を比較する

まずこの２つの機能について、比較してみましょう。

||格上げ|Custom Widget Plugin|
|:--|:--|:--|
|利用のしやすさ|簡単|難しい。また、Qt Designerで利用できるまでに時間が掛かる|
|Qt Designer上の見た目|QWidgetそのまま変わらない|カスタマイズした見た目が反映される|
|誰向け？|QWidgetのサブクラスのコードを持っている人向け。<br>自分でサブクラスを作って、自分で画面を作る|QWidgetのサブクラスのコードを持っていない第３者|
|QWidgetのサブクラスを使用する時に必要なファイル|.hファイル及び.cppファイル|.hファイル及び動的ライブラリファイル(.so or .dll)|

## 2.2. どんな機能があるのか
### 2.2.1. Extension機能
[Creating Custom Widget Extensions](http://doc.qt.io/qt-5/designer-creating-custom-widgets-extensions.html)で説明されています。
下記の４つの機能があります。

- QDesignerTaskMenuExtension
 右クリックで表示されるTaskMenuをカスタマイズするExtensionです。
例）[Task Menu Extension Example](http://doc.qt.io/qt-5/qtdesigner-taskmenuextension-example.html)

- QDesignerContainerExtension
 カスタムの複数ページのコンテナを実装する場合に使用するExtensionです。Qt Designerで複数ページのコンテナプラグインのページを追加および削除できる拡張機能が用意されています。
例）[Container Extension Example](http://doc.qt.io/qt-5/qtdesigner-containerextension-example.html)
- QDesignerMemberSheetExtension
 オリジナルのシグナル・スロットを追加するExtensionです。
 右クリック→「スロットへ移動...」選択時に表示されるシグナル一覧に、自作Qwidgetサブクラスのシグナルが追加で表示されます。
- QDesignerPropertySheetExtension, QDesignerDynamicPropertySheetExtension
 プロパティ一覧の項目をカスタマイズするExtensionです。

## 2.3. 開発の流れ

Custom Widget Pluginを作成し、利用する流れは下記になります。

- QWidgetのサブクラスを作成（QOpenGLWidgetのようなOpenGL機能付きでも可）
- 「Custom Widget Plugin」をビルド(ライブラリが作成され、任意の場所にライブラリが移動) ※
- 実行すると、対象アプリケーションが指定するよう指示される為、qtcreator.exe(Linuxならqtcreator.shも可)を指定して、別のQt Creatorを起動
- 新しいuiファイル追記ウィジェットプロジェクトを作成
- uiファイルをダブルクリックし、Qt Desginerを起動
- 自作Qwidgetサブクラスを中央部にドラッグして操作
- プロジェクトファイルに「LIBS += -L〇〇 -l〇〇」を追加
- ビルドして実行

※Custom Widget Pluginのライブラリを格納する場所は、qquickwidget.dll(qquickwidget.so)が格納されている場所と同じ場所です。
２つ以上ある場合は、Toolディレクトリを含む方が正解です。

# 3. Custom Widget Pluginの問題
ここまでの話だと、Custom Widget Pluginっていう機能はおもしろそうじゃないか、オラわくわくしてきたぞ、と思われる方もいらっしゃるかもしれません。
Custom Widget Pluginの存在に気付いた時は当初、私もその一人でした。
しかし、実際に使用しようとすると・・・厳しい現実が待っていました。

## 3.1. サンプルが動かない
Qt SDKに用意されているCustom Widget Pluginの一番シンプルなサンプルである[World Time Clock Plugin Example](http://doc.qt.io/qt-5/qtdesigner-worldtimeclockplugin-example.html)をインストーラでインストールしたQt SDKで動作させようとすると動作しません。（他のサンプルも同様）
それぞれのOSについて見ていきます。
### 3.1.1. Windowsでの動作
結論から言いますと、**Windows(MinGW)インストーラ版では、Custom Widget Pluginは動作しません。**
これは、「Qt CreatorをビルドするコンパイラとCustom Widget Pluginをビルドするコンパイラが異なると動作しない」という制約に引っかかってしまう為です。
手元のQt5.9.2だと、Qt CreatorはVisualStudio2015でビルドされていますが、VisualStudioはインストールしていない環境なので、Qt SDKはMigGW版であり、Custom Widget Pluginをビルドする環境もMinGW版です。

解決策としては、

- Qt Creatorのコードをダウンロードして自分でMinGWでビルドする
- VisualStudioコンパイラ版のQt SDKを使用する。

のどちらかにするしか対応方法はないようです。
最初から全てコードを落としてきてビルドした環境であれば問題なく動作します。

### 3.1.2. Linuxでの動作
Linuxのインストーラ版は、サンプルコードそのままでは動作しませんが、**プロジェクトファイルを書き換えれば**動作します。
何が問題かというと、**Qt独自のビルド環境変数「QT_INSTALL_PLUGINS」の示す先がQt Designerが認識する場所を指していない**ことが問題です。
自分でQtをビルドした場合は、「QT_INSTALL_PLUGINS」に正しい値がセットされるので問題は発生しません。
※インストーラ版と自分ビルド版では、ディレクトリ構成が異なります。自分ビルド版ではToolディレクトリは存在しません。従って、インストーラ化した時の何かにバグがあるのではないかと予想しています。

また、もう１つ別の観点で問題があります。
プロジェクトファイルの書き方についてです。
[ここ](http://doc.qt.io/qt-5/qtdesigner-customwidgetplugin-example.html)にあるように

```
target.path = $$[QT_INSTALL_PLUGINS]/designer
INSTALLS += target
```
のように記述すればよいとのことですが、これは**QMakeをシェル上から実行した場合のみ有効**となる記述方法で、Qt Creator上の操作からビルドしても上記の設定は認識してくれません。
そこで、代案として私が考えたのが、「DESTDIR」に直接Custom Widget Pluginライブラリを格納するパスを記述する方法です。
QtのVersionが上がった時に、パスがいちいち変わる可能性があるので、微妙と言えば微妙ですが、Qt Creator上で操作できるという観点において、公式サイトの記述よりましだと思っています。

## 3.2. プロパティExtensionが微妙すぎる
2.2.1.の説明で、あえてぼかして説明しましたが、QDesignerPropertySheetExtensionはQDesignerMemberSheetExtensionと異なり、プロパティを**追加**することはできません。
例えば、QPushButtonをQt Designer上に張り付けると、「QObject」、「QWidget」、「QAbstractButton」、「QPushButton」の各クラスのプロパティが全て表示されます。
この動作のイメージで、QWidgetのサブクラスのプロパティExtensionを作成したら、「QObject」、「QWidget」、「QWidgetのサブクラス」の３つのクラスのプロパティが表示されるイメージになると思いますが、残念ながらQDesignerPropertySheetExtensionのサブクラスに、自分で定義したプロパティしか表示できません。「QObject」、「QWidget」のプロパティを表示したかったら、「自分で定義する」しか方法がありません。
※これは、QDesignerPropertySheetExtensionのメンバ関数が全て純粋仮想関数なのが原因かと思います。（要は設計ミス）デフォルト実装はQDesignerPropertySheetExtensionの中でしておく方がよかったのではないと思います。Qt SDK側は恐らくd-pointer化したPrivateクラスでデフォルトの実装を行っているはずで、なぜか公開した側のクラスはかなり締め付けられています。

ちなみに、QObjectの「objectName」プロパティとQWidgetの「geometry」プロパティはQDesignerPropertySheetExtensionのサブクラスで再定義しておく必要があるようです。存在しないとQt Designerで変な挙動を起こします。
他にも必須プロパティがあるのか、Qtの中の人に問い合わせたこともありますが、知らないと回答されました。。。

## 3.3. Qt5.9.1までは・・・
Qt5.9.1までは、QDesignerMemberSheetExtensionとQDesignerPropertySheetExtensionを１つのPluginに組み込むとQDesignerMemberSheetExtensionが正しく動作しません。
http://bugreports.qt.io/browse/QTBUG-62936/
が原因らしいです。
Qt5.9.2では直っていますが、正直なぜ、こんなド正常の動作にバグがあるのか理解できません。
（他のQt SDKのサンプルはこれでもかという位リッチなのに）Custom Widget Pluginのサンプルは充実していないことから、窓際的な扱いをされているのでしょうか？

# 4. まとめ
いろいろと大変なCustom Widget Pluginですが、やっぱり、Qt Designer上でペタペタ貼り付けて画面を作るのは楽しいものです。
様々な障害を乗り越えて、「Custom Widget Pluginを使いたい！」と言ってくださる方がいらっしゃいましたら、ぜひコメントください。
Custom Widget Pluginの作り方の記事を書きたくなるかもしれません。

# 5. 最後に
今年のQt Advent Calendarは、当初なかなか枠が埋まらなくて、「読専」だった私もデビューしてしまいました。
調子に乗って３つも書いてしまいました。
来年も自分に続いてくれるNew Challengerをお待ちしています。

それでは、みなさま良いお年を！