Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
accelerators.h File Reference

Handle default and user-set shortcuts (accelerators) More...

#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include "common/dtpthread.h"
+ Include dependency graph for accelerators.h:
+ This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Data Structures

struct  dt_accels_t
 
struct  dt_accels_t::scroll
 
struct  dt_shortcut_t
 

Typedefs

typedef struct dt_accels_t dt_accels_t
 
typedef enum dt_shortcut_type_t dt_shortcut_type_t
 
typedef struct dt_shortcut_t dt_shortcut_t
 

Enumerations

enum  dt_shortcut_type_t {
  DT_SHORTCUT_UNSET = 0 ,
  DT_SHORTCUT_DEFAULT = 1 ,
  DT_SHORTCUT_USER = 2
}
 

Functions

dt_accels_tdt_accels_init (char *config_file, GtkAccelFlags flags)
 
void dt_accels_cleanup (dt_accels_t *accels)
 
gchar * dt_accels_build_path (const gchar *scope, const gchar *feature)
 
void dt_accels_load_user_config (dt_accels_t *accels)
 Loads keyboardrc.lang from config dir. This needs to run after we inited the accel map from widgets creation.
 
void dt_accels_connect_accels (dt_accels_t *accels)
 Actually enable accelerators after having loaded user config.
 
void dt_accels_connect_active_group (dt_accels_t *accels, const gchar *group)
 Connect the contextual active accels group to the window. Views can declare their own set of contextual accels, which can override the global accels, in case they use the same keys.
 
void dt_accels_disconnect_active_group (dt_accels_t *accels)
 Disconnect the contextual active accels group from the window.
 
void dt_accels_new_virtual_shortcut (dt_accels_t *accels, GtkAccelGroup *accel_group, const gchar *accel_path, GtkWidget *widget, guint key_val, GdkModifierType accel_mods)
 Add a new virtual shortcut. Virtual shortcuts are immutable, read-only and don't trigger any action. They are meant to serve as placeholders, in a purely declarative way, for key combinations hardcoded in the key-pressed events handlers of widgets able to capture focus. Once declared here, they will prevent users from declaring their own shortcuts using hardcoded combinations for the corresponding accel_group.
 
void dt_accels_new_virtual_instance_shortcut (dt_accels_t *accels, gboolean(*action_callback)(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data), gpointer data, GtkAccelGroup *accel_group, const gchar *action_scope, const gchar *action_name)
 
void dt_accels_new_widget_shortcut (dt_accels_t *accels, GtkWidget *widget, const gchar *signal, GtkAccelGroup *accel_group, const gchar *accel_path, guint key_val, GdkModifierType accel_mods, const gboolean lock)
 Register a new shortcut for a widget, setting up its path, default keys and accel group. This does everything but connecting it, so exists only as a defined slot to be connected later.
 
void dt_accels_new_action_shortcut (dt_accels_t *accels, gboolean(*action_callback)(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data), gpointer data, GtkAccelGroup *accel_group, const gchar *action_scope, const gchar *action_name, guint key_val, GdkModifierType accel_mods, const gboolean lock, const char *description)
 Register a new shortcut for a generic action, setting up its path, default keys and accel group. This does everything but connecting it, so exists only as a defined slot to be connected later.
 
gboolean dt_accels_dispatch (GtkWidget *w, GdkEvent *event, gpointer user_data)
 Force our listener for all key strokes to bypass reserved Gtk keys.
 
void dt_accels_attach_scroll_handler (dt_accels_t *accels, gboolean(*callback)(GdkEventScroll event, void *data), void *data)
 Attach a new global scroll event callback. So far this is used in darkroom to redirect scroll events to a Bauhaus widget when the focusing shortcut of that widget is held down on keyboard.
 
void dt_accels_detach_scroll_handler (dt_accels_t *accels)
 
static void dt_accels_disable (dt_accels_t *accels, gboolean state)
 
