4.6がやってきた-Qt最新事情2010

第7回 ステートマシーンフレームワークの詳細[その3]

この記事を読むのに必要な時間:およそ 13 分

今回は,ステートマシーンフレームワークの最後です。マウスやキーイベント遷移がどのようなものか,イベント遷移の独自拡張,そして履歴状態や並列状態,遷移とアニメーション効果について説明します。

マウスイベント遷移

前回取り上げたシグナル遷移はステートマシンフレームワークのリファレンスで,コード例を示して説明していますが,マウスイベント遷移とキーイベント遷移についてはシグナル遷移のような説明がありません。そこで,簡単なコードで,まずマウス遷移を説明しましょう。

マウスイベント遷移の説明のためにEventWidgetウィジェットを作りました。このウィジェットは,テキストを表示し,有効化されていれば(enabledプロパティがtrue)テキストを緑色で,無効化されていれば(enabled プロパティがfalse)赤色で描画するようにします。そして,ボタンが押されている間はPressedと表示されるように,ステートマシンフレームワークで制御をするようにしています。動作は図1のようになります。

図1 マウスイベントによる表示の制御

図1 マウスイベントによる表示の制御

リスト1 eventwidget.h

 1: #ifndef EVENTWIDGET_H
 2: #define EVENTWIDGET_H
 3: 
 4: #include <QWidget>
 5: 
 6: class EventWidgetPrivate;
 7: 
 8: class EventWidget : public QWidget
 9: {
10:     Q_OBJECT
11:     Q_DECLARE_PRIVATE( EventWidget )
12:     Q_PROPERTY( QString text READ text WRITE setText )
13: 
14: public:
15:     explicit EventWidget( QWidget* parent = 0 );
16:     ~EventWidget();
17:     QString text() const;
18:     QSize sizeHint() const;
19: 
20: public slots:
21:     void setText( const QString& text );
22: 
23: protected:
24:     void paintEvent( QPaintEvent* event );
25: 
26: private:
27:     EventWidgetPrivate* d_ptr;
28: };
29: 
30: #endif

EventWidgetのヘッダです。今までも見て来たように,ステートマシーンフレームワークでは,ある状態になった時にプロパティを変更する仕組みを持っています。そこで,textプロパティでテキストをアクセスできるようにしておき,状態が変わった時にQState::assignProperty()でテキストを変えられるようにしておきます。

リスト2 eventwidget.cpp

 1: #include <QStateMachine>
 2: #include <QPainter>
 3: #include <QPaintEvent>
 4: #include <QEventTransition>
 5: 
 6: #include "eventwidget.h"
 7: 
 8: class EventWidgetPrivate
 9: {
10: public:
11:     EventWidgetPrivate() {}
12: 
13:     QString text;
14: };
15: 
16: EventWidget::EventWidget( QWidget* parent )
17:     : QWidget( parent ), d_ptr( new EventWidgetPrivate )
18: {
19:     QPalette palette = this->palette();
20:     palette.setColor( QPalette::Window, Qt::gray );
21:     palette.setColor( QPalette::Normal, QPalette::WindowText, Qt::green );
22:     palette.setColor( QPalette::Disabled, QPalette::WindowText, Qt::red );
23:     setPalette( palette );
24:     setAutoFillBackground( true );
25: 

背景色を灰色にするには,パレットのQPalette::Windowロールに色を設定し,setAutoFillBackground( true ) を呼び出して,背景色で塗り潰されるようにします。

有効化と無効化の各状態でのテキストの色は,パレットのQPalette::NormalとQPalette::Disabledの各カラーグループごとに,QPalette::WindowTextロールで指定します。こうしておくとテキスト描画時に,QPainter::drawText()が用いる色が有効化/無効化に合わせてパレットから取り出されて使われます。

26:     setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding );
27: 
28:     QStateMachine* stateMachine = new QStateMachine( this );
29: 
30:     QState* idleState = new QState;
31:     idleState->setObjectName( "idle" );
32:     idleState->assignProperty( this, "text", "Idle" );
33: 
34:     QState* pressedState = new QState;
35:     pressedState->setObjectName( "pressed" );
36:     pressedState->assignProperty( this, "text", "Pressed" );
37: 

通常状態とボタンが押されている状態を用意しています。

38:     QEventTransition* pressTransition = new QEventTransition( this, QEvent::MouseButtonPress, idleState );
39:     pressTransition->setTargetState( pressedState );
40: 
41:     QEventTransition* dblClickTransition = new QEventTransition( this, QEvent::MouseButtonDblClick, idleState );
42:     dblClickTransition->setTargetState( pressedState );
43: 
44:     QEventTransition* releaseTransition = new QEventTransition( this, QEvent::MouseButtonRelease, pressedState );
45:     releaseTransition->setTargetState( idleState );
46: 

本連載の第4回「ステートマシーンフレームワーク」の図1「ステートマシンフレームワークのクラス」で示したように,QEventTransitionクラスは,マウスとキーイベントのベースクラスになっています。その基本機能は,イベントタイプに対して反応する遷移となっていて,コンストラクタで指定した状態の時に,指定したタイプのイベントが発生すると遷移が起きます。たとえばpressTransitionは,このウィジェットidleState状態にある時にイベントタイプがQEvent::MouseButtonPressのイベントが発生すると,pressedState状態への遷移が起きます。

