I am developing an application with several tabs which have different operation modes. I use GTK3.
What makes things compicated is
- There can be several tabs of one type
- Some widgets inside tabs (they are subclassed from
GtkDrawingArea
) manually handle mouse and keyboard events. However, this is not much of a problem. - Wigets in these tabs can also have different modes.
For example, one of the tabs is map view. It allows measurements and drawing. I want to use keybindings both to switch between modes and within modes. Like this:
MapTab1
├ <Ctrl>M - measurement mode
└ <Ctrl>D - drawing mode
├ <Esc> - leave this mode
├ <Ctrl>M - draw a square
└ <Ctrl>O - draw a circle
MapTab2
├ <Ctrl>M - measurement mode
└ <Ctrl>D - drawing mode
├ <Esc> - leave this mode
├ <Ctrl>M - draw a square
└ <Ctrl>O - draw a circle
Another Tab
└ <Ctrl>D - destroying all humans mode
As you can see, some keybinding may overlap. To resolve these conflicts I thought of several approaches:
1. Dynamically add/remove actions and keybindings when a tab switches and mode entered.
Keybindings would never conflict, because they are automatically added/removed.
static GActionEntry tab_entries[] = {
{ "draw_mode", enter_draw, NULL, NULL, NULL },
{ "measure_mode", enter_measure, NULL, NULL, NULL },
{ "zzz", action_z, NULL, NULL, NULL },
};
static tab_keybinding[] = {
{"draw_mode", {"<Ctrl>D", NULL}},
{"measure_mode", {"<Ctrl>M", NULL}},
}
static GActionEntry draw_mode_entries[] = {
{ "circle", action_circle, NULL, NULL, NULL },
{ "square", action_square, NULL, NULL, NULL },
};
static draw_mode_keybinding[] = {
{"circle", {"<Ctrl>M", NULL}},
{"square", {"<Ctrl>O", NULL}},
}
void tab_leave (MyTabWidget *self, GtkApplicationWindow *app_window)
{
GApplication * app = g_application_get_default ();
for (i = 0; i < G_N_ELEMENTS (entries); ++i)
g_action_map_remove_action (G_ACTION_MAP (app_window), entries[i].name)
for (i = 0; i < G_N_ELEMENTS (keybinding); ++i)
gtk_application_set_accels_for_action (app, keybinding[i].name, NULL);
}
void tab_enter (MyTabWidget *self, GtkApplicationWindow *app_window)
{
g_action_map_add_action_entries (G_ACTION_MAP (app_window),
entries, G_N_ELEMENTS (entries),
self);
for (i = 0; i < G_N_ELEMENTS (keybinding); ++i)
gtk_application_set_accels_for_action (app, keybinding[i].name, keybinding[i].accels);
}
// Plus mode_leave and mode_enter methods called at entering modes
2. Make every tab prefix it’s actions with some id. Dynamically enable/disable actions.
Action names never conflict. Keybindings never conflict, as actions get automatically enabled/disabled.
void tab_created (MyTabWidget *self, GtkApplicationWindow *app_window)
{
gchar *prefix = my_tab_get_prefix (self);
gchar *name;
GActionEntry *prefixed_entries;
// For the purpose of simplicity I omit dynamic renaming of entries.
self->priv->prefixed_entries = my_tab_get_prefixed_entries (self, entries, prefix);
g_action_map_add_action_entries (G_ACTION_MAP (app_window),
self->priv->prefixed_entries,
G_N_ELEMENTS (entries), self);
for (i = 0; i < G_N_ELEMENTS (keybinding); ++i)
{
name = g_strdup_printf ("%s%s", prefix, keybinding[i].name);
gtk_application_set_accels_for_action (app, name, keybinding[i].accels);
}
// Disable all actions by default.
tab_leave(self, app_window);
}
void tab_leave (MyTabWidget *self, GtkApplicationWindow *app_window)
{
for (i = 0; i < G_N_ELEMENTS (entries); ++i)
g_simple_action_set_enabled (self->priv->prefixed_entries[i].name, FALSE);
}
void tab_enter (MyTabWidget *self, GtkApplicationWindow *app_window)
{
for (i = 0; i < G_N_ELEMENTS (entries); ++i)
g_simple_action_set_enabled (self->priv->prefixed_entries[i].name, TRUE);
}
// Plus mode_leave and mode_enter methods called at entering modes
3. Have keybinding only for entering modes and continue handling key/mouse events manually.
I don’t see any pros. Possible keybinding conflicts must be resolved at compile-time and changing keybings is very complicated. Looks like it’s a bad approach.
The final questions are:
- Are there any pros/cons of the first two approaches which I don’t see?
- Are there simpler ways to achieve what I need?