void dt_accels_remove_accel (dt_accels_t *accels, const char *path, gpointer data)
 Recursively remove all accels for all shortcuts containing path. This is unneeded for accels attached to Gtk widgets through dt_accels_new_widget_shortcut because Gtk will handle that internally when deleting a widget. But for our own widget-less dt_accels_new_action_shortcut, we need to handle that ourselves.
 
void dt_accels_remove_shortcut (dt_accels_t *accels, const char *path)
 Remove the shortcut object identified by path and all its accels.
 
void dt_accels_window (dt_accels_t *accels, GtkWindow *main_window)
 Show the modal dialog listing all available keyboard shortcuts and letting user to set them.
 
void dt_accels_search (dt_accels_t *accels, GtkWindow *main_window)
 

Detailed Description

Handle default and user-set shortcuts (accelerators)

Gtk is a bit weird here, so we need to :

  1. have each acceleratable widget declare its accel path and default shortcut (if any),
  2. read the accels map file, which only import accels for already-known pathes (from previous step), and may update the default shortcut with user-defined one,
  3. connect widget relevant signal and shortcut through the active GtkAccelGroup. But the actual signal depends on the widget type.

Because of that, we need to prepare the list of acceleratable widgets first, populate the accel maps with their pathes, and only after fetching user-defined accels, we can connect actual actions. So it's not straight-forward.

That is:

// 1. Init accels handlers
dt_accels_init(char *config_file, GtkAccelFlags flags);
// 2. Init GUI widgets where each acceleratable declares its slot using either:
dt_accels_new_widget_shortcut(dt_accels_t *accels, GtkWidget *widget, const gchar *signal,
GtkAccelGroup *accel_group, const gchar *accel_path, guint key_val,
GdkModifierType accel_mods, const gboolean lock);
// or:
const dt_shortcut_t *dt_accels_new_action_shortcut(dt_accels_t *accels, void(*action_callback), gpointer data,
GtkAccelGroup *accel_group, const gchar *action_scope, const gchar *action_name,
guint key_val, GdkModifierType accel_mods, const gboolean lock);
// 3. Read shortcutsrc.lang file to assign user-defined shortcuts to those slots:
// 4. Actually enable those shortcuts:
// 5. Connect/Disconnect contextual accel groups when entering/exiting some view:
// NOTE: contextual accel groups may redefine/overwrite some global keys combinations temporarily.
void dt_accels_connect_active_group(dt_accels_t *accels, const gchar *group);
// 6. Install our own shortcuts listener. This is used within an event handler callback.
gboolean dt_accels_dispatch(GtkWidget *w, GdkEvent *event, gpointer user_data);
void dt_accels_connect_accels(dt_accels_t *accels)
Actually enable accelerators after having loaded user config.
Definition accelerators.c:618
void dt_accels_connect_active_group(dt_accels_t *accels, const gchar *group)
Connect the contextual active accels group to the window. Views can declare their own set of contextu...
Definition accelerators.c:211
void dt_accels_disconnect_active_group(dt_accels_t *accels)
Disconnect the contextual active accels group from the window.
Definition accelerators.c:232
gboolean dt_accels_dispatch(GtkWidget *w, GdkEvent *event, gpointer user_data)
Force our listener for all key strokes to bypass reserved Gtk keys.
Definition accelerators.c:822
dt_accels_t * dt_accels_init(char *config_file, GtkAccelFlags flags)
Definition accelerators.c:162
void dt_accels_load_user_config(dt_accels_t *accels)
Loads keyboardrc.lang from config dir. This needs to run after we inited the accel map from widgets c...
Definition accelerators.c:566
void dt_accels_new_widget_shortcut(dt_accels_t *accels, GtkWidget *widget, const gchar *signal, GtkAccelGroup *accel_group, const gchar *accel_path, guint key_val, GdkModifierType accel_mods, const gboolean lock)
Register a new shortcut for a widget, setting up its path, default keys and accel group....
Definition accelerators.c:459
void dt_accels_new_action_shortcut(dt_accels_t *accels, gboolean(*action_callback)(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data), gpointer data, GtkAccelGroup *accel_group, const gchar *action_scope, const gchar *action_name, guint key_val, GdkModifierType accel_mods, const gboolean lock, const char *description)
Register a new shortcut for a generic action, setting up its path, default keys and accel group....
Definition accelerators.c:511
dt_mipmap_buffer_dsc_flags flags
Definition mipmap_cache.c:4
Definition accelerators.h:87
Definition accelerators.h:132

