今回は、
マウスイベント遷移
前回取り上げたシグナル遷移はステートマシンフレームワークのリファレンスで、
マウスイベント遷移の説明のためにEventWidgetウィジェットを作りました。このウィジェットは、

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のヘッダです。今までも見て来たように、
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:
背景色を灰色にするには、
有効化と無効化の各状態でのテキストの色は、
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回
#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を使うと上記のように書けて、
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プロパティのゲッターとセッターで、
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: }
前述したようにパレットで背景色と状態に合わせた色を設定しているので、
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のように無効化されていてもマウスイベント遷移が起きます。

これはウィジェットのイベントへの振舞いを考えると、
QMouseEventTransition( QObject* object, QEvent::Type type, Qt::MouseButtonbutton, QState* sourceState = 0 )
QObjectである必要はであるでしょう。しかし、
イベント遷移の独自拡張
QMouseEventTransitionを拡張して、
1: #ifndef WIDGETMOUSEEVENTTRANSITION_H
2: #define WIDGETMOUSEEVENTTRANSITION_H
3:
4: #include <QMouseEventTransition>
5:
6: class WidgetMouseEventTransition : public QMouseEventTransition
7: {
8: Q_OBJECT
9:
10: public:
11: explicit WidgetMouseEventTransition( QState* sourceState = 0 );
12: WidgetMouseEventTransition( QObject* object, QEvent::Type type, Qt::MouseButton button, QState* sourceState = 0 );
13:
14: protected:
15: bool eventTest( QEvent* event );
16: };
17:
18: #endif
ヘッダ部です。QMouseEventTransitionのサブクラスとし、
1: #include <QWidget>
2: #include "widgetmouseeventtransition.h"
3:
4: WidgetMouseEventTransition::WidgetMouseEventTransition( QState* sourceState )
5: : QMouseEventTransition( sourceState )
6: {
7: }
8:
9: WidgetMouseEventTransition::WidgetMouseEventTransition( QObject* object, QEvent::Type type, Qt::MouseButton button, QState* sourceState )
10: : QMouseEventTransition( object, type, button, sourceState )
11: {
12: }
13:
コンストラクタはQMouseEventTransitionに合わせて2通り用意しておきましょう。
14: bool WidgetMouseEventTransition::eventTest( QEvent* event )
15: {
16: if ( !QMouseEventTransition::eventTest( event ) )
17: return false;
18:
19: QWidget* widget = qobject_cast<QWidget*>( eventSource() );
20: if ( widget && widget->isEnabled() )
21: return true;
22:
23: return false;
24: }
まず、
#include "widgetmouseeventtransition.h"
...
EventWidget::EventWidget( QWidget* parent )
: QWidget( parent ), d_ptr( new EventWidgetPrivate )
{
...
WidgetMouseEventTransition* pressTransition = new WidgetMouseEventTransition( this, QEvent::MouseButtonPress, Qt::LeftButton, idleState );
pressTransition->setTargetState( pressedState );
WidgetMouseEventTransition* dblClickTransition = new WidgetMouseEventTransition( this, QEvent::MouseButtonDblClick, Qt::LeftButton, idleState );
dblClickTransition->setTargetState( pressedState );
WidgetMouseEventTransition* releaseTransition = new WidgetMouseEventTransition( this, QEvent::MouseButtonRelease, Qt::LeftButton, pressedState );
releaseTransition->setTargetState( idleState );
拡張したWidgetMouseEventTransitionを使う部分は上記のように置き換えます。これで、
キーイベント遷移
キーイベント遷移は、
- Sub-Attaq Demo
- URL:http://
doc. trolltech. com/ 4. 6/ demos-sub-attaq. html - Rogue Example
- URL:http://
doc. trolltech. com/ 4. 6/ statemachine-rogue. html - Pad Navigator Exmaple
- URL:http://
doc. trolltech. com/ 4. 6/ graphicsview-padnavigator. html
Sub-AttaqとRogueには、
履歴状態
第4回
ステートマシンフレームワークのリファレンスの履歴状態の説明で使われているコード断片は、
1: #include <QApplication>
2: #include <QPushButton>
3: #include <QLabel>
4: #include <QLayout>
5: #include <QStateMachine>
6: #include <QState>
7: #include <QHistoryState>
8: #include <QFinalState>
9:
10: int main( int argc, char** argv )
11: {
12: QApplication app( argc, argv );
13:
14: QLabel* stateLabel = new QLabel;
15: QPushButton* stateChangeButton = new QPushButton( "Change" );
16: QPushButton* interruptButton = new QPushButton( "Interrupt" );
17: QPushButton* quitButton = new QPushButton( "Quit" );
18:
状態を順に変えるためのボタン、
19: QVBoxLayout* topLayout = new QVBoxLayout;
20: topLayout->addWidget( stateLabel );
21: topLayout->addWidget( stateChangeButton );
22: topLayout->addWidget( interruptButton );
23: topLayout->addWidget( quitButton );
24:
25: QWidget topWidget;
26: topWidget.setLayout( topLayout );
27:
28: QStateMachine* stateMachine = new QStateMachine( &topWidget );
29: QState* s1 = new QState( stateMachine );
30: QState* s11 = new QState( s1 );
31: QState* s12 = new QState( s1 );
32: QState* s13 = new QState( s1 );
33: QFinalState* finalState = new QFinalState( stateMachine );
34: s1->setInitialState( s11 );
35:
履歴状態を使うには、
36: s11->assignProperty( stateLabel, "text", "In s11" );
37: s12->assignProperty( stateLabel, "text", "In s12" );
38: s13->assignProperty( stateLabel, "text", "In s13" );
39:
40: s11->addTransition( stateChangeButton, SIGNAL(clicked()), s12 );
41: s12->addTransition( stateChangeButton, SIGNAL(clicked()), s13 );
42: s13->addTransition( stateChangeButton, SIGNAL(clicked()), s11 );
43:
ボタンstateChangeButtonをクリックするとs11、

44: s1->addTransition( quitButton, SIGNAL(clicked()), finalState );
45: app.connect( stateMachine, SIGNAL(finished()), SLOT(quit()) );
46:
複合状態s1に居るときにボタンquitButtonをクリックすると終了状態finalStateに遷移するようにします。終了状態になると状態遷移機械stateMachineはfinished()シグナルを送信するので、
47: QHistoryState* s1h = new QHistoryState( s1 );
48:
49: QState* s3 = new QState( stateMachine );
50: s3->assignProperty( stateLabel, "text", "In s3" );
51: s3->assignProperty( interruptButton, "text", "Restart" );
52: s1->assignProperty( interruptButton, "text", "Interrupt" );
53: s3->addTransition( interruptButton, SIGNAL(clicked()), s1h );
54: s1->addTransition( interruptButton, SIGNAL(clicked()), s3 );
55:
履歴状態の作成です。記憶しておきたい子状態 s11、
s3から履歴状態s1hに遷移するようにしているのが要点で、
56: stateMachine->setInitialState( s1 );
57: stateMachine->start();
58:
59: topWidget.show();
60:
61: return app.exec();
62: }
63:
並列状態
ステートマシンフレームワークのリファレンスUsing Parallel States to Avoid a Combinatorial Explosion of Statesに説明されているように、
図4は以下のことを図示しています。
- 状態遷移機械が開始されるとpingerとpongerの2つの状態に同時に入る。
- pinger状態に入るとpingイベントを状態遷移機械にポストする。
- pinger状態はpongイベントを受取るとpingイベントを状態遷移機械にポストする。
- ponger状態はpingイベントを受取るとpoイベントを状態遷移機械にポストする。

終了条件がないので、
1: #include <QtCore>
2: #include <stdio.h>
3:
4: class PingEvent : public QEvent
5: {
6: public:
7: PingEvent() : QEvent(QEvent::Type(QEvent::User+2))
8: {}
9: };
10:
11: class PongEvent : public QEvent
12: {
13: public:
14: PongEvent() : QEvent(QEvent::Type(QEvent::User+3))
15: {}
16: };
17:
ステートマシンフレームワークでは、
18: class Pinger : public QState
19: {
20: public:
21: Pinger(QState *parent)
22: : QState(parent) {}
23:
24: protected:
25: virtual void onEntry(QEvent *)
26: {
27: machine()->postEvent(new PingEvent());
28: fprintf(stdout, "ping?\n");
29: }
30: };
31:
最初にpinger状態に入ったときにpongEventイベントを状態遷移機械にポストするために、
32: class PongTransition : public QAbstractTransition
33: {
34: public:
35: PongTransition() {}
36:
37: protected:
38: virtual bool eventTest(QEvent *e) {
39: return (e->type() == QEvent::User+3);
40: }
41: virtual void onTransition(QEvent *)
42: {
43: machine()->postDelayedEvent(new PingEvent(), 500);
44: fprintf(stdout, "ping?\n");
45: }
46: };
47:
状態pingerに設定する遷移です。イベントタイプがQEvent::User+3のPongEventを受け取ると遷移し、
48: class PingTransition : public QAbstractTransition
49: {
50: public:
51: PingTransition() {}
52:
53: protected:
54: virtual bool eventTest(QEvent *e) {
55: return (e->type() == QEvent::User+2);
56: }
57: virtual void onTransition(QEvent *)
58: {
59: machine()->postDelayedEvent(new PongEvent(), 500);
60: fprintf(stdout, "pong!\n");
61: }
62: };
63:
こちらは、
64: int main(int argc, char **argv)
65: {
66: QCoreApplication app(argc, argv);
67:
68: QStateMachine machine;
69: QState *group = new QState(QState::ParallelStates);
70: group->setObjectName("group");
71:
並列状態を作るには、
72: Pinger *pinger = new Pinger(group);
73: pinger->setObjectName("pinger");
74: pinger->addTransition(new PongTransition());
75:
76: QState *ponger = new QState(group);
77: ponger->setObjectName("ponger");
78: ponger->addTransition(new PingTransition());
79:
この状態groupを親状態にして子状態を作成すれば、
- pinger状態に最初に入ったときに、
PingerEventがポストされる。 - 状態遷移機械は、
各状態の遷移を調べて eventTest() を呼び、 遷移するものを見つけようとする。 - ponger状態に設定されたPingTransition遷移がPingerEventで遷移するので、
PingerEvent::onTransition() が呼び出されてPongEventがポストされる。 - 2.と同じ。
- pinger状態に設定された PongTransition 遷移がPongerEventで遷移するので、
PongerEvent::onTransition()が呼び出されてPingEventがポストされる。 - 2.に戻る。
80: machine.addState(group);
81: machine.setInitialState(group);
82: machine.start();
83:
84: return app.exec();
85: }
遷移とアニメーション効果
ステートマシンフレームワークAPIはアニメーションAPIに関連付けられていて、
1: #include <QApplication>
2: #include <QStateMachine>
3: #include <QSignalTransition>
4: #include <QPropertyAnimation>
5: #include <QPushButton>
6: #include <QLayout>
7:
8: int main( int argc, char** argv )
9: {
10: QApplication app( argc, argv );
11:
12: QWidget* drawWidget = new QWidget;
13: drawWidget->setFixedSize( 400, 200 );
14:
15: QWidget* leftWidget = new QWidget( drawWidget );
16: QPalette palette = leftWidget->palette();
17: palette.setColor( QPalette::Window, Qt::green );
18: leftWidget->setPalette( palette );
19: leftWidget->setAutoFillBackground( true );
20: leftWidget->setGeometry( 90, 90, 20, 20 );
21:
22: QWidget* rightWidget = new QWidget( drawWidget );
23: palette = leftWidget->palette();
24: palette.setColor( QPalette::Window, Qt::red );
25: rightWidget->setPalette( palette );
26: rightWidget->setAutoFillBackground( true );
27: rightWidget->setGeometry( 290, 90, 20, 20 );
28:
drawWidgetの上に、
29: QPushButton* leftAnimateButton = new QPushButton( "Animate" );
30: QPushButton* rightAnimateButton = new QPushButton( "Animate" );
31:
32: QHBoxLayout* buttonLayout = new QHBoxLayout;
33: buttonLayout->addStretch();
34: buttonLayout->addWidget( leftAnimateButton );
35: buttonLayout->addStretch();
36: buttonLayout->addWidget( rightAnimateButton );
37: buttonLayout->addStretch();
38:
39: QVBoxLayout* topLayout = new QVBoxLayout;
40: topLayout->addWidget( drawWidget );
41: topLayout->addLayout( buttonLayout );
42:
操作用のボタンを作り、
43: QWidget topWidget;
44: topWidget.setLayout( topLayout );
45:
46: QStateMachine* stateMachine = new QStateMachine( &topWidget );
47:
48: QState* parallelState = new QState(QState::ParallelStates, stateMachine );
49: QState* leftStateGroup = new QState( parallelState );
50: QState* rightStateGroup = new QState( parallelState );
51:
緑と赤の両ウィジェットを並行して操作するために並列状態を使っています。
52: QState* leftWidgetStateSmall = new QState( leftStateGroup );
53: QState* leftWidgetStateLarge = new QState( leftStateGroup );
54: leftWidgetStateSmall->assignProperty( leftWidget, "geometry", QRectF( 90, 90, 20, 20 ) );
55: leftWidgetStateLarge->assignProperty( leftWidget, "geometry", QRectF( 10, 10, 180, 180 ) );
56: leftStateGroup->setInitialState( leftWidgetStateSmall );
57:
小さいときの状態と大きいときの状態を用意し、
58: QSignalTransition* leftMaximizeTransition = leftWidgetStateSmall->addTransition( leftAnimateButton, SIGNAL(clicked()), leftWidgetStateLarge );
59: QPropertyAnimation* animation = new QPropertyAnimation( leftWidget, "geometry" );
60: animation->setDuration( 4 * 1000 );
61: leftMaximizeTransition->addAnimation( animation );
62: QSignalTransition* leftMinimizeTransition = leftWidgetStateLarge->addTransition( leftAnimateButton, SIGNAL(clicked()), leftWidgetStateSmall );
63: animation = new QPropertyAnimation( leftWidget, "geometry" );
64: animation->setDuration( 4 * 1000 );
65: leftMinimizeTransition->addAnimation( animation );
66:
ボタンをクリックすると緑のウィジェットが大きくなったり小さくなったりするようなアニメーションを作成し、
67: QState* rightWidgetStateSmall = new QState( rightStateGroup );
68: QState* rightWidgetStateLarge = new QState( rightStateGroup );
69: rightWidgetStateSmall->assignProperty( rightWidget, "geometry", QRectF( 90 + 200, 90, 20, 20 ) );
70: rightWidgetStateLarge->assignProperty( rightWidget, "geometry", QRectF( 10 + 200, 10, 180, 180 ) );
71: rightStateGroup->setInitialState( rightWidgetStateSmall );
72:
73: QSignalTransition* rightMaximizeTransition = rightWidgetStateSmall->addTransition( rightAnimateButton, SIGNAL(clicked()), rightWidgetStateLarge );
74: animation = new QPropertyAnimation( rightWidget, "geometry" );
75: animation->setDuration( 4 * 1000 );
76:
77: rightMaximizeTransition->addAnimation( animation );
78: QSignalTransition* rightMinimizeTransition = rightWidgetStateLarge->addTransition( rightAnimateButton, SIGNAL(clicked()), rightWidgetStateSmall );
79: animation = new QPropertyAnimation( rightWidget, "geometry" );
80: animation->setDuration( 4 * 1000 );
81: rightMinimizeTransition->addAnimation( animation );
82:
右の赤いウィジェットについて、
83: stateMachine->setInitialState( parallelState );
84: stateMachine->start();
85:
86: topWidget.show();
87:
88: return app.exec();
89: }
おわりに
次回は、