チュートリアルをやって、ウィジェットを作れるようになったらスタート地点。Qtの流儀でプロっぽいGUIを創るためには、土台から作りこみが必要っぽいので、調査メモ。
いわゆる値を格納するオブジェクト。QStringとか。Qtの中では、Implicit Sharingというパターンですべて統一されている。
http://qt-project.org/doc/qt-5.1/qtcore/implicit-sharing.html
ユーザが操作するクラス(QString)と、内部のデータを管理するクラス(QStringData)に分け、単純な代入では内部データをシェアするようにする(浅いコピー)。もし、データに変更が加わり、なおかつ参照数が2以上の時に初めて、内部のデータをディープコピーする。実装時には、 QSharedData と、 QShareadDataPointer などを使う。QShareadDataPointerに実装のもうちょっと詳しい説明と、サンプルコードがある。
これにより、newをするとか、deleteしなきゃとか、そういうメモリ管理を気にする箇所を減らし、int型の変数のように、気軽にオブジェクトを関数の引数に値で渡したり、返り値で返したりできるようなオブジェクトが作れる。boost::shared_ptrは要素の外部にスマートポインタとして機能を付与するけど、こいつは内部に似たような仕組みをつくる。クライアントコードはタイプ量がへってシンプルに綺麗になるし、Qt以外とコードをシェアしないのであれば、こっちの方が良いかと。
なお、明示的なコピー以外を禁止するにはQSharedDataPointerの代わりに、 QExplicitlySharedDataPointer を使う。最終的にストレージなりDBに保存されるようなものはExplicitlyにしておいても良さそう。
後から追加しようとしても、大混乱必至なのがUndo/Redo。Qtでは、 QUndoCommand と、 QUndoStack が用意されている。
QUndoCommandはその名の通り、GoFのCommandパターンを体現している。違うのは、undo()、redo()の二種のアクションを一つに書くところ。また、名前を持つところ。また、コマンドの実行に必要なデータを保持するコードを書く必要がある・持たせることができる(Mementoパターン)点。また、QUndoStackはかなり高性能な処理ができて、beginMacro()/endMacro()を使って、複数コマンドを組み合わせたものに名前をつけることができる。また、QUndoGroupを使えば、Stack自体も複数グルーピングでき、2通りの方法でCompositeパターンが実現できる。
複数コマンドを一つのコマンドのように扱えるので、プリミティブな単位でコマンドクラス群を設計していく。
- CRUDのCreate, Update, Delete
- 名前、IDの変更
- 選択状態の切り替え
値を完全に持ったオブジェクトを創るには、作成+更新の2コマンドを組み合わせれば良いので、Createを多種揃える必要はない。値の取得系、検索系はコマンドにする必要はなさそう。
エンティティクラスの中で、状態を変更するメソッドの中身は、QUndoStack.push()でコマンドを登録するだけ、になるはず。完全コンストラクタみたいなのは、beginMacro(), QUndoStack.push()複数呼び出し、endMacro()だけになるはず。newして値を返すようなケースでは、QUndoCommandに記録した要素を返す感じかな。エンティティクラスでは、friend classとして、QUndoCommandの子クラス群を登録する必要がありそう。
単体コマンド、複数コマンドの組み合わせはそれぞれ名前がつけることができるが、これはUndo/Redoのメニューのテキスト(何をするかの解説)として利用できる。QUndoViewオブジェクトを使うと、いままでのコマンドのヒストリをGUIに表示させることもできる。
なお、QUndoCommandはとてもシンプルな仕組みで(メニュー登録とヒストリ一覧表示以外は)特にQtのGUIならではの機能はない。データ構造をいじったり記録する補助機能は一切無く、undo/redoの中はプログラマがゼロから書く必要がある。逆に、そのため、どんな処理にも使うことができる。
Qtでマクロというと、 QScriptEngine (JavaScriptCore)によるJavaScriptでのプログラミング。エンジンにはQObject由来のクラスを登録する機能がある。newQObjectメソッドを使って、QScriptValueオブジェクトを作り、QScriptEngineのglobalObject()メソッドでとってきたGlobalオブジェクト(QScriptValue型)のsetPropertyで追加してやれば、データ値を操作することはできると思う。また、専用のメソッドとか関数を登録するのもできる。基本、JavaScriptの中の世界に自由自在にコードを追加できるので、requireで外部ファイルロードとかしたりもできるし、マクロ的な要素をFlashのJSFLのようにfl名前空間内に閉じ込めたり、となんでもできる。
QScriptEngine自体は生のECMAScriptで、Qt向けの特別な機能はnewQObjectぐらい。QtのGUIをQScriptEngineから操作できるようにするには、QtScriptBinding Generatorのちからを借りる必要がある。
なお、Qt5ではQJSEngineというV8のエンジンもあるけど、これはQML用に作られていて、GUIをバインディングするのは今のところ至難の業。QObjectは登録できたけど、機能はだいぶQScriptEngineより少ないし、マクロエンジンとして使うなら多少遅くてもQScriptEngineにしておく方がいまのところは無難。
Flashみたいにコマンドヒストリからマクロコードを生成、もしくはVBAみたいに録画してコード生成、とやるにはUndo/Redoと連携するのが良さげ。
やっぱり、GUIをユーザが操作できるようにしないといけないよね。 QDockWidget あたりを調査予定。
使い方全体のサンプルはこちら。
http://doc-snapshot.qt-project.org/qt5-stable/qtwidgets/mainwindows-dockwidgets.html
ドックを実現するのはそう難しくはなさそう。アプリのリロード後にシリアライズして戻す方法とか、ウインドウ配置レイアウトをなんパターンか用意する方法を調査する。
ウインドウ全体。
各コンポーネントでの対応
http://qt-project.org/doc/qt-5.0/qtwidgets/qwidget.html#saveGeometry
i18n もたぶん必要。
QtはHTMLのCSSとほぼ同じ Stylesheet が使える。ウインドウ全体のデザインを一括で変えることができる。あまり変えすぎるのも安っぽいかもしれないけど、スのままでは努力しているように見えない・・・というのもあるので、ちょっとかっちょいいデザインにしたいところ。QtCreatorから拝借してきてそこをスタートにするといいかも。
なお、スタイルシートにすると、要素の重ね方(QFrameで枠線のデザインをやりくりして3枚ぐらいネストさせる)によってはデザインが破綻して見えるので、あくまでUIはシンプルかつ必要十分、というのを狙う必要がありそう。スタイルシート適用する前でも、見た目に関する項目のプロパティを修正しないでも普通に見えるようにする、というのが良いかと。タブとグループのネストとかも要注意。
Qt5ではJSONも使える。
ユーザ設定など、再起動しても残る永続的なデータは QSettings を使うと実現できるらしい。MacだとXML、Windowsだと.iniファイルとか、レジストリにデータを保存する。
必要な部品がなければカスタムウィジェットを実装することになるが、これもスタイルシートの情報を利用して描画するようにした方が良い。
http://doc-snapshot.qt-project.org/qt5-stable/qtwidgets/qstyle.html#drawComplexControl
http://qt-project.org/doc/qt-4.8/itemviews-simpletreemodel.html
とりあえず、入門的なやつ。
本格的なのが作れれば、ツリー、表形式のデータに対して、カスタムのビューを作ったりできるらしい。 オライリーの実践Qtで3章ぐらい割かれているし、すごい重要そう。