UI Actions: Difference between revisions

From vice-emu
Jump to navigation Jump to search
(Start API section)
No edit summary
 
(10 intermediate revisions by the same user not shown)
Line 1: Line 1:
This page explains the UI actions system used in the Gtk3 port and which is supposed to be used in the SDL port and future ports.
<span id="ui-actions"></span>
= UI Actions =


== Rationale ==
<span id="introduction"></span>
== Introduction ==


In the Gtk3 UI the concept of "UI actions" was introduced to allow for a unified interface to trigger certain actions in the emulator and its user interface . UI actions decouple hotkeys from menu items so hotkeys can be defined that do not have a corresponding menu item. This interface will also allow calling actions from customizable joystick mappings, reusing the code triggered by menu items and/or hotkeys.
The UI actions are a way to trigger specific emulators actions from menu items, hotkeys or controller buttons without code duplication and letting VICE deal with issues like threading and avoiding showing multiple dialogs or retriggering actions that haven’t finished yet.


Since most user interactions are independent of what UI toolkit is used it makes sense to generalize these interactions and make all current and future UIs use this UI actions system. For example "soft reset" or "attach disk to unit #8" may use different dialogs, menu items or hotkeys, the underlying logic (and code) will be the same. So providing a predefined -- and extendable -- set of UI actions will aid in development of future UIs.
UI actions are managed through an API living in <code>src/arch/shared/uiactions.h</code>. Actions are triggered by specifying action ID in menu items, or by using an action name in hotkey and joy map files (which VICE translates to IDs).


== Mechanism ==
The file <code>src/arch/shared/uiactions.h</code> contains the UI action IDs available, while the file <code>src/arch/shared/uiactions.c</code> contains a table mapping these IDs to action names.


