Lomse Hacking Guide
Lomse uses the ‘taskmaster’ architecture, described in [1]. In this architecture, to decide on the appropriate action, the Interactor delegates on a Task that is chosen by using the State pattern: The Task is an abstract object and the real object receiving the user event depends on the Interactor‘s state. Each derivative of Task encapsulates an interaction with the user and eventually communicates with the document. The appropriate derivative of Task is selected by a menu command or click on a palette item, etc.
All Task derivatives are finite state machines. The events that drive the finite state machine are the mouse and keyboard events that are delegated by the Interactor to the Task. Indeed, the Task class consists of little more than a set of pure virtual functions representing these events.
The Task responsibility is to decide on the appropriate action for an event or sequence of events.
A tasks will be associated with a View as a result of invoking method Interactor::select_task(). The user application must invoke this method as a result of some kind of user action; perhaps a menu choice, a keyboard shortcut, or a click in the appropriate button of a palette or toolbar.
Once associated with the view, the task will continue to collect events and communicate with the Document class until its lifecycle ends. This may be as a result of completing its job, or because it was somehow cancelled. Then another task will replace it.
Thus, the current task within a View represents the global state of that View. It defines how the View will react to events.
In order to implement this, a suite of other interfaces are required within Task. These interfaces provide for task cancellation, task restart, backstepping, pausing, etc.
When a View object is created, its associated Interactor will be initialized with an instace o class SelectionTask.
Each task is isolated from the associated view and document classes. The tasks can be changed without affecting any other part of the system. Indeed, the interactions can be reused in other applications that have different document and view classes. Also the document and view classes can be reused in systems that have different interactions.
All Task are finite state machines. The events that drive the finite state machine are the mouse and keyboard events that are delegated by the Interactor to the Task. Indeed, the Task class consists of little more than a set of pure virtual functions representing these events. To implement the finite state machine representing a task I prefer to use nested switch-case statements. This type of implementation is more compact and it is easy to understand and follow the finite state machine without relying on state transition diagrams or tables.
The operation of this state machine is quite simple. When the task is associated with the view, its method ini_task() is invoked and it places the task in its initial state and kicks everything off. When, finally, it arrives to the ‘Done’ state, it remains there forever unless the task is re-started (by invoking init_task again).
In following transition tables:
Current State | Event | Next State | Action |
---|---|---|---|
WaitingForFirstPoint | left_down | WaitingForSecondPoint | start_scroll |
WaitingForSecondPoint | move_mouse | WaitingForSecondPoint | do_scroll |
left_up | WaitingForFirstPoint | end_scroll |
Current State | Event | Next State | Action |
---|---|---|---|
WaitingForFirstPoint | left_down | WaitingForPoint2Left | record_first_point |
right_down | WaitingForPoint2Right | record_first_point | |
move_mouse | WaitingForFirstPoint | mouse_in_out | |
WaitingForPoint2Left | move_mouse | WaitingForPoint2Left | track_sel_rectangle |
left_up | WaitingForFirstPoint | select_objects_or_click | |
WaitingForPoint2Right | right_up | WaitingForFirstPoint | select_object_at_first_point, show_contextual_menu |
[1] | “TASKMASTER: An Architecture Pattern for GUI Applications”, Robert C. Martin, James W. Newkirk & Bhama Rao. Available at http://www.objectmentor.com/resources/articles/taskmast.pdf |