Some keys appear reserved to Gtk/System, like Tab or Enter, depending on OS. So we have to use our own, custom, shortcut handler, which is mostly a thin wrapper over Gtk native features.

This allows us to decide in which order we process the several sets of shortcuts we maintain (global, lighttable, darkroom). Global shortcuts are processed last, for all views. Lighttable and darkroom shortcuts are processed first, for the relevant view. This lets user have different actions mapped to the same shortcuts, depending on view but also have the view-centric shortcuts overwrite global ones if needed.

Module (IOP) instances will reuse the same shortcut for the main object and for all its children (sliders, comboboxes, buttons, etc.), because they share the same path (Darkroom/Modules/module_name/module_control) for all instances. We handle instances by appending a new (PayloadClosure *) object to the (GList *)shortcut->closure stack. (PayloadClosure *) contains a regular (GClosure *) followed by a pointer reference to the parent object (module) to identify the instance. When removing a module instance, we also remove the (PayloadClosure *) instance matching this module for all children shortcuts. This puts an hard assumption on 3 things:

  • The shortcut of the parent object is declared before the shortcuts of the children objects,
  • The accel path of the parent is the root of the accel pathes of all children. We don't check if children widgets are children of the parent widget, because we attach actions to any callback/data pointer, not just widgets, so all that is abstracted and we only look at pathes.
  • The shortcut of the parent is declared with an user_data pointer reference (non NULL), that can be anything really as long as it is unique, belongs to the parent widget/module, and is constant over the lifetime of the parent and children.

At any given time, we only pass the first-recorded (GClosure *) to the shortcut handler/GtkAccelMap/GtkAccelGroup. It is only when an instance is removed that we remove the corresponding parent and children, wherever the are in the stack, and then rewire the shortcut with the first item, because it's typically the global instance.

Typedef Documentation

◆ dt_accels_t

typedef struct dt_accels_t dt_accels_t

◆ dt_shortcut_t

typedef struct dt_shortcut_t dt_shortcut_t

◆ dt_shortcut_type_t

Enumeration Type Documentation

◆ dt_shortcut_type_t

Enumerator
DT_SHORTCUT_UNSET 
DT_SHORTCUT_DEFAULT 
DT_SHORTCUT_USER 

Function Documentation

◆ dt_accels_attach_scroll_handler()

void dt_accels_attach_scroll_handler ( dt_accels_t accels,
gboolean(*)(GdkEventScroll event, void *data)  callback,
void data 
)

Attach a new global scroll event callback. So far this is used in darkroom to redirect scroll events to a Bauhaus widget when the focusing shortcut of that widget is held down on keyboard.

Parameters
callback
data

References dt_accels_t::scroll::callback, dt_accels_t::scroll::data, and dt_accels_t::scroll.

Referenced by enter().

◆ dt_accels_build_path()

◆ dt_accels_cleanup()

◆ dt_accels_connect_accels()

void dt_accels_connect_accels ( dt_accels_t accels)

Actually enable accelerators after having loaded user config.

Parameters
accels

References _connect_accel_hashtable(), dt_accels_t::acceleratables, dt_pthread_mutex_lock(), dt_pthread_mutex_unlock(), and dt_accels_t::lock.

Referenced by dt_init(), and enter().

◆ dt_accels_connect_active_group()

void dt_accels_connect_active_group ( dt_accels_t accels,
const gchar *  group 
)

Connect the contextual active accels group to the window. Views can declare their own set of contextual accels, which can override the global accels, in case they use the same keys.

Parameters
accels
groupany of the following: "darkroom", "lighttable".

References dt_accels_t::active_group, dt_accels_t::darkroom_accels, dt_accels_t::lighttable_accels, and dt_accels_t::reset.

Referenced by enter().