The UI actions API code resides in [https://sourceforge.net/p/vice-emu/code/HEAD/tree/trunk/vice/src/arch/shared/uiactions.h src/arch/uiactions.h] and [https://sourceforge.net/p/vice-emu/code/HEAD/tree/trunk/vice/src/arch/shared/uiactions.c src/arch/uiactions.c]. The code is documented using Doxygen docblocks, so the documentation can be generated with <tt>doc/mkdoxy.sh</tt> or just read in the files themselves.
New actions can be created by adding a unqiue ID in the header file and an entry (with a unique name) in the aformentioned table in the source file. An ''action handler'' is required to implement the action’s logic, this handler is a callback function that gets passed some information about itself (the entry in the table, an <code>ui_action_map_t*</code>). The action will need to be registered with the UI actions system via <code>ui_actions_register()</code>. (See below)


UI actions are triggered using a unique ID and calling a function: <tt><b>ui_action_trigger</b>(<i>action-ID</i>)</tt>. The IDs are available in the [https://sourceforge.net/p/vice-emu/code/HEAD/tree/trunk/vice/src/arch/shared/uiactions.h src/arch/uiactions.h] header, these are all prefixed with <tt>ACTION_</tt>.
<span id="basic-api-usage"></span>
== Basic API usage ==


At emulator boot the UI code calls <tt><b>ui_actions_init</b>()</tt> to initialize the UI actions system, sets a UI-specific dispatch function with <tt><b>ui_actions_set_dispatch</b>(<i>dispatch-func</i>)</tt> and registers UI action handlers with <tt><b>ui_actions_register</b>(<i>list-of-actions</i>)</tt>.
The basic functions and types used by the UI actions are relatively simple. A selection of the most used functions and types is listed here.


=== Example ===
<span id="general-functions-dealing-with-the-ui-actions-system-itself"></span>
Example code helps a lot, the following is taken from the Gtk3 UI code and cobbled together.
=== General functions dealing with the UI actions system itself: ===


Here we have a few action handler implementations (from src/arch/gtk3/actions-machine.c. Some of this is Gtk3-specific, other handlers can be moved into generic code:
<syntaxhighlight lang="c">void  ui_actions_init          (void);
<syntaxhighlight lang="c" line>
void ui_actions_set_dispatch  (void (*dispatch)(ui_action_map_t *));
static void confirm_exit_callback(GtkDialog *dialog, gboolean result)
void  ui_actions_register      (const ui_action_map_t *mappings);
{
void  ui_actions_shutdown      (void);</syntaxhighlight>
    if (result) {
VICE will call <code>ui_actions_init()</code> and <code>ui_actions_shutdown()</code>, and calling <code>ui_actions_set_dispatch()</code> is optional. So the only function required to use is <code>ui_actions_register()</code>.
        mainlock_release();
        archdep_vice_exit(0);
        mainlock_obtain();
    }
    /* mark action finished in case the user selected "No" */
    ui_action_finish(ACTION_QUIT);
}


/** \brief  Quit emulator, possibly popping up a confirmation dialog */
The function <code>ui_actions_set_dispatch()</code> can be used to install a function that “intercepts” an action before it’s triggered by VICE, in which case the dispatch function is responsible for calling <code>ui_action_trigger(map-&gt;action)</code>. The dispatch function is used in the Gtk3 port to make sure an action that is marked as requiring the UI thread is executed on the UI thread. More on this later.
static void quit_action(void)
{
    int confirm = FALSE;


    resources_get_int("ConfirmOnExit", &confirm);
The '''<code>ui_action_map_t</code>''' type is used to register actions and to access information on an action. It maps action IDs to actions handlers and hotkeys.
    if (!confirm) {
        ui_action_finish(ACTION_QUIT);
        archdep_vice_exit(0);
        return;
    }


    vice_gtk3_message_confirm(
<syntaxhighlight lang="c">typedef struct ui_action_map_s {
            confirm_exit_callback,
     int    action;          /**< action ID */
            "Exit VICE",
            "Do you really wish to exit VICE?");
}
 
/** \brief  Open the monitor */
static void monitor_open_action(void)
{
    int server = 0;
 
    /* don't spawn the monitor if the monitor server is running */
    resources_get_int("MonitorServer", &server);
    if (server == 0) {
        vsync_suspend_speed_eval();
        if (ui_pause_active()) {
            ui_pause_enter_monitor();
        } else {
            monitor_startup_trap();
        }
    }
}
 
/** \brief  Trigger soft reset of the machine */
static void reset_soft_action(void)
{
    machine_trigger_reset(MACHINE_RESET_MODE_SOFT);
    ui_pause_disable();
}
 
/** \brief  Trigger hard reset of the machine */
static void reset_hard_action(void)
{
    machine_trigger_reset(MACHINE_RESET_MODE_HARD);
    ui_pause_disable();
}
 
/** \brief  Toggle PET userport diagnostic pin */
static void diagnostic_pin_toggle_action(void)
{
    int active = 0;
 
    resources_get_int("DiagPin", &active);
    resources_set_int("DiagPin", active ? 0 : 1);
}
</syntaxhighlight>
 
This is the list of action handlers defined above we'll be passing to <tt>ui_actions_register()</tt>:
<syntaxhighlight lang="c" line>
/** \brief  List of machine-related actions */
static const ui_action_map_t machine_actions[] = {
     {
        .action = ACTION_QUIT,
        .handler = quit_action,
        .blocks = true,
        .dialog = true
    },
    {
        .action = ACTION_MONITOR_OPEN,
        .handler = monitor_open_action,
        .uithread = true
    },
    {
        .action = ACTION_RESET_SOFT,
        .handler = reset_soft_action
    },
    {
        .action = ACTION_RESET_HARD,
        .handler = reset_hard_action
    },
    {
        .action = ACTION_DIAGNOSTIC_PIN_TOGGLE,
        .handler = diagnostic_pin_toggle_action,
        /* no need for UI thread, the status bar code will update the LED when
        * it runs */
        .uithread = false
    },


     UI_ACTION_MAP_TERMINATOR
     /*
};
    * Action handler data
</syntaxhighlight>
    */


Here we initialize the UI actions system, set a dispatch handler and register the action handlers listed above:
     void (*handler)(struct ui_action_map_s*); /**< function handling the action */
<syntaxhighlight lang="c" line>
     void *data;           /**< optional user data */
/*
* Helper that pushes the handler on the UI thread (details will follow)
*/
static gboolean ui_action_dispatch_impl(gpointer data)
{
     void (*handler)(void) = data;
    handler();
    return FALSE;
}
 
/*
* This is the action dispatch function, whenever ui_action_trigger() is called this
* function is called in response.
*/
static void ui_action_dispatch(const ui_action_map_t *map)
{
    if (mainlock_is_vice_thread()) {
        /* we're on the main thread, push to UI thread */
        gdk_threads_add_timeout(0, ui_action_dispatch_impl, (gpointer)(map->handler));
    } else {
        /* we're already on the UI thread */
        map->handler();
    }
}
 
/* Initialize UI actions */
ui_actions_init();
 
/* Set dispatch function */
ui_actions_set_dispatch(ui_action_dispatch);
 
/* Register some actions */
ui_actions_register(machine_actions);
</syntaxhighlight>
 
On emulator shutdown call the UI actions shutdown function:
<syntaxhighlight lang="c" line>
ui_actions_shutdown();
</syntaxhighlight>
 
Some details of the API are already shown here, such as actions having certain properties: does an action pop up a dialog? does an action need to run on the UI thread? does an action need to block before it can be triggered again? This will be explained in the next section.
 
Hotkey files (<tt>.vhk</tt>) used in the Gtk3 UI refer to these actions with strings as IDs, for better readability, manual editing and to allow changing the internal integer IDs without breaking these files. A table of mappings of action integer IDs to strings can be found in [https://sourceforge.net/p/vice-emu/code/HEAD/tree/trunk/vice/src/arch/shared/uiactions.c src/arch/shared/uiactions.c], in the <tt><b>action_info_list</b>[]</tt> array, at the time of writing at [https://sourceforge.net/p/vice-emu/code/HEAD/tree/trunk/vice/src/arch/shared/uiactions.c#l67 line 67]. The table also provides descriptions of each action for use in the UI (e.g. the Gtk3 hotkeys editor widget) and a bitmask of which emulators support the action; all this is accessible through the API.
 
== API ==
 
=== Main functions ===
 
The API is exposed in the [https://sourceforge.net/p/vice-emu/code/HEAD/tree/trunk/vice/src/arch/shared/uiactions.h src/arch/uiactions.h] header. The main functions used are, as seen in the example code:
<syntaxhighlight lang="c" line>
void ui_actions_init        (void);
void ui_actions_set_dispatch(void (*dispatch)(const ui_action_map_t *));
void ui_actions_register    (const ui_action_map_t *mappings);
void ui_actions_shutdown    (void);
</syntaxhighlight>
 
=== The ui_action_map_t struct ===
 
The <tt><b>ui_action_map_t</b></tt> typedef can be found in the header as well:
<syntaxhighlight lang="c" line>
/** \brief  Mapping of an action ID to a handler
*/
typedef struct ui_action_map_s {
    int    action;          /**< action ID */
     void (*handler)(void); /**< function handling the action */


     /* modes */
     /* modes */
Line 203: Line 51:
     /* state */
     /* state */
     bool  is_busy;        /**< action is busy */
     bool  is_busy;        /**< action is busy */
} ui_action_map_t;
</syntaxhighlight>


==== Members ====
    /* Hotkey data left out */
} ui_action_map_t;</syntaxhighlight>
The “Hotkey data” can be ignored: UI actions and hotkeys are closely related, and VICE uses the same struct to store hotkey data.
 
In its simplest form an initialization of the map requires providing values for <code>.action</code> and <code>.handler</code>. When providing initializers for UI action maps please use C99 designated initializers: this looks a lot cleaner and allows for adding members to the UI action map type without causing compiler warnings with existing code.
 
<span id="simple-example-of-an-action-handler"></span>
==== Simple example of an action handler ====
 
<syntaxhighlight lang="c">/* Action handler for toggling warp mode */
static void warp_mode_toggle_action(ui_action_map_t *self)
{
    vsync_set_warp_mode(!vsync_get_warp_mode());
}


===== action =====
/* List of actions to register (just one here) */
Pretty self-explanatory, an integer containing the unique ID as found in the header as <tt>ACTION_</tt> defines.
static const ui_action_map_t some_actions[] = {
    {  .action = ACTION_WARP_MODE_TOGGLE,
        .handler = warp_mode_toggle_action
    },
    UI_ACTION_MAP_TERMINATOR    /* macro used to terminate a list of actions */
};


===== handler =====
The function to be called when <tt><b>ui_action_trigger</b>()</tt> is invoked. It takes no arguments and returns nothing.


===== blocks =====
/* Register the action: */
A boolean indicating the same action cannot be triggered again until it is marked finished with <tt><b>ui_action_finish</b>()</tt>. Actions that require user interaction or take time to complete can set this to <tt>true</tt>.
ui_actions_register(some_actions);</syntaxhighlight>
Here we have a single action handler to toggle warp mode, in this case it does not use its ''self'' argument, but the argument can be used to access, for example, the action’s<code>.id</code> member or the <code>.data</code> member to be able to write an action handler that can be used for multiple IDs.


===== dialog =====
We register the action with a call to <code>ui_actions_register()</code>, after which VICE will call <code>warp_mode_toggle_action()</code> when a menu item, hotkey or controller button is used that is mapped to <code>ACTION_WARP_MODE_TOGGLE</code>.
A boolean indicating the action shows a dialog. Since we don't want a user pressing a controller button mapped to "smart-attach" twenty times and the UI showing twenty dialogs, the UI action system allows only a single dialog active at a time for actions having set this to <tt>true</tt>.
This flag also implies the action must be run on the UI thread.


===== uithread =====
<span id="functions-dealing-with-individual-actions"></span>
A boolean indicating the action must be run on the UI thread. Due to the multi-threaded nature of current VICE any non-UI thread cannot directly call UI code, so any action that shows dialogs or updates UI elements must run on the UI thread. The <tt><i>dispatch</i></tt> function set by <tt><b>ui_actions_set_dispatch</b>()</tt> is responsible for handling thread-safety.
=== Functions dealing with individual actions: ===


===== is_busy =====
<syntaxhighlight lang="c">void        ui_action_trigger         (int action);
Internal flag used for blocking actions. It is set by the UI action system when <tt><b>ui_action_trigger</b>()</tt> function is called with a blocking action and cleared when the <tt><b>ui_action_finish</b>()</tt> function is called for that same action (inside the action handler).
void        ui_action_finish          (int action);
On registration of actions this is always set to <tt>false</tt> so just leave it out when using C99 designated initializers.
int        ui_action_get_id          (const char *name);
const char *ui_action_get_name        (int action);
const char *ui_action_get_desc        (int action);
char *      ui_action_get_hotkey_label(int action);</syntaxhighlight>

Latest revision as of 08:20, 2 October 2023

UI Actions

Introduction

The UI actions are a way to trigger specific emulators actions from menu items, hotkeys or controller buttons without code duplication and letting VICE deal with issues like threading and avoiding showing multiple dialogs or retriggering actions that haven’t finished yet.

UI actions are managed through an API living in src/arch/shared/uiactions.h. Actions are triggered by specifying action ID in menu items, or by using an action name in hotkey and joy map files (which VICE translates to IDs).

The file src/arch/shared/uiactions.h contains the UI action IDs available, while the file src/arch/shared/uiactions.c contains a table mapping these IDs to action names.

New actions can be created by adding a unqiue ID in the header file and an entry (with a unique name) in the aformentioned table in the source file. An action handler is required to implement the action’s logic, this handler is a callback function that gets passed some information about itself (the entry in the table, an ui_action_map_t*). The action will need to be registered with the UI actions system via ui_actions_register(). (See below)

Basic API usage

The basic functions and types used by the UI actions are relatively simple. A selection of the most used functions and types is listed here.

General functions dealing with the UI actions system itself:

void  ui_actions_init           (void);
void  ui_actions_set_dispatch   (void (*dispatch)(ui_action_map_t *));
void  ui_actions_register       (const ui_action_map_t *mappings);
void  ui_actions_shutdown       (void);

VICE will call ui_actions_init() and ui_actions_shutdown(), and calling ui_actions_set_dispatch() is optional. So the only function required to use is ui_actions_register().

The function ui_actions_set_dispatch() can be used to install a function that “intercepts” an action before it’s triggered by VICE, in which case the dispatch function is responsible for calling ui_action_trigger(map->action). The dispatch function is used in the Gtk3 port to make sure an action that is marked as requiring the UI thread is executed on the UI thread. More on this later.

The ui_action_map_t type is used to register actions and to access information on an action. It maps action IDs to actions handlers and hotkeys.

typedef struct ui_action_map_s {
    int    action;          /**< action ID */

    /*
     * Action handler data
     */

    void (*handler)(struct ui_action_map_s*); /**< function handling the action */
    void  *data;            /**< optional user data */

    /* modes */
    bool   blocks;          /**< action blocks (the same action cannot be
                                 triggered again until it finishes) */
    bool   dialog;          /**< action pops up a dialog (only one dialog action
                                 is allowed at a time), this implies using the
                                 UI thread */
    bool   uithread;        /**< must run on the UI thread */

    /* state */
    bool   is_busy;         /**< action is busy */

    /* Hotkey data left out */
} ui_action_map_t;

The “Hotkey data” can be ignored: UI actions and hotkeys are closely related, and VICE uses the same struct to store hotkey data.

In its simplest form an initialization of the map requires providing values for .action and .handler. When providing initializers for UI action maps please use C99 designated initializers: this looks a lot cleaner and allows for adding members to the UI action map type without causing compiler warnings with existing code.

Simple example of an action handler

/* Action handler for toggling warp mode */
static void warp_mode_toggle_action(ui_action_map_t *self)
{
    vsync_set_warp_mode(!vsync_get_warp_mode());
}

/* List of actions to register (just one here) */
static const ui_action_map_t some_actions[] = {
    {   .action  = ACTION_WARP_MODE_TOGGLE,
        .handler = warp_mode_toggle_action
    },
    UI_ACTION_MAP_TERMINATOR    /* macro used to terminate a list of actions */
};


/* Register the action: */
ui_actions_register(some_actions);

Here we have a single action handler to toggle warp mode, in this case it does not use its self argument, but the argument can be used to access, for example, the action’s.id member or the .data member to be able to write an action handler that can be used for multiple IDs.

We register the action with a call to ui_actions_register(), after which VICE will call warp_mode_toggle_action() when a menu item, hotkey or controller button is used that is mapped to ACTION_WARP_MODE_TOGGLE.

Functions dealing with individual actions:

void        ui_action_trigger         (int action);
void        ui_action_finish          (int action);
int         ui_action_get_id          (const char *name);
const char *ui_action_get_name        (int action);
const char *ui_action_get_desc        (int action);
char *      ui_action_get_hotkey_label(int action);