#include <QMouseEventTransition>
...
EventWidget::EventWidget( QWidget* parent )
	: QWidget( parent ), d_ptr( new EventWidgetPrivate )
{
    ...
    QMouseEventTransition* pressTransition = new QMouseEventTransition( this, QEvent::MouseButtonPress, Qt::LeftButton, idleState );
    pressTransition->setTargetState( pressedState );

    QMouseEventTransition* dblClickTransition = new QMouseEventTransition( this, QEvent::MouseButtonDblClick, Qt::LeftButton, idleState );
    dblClickTransition->setTargetState( pressedState );

    QMouseEventTransition* releaseTransition = new QMouseEventTransition( this, QEvent::MouseButtonRelease, Qt::LeftButton, pressedState );
    releaseTransition->setTargetState( idleState );

QEventTransitionの代わりにそのサブクラスのQMouseEventTransitionを使うと上記のように書けて,どのボタンで遷移するかも指定できます。この場合には,左ボタンでのみ遷移が起きるようになります。ただし,X11では意図した通りに動きますが,Mac OS Xでは,左ボタンを押したまま右ボタンをクリックしてから左ボタンを離しても左ボタンのリリースイベントが出ないためidleStateに戻らず,意図した動きになりません。

47:     stateMachine->addState( idleState );
48:     stateMachine->addState( pressedState );
49: 
50:     stateMachine->setInitialState( idleState );
51: 
52:     stateMachine->start();
53: }
54: 

ステートマシンの動かし方は今まで通りです。

68: {
69:     Q_D( EventWidget );
70: 
71:     if ( d->text == text )
72:         return;
73: 
74:     d->text = text;
75:     update();
76:     updateGeometry();
77: }
78: 

textプロパティのゲッターとセッターで,テキストが変更されたならば,update()で再描画イベントがスケジュールされるようにし,updateGeometry()でレイアウトマネージャにレイアウトの変更を知らせて,テキストの大きさの変更に追従させます。

79: QSize EventWidget::sizeHint() const
80: {
81:     Q_D( const EventWidget );
82: 
83:     return fontMetrics().boundingRect( d->text ).size();
84: }
85: 

テキストが表示される最小限の大きさをsizeHint()で返すようにして,レイアウトマネージャがちょうどよい大きさでこのウィジェットを配置するようにします。

86: void EventWidget::paintEvent( QPaintEvent* event )
87: {
88:     Q_D( EventWidget );
89:     Q_UNUSED( event );
90: 
91:     QPainter painter( this );
92:     painter.drawText( rect(), Qt::AlignCenter, d->text );
93: }

前述したようにパレットで背景色と状態に合わせた色を設定しているので,drawText()でテキストを描画するだけで,ウィジェットの背景が灰色になり,テキストにも状態に合った色が付きます。

リスト3 main.cpp

 1: #include <QApplication>
 2: #include <QLayout>
 3: 
 4: #include "eventwidget.h"
 5: 
 6: class Harness : public QWidget
 7: {
 8:     Q_OBJECT
 9: 
10: public:
11:     Harness();
12: };
13: 
14: Harness::Harness()
15:     : QWidget( 0 )
16: {
17:     EventWidget* firstWidget = new EventWidget;
18:     EventWidget* secondWidget = new EventWidget;
19:     secondWidget->setEnabled( false );
20: 
21:     QVBoxLayout* topLayout = new QVBoxLayout;
22:     topLayout->addWidget( firstWidget );
23:     topLayout->addWidget( secondWidget );
24: 
25:     setLayout( topLayout );
26: }
27: 
28: int main( int argc, char** argv )
29: {
30:     QApplication app( argc, argv );
31: 
32:     Harness harness;
33:     harness.show();
34: 
35:     return app.exec();
36: }
37: 
38: #include "main.moc"

有効化と無効化の両状態のEventWidgetを表示して動作を確認するコードです。図2のように無効化されていてもマウスイベント遷移が起きます。

図2 無効状態でもマウスイベント遷移をしてしまう

図2 無効状態でもマウスイベント遷移をしてしまう

これはウィジェットのイベントへの振舞いを考えると,直感に反した動作でしょう。QWidgetは無効化されていれば,QWidget::event()でQEvent::MouseButtonPressやQEvent::MouseButtonReleaseに対して対応するイベントハンドラーを呼び出しません。QWidgetのリファレンスにもそのように書かれています。一方,以下のようにQMouseEventTransitionは,QObjectを受け付けるようになっています。

QMouseEventTransition( QObject* object, QEvent::Type type, Qt::MouseButtonbutton, QState* sourceState = 0 )

QObjectである必要はであるでしょう。しかし,マウスイベントはウィジェットと密接に関連しているので,enabledプロパティに従うのが直感にも合った動きです。対象がウィジェットならば,QMouseEventTransitionにenabledプロパティに従ってイベントを扱うようなオプションがあってもよさそうに思えます。

著者プロフィール

杉田研治(すぎたけんじ)

1955年生まれ。東京都出身。株式会社SRAに勤務。プログラマ。

仕事のほとんどをMac OS XとKubuntu KDE 4でQtと供に過ごす。

コメント

コメントの記入