◆ dt_accels_detach_scroll_handler()

void dt_accels_detach_scroll_handler ( dt_accels_t accels)

◆ dt_accels_disable()

static void dt_accels_disable ( dt_accels_t accels,
gboolean  state 
)
inlinestatic

◆ dt_accels_disconnect_active_group()

void dt_accels_disconnect_active_group ( dt_accels_t accels)

Disconnect the contextual active accels group from the window.

Parameters
accels

References dt_accels_t::active_group, and dt_accels_t::reset.

Referenced by leave().

◆ dt_accels_dispatch()

gboolean dt_accels_dispatch ( GtkWidget *  w,
GdkEvent *  event,
gpointer  user_data 
)

Force our listener for all key strokes to bypass reserved Gtk keys.

Parameters
w
event
user_data
Returns
gboolean

References _accels_keys_decode(), _key_pressed(), dt_accels_t::active_group, dt_accels_t::active_key, dt_accels_t::scroll::callback, dt_accels_t::scroll::data, dt_accels_t::disable_accels, FALSE, dt_accels_t::reset, and dt_accels_t::scroll.

Referenced by dt_gui_gtk_init().

◆ dt_accels_init()

◆ dt_accels_load_user_config()

void dt_accels_load_user_config ( dt_accels_t accels)

Loads keyboardrc.lang from config dir. This needs to run after we inited the accel map from widgets creation.

Parameters
accels

References dt_accels_t::config_file.

Referenced by dt_init().

◆ dt_accels_new_action_shortcut()

void dt_accels_new_action_shortcut ( dt_accels_t accels,
gboolean(*)(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data)  action_callback,
gpointer  data,
GtkAccelGroup *  accel_group,
const gchar *  action_scope,
const gchar *  action_name,
guint  key_val,
GdkModifierType  accel_mods,
const gboolean  lock,
const char *  description 
)

Register a new shortcut for a generic action, setting up its path, default keys and accel group. This does everything but connecting it, so exists only as a defined slot to be connected later.

The callback should have the following signature:

gboolean action_callback(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval, GdkModifierType
modifier, gpointer data)
Parameters
accels
data
accel_group
action_scopeHuman-readable, translated, category or scope of the action. Will be turned into path internally
action_nameHuman-readable, translated, name or description of the action. Will be turned into path internally
key_val
accel_mods
lockprevent user edition

References _add_generic_accel(), _insert_accel(), _remove_generic_accel(), dt_shortcut_t::accel_group, dt_accels_t::acceleratables, dt_shortcut_t::accels, dt_shortcut_t::closure, dt_shortcut_t::description, description(), dt_accels_build_path(), dt_pthread_mutex_lock(), dt_pthread_mutex_unlock(), dt_shortcut_get_closure(), dt_shortcut_set_closure(), DT_SHORTCUT_UNSET, FALSE, dt_accels_t::flags, dt_shortcut_t::key, dt_accels_t::lock, dt_shortcut_t::locked, dt_shortcut_t::mods, dt_shortcut_t::path, dt_shortcut_t::signal, dt_shortcut_t::type, dt_shortcut_t::virtual_shortcut, and dt_shortcut_t::widget.

Referenced by dt_lib_init_module(), and set_menu_entry().

◆ dt_accels_new_virtual_instance_shortcut()

void dt_accels_new_virtual_instance_shortcut ( dt_accels_t accels,
gboolean(*)(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data)  action_callback,
gpointer  data,
GtkAccelGroup *  accel_group,
const gchar *  action_scope,
const gchar *  action_name 
)

◆ dt_accels_new_virtual_shortcut()

void dt_accels_new_virtual_shortcut ( dt_accels_t accels,
GtkAccelGroup *  accel_group,
const gchar *  accel_path,
GtkWidget *  widget,
guint  key_val,
GdkModifierType  accel_mods 
)

Add a new virtual shortcut. Virtual shortcuts are immutable, read-only and don't trigger any action. They are meant to serve as placeholders, in a purely declarative way, for key combinations hardcoded in the key-pressed events handlers of widgets able to capture focus. Once declared here, they will prevent users from declaring their own shortcuts using hardcoded combinations for the corresponding accel_group.

