Skip to content

QwtPicker State Machine

In a graphical interaction system, a user action is often a sequence of events rather than a single instant. For example:

  • Drag operation: Press mouse -> Move mouse -> Release mouse
  • Rectangle selection: Click first point -> Move mouse -> Click second point
  • Polygon drawing: Click multiple points -> Right-click to finish

Without a state machine, event handlers would need many if-else branches and state variables, which is hard to maintain.

Qwt's QwtPicker uses a state machine to manage the interaction process. The corresponding implementation is located in the qwt_picker_machine.h/cpp files.

Advantages of the QwtPicker State Machine

The state machine separates event-sequence processing from the actual operation:

  • The state machine is responsible for recognizing complete event sequences
  • QwtPicker is responsible for executing specific drawing, selection, and other operations

All QwtPicker state machines inherit from QwtPickerMachine and provide a unified transition() interface. QwtPicker does not need to know which specific interaction mode is in use.

Adding a new interaction mode only requires inheriting from QwtPickerMachine and implementing the state transition logic, without modifying QwtPicker core code.

State transitions during the interaction process are managed automatically, avoiding errors caused by manually maintaining state variables.

How the QwtPicker State Machine Works

The QwtPicker state machine mechanism combines an observer pattern with a state pattern:

1
Event flow: QEvent -> QwtPicker -> QwtPickerMachine -> Commands -> QwtPicker methods

QwtPicker acts as an event filter installed on the parent widget. It receives events from the parent window, dispatches them to the corresponding widgetXXXEvent methods, and invokes the state machine for transitions.

Specific drawing actions are abstracted by QwtPicker into the following states:

State Description
begin() Start selection
append() Add point
move() Move point
remove() Remove point
end() End selection

The specific operations that compose moving, removing, and adding points are managed by the state machine. For example: clicking a point, moving the mouse, releasing the mouse, and ending the selection. This way, QwtPicker handles mouse and keyboard events without also tracking state transitions.

The core of the state machine processing is the transition method. The widgetXXXEvent methods in QwtPicker all call the transition method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
void QwtPicker::transition(const QEvent* event)
{
    if (!m_data->stateMachine)
        return;

    // State machine analyzes the event and returns a command sequence
    const QList<QwtPickerMachine::Command> commandList =
        m_data->stateMachine->transition(*this, event);

    // Get current mouse position
    QPoint pos;
    switch (event->type()) {
        case QEvent::MouseButtonPress:
        case QEvent::MouseMove:
            pos = static_cast<const QMouseEvent*>(event)->pos();
            break;
        default:
            pos = parentWidget()->mapFromGlobal(QCursor::pos());
    }

    // Execute commands returned by the state machine
    for (int i = 0; i < commandList.count(); i++) {
        switch (commandList[i]) {
            case QwtPickerMachine::Begin:
                begin();        // Start selection
                break;
            case QwtPickerMachine::Append:
                append(pos);    // Add point
                break;
            case QwtPickerMachine::Move:
                move(pos);      // Move point
                break;
            case QwtPickerMachine::Remove:
                remove();       // Remove point
                break;
            case QwtPickerMachine::End:
                end();          // End selection
                break;
        }
    }
}

This transition() method receives an event and returns a command sequence in commandList. Each state machine instance inherits from QwtPickerMachine and implements transition() to produce that sequence.

The following flowchart shows the path:

