PDA

View Full Version : Help using QStateMachine



d_stranz
7th October 2011, 23:49
I am trying to implement a state machine for handling a complex set of mouse and keyboard interactions with a graphical data plot. I have partially finished the state chart, and have more than 25 states defined so far. The types of interactions include zooming in and out, selecting, moving, and resizing objects, panning, etc. Nearly all of the transitions between states occur on mouse, key, or mouse wheel events. There are a few transitions which could be driven by external events (for example, a button or menu signal to transition between zoom and pan modes).

I am having trouble understanding how to extract event information from transitions.

For example, a mouse-based zoom operation involves 4 states: Idle, ZoomStart, ZoomTrack, and ZoomEnd. The transition from the Idle state into ZoomStart occurs when the left mouse button is pressed. The transition from ZoomStart to ZoomTrack occurs on mouse move, and from ZoomTrack to ZoomEnd on mouse up. On entry into each of the Zoom... states, I need to capture the mouse position.

In the widget that owns the state machine, I can define Q_PROPERTY variables to hold the initial, current, and final positions, but how do I set them when the transition occurs? All of the examples I have seen for QState::assignProperty() use essentially a constant for the QVariant, not some value that changes dynamically like a mouse position.

Should I derive custom QState classes and reimplement the QState::onEntry() method? Or should I connect to the QEventTransition::triggered() signals and somehow get back to the QEvent instance to pull out the data?

It would seem this is a common problem, but I haven't found an example (or answer here on the forum) that addresses this issue.

Can anyone give me some help, please? Simply a link to some example code would be sufficient. Thanks in advance.

Edit: a secondary question - is it possible explicitly set up a transition from a state back into itself? For example, ZoomTrack needs to be re-entered each time the mouse moves so the current mouse position can be updated (and a new rubberband drawn). Can I add a transition from ZoomTrack to ZoomTrack on QEvent::MouseMove?

d_stranz
9th October 2011, 04:29
No one has any help or advice?

wysota
9th October 2011, 10:58
I am having trouble understanding what you want, however I think QStateMachine might not be a good solution to your problem. I don't see any benefit of tracking the mouse state using an external state machine. I think it would be much easier to have a flag for marking the current mode and invoking code based on current mode from within mouse event handler methods. Of course you could make a state machine for it (and I'd suggest to make it a hierarchical one) but it will not make your code cleaner or better, it will just require your time to implement it.

d_stranz
9th October 2011, 17:59
Wysota, thanks for the reply. I already have a smaller implementation similar to your description, that handles zooming, panning, and selecting areas within the plot, but does not use the Qt state machine framework.

However, I now want to be able to add the ability to select and manipulate individual items within the plot (for example, to select a glyph marking a data point, or to add a new item that selects a region and then adjust its position). The logic in the mouse handlers starts to get very complex, keeping track of which state the mouse is in, whether an object is selected, being moved or resized, etc. It also becomes harder and harder to modify it - suppose I want to use the right button for some action, but also want to use context menus when that action is not "active"?

So, this is why I think a hierarchical (as you suggest) state machine would be better. It would allow me to encapsulate the responses to various mouse and key actions to the various states (and greatly simplify mouse handlers since each state would have no conditional branching based on other states).

But as I asked in my o.p., the QStateMachine framework allows you to build such a system, but it seems that in the base QAbstractState / QState classes, there isn't a way to find out -how- you got into that state. There is a QAbstractState:: onEntry() method, but to use it requires derivation of a new class for each state instead of simply using QState as-is.

The QState::assignProperty() method would do everything I need, if I could figure out a way to get the current mouse position assigned to a property on entry into a state.

Anyway, I will continue to pound my head against this wall until the wall falls down or I fall unconcious from blood loss.

wysota
9th October 2011, 18:08
You should think about decentralizing your code. Instead of stuffing everything into one event handler you should have separate components responsible for separate pieces of logic for your framework. Moreover if it makes a difference how you got into a particular state then most likely it seems you identified the states incorrectly. If you wish to set some values when you enter some state, then sure, you can reimplement onEntry(). However if I understand correctly what you are trying to do, I think it is better to subclass the transition class and reimplement QAbstractTransition::onTransition().

d_stranz
9th October 2011, 18:37
Instead of stuffing everything into one event handler you should have separate components responsible for separate pieces of logic for your framework.

Thus my interest in using the Qt state machine framework.


I think it is better to subclass the transition class and reimplement QAbstractTransition::onTransition().

Ah, that might do it. I actually don't care how I got into the state; I need to capture information from the event that caused the transition (mouse position and button, key code, or mouse wheel delta), then use that to assign properties.

Thanks for the help.

Edit: Yes, that will definitely work. Thanks.

wysota
10th October 2011, 07:52
Thus my interest in using the Qt state machine framework.
In my opinion this is not a correct approach. Using a state machine will not divide your code into logical components, you'll still have a single monster that does everything.