Parameters
accel_group
accel_path
key_val
accel_mods

References _insert_accel(), _virtual_shortcut_callback(), dt_shortcut_t::accel_group, dt_accels_t::acceleratables, dt_shortcut_t::accels, dt_shortcut_t::closure, dt_shortcut_t::description, dt_pthread_mutex_lock(), dt_pthread_mutex_unlock(), dt_shortcut_set_closure(), DT_SHORTCUT_UNSET, dt_shortcut_t::key, dt_accels_t::lock, dt_shortcut_t::locked, dt_shortcut_t::mods, dt_shortcut_t::path, dt_shortcut_t::signal, TRUE, dt_shortcut_t::type, dt_shortcut_t::virtual_shortcut, and dt_shortcut_t::widget.

Referenced by dt_bauhaus_init(), and dt_thumbtable_new().

◆ dt_accels_new_widget_shortcut()

void dt_accels_new_widget_shortcut ( dt_accels_t accels,
GtkWidget *  widget,
const gchar *  signal,
GtkAccelGroup *  accel_group,
const gchar *  accel_path,
guint  key_val,
GdkModifierType  accel_mods,
const gboolean  lock 
)

Register a new shortcut for a widget, setting up its path, default keys and accel group. This does everything but connecting it, so exists only as a defined slot to be connected later.

Parameters
accels
widget
signal
accel_group
accel_path
key_val
accel_mods
lockprevent user edition

References _add_widget_accel(), _insert_accel(), _remove_widget_accel(), dt_shortcut_t::accel_group, dt_accels_t::acceleratables, dt_shortcut_t::accels, dt_shortcut_t::closure, dt_shortcut_t::description, dt_pthread_mutex_lock(), dt_pthread_mutex_unlock(), DT_SHORTCUT_UNSET, FALSE, dt_accels_t::flags, key, dt_shortcut_t::key, dt_accels_t::lock, dt_shortcut_t::locked, dt_shortcut_t::mods, dt_shortcut_t::path, dt_shortcut_t::signal, dt_shortcut_t::type, dt_shortcut_t::virtual_shortcut, and dt_shortcut_t::widget.

Referenced by gui_init().

◆ dt_accels_remove_accel()

void dt_accels_remove_accel ( dt_accels_t accels,
const char *  path,
gpointer  data 
)

Recursively remove all accels for all shortcuts containing path. This is unneeded for accels attached to Gtk widgets through dt_accels_new_widget_shortcut because Gtk will handle that internally when deleting a widget. But for our own widget-less dt_accels_new_action_shortcut, we need to handle that ourselves.

Accels are typically added at gui_init() time of their attached GUI object, and the typical use case of this API assumes those objects live until the app is closed. But IOP modules can be added/removed at runtime (instances), so we need to destroy accels when their target object (user_data pointer/callback) is destroyed. If some accels are left dangling with a reference to a non-existing callback/data/closure, the app will crash with segfault upon shortcut activation.

This will remove in one shot all accels attached to a parent and to all of its children, assuming that children will share their path root with their parent, and that rule is entirely up to the developer to enforce.

This does not remove the shortcut object.

Parameters
accels
pathaccel path
datathe user-data used by the initial callback, if any

References _remove_accel_hashtable(), dt_accels_t::acceleratables, dt_pthread_mutex_lock(), dt_pthread_mutex_unlock(), and dt_accels_t::lock.

◆ dt_accels_remove_shortcut()

void dt_accels_remove_shortcut ( dt_accels_t accels,
const char *  path 
)

Remove the shortcut object identified by path and all its accels.

Parameters
accels
path

References dt_accels_t::acceleratables, dt_pthread_mutex_lock(), dt_pthread_mutex_unlock(), and dt_accels_t::lock.

Referenced by _iop_panel_label().

◆ dt_accels_search()

◆ dt_accels_window()

void dt_accels_window ( dt_accels_t accels,
GtkWindow *  main_window 
)