flowchart TD
    A[User Interaction Event<br>Mouse/Keyboard Event] --> B{QwtPicker Event Filter}

    B --> C[Dispatch Event to Corresponding Method]
    C --> D{widgetMousePressEvent etc.}

    D --> E[Call transition Method]
    E --> F{QwtPickerMachine<br>State Manager}

    subgraph F [State Machine Internal Processing]
        F1[Receive Event] --> F2[Match Event Pattern]
        F2 --> F3[State Transition Logic]
        F3 --> F4[Generate Command Sequence]
        F4 --> F5[Update Internal State]
    end

    F --> G[Return Command Sequence<br>Begin/Append/Move/Remove/End]

    G --> H[QwtPicker Executes Commands]

    subgraph H [Command Executor]
        H1[begin] --> H2[append]
        H2 --> H3[move]
        H3 --> H4[remove]
        H4 --> H5[end]
    end

    H --> I[Specific Business Logic]

    subgraph I [QwtPicker Business Processing]
        I1[Update pickedPoints Array]
        I2[Show/Hide Rubber Band]
        I3[Update Tracker Display]
        I4[Emit Corresponding Signals]
    end

    I --> J[State Feedback]
    J --> F

    style F fill:#e1f5fe
    style H fill:#f3e5f5
    style I fill:#e8f5e8

Taking QwtPickerDragPointMachine as an example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
QList<QwtPickerMachine::Command> QwtPickerDragPointMachine::transition(
    const QwtEventPattern& eventPattern, const QEvent* event)
{
    QList<QwtPickerMachine::Command> cmdList;

    switch (event->type()) {
        case QEvent::MouseButtonPress:
            if (eventPattern.mouseMatch(QwtEventPattern::MouseSelect1,
                static_cast<const QMouseEvent*>(event))) {
                if (state() == 0) {  // Initial state
                    cmdList += Begin;   // Start selection
                    cmdList += Append;  // Add first point
                    setState(1);       // Enter drag state
                }
            }
            break;

        case QEvent::MouseMove:
        case QEvent::Wheel:
            if (state() != 0)          // If in drag state
                cmdList += Move;       // Continue moving
            break;

        case QEvent::MouseButtonRelease:
            if (state() != 0) {
                cmdList += End;        // End selection
                setState(0);           // Return to initial state
            }
            break;
    }
    return cmdList;
}

The sequence diagram for a mouse drag looks like this:

sequenceDiagram
    participant User as User
    participant System as Event System
    participant Picker as QwtPicker
    participant Machine as State Machine (DragPointMachine)
    participant Actions as QwtPicker Actions

    Note over User,Actions: t0: Mouse press time

    User->>System: Mouse press
    System->>Picker: widgetMousePressEvent()
    Picker->>Machine: transition(mouse press event)
    Note right of Machine: State 0 -> State 1
    Machine-->>Picker: [Begin, Append]
    Picker->>Actions: begin()
    Picker->>Actions: append(pos)
    Note over Actions: Activate picker<br>Add first point

    Note over User,Actions: t1-t3: Mouse move process

    loop Each mouse move
        User->>System: Mouse move
        System->>Picker: widgetMouseMoveEvent()
        Picker->>Machine: transition(mouse move event)
        Note right of Machine: Stay in State 1
        Machine-->>Picker: [Move]
        Picker->>Actions: move(pos)
        Note over Actions: Update last point position<br>Redraw rubber band in real-time
    end

    Note over User,Actions: t4: Mouse release time

    User->>System: Mouse release
    System->>Picker: widgetMouseReleaseEvent()
    Picker->>Machine: transition(mouse release event)
    Note right of Machine: State 1 -> State 0
    Machine-->>Picker: [End]
    Picker->>Actions: end()
    Note over Actions: End selection<br>Validate and emit selected signal

In QwtPickerMachine, the parameter of the setState() function represents the internal state of the state machine. These state values are defined and maintained by each state machine individually and have no unified meaning. Different state machines use different state values to represent their specific interaction phases.

For example, QwtPickerDragPointMachine has state values 0 and 1, representing the initial state and drag state respectively.

State transitions can be described using a state transition diagram. The state transition diagram for QwtPickerDragPointMachine is as follows:

stateDiagram-v2
    [*] --> State0: Initialization complete
    State0 --> State1: Mouse press -> Begin + Append
    State1 --> State1: Mouse move -> Move
    State1 --> State0: Mouse release -> End

The state machine turns event types into plot operations.

