Amiga GUI program using a State Machine

This example assumes a window already created, and gadtools gadgets attached. One could alter the state machine so that it worked differently, advancing states as more stuff is initialized.

The actual state machine is rather simple:

The code is nearly as simple:

/*
 *   Example of state machine driven GUI program...
 */

typedef PROGEVENT (*FUNCPTR)(struct IntuiMessage *msg);

typedef enum e_prog_states {
    NO_DOC, EMPTY_DOC, SAVED_DOC, UNSAVED_DOC,
    QUERY_SAVE, QUERY_DELETE, ASK_FILENAME, QUITTING
} PROGSTATE;

typedef enum e_prog_events {
    NEW_BUTTON, CLOSE_BUTTON, OPEN_BUTTON, SAVE_BUTTON,
    KEY_EVENT, MOUSE_EVENT, OK_BUTTON, CANCEL_BUTTON,
    QUIT_EVENT, NO_EVENT
} PROGEVENT;

typedef struct {
    PROGSTATE           old_state;
    PROGEVENT           event;
    PROGSTATE           new_state;
    FUNCPTR             action;
} AUTOMATA_RECORD;

/*
 *   Note that we only define those combinations of
 *     state and event which are valid (or which require
 *     special error processing)...
 */
AUTOMATA_RECORD state_machine[] = {
    { NO_DOC, NEW_BUTTON, EMPTY_DOC, create_document },
    { NO_DOC, OPEN_BUTTON, SAVED_DOC, open_document },

    { EMPTY_DOC, CLOSE_BUTTON, NO_DOC, free_document },
    { EMPTY_DOC, KEY_EVENT, UNSAVED_DOC, doc_process_event },
    { EMPTY_DOC, MOUSE_EVENT, UNSAVED_DOC, doc_process_event },
    { EMPTY_DOC, OPEN_BUTTON, SAVED_DOC, free_and_open_doc },

    { SAVED_DOC, CLOSE_BUTTON, NO_DOC, free_document },
    { SAVED_DOC, KEY_EVENT, UNSAVED_DOC, doc_process_event },
    { SAVED_DOC, MOUSE_EVENT, UNSAVED_DOC, doc_process_event },

    { UNSAVED_DOC, KEY_EVENT, UNSAVED_DOC, doc_process_event },
    { UNSAVED_DOC, MOUSE_EVENT, UNSAVED_DOC, doc_process_event },
    { UNSAVED_DOC, CLOSE_BUTTON, QUERY_SAVE, save_requester },
    { UNSAVED_DOC, SAVE_BUTTON, SAVED_DOC, save_document },

    { QUERY_SAVE, OK_BUTTON, ASK_FILENAME, filename_requester },
    { QUERY_SAVE, CANCEL_BUTTON, QUERY_DELETE, delete_requester },

    { ASK_FILENAME, OK_BUTTON, NO_DOC, save_and_free_doc },
    { ASK_FILENAME, CANCEL_BUTTON, UNSAVED_DOC, NULL },

    { QUERY_DELETE, OK_BUTTON, NO_DOC, free_document },
    { QUERY_DELETE, CANCEL_BUTTON, UNSAVED_DOC, NULL }
};
int num_records = sizeof(state_machine) / sizeof(state_machine[0]);
PROGSTATE current_state = NO_DOC;

/*
 * Obviously, this is NOT complete code, but it should be
 *    enough to give you an idea of what could be done.
 */
int event_loop(struct Window *win)
{
    struct IntuiMessage *msg, my_msg;
    PROGEVENT event;
    int i;

    while (current_state != QUITTING) {
	WaitPort(win->UserPort);

	while ((msg = GT_GetIMsg(win->UserPort)) != NULL) {
	    memcpy(&my_msg,msg,sizeof(struct IntuiMessage));
	    GT_ReplyIMsg(msg);

	    if (my_msg.Class == GADGET_UP) {
		struct Gadget *gad = (struct Gadget *)my_msg.IAddress;
		event = gad->GadgetID;
	    } else if ((my_msg.Class == MOUSE_UP) || (my_msg.Class == MOUSE_DOWN))
		event = MOUSE_EVENT;
	    else if ((my_msg.Class == KEY_UP) || (my_msg.Class == KEY_DOWN))
		event = KEY_EVENT;
	    else if (my_msg.Class == CLOSEWINDOW)
		event = QUIT_EVENT;
	    else
		event = NO_EVENT;

	    /*
	     * This while-loop gives action functions a chance to
	     *    generate their own events (such as responses to
	     *    modal dialog boxes...
	     */
	    while (event != NO_EVENT) {
		for (i = 0; i < num_records; i++) {
		    if ((state_machine[i].old_state == current_state) &&
			(state_machine[i].event == event)) {
			if (state_machine[i].action)
			    event = (*(state_machine[i].action))(&my_msg);
			current_state = state_machine[i].new_state;
			break;
		    }
		}
	    }
	}
    }
}