Complex interaction logic is decomposed into:
- Event recognition (state machine)
- Command execution (QwtPicker)
- Drawing (rubber band, tracker)

graph TB
    A[Event Types] --> B{State Machine Processing}

    subgraph A [Input Event Types]
        A1[QEvent::MouseButtonPress]
        A2[QEvent::MouseButtonRelease]
        A3[QEvent::MouseMove]
        A4[QEvent::KeyPress]
        A5[QEvent::KeyRelease]
        A6[QEvent::Wheel]
        A7[QEvent::Enter]
        A8[QEvent::Leave]
    end

    B --> C[DragPointMachine]
    B --> D[ClickPointMachine]
    B --> E[DragRectMachine]
    B --> F[TrackerMachine]

    subgraph C [Drag Point Selection State Machine]
        C1[Press: Begin + Append<br>Enter State 1]
        C2[Move: Move<br>Stay in State 1]
        C3[Release: End<br>Return to State 0]
        C4[Key: Toggle State]
    end

    subgraph D [Click Point Selection State Machine]
        D1[Press: Begin + Append + End<br>Single-step completion]
        D2[Move: No action]
        D3[Release: No action]
    end

    subgraph E [Drag Rectangle Selection State Machine]
        E1[Press: Begin + Append + Append<br>Enter State 2]
        E2[Move: Move<br>Stay in State 2]
        E3[Release: End<br>Return to State 0]
    end

    subgraph F [Tracker State Machine]
        F1[Enter: Begin + Append<br>Enter State 1]
        F2[Move: Move<br>Stay in State 1]
        F3[Leave: Remove + End<br>Return to State 0]
    end

    style A fill:#f5f5f5
    style C fill:#e8f5e8
    style D fill:#e3f2fd
    style E fill:#fff3e0
    style F fill:#f3e5f5

Built-in QwtPicker State Machines

1. QwtPickerTrackerMachine (Tracker State Machine)

Purpose

Real-time tracking of mouse position with information display, without performing any selection operations.

State Transition Process

The list below enumerates, for each QEvent type, the command sequence produced by QwtPickerTrackerMachine::transition() and the resulting state: entering the widget or moving the mouse emits Begin + Append, subsequent moves emit Move, and leaving the widget emits Remove + End:

1
2
3
4
Initial state: 0 (not tracking)
- QEvent::Enter / QEvent::MouseMove -> [Begin, Append] -> State 1
- QEvent::MouseMove (State 1) -> [Move] -> State 1
- QEvent::Leave -> [Remove, End] -> State 0

State Transition Diagram

The state transition diagram below visualizes the same logic; the meaning of each state (State 0 = not tracking, State 1 = tracking) is defined by QwtPickerTrackerMachine itself via the setState() calls inside its transition() implementation:

stateDiagram-v2
    [*] --> State0: Initialization
    State0 --> State1: Enter/Move -> Begin + Append
    State1 --> State1: Move -> Move
    State1 --> State0: Leave -> Remove + End

Use Cases

  • Coordinate display
  • Mouse hover tooltips
  • Real-time value display
  • Position feedback for measurement tools

2. QwtPickerClickPointMachine (Click Point Selection State Machine)

Purpose

Select a point through a single click.

State Transition Process

QwtPickerClickPointMachine::transition() completes a selection in one step: a press matching MouseSelect1, or a key matching KeySelect1, emits Begin, Append, and End at once and leaves the machine in State 0 — there is no intermediate state:

1
2
3
Initial state: 0 (waiting for click)
- QEvent::MouseButtonPress (matches MouseSelect1) -> [Begin, Append, End] -> State 0
- QEvent::KeyPress (matches KeySelect1) -> [Begin, Append, End] -> State 0

State Transition Diagram

The diagram below shows this one-shot machine as a self-loop on State 0; the state values are those maintained by QwtPickerClickPointMachine through its internal setState() calls:

stateDiagram-v2
    [*] --> State0: Initialization
    State0 --> State0: Mouse press/Key press -> Begin + Append + End

Use Cases

  • Selecting data points
  • Marking specific positions
  • Simple coordinate selection
  • Quick positioning operations

3. QwtPickerDragPointMachine (Drag Point Selection State Machine)

Purpose

Precisely select a point position through drag operations.

State Transition Process

QwtPickerDragPointMachine::transition() starts the drag on a MouseSelect1 press (Begin + Append, entering State 1), tracks mouse moves and wheel events with Move, and finishes on release with End; a KeySelect1 key press toggles between State 0 and State 1 for keyboard operation:

1
2
3
4
5
6
Initial state: 0 (waiting to start)
- QEvent::MouseButtonPress (matches MouseSelect1) -> [Begin, Append] -> State 1
- QEvent::MouseMove (State 1) -> [Move] -> State 1
- QEvent::Wheel (State 1) -> [Move] -> State 1
- QEvent::MouseButtonRelease (State 1) -> [End] -> State 0
- QEvent::KeyPress (matches KeySelect1) -> Toggle State 0/1

State Transition Diagram

The state transition diagram below summarizes these rules; State 0 (idle) and State 1 (dragging) are the values QwtPickerDragPointMachine assigns via setState() in its transition() implementation:

stateDiagram-v2
    [*] --> State0: Initialization
    State0 --> State1: Mouse press -> Begin + Append
    State1 --> State1: Mouse move/Wheel -> Move
    State1 --> State0: Mouse release -> End
    State0 --> State1: Key press -> Begin + Append
    State1 --> State0: Key press -> End

Use Cases

  • Precise position selection (e.g., real-time Panner)
  • Point selection requiring fine adjustment
  • Drag positioning tools
  • High-precision measurement

4. QwtPickerClickRectMachine (Click Rectangle Selection State Machine)

Purpose

Define a rectangular area through two clicks.

State Transition Process

QwtPickerClickRectMachine::transition() spans three states: the first press emits Begin + Append (State 1), the first release appends the opposite corner (State 2), and only the next press emits End and returns to State 0 — the rectangle is thus fixed by two separate clicks:

1
2
3
4
5
6
Initial state: 0 (waiting for first click)
- QEvent::MouseButtonPress (matches MouseSelect1) -> [Begin, Append] -> State 1
- QEvent::MouseMove (State 1) -> [Move] -> State 1
- QEvent::MouseButtonRelease (State 1) -> [Append] -> State 2
- QEvent::MouseButtonPress (State 2) -> [End] -> State 0
- Keyboard events have corresponding multi-step transitions

State Transition Diagram

The diagram below shows this two-click sequence; State 0/1/2 are exactly the values maintained by QwtPickerClickRectMachine via setState(), marking idle, first corner placed, and rectangle awaiting confirmation:

stateDiagram-v2
    [*] --> State0: Initialization
    State0 --> State1: First press -> Begin + Append
    State1 --> State1: Move -> Move
    State1 --> State2: First release -> Append
    State2 --> State0: Second press -> End

Use Cases

  • Precise rectangular area selection
  • Zoom area requiring precise positioning
  • Coordinate-aligned rectangle drawing
  • Architectural drawing measurement

5. QwtPickerDragRectMachine (Drag Rectangle Selection State Machine)

Purpose

Quickly select a rectangular area through drag operations.

State Transition Process

QwtPickerDragRectMachine::transition() emits Begin + Append + Append on the initial MouseSelect1 press and jumps directly to State 2, where mouse moves produce Move (resizing the rubber-band rectangle) and the release produces End; KeySelect1 toggles between State 0 and State 2:

1
2
3
4
5
Initial state: 0 (waiting to start)
- QEvent::MouseButtonPress (matches MouseSelect1) -> [Begin, Append, Append] -> State 2
- QEvent::MouseMove (State 2) -> [Move] -> State 2
- QEvent::MouseButtonRelease (State 2) -> [End] -> State 0
- QEvent::KeyPress (matches KeySelect1) -> Toggle State 0/2

State Transition Diagram

The diagram below contrasts with the click-rectangle machine by skipping State 1 entirely; the state values are those QwtPickerDragRectMachine assigns through setState() — State 0 (idle) and State 2 (dragging a live rectangle):

stateDiagram-v2
    [*] --> State0: Initialization
    State0 --> State2: Mouse press -> Begin + Append + Append
    State2 --> State2: Mouse move -> Move
    State2 --> State0: Mouse release -> End
    State0 --> State2: Key press -> Begin + Append + Append
    State2 --> State0: Key press -> End

Use Cases

  • Quick rectangular area selection
  • Interactive zoom
  • Area screenshot tools
  • Batch selection operations

6. QwtPickerDragLineMachine (Drag Line Selection State Machine)

Purpose

Select a line segment through drag operations.

State Transition Process

QwtPickerDragLineMachine::transition() works like the drag-rectangle machine but selects a line segment: the initial press emits Begin + Append + Append (start and end points) and enters State 1, moves update the end point with Move, and the release emits End; KeySelect1 toggles State 0/1:

1
2
3
4
5
Initial state: 0 (waiting to start)
- QEvent::MouseButtonPress (matches MouseSelect1) -> [Begin, Append, Append] -> State 1
- QEvent::MouseMove (State 1) -> [Move] -> State 1
- QEvent::MouseButtonRelease (State 1) -> [End] -> State 0
- QEvent::KeyPress (matches KeySelect1) -> Toggle State 0/1

State Transition Diagram

The state transition diagram below shows this two-point drag; State 0 and State 1 are defined by QwtPickerDragLineMachine's own setState() calls — idle versus dragging the line's end point:

stateDiagram-v2
    [*] --> State0: Initialization
    State0 --> State1: Mouse press -> Begin + Append + Append
    State1 --> State1: Mouse move -> Move
    State1 --> State0: Mouse release -> End
    State0 --> State1: Key press -> Begin + Append + Append
    State1 --> State0: Key press -> End

Use Cases

  • Distance measurement tools
  • Line segment drawing
  • Direction indicators
  • Angle measurement

7. QwtPickerPolygonMachine (Polygon Selection State Machine)

Purpose

Create a polygon area through multiple clicks.

State Transition Process

QwtPickerPolygonMachine::transition() collects vertices one by one: the first MouseSelect1 press emits Begin + Append + Append (State 1), each later press adds a vertex with Append, moves preview the current edge with Move, and a press matching MouseSelect2 closes the polygon with End:

1
2
3
4
5
6
Initial state: 0 (not started)
- QEvent::MouseButtonPress (matches MouseSelect1) -> [Begin, Append, Append] -> State 1
- QEvent::MouseButtonPress (State 1, matches MouseSelect1) -> [Append] -> State 1
- QEvent::MouseMove (State 1) -> [Move] -> State 1
- QEvent::MouseButtonPress (matches MouseSelect2) -> [End] -> State 0
- Keyboard events have corresponding polygon drawing logic

State Transition Diagram

The diagram below shows the vertex-accumulation loop in State 1 and its exit via the second mouse button; the states are those maintained by QwtPickerPolygonMachine through its setState() calls:

stateDiagram-v2
    [*] --> State0: Initialization
    State0 --> State1: First press -> Begin + Append + Append
    State1 --> State1: Subsequent press -> Append
    State1 --> State1: Move -> Move
    State1 --> State0: Right-click/Second button -> End

Use Cases

  • Complex area selection
  • Polygon drawing tools
  • Custom shape annotations
  • Geographic information system area selection

Summary

All built-in state machines map events to the same five commands (begin, append, move, remove and end), and each defines its own state values. To add an interaction mode, inherit from QwtPickerMachine and implement transition(); QwtPicker itself does not need to change.