1 /*
2 This file is part of darktable,
3 Copyright (C) 2011-2021 darktable developers.
4
5 darktable is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 darktable is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with darktable. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "gui/accelerators.h"
20 #include "common/action.h"
21 #include "common/darktable.h"
22 #include "common/debug.h"
23 #include "common/file_location.h"
24 #include "common/utility.h"
25 #include "control/control.h"
26 #include "develop/blend.h"
27 #include "gui/presets.h"
28 #include "dtgtk/expander.h"
29
30 #include <assert.h>
31 #include <gtk/gtk.h>
32 #ifdef GDK_WINDOWING_QUARTZ
33 #include "osx/osx.h"
34 #endif
35
36 typedef struct dt_shortcut_t
37 {
38 dt_view_type_flags_t views;
39
40 dt_input_device_t key_device;
41 guint key;
42 guint mods;
43 guint press : 3;
44 guint button : 3;
45 guint click : 3;
46 guint direction : 2;
47 dt_input_device_t move_device;
48 dt_shortcut_move_t move;
49
50 dt_action_t *action;
51
52 dt_action_element_t element;
53 dt_action_effect_t effect;
54 float speed;
55 int instance; // 0 is from prefs, >0 counting from first, <0 counting from last
56 } dt_shortcut_t;
57
58
59 typedef struct dt_device_key_t
60 {
61 dt_input_device_t key_device;
62 guint key;
63 const dt_action_def_t *hold_def;
64 dt_action_element_t hold_element;
65 } dt_device_key_t;
66
67 typedef struct dt_action_target_t
68 {
69 dt_action_t *action;
70 void *target;
71 } dt_action_target_t;
72
73 #define DT_SHORTCUT_DEVICE_KEYBOARD_MOUSE 0
74
75 const char *move_string[]
76 = { "",
77 N_("scroll"),
78 N_("pan"),
79 N_("horizontal"),
80 N_("vertical"),
81 N_("diagonal"),
82 N_("skew"),
83 N_("leftright"),
84 N_("updown"),
85 N_("pgupdown"),
86 NULL };
87
88 const struct _modifier_name
89 {
90 GdkModifierType modifier;
91 char *name;
92 } modifier_string[]
93 = { { GDK_SHIFT_MASK , N_("shift") },
94 { GDK_CONTROL_MASK, N_("ctrl" ) },
95 { GDK_MOD1_MASK , N_("alt" ) },
96 { GDK_MOD2_MASK , N_("cmd" ) },
97 { GDK_MOD5_MASK , N_("altgr") },
98 { 0, NULL } };
99
100 static dt_shortcut_t _sc = { 0 }; // shortcut under construction
101 static guint _previous_move = DT_SHORTCUT_MOVE_NONE;
102
103 const gchar *dt_action_effect_value[]
104 = { N_("edit"),
105 N_("up"),
106 N_("down"),
107 N_("reset"),
108 N_("top"),
109 N_("bottom"),
110 N_("set"),
111 NULL };
112 const gchar *dt_action_effect_selection[]
113 = { N_("popup"),
114 N_("next"),
115 N_("previous"),
116 N_("reset"),
117 N_("last"),
118 N_("first"),
119 NULL };
120 const gchar *dt_action_effect_toggle[]
121 = { N_("toggle"),
122 N_("on"),
123 N_("off"),
124 N_("ctrl-toggle"),
125 N_("ctrl-on"),
126 N_("right-toggle"),
127 N_("right-on"),
128 NULL };
129 const gchar *dt_action_effect_hold[]
130 = { N_("hold"),
131 N_("on"),
132 N_("off"),
133 N_("toggle"),
134 NULL };
135 const gchar *dt_action_effect_activate[]
136 = { N_("activate"),
137 N_("ctrl-activate"),
138 N_("right-activate"),
139 NULL };
140 const gchar *dt_action_effect_presets[]
141 = { N_("show"),
142 N_("previous"),
143 N_("next"),
144 N_("store"),
145 N_("delete"),
146 N_("edit"),
147 N_("update"),
148 N_("preferences"),
149 NULL };
150 const gchar *dt_action_effect_preset_iop[]
151 = { N_("apply"),
152 N_("apply on new instance"),
153 NULL };
154
155 const dt_action_element_def_t dt_action_elements_hold[]
156 = { { NULL, dt_action_effect_hold } };
157
158 const dt_action_element_def_t _action_elements_toggle[]
159 = { { NULL, dt_action_effect_toggle } };
160 const dt_action_element_def_t _action_elements_button[]
161 = { { NULL, dt_action_effect_activate } };
162 const dt_action_element_def_t _action_elements_value_fallback[]
163 = { { NULL, dt_action_effect_value } };
164
_action_process_toggle(gpointer target,dt_action_element_t element,dt_action_effect_t effect,float move_size)165 static float _action_process_toggle(gpointer target, dt_action_element_t element, dt_action_effect_t effect, float move_size)
166 {
167 float value = gtk_toggle_button_get_active(target);
168
169 if(!isnan(move_size) &&
170 !((effect == DT_ACTION_EFFECT_ON ||
171 effect == DT_ACTION_EFFECT_ON_CTRL ||
172 effect == DT_ACTION_EFFECT_ON_RIGHT) && value) &&
173 (effect != DT_ACTION_EFFECT_OFF || value))
174 {
175 GdkEvent *event = gdk_event_new(GDK_BUTTON_PRESS);
176 event->button.state = (effect == DT_ACTION_EFFECT_TOGGLE_CTRL ||
177 effect == DT_ACTION_EFFECT_ON_CTRL)
178 ? GDK_CONTROL_MASK : 0;
179 event->button.button = (effect == DT_ACTION_EFFECT_TOGGLE_RIGHT ||
180 effect == DT_ACTION_EFFECT_ON_RIGHT)
181 ? GDK_BUTTON_SECONDARY : GDK_BUTTON_PRIMARY;
182
183 if(!gtk_widget_get_realized(target)) gtk_widget_realize(target);
184 event->button.window = gtk_widget_get_window(target);
185 g_object_ref(event->button.window);
186
187 // some togglebuttons connect to the clicked signal, others to toggled or button-press-event
188 if(!gtk_widget_event(target, event))
189 gtk_button_clicked(GTK_BUTTON(target));
190 event->type = GDK_BUTTON_RELEASE;
191 gtk_widget_event(target, event);
192
193 gdk_event_free(event);
194
195 value = gtk_toggle_button_get_active(target);
196
197 if(!gtk_widget_is_visible(target))
198 dt_action_widget_toast(NULL, target, value ? _("on") : _("off"));
199 }
200
201 return value;
202 }
203
_action_process_button(gpointer target,dt_action_element_t element,dt_action_effect_t effect,float move_size)204 static float _action_process_button(gpointer target, dt_action_element_t element, dt_action_effect_t effect, float move_size)
205 {
206 if(!gtk_widget_get_realized(target)) gtk_widget_realize(target);
207
208 if(!isnan(move_size) && gtk_widget_is_sensitive(target))
209 {
210 if(effect != DT_ACTION_EFFECT_ACTIVATE || !gtk_widget_activate(GTK_WIDGET(target)))
211 {
212 GdkEvent *event = gdk_event_new(GDK_BUTTON_PRESS);
213 event->button.state = effect == DT_ACTION_EFFECT_ACTIVATE_CTRL
214 ? GDK_CONTROL_MASK : 0;
215 event->button.button = effect == DT_ACTION_EFFECT_ACTIVATE_RIGHT
216 ? GDK_BUTTON_SECONDARY : GDK_BUTTON_PRIMARY;
217
218 event->button.window = gtk_widget_get_window(target);
219 g_object_ref(event->button.window);
220
221 gtk_widget_event(target, event);
222 event->type = GDK_BUTTON_RELEASE;
223 gtk_widget_event(target, event);
224
225 gdk_event_free(event);
226 }
227 }
228
229 return NAN;
230 }
231
232 static const dt_shortcut_fallback_t _action_fallbacks_toggle[]
233 = { { .mods = GDK_CONTROL_MASK , .effect = DT_ACTION_EFFECT_TOGGLE_CTRL },
234 { .button = DT_SHORTCUT_RIGHT , .effect = DT_ACTION_EFFECT_TOGGLE_RIGHT },
235 { .press = DT_SHORTCUT_LONG , .effect = DT_ACTION_EFFECT_TOGGLE_RIGHT },
236 { } };
237
238 const dt_action_def_t dt_action_def_toggle
239 = { N_("toggle"),
240 _action_process_toggle,
241 _action_elements_toggle,
242 _action_fallbacks_toggle };
243
244 static const dt_shortcut_fallback_t _action_fallbacks_button[]
245 = { { .mods = GDK_CONTROL_MASK , .effect = DT_ACTION_EFFECT_ACTIVATE_CTRL },
246 { .button = DT_SHORTCUT_RIGHT , .effect = DT_ACTION_EFFECT_ACTIVATE_RIGHT },
247 { .press = DT_SHORTCUT_LONG , .effect = DT_ACTION_EFFECT_ACTIVATE_RIGHT },
248 { } };
249
250 const dt_action_def_t dt_action_def_button
251 = { N_("button"),
252 _action_process_button,
253 _action_elements_button,
254 _action_fallbacks_button };
255
256 static const dt_shortcut_fallback_t _action_fallbacks_value[]
257 = { { .mods = GDK_CONTROL_MASK , .effect = -1, .speed = 0.1 },
258 { .mods = GDK_SHIFT_MASK , .effect = -1, .speed = 10. },
259 { .move = DT_SHORTCUT_MOVE_HORIZONTAL, .effect = -1, .speed = 0.1 },
260 { .move = DT_SHORTCUT_MOVE_VERTICAL , .effect = -1, .speed = 10. },
261 { .effect = DT_ACTION_EFFECT_RESET , .button = DT_SHORTCUT_LEFT, .click = DT_SHORTCUT_DOUBLE },
262 { .effect = DT_ACTION_EFFECT_TOP , .button = DT_SHORTCUT_LEFT, .click = DT_SHORTCUT_DOUBLE, .move = DT_SHORTCUT_MOVE_VERTICAL, .direction = DT_SHORTCUT_UP },
263 { .effect = DT_ACTION_EFFECT_BOTTOM , .button = DT_SHORTCUT_LEFT, .click = DT_SHORTCUT_DOUBLE, .move = DT_SHORTCUT_MOVE_VERTICAL, .direction = DT_SHORTCUT_DOWN },
264 { } };
265
266 const dt_action_def_t dt_action_def_value
267 = { N_("value"),
268 NULL,
269 _action_elements_value_fallback,
270 _action_fallbacks_value };
271
272 const dt_action_def_t _action_def_dummy
273 = { };
274
_action_find_definition(dt_action_t * action)275 static const dt_action_def_t *_action_find_definition(dt_action_t *action)
276 {
277 if(!action) return NULL;
278
279 dt_action_type_t type = action->type != DT_ACTION_TYPE_FALLBACK
280 ? action->type : GPOINTER_TO_INT(action->target);
281 const int index = type - DT_ACTION_TYPE_WIDGET - 1;
282
283 if(index >= 0 && index < darktable.control->widget_definitions->len)
284 return darktable.control->widget_definitions->pdata[index];
285 else if(type == DT_ACTION_TYPE_IOP)
286 return &dt_action_def_iop;
287 else if(type == DT_ACTION_TYPE_LIB)
288 return &dt_action_def_lib;
289 else if(type == DT_ACTION_TYPE_VALUE_FALLBACK)
290 return &dt_action_def_value;
291 else
292 return NULL;
293 }
294
_action_find_elements(dt_action_t * action)295 static const dt_action_element_def_t *_action_find_elements(dt_action_t *action)
296 {
297 const dt_action_def_t *definition = _action_find_definition(action);
298
299 if(!definition)
300 return NULL;
301 else
302 return definition->elements;
303 }
304
_shortcut_compare_func(gconstpointer shortcut_a,gconstpointer shortcut_b,gpointer user_data)305 static gint _shortcut_compare_func(gconstpointer shortcut_a, gconstpointer shortcut_b, gpointer user_data)
306 {
307 const dt_shortcut_t *a = (const dt_shortcut_t *)shortcut_a;
308 const dt_shortcut_t *b = (const dt_shortcut_t *)shortcut_b;
309
310 dt_view_type_flags_t active_view = GPOINTER_TO_INT(user_data);
311 const int a_in_view = a->views ? a->views & active_view : -1; // put fallbacks last
312 const int b_in_view = b->views ? b->views & active_view : -1; // put fallbacks last
313
314 if(a_in_view != b_in_view)
315 // reverse order; in current view first, fallbacks last
316 return b_in_view - a_in_view;
317 if(!a->views && a->action && b->action && a->action->target != b->action->target)
318 // order fallbacks by referred type
319 return GPOINTER_TO_INT(a->action->target) - GPOINTER_TO_INT(b->action->target);
320 if(a->key_device != b->key_device)
321 return a->key_device - b->key_device;
322 if(a->key != b->key)
323 return a->key - b->key;
324 if(a->press != b->press)
325 return a->press - b->press;
326 if(a->move_device != b->move_device)
327 return a->move_device - b->move_device;
328 if(a->move != b->move)
329 return a->move - b->move;
330 if(a->mods != b->mods)
331 return a->mods - b->mods;
332 if(a->button != b->button)
333 return a->button - b->button;
334 if(a->click != b->click)
335 return a->click - b->click;
336 if((a->direction | b->direction) == (DT_SHORTCUT_UP | DT_SHORTCUT_DOWN))
337 return a->direction - b->direction;
338
339 return 0;
340 };
341
_action_full_id(dt_action_t * action)342 static gchar *_action_full_id(dt_action_t *action)
343 {
344 if(action->owner)
345 {
346 gchar *owner_id = _action_full_id(action->owner);
347 gchar *full_label = g_strdup_printf("%s/%s", owner_id, action->id);
348 g_free(owner_id);
349 return full_label;
350 }
351 else
352 return g_strdup(action->id);
353 }
354
_action_full_label(dt_action_t * action)355 static gchar *_action_full_label(dt_action_t *action)
356 {
357 if(action->owner)
358 {
359 gchar *owner_label = _action_full_label(action->owner);
360 gchar *full_label = g_strdup_printf("%s/%s", owner_label, action->label);
361 g_free(owner_label);
362 return full_label;
363 }
364 else
365 return g_strdup(action->label);
366 }
367
_action_distinct_label(gchar ** label,dt_action_t * action,gchar * instance)368 static void _action_distinct_label(gchar **label, dt_action_t *action, gchar *instance)
369 {
370 if(!action || action->type <= DT_ACTION_TYPE_GLOBAL)
371 return;
372
373 gchar *instance_label = action->type == DT_ACTION_TYPE_IOP && *instance
374 ? g_strdup_printf("%s %s", action->label, instance)
375 : g_strdup(action->label);
376
377 if(*label)
378 {
379 if(!strstr(action->label, *label) || *instance)
380 {
381 gchar *distinct_label = g_strdup_printf("%s / %s", instance_label, *label);
382 g_free(*label);
383 *label = distinct_label;
384 }
385
386 g_free(instance_label);
387 }
388 else
389 *label = instance_label;
390
391 _action_distinct_label(label, action->owner, instance);
392 }
393
_dump_actions(FILE * f,dt_action_t * action)394 static void _dump_actions(FILE *f, dt_action_t *action)
395 {
396 while(action)
397 {
398 gchar *label = _action_full_id(action);
399 fprintf(f, "%s %s %d\n", label, !action->target ? "*" : "", action->type);
400 g_free(label);
401 if(action->type <= DT_ACTION_TYPE_SECTION)
402 _dump_actions(f, action->target);
403 action = action->next;
404 }
405 }
406
dt_register_input_driver(dt_lib_module_t * module,const dt_input_driver_definition_t * callbacks)407 dt_input_device_t dt_register_input_driver(dt_lib_module_t *module, const dt_input_driver_definition_t *callbacks)
408 {
409 dt_input_device_t id = 10;
410
411 for(GSList *d = darktable.control->input_drivers; d; d = d->next, id += 10)
412 if(((dt_input_driver_definition_t *)d->data)->module == module) return id;
413
414 dt_input_driver_definition_t *new_driver = calloc(1, sizeof(dt_input_driver_definition_t));
415 *new_driver = *callbacks;
416 new_driver->module = module;
417 darktable.control->input_drivers = g_slist_append(darktable.control->input_drivers, (gpointer)new_driver);
418
419 return id;
420 }
421
422 #define DT_MOVE_NAME -1
_shortcut_key_move_name(dt_input_device_t id,guint key_or_move,guint mods,gboolean display)423 static gchar *_shortcut_key_move_name(dt_input_device_t id, guint key_or_move, guint mods, gboolean display)
424 {
425 gchar *name = NULL, *post_name = NULL;
426 if(id == DT_SHORTCUT_DEVICE_KEYBOARD_MOUSE)
427 {
428 if(mods == DT_MOVE_NAME)
429 return g_strdup(display && key_or_move != 0 ? _(move_string[key_or_move]) : move_string[key_or_move]);
430 else
431 {
432 if(display)
433 {
434 gchar *key_name = gtk_accelerator_get_label(key_or_move, 0);
435 post_name = g_utf8_strdown(key_name, -1);
436 g_free(key_name);
437 }
438 else
439 name = key_or_move ? gtk_accelerator_name(key_or_move, 0) : g_strdup("None");
440 }
441 }
442 else
443 {
444 GSList *driver = darktable.control->input_drivers;
445 while(driver && (id -= 10) >= 10)
446 driver = driver->next;
447
448 if(!driver)
449 name = g_strdup(_("unknown driver"));
450 else
451 {
452 dt_input_driver_definition_t *callbacks = driver->data;
453 gchar *without_device
454 = mods == DT_MOVE_NAME
455 ? callbacks->move_to_string(key_or_move, display)
456 : callbacks->key_to_string(key_or_move, display);
457
458 if(display && id == 0)
459 post_name = without_device;
460 else
461 {
462 char id_str[2] = "\0\0";
463 if(id) id_str[0] = '0' + id;
464
465 name = g_strdup_printf("%s%s:%s", display ? "" : callbacks->name, id_str, without_device);
466 g_free(without_device);
467 }
468 }
469 }
470 if(mods != DT_MOVE_NAME)
471 {
472 for(const struct _modifier_name *mod_str = modifier_string;
473 mod_str->modifier;
474 mod_str++)
475 {
476 if(mods & mod_str->modifier)
477 {
478 gchar *save_name = name;
479 name = display
480 ? g_strdup_printf("%s%s+", name ? name : "", _(mod_str->name))
481 : g_strdup_printf("%s;%s", name ? name : "", mod_str->name);
482 g_free(save_name);
483 }
484 }
485 }
486
487 if(post_name)
488 {
489 gchar *save_name = name;
490 name = g_strdup_printf("%s%s", name ? name : "", post_name);
491 g_free(save_name);
492 g_free(post_name);
493 }
494
495 return name;
496 }
497
_shortcut_is_move(dt_shortcut_t * s)498 static gboolean _shortcut_is_move(dt_shortcut_t *s)
499 {
500 return (s->move_device != DT_SHORTCUT_DEVICE_KEYBOARD_MOUSE ||
501 s->move != DT_SHORTCUT_MOVE_NONE) && !s->direction;
502 }
503
_shortcut_description(dt_shortcut_t * s)504 static gchar *_shortcut_description(dt_shortcut_t *s)
505 {
506 static gchar hint[1024];
507 int length = 0;
508
509 #define add_hint(format, ...) length += length >= sizeof(hint) ? 0 : snprintf(hint + length, sizeof(hint) - length, format, ##__VA_ARGS__)
510
511 gchar *key_name = _shortcut_key_move_name(s->key_device, s->key, s->mods, TRUE);
512 gchar *move_name = _shortcut_key_move_name(s->move_device, s->move, DT_MOVE_NAME, TRUE);
513
514 add_hint("%s%s", key_name, s->key_device || s->key ? "" : move_name);
515
516 if(s->press & DT_SHORTCUT_DOUBLE) add_hint(" %s", _("double"));
517 if(s->press & DT_SHORTCUT_TRIPLE) add_hint(" %s", _("triple"));
518 if(s->press & DT_SHORTCUT_LONG ) add_hint(" %s", _("long"));
519 if(s->press) add_hint(" %s", _("press"));
520 if(s->button)
521 {
522 if(*key_name || *move_name) add_hint(",");
523 if(s->button & DT_SHORTCUT_LEFT ) add_hint(" %s", C_("accel", "left"));
524 if(s->button & DT_SHORTCUT_RIGHT ) add_hint(" %s", C_("accel", "right"));
525 if(s->button & DT_SHORTCUT_MIDDLE) add_hint(" %s", C_("accel", "middle"));
526 if(s->click & DT_SHORTCUT_DOUBLE) add_hint(" %s", C_("accel", "double"));
527 if(s->click & DT_SHORTCUT_TRIPLE) add_hint(" %s", C_("accel", "triple"));
528 if(s->click & DT_SHORTCUT_LONG ) add_hint(" %s", C_("accel", "long"));
529 add_hint(" %s", _("click"));
530 }
531
532 if(*move_name && (s->key_device || s->key))
533 add_hint(", %s", move_name);
534 if(s->direction)
535 add_hint(", %s", s->direction == DT_SHORTCUT_UP ? _("up") : _("down"));
536
537 g_free(key_name);
538 g_free(move_name);
539
540 return hint + (hint[0] == ' ' ? 1 : 0);
541 }
542
_action_description(dt_shortcut_t * s,int components)543 static gchar *_action_description(dt_shortcut_t *s, int components)
544 {
545 static gchar hint[1024];
546 int length = 0;
547 hint[0] = 0;
548
549 if(components == 2)
550 {
551 gchar *action_label = _action_full_label(s->action);
552 add_hint("%s", action_label);
553 g_free(action_label);
554 }
555
556 if(s->instance == 1)
557 add_hint(", %s", _("first instance"));
558 else if(s->instance == -1)
559 add_hint(", %s", _("last instance"));
560 else if(s->instance != 0)
561 add_hint(", %s %+d", _("relative instance"), s->instance);
562
563 const dt_action_def_t *def = _action_find_definition(s->action);
564
565 if(def && def->elements)
566 {
567 if(components && (s->element || (!def->fallbacks && def->elements->name)))
568 add_hint(", %s", _(def->elements[s->element].name));
569 if(def->elements[s->element].effects == dt_action_effect_selection
570 && s->effect > DT_ACTION_EFFECT_COMBO_SEPARATOR)
571 {
572 dt_introspection_type_enum_tuple_t *values
573 = g_hash_table_lookup(darktable.control->combo_introspection, s->action);
574 if(values)
575 add_hint(", %s", _(values[s->effect - DT_ACTION_EFFECT_COMBO_SEPARATOR - 1].description));
576 else
577 {
578 gchar **strings
579 = g_hash_table_lookup(darktable.control->combo_list, s->action);
580 if(strings)
581 add_hint(", %s", _(strings[s->effect - DT_ACTION_EFFECT_COMBO_SEPARATOR - 1]));
582 }
583 }
584 else if(s->effect > 0) add_hint(", %s", _(def->elements[s->element].effects[s->effect]));
585 }
586
587 if(s->speed != 1.0)
588 add_hint(", %s *%g", _("speed"), s->speed);
589
590 #undef add_hint
591
592 return hint;
593 }
594
_insert_shortcut_in_list(GHashTable * ht,char * shortcut,dt_action_t * ac,char * label)595 static void _insert_shortcut_in_list(GHashTable *ht, char *shortcut, dt_action_t *ac, char *label)
596 {
597 if(ac->owner && ac->owner->owner)
598 _insert_shortcut_in_list(ht, shortcut, ac->owner, g_strdup_printf("%s/%s", ac->owner->label, label));
599 {
600 GtkListStore *list_store = g_hash_table_lookup(ht, ac->owner);
601 if(!list_store)
602 {
603 list_store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
604 g_hash_table_insert(ht, ac->owner, list_store);
605 }
606
607 gtk_list_store_insert_with_values(list_store, NULL, -1, 0, shortcut, 1, label, -1);
608 }
609
610 g_free(label);
611 }
612
dt_shortcut_category_lists(dt_view_type_flags_t v)613 GHashTable *dt_shortcut_category_lists(dt_view_type_flags_t v)
614 {
615 GHashTable *ht = g_hash_table_new(NULL, NULL);
616
617 for(GSequenceIter *iter = g_sequence_get_begin_iter(darktable.control->shortcuts);
618 !g_sequence_iter_is_end(iter);
619 iter = g_sequence_iter_next(iter))
620 {
621 dt_shortcut_t *s = g_sequence_get(iter);
622 if(s && s->views & v)
623 _insert_shortcut_in_list(ht, _shortcut_description(s), s->action, g_strdup_printf("%s%s", s->action->label, _action_description(s, 1)));
624 }
625
626 return ht;
627 }
628
_shortcut_tooltip_callback(GtkWidget * widget,gint x,gint y,gboolean keyboard_mode,GtkTooltip * tooltip,gpointer user_data)629 static gboolean _shortcut_tooltip_callback(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode,
630 GtkTooltip *tooltip, gpointer user_data)
631 {
632 gchar *markup_text = NULL;
633 gchar *description = NULL;
634 dt_action_t *action = NULL;
635
636 if(GTK_IS_TREE_VIEW(widget))
637 {
638 if(!gtk_widget_is_sensitive(widget)) return FALSE;
639 if(user_data) // shortcuts treeview
640 {
641 gtk_tooltip_set_text(tooltip, _("press Del to delete selected shortcut\ndouble click to add new shortcut\nstart typing for incremental search"));
642 return TRUE;
643 }
644
645 GtkTreePath *path = NULL;
646 GtkTreeModel *model;
647 GtkTreeIter iter;
648 if(!gtk_tree_view_get_tooltip_context(GTK_TREE_VIEW(widget), &x, &y, keyboard_mode, &model, &path, &iter))
649 return FALSE;
650
651 gtk_tree_model_get(model, &iter, 0, &action, -1);
652 gtk_tree_view_set_tooltip_row(GTK_TREE_VIEW(widget), tooltip, path);
653 gtk_tree_path_free(path);
654
655 markup_text = g_markup_escape_text(_("click to filter shortcut list\ndouble click to define new shortcut\nstart typing for incremental search"), -1);
656 }
657 else
658 {
659 action = g_hash_table_lookup(darktable.control->widgets, widget);
660
661 if(darktable.control->mapping_widget == widget)
662 {
663 const int add_remove_qap = darktable.develop
664 ? dt_dev_modulegroups_basics_module_toggle(darktable.develop, widget, FALSE)
665 : 0;
666
667 markup_text = g_markup_printf_escaped("%s\n%s\n%s%s",
668 _("press keys with mouse click and scroll or move combinations to create a shortcut"),
669 _("click to open shortcut configuration"),
670 add_remove_qap > 0 ? _("ctrl+click to add to quick access panel\n") :
671 add_remove_qap < 0 ? _("ctrl+click to remove from quick access panel\n") : "",
672 _("right click to exit mapping mode"));
673 }
674 }
675
676 const dt_action_def_t *def = _action_find_definition(action);
677 const gboolean has_fallbacks = def && def->fallbacks;
678
679 if(def && (darktable.control->element || !has_fallbacks))
680 {
681 const gchar *element_name = NULL;
682 for(int i = 0; i <= darktable.control->element; i++)
683 {
684 element_name = def->elements[i].name;
685 if(!element_name) break;
686 }
687 if(element_name) description = g_markup_escape_text(_(element_name), -1);
688 }
689
690 for(GSequenceIter *iter = g_sequence_get_begin_iter(darktable.control->shortcuts);
691 !g_sequence_iter_is_end(iter);
692 iter = g_sequence_iter_next(iter))
693 {
694 dt_shortcut_t *s = g_sequence_get(iter);
695 if(s->action == action &&
696 (!def || darktable.control->element == -1 ||
697 s->element == darktable.control->element ||
698 (s->element == DT_ACTION_ELEMENT_DEFAULT && has_fallbacks)))
699 {
700 gchar *sc_escaped = g_markup_escape_text(_shortcut_description(s), -1);
701 gchar *ac_escaped = g_markup_escape_text(_action_description(s, 0), -1);
702 description = dt_util_dstrcat(description, "%s<b><big>%s</big></b><i>%s</i>",
703 description ? "\n" : "",
704 sc_escaped, ac_escaped);
705 g_free(sc_escaped);
706 g_free(ac_escaped);
707 }
708 }
709
710 gchar *original_markup = gtk_widget_get_tooltip_markup(widget);
711 if(description || (original_markup && markup_text))
712 {
713 markup_text = dt_util_dstrcat(markup_text, "%s%s%s%s",
714 markup_text? "\n\n" : "",
715 original_markup ? original_markup : "",
716 original_markup && description ? "\n" : "",
717 description ? description : "");
718 g_free(description);
719 }
720 g_free(original_markup);
721
722 if(markup_text)
723 {
724 gtk_tooltip_set_markup(tooltip, markup_text);
725 g_free(markup_text);
726
727 return TRUE;
728 }
729
730 return FALSE;
731 }
732
_find_views(dt_action_t * action)733 static dt_view_type_flags_t _find_views(dt_action_t *action)
734 {
735 dt_view_type_flags_t vws = 0;
736
737 dt_action_t *owner = action;
738 while(owner && owner->type >= DT_ACTION_TYPE_SECTION)
739 owner = owner->owner;
740
741 if(owner)
742
743 switch(owner->type)
744 {
745 case DT_ACTION_TYPE_IOP:
746 vws = DT_VIEW_DARKROOM;
747 break;
748 case DT_ACTION_TYPE_VIEW:
749 {
750 dt_view_t *view = (dt_view_t *)owner;
751
752 vws = view->view(view);
753 }
754 break;
755 case DT_ACTION_TYPE_LIB:
756 {
757 dt_lib_module_t *lib = (dt_lib_module_t *)owner;
758
759 const gchar **views = lib->views(lib);
760 while (*views)
761 {
762 if (strcmp(*views, "lighttable") == 0)
763 vws |= DT_VIEW_LIGHTTABLE;
764 else if(strcmp(*views, "darkroom") == 0)
765 vws |= DT_VIEW_DARKROOM;
766 else if(strcmp(*views, "print") == 0)
767 vws |= DT_VIEW_PRINT;
768 else if(strcmp(*views, "slideshow") == 0)
769 vws |= DT_VIEW_SLIDESHOW;
770 else if(strcmp(*views, "map") == 0)
771 vws |= DT_VIEW_MAP;
772 else if(strcmp(*views, "tethering") == 0)
773 vws |= DT_VIEW_TETHERING;
774 else if(strcmp(*views, "*") == 0)
775 vws |= DT_VIEW_DARKROOM | DT_VIEW_LIGHTTABLE | DT_VIEW_TETHERING |
776 DT_VIEW_MAP | DT_VIEW_PRINT | DT_VIEW_SLIDESHOW;
777 views++;
778 }
779 }
780 break;
781 case DT_ACTION_TYPE_CATEGORY:
782 if(owner == &darktable.control->actions_blend)
783 vws = DT_VIEW_DARKROOM;
784 else if(owner == &darktable.control->actions_fallbacks)
785 vws = 0;
786 else if(owner == &darktable.control->actions_lua)
787 vws = DT_VIEW_DARKROOM | DT_VIEW_LIGHTTABLE | DT_VIEW_TETHERING |
788 DT_VIEW_MAP | DT_VIEW_PRINT | DT_VIEW_SLIDESHOW;
789 else if(owner == &darktable.control->actions_thumb)
790 {
791 vws = DT_VIEW_DARKROOM | DT_VIEW_MAP | DT_VIEW_TETHERING | DT_VIEW_PRINT;
792 if(!strcmp(action->id,"rating") || !strcmp(action->id,"color label"))
793 vws |= DT_VIEW_LIGHTTABLE; // lighttable has copy/paste history shortcuts in separate lib
794 }
795 else
796 fprintf(stderr, "[find_views] views for category '%s' unknown\n", owner->id);
797 break;
798 case DT_ACTION_TYPE_GLOBAL:
799 vws = DT_VIEW_DARKROOM | DT_VIEW_LIGHTTABLE | DT_VIEW_TETHERING |
800 DT_VIEW_MAP | DT_VIEW_PRINT | DT_VIEW_SLIDESHOW;
801 break;
802 default:
803 break;
804 }
805
806 return vws;
807 }
808
809 static GtkTreeStore *_shortcuts_store = NULL;
810 static GtkTreeStore *_actions_store = NULL;
811 static GtkWidget *_grab_widget = NULL, *_grab_window = NULL;
812
813 #define NUM_CATEGORIES 3
814 const gchar *category_label[NUM_CATEGORIES]
815 = { N_("active view"),
816 N_("other views"),
817 N_("fallbacks") };
818 #define CATEGORY_FALLBACKS 2
819
_shortcuts_store_category(GtkTreeIter * category,dt_shortcut_t * s,dt_view_type_flags_t view)820 static void _shortcuts_store_category(GtkTreeIter *category, dt_shortcut_t *s, dt_view_type_flags_t view)
821 {
822 gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(_shortcuts_store), category, NULL,
823 s && s->views ? s->views & view ? 0 : 1 : 2);
824 }
825
_remove_shortcut_from_store(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)826 static gboolean _remove_shortcut_from_store(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
827 {
828 gpointer iter_data;
829 gtk_tree_model_get(model, iter, 0, &iter_data, -1);
830 if(iter_data == data)
831 {
832 gtk_tree_store_remove(GTK_TREE_STORE(model), iter);
833 return TRUE;
834 }
835
836 return FALSE;
837 }
838
_remove_shortcut(GSequenceIter * shortcut)839 static void _remove_shortcut(GSequenceIter *shortcut)
840 {
841 if(_shortcuts_store)
842 gtk_tree_model_foreach(GTK_TREE_MODEL(_shortcuts_store), _remove_shortcut_from_store, shortcut);
843
844 dt_shortcut_t *s = g_sequence_get(shortcut);
845 if(s && s->direction) // was this a split move?
846 {
847 // unsplit the other half of the move
848 s->direction = 0;
849 dt_shortcut_t *o = g_sequence_get(g_sequence_iter_prev(shortcut));
850 if(g_sequence_iter_is_begin(shortcut)
851 || _shortcut_compare_func(s, o, GINT_TO_POINTER(s->views)))
852 o = g_sequence_get(g_sequence_iter_next(shortcut));
853 o->direction = 0;
854 }
855
856 g_sequence_remove(shortcut);
857 }
858
_add_shortcut(dt_shortcut_t * shortcut,dt_view_type_flags_t view)859 static void _add_shortcut(dt_shortcut_t *shortcut, dt_view_type_flags_t view)
860 {
861 GSequenceIter *new_shortcut = g_sequence_insert_sorted(darktable.control->shortcuts, shortcut,
862 _shortcut_compare_func, GINT_TO_POINTER(view));
863
864 GtkTreeModel *model = GTK_TREE_MODEL(_shortcuts_store);
865 if(model)
866 {
867 GSequenceIter *prev_shortcut = g_sequence_iter_prev(new_shortcut);
868 GSequenceIter *seq_iter = NULL;
869 GtkTreeIter category, child;
870 _shortcuts_store_category(&category, shortcut, view);
871
872 gint position = 1, found = 0;
873 if(gtk_tree_model_iter_children(model, &child, &category))
874 do
875 {
876 gtk_tree_model_get(model, &child, 0, &seq_iter, -1);
877 if(seq_iter == prev_shortcut)
878 {
879 found = position;
880 break;
881 }
882 position++;
883 } while(gtk_tree_model_iter_next(model, &child));
884
885 gtk_tree_store_insert_with_values(_shortcuts_store, NULL, &category, found, 0, new_shortcut, -1);
886 }
887 }
888
_shortcut_row_inserted(GtkTreeModel * tree_model,GtkTreePath * path,GtkTreeIter * iter,gpointer view)889 static void _shortcut_row_inserted(GtkTreeModel *tree_model, GtkTreePath *path, GtkTreeIter *iter, gpointer view)
890 {
891 // connect to original store, not filtered one, because otherwise view not sufficiently updated to expand
892
893 GtkTreePath *filter_path = gtk_tree_model_filter_convert_child_path_to_path
894 (GTK_TREE_MODEL_FILTER(gtk_tree_view_get_model(view)), path);
895 if(!filter_path) return;
896
897 gtk_tree_view_expand_to_path(view, filter_path);
898 gtk_tree_view_scroll_to_cell(view, filter_path, NULL, TRUE, 0.5, 0);
899 gtk_tree_view_set_cursor(view, filter_path, NULL, FALSE);
900 gtk_tree_path_free(filter_path);
901 }
902
_yes_no_dialog(gchar * title,gchar * question)903 static gboolean _yes_no_dialog(gchar *title, gchar *question)
904 {
905 GtkWindow *win = NULL;
906 for(GList *wins = gtk_window_list_toplevels(); wins; wins = g_list_delete_link(wins, wins))
907 if(gtk_window_is_active(wins->data)) win = wins->data;
908
909 GtkWidget *dialog = gtk_message_dialog_new(win, GTK_DIALOG_DESTROY_WITH_PARENT,
910 GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, "%s", question);
911 gtk_window_set_title(GTK_WINDOW(dialog), title);
912
913 #ifdef GDK_WINDOWING_QUARTZ
914 dt_osx_disallow_fullscreen(dialog);
915 #endif
916
917 const int resp = gtk_dialog_run(GTK_DIALOG(dialog));
918
919 gtk_widget_destroy(dialog);
920
921 return resp == GTK_RESPONSE_YES;
922 }
923
_insert_shortcut(dt_shortcut_t * shortcut,gboolean confirm)924 static gboolean _insert_shortcut(dt_shortcut_t *shortcut, gboolean confirm)
925 {
926 dt_shortcut_t *s = calloc(sizeof(dt_shortcut_t), 1);
927 *s = *shortcut;
928 s->views = _find_views(s->action);
929 dt_view_type_flags_t real_views = s->views;
930
931 const dt_view_t *vw = NULL;
932 if(darktable.view_manager)
933 vw = dt_view_manager_get_current_view(darktable.view_manager);
934
935 dt_view_type_flags_t view = vw && vw->view ? vw->view(vw) : DT_VIEW_LIGHTTABLE;
936
937 // check (and remove if confirmed) clashes in current and other views
938 gboolean remove_existing = !confirm;
939 do
940 {
941 gchar *existing_labels = NULL;
942 int active_view = 1;
943 do
944 {
945 GSequenceIter *existing = g_sequence_lookup(darktable.control->shortcuts, s, _shortcut_compare_func, GINT_TO_POINTER(view));
946 if(existing) // at least one found
947 {
948 // go to first one that has same shortcut
949 while(!g_sequence_iter_is_begin(existing)
950 && !_shortcut_compare_func(s, g_sequence_get(g_sequence_iter_prev(existing)), GINT_TO_POINTER(view)))
951 existing = g_sequence_iter_prev(existing);
952
953 do
954 {
955 GSequenceIter *saved_next = g_sequence_iter_next(existing);
956
957 dt_shortcut_t *e = g_sequence_get(existing);
958
959 if(e->action == s->action)
960 {
961 if(_shortcut_is_move(e) && e->effect != DT_ACTION_EFFECT_DEFAULT_MOVE)
962 {
963 if(!confirm ||
964 _yes_no_dialog(_("shortcut for move exists with single effect"),
965 _("create separate shortcuts for up and down move?")))
966 {
967 e->direction = (DT_SHORTCUT_UP | DT_SHORTCUT_DOWN) ^ s->direction;
968 if(s->effect == DT_ACTION_EFFECT_DEFAULT_MOVE)
969 s->effect = DT_ACTION_EFFECT_DEFAULT_KEY;
970 _add_shortcut(s, view);
971 return TRUE;
972 }
973 }
974 else if(e->element != s->element ||
975 e->effect != s->effect ||
976 e->speed != s->speed ||
977 e->instance != s->instance )
978 {
979 if(!confirm ||
980 _yes_no_dialog(_("shortcut exists with different settings"),
981 _("reset the settings of the shortcut?")))
982 {
983 e->element = s->element;
984 e->effect = s->effect;
985 e->speed = s->speed;
986 e->instance = s->instance;
987 }
988 }
989 else
990 {
991 // there should be no other clashes because same mapping already existed
992 if(confirm &&
993 _yes_no_dialog(_("shortcut already exists"),
994 _("remove the shortcut?")))
995 {
996 _remove_shortcut(existing);
997 }
998 }
999 g_free(s);
1000 return FALSE;
1001 }
1002
1003 if(e->views & real_views) // overlap
1004 {
1005 if(remove_existing)
1006 _remove_shortcut(existing);
1007 else
1008 {
1009 gchar *old_labels = existing_labels;
1010 existing_labels = g_strdup_printf("%s\n%s",
1011 existing_labels ? existing_labels : "",
1012 _action_description(e, 2));
1013 g_free(old_labels);
1014 }
1015 }
1016
1017 existing = saved_next;
1018 } while(!g_sequence_iter_is_end(existing) && !_shortcut_compare_func(s, g_sequence_get(existing), GINT_TO_POINTER(view)));
1019 }
1020
1021 s->views ^= view; // look in the opposite selection
1022 } while(active_view--);
1023
1024 if(existing_labels)
1025 {
1026 gchar *question = g_strdup_printf("%s\n%s",
1027 _("remove these existing shortcuts?"),
1028 existing_labels);
1029 remove_existing = _yes_no_dialog(_("clashing shortcuts exist"), question);
1030
1031 g_free(existing_labels);
1032 g_free(question);
1033
1034 if(!remove_existing)
1035 {
1036 g_free(s);
1037 return FALSE;
1038 }
1039 }
1040 else
1041 {
1042 remove_existing = FALSE;
1043 }
1044
1045 } while(remove_existing);
1046
1047 s->direction = shortcut->direction = 0;
1048 _add_shortcut(s, view);
1049
1050 return TRUE;
1051 }
1052
1053 typedef enum
1054 {
1055 SHORTCUT_VIEW_DESCRIPTION,
1056 SHORTCUT_VIEW_ACTION,
1057 SHORTCUT_VIEW_ELEMENT,
1058 SHORTCUT_VIEW_EFFECT,
1059 SHORTCUT_VIEW_SPEED,
1060 SHORTCUT_VIEW_INSTANCE,
1061 SHORTCUT_VIEW_COLUMNS
1062 } field_id;
1063
1064 #define NUM_INSTANCES 5 // or 3, but change char relative[] = "-2" to "-1"
1065 const gchar *instance_label[/*NUM_INSTANCES*/]
1066 = { N_("preferred"),
1067 N_("first"),
1068 N_("last"),
1069 N_("second"),
1070 N_("last but one") };
1071
_fill_shortcut_fields(GtkTreeViewColumn * column,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,gpointer data)1072 static void _fill_shortcut_fields(GtkTreeViewColumn *column, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
1073 {
1074 void *data_ptr = NULL;
1075 gtk_tree_model_get(model, iter, 0, &data_ptr, -1);
1076 const field_id field = GPOINTER_TO_INT(data);
1077 gchar *field_text = NULL;
1078 gboolean editable = FALSE;
1079 PangoUnderline underline = PANGO_UNDERLINE_NONE;
1080 int weight = PANGO_WEIGHT_NORMAL;
1081
1082 if(GPOINTER_TO_UINT(data_ptr) < NUM_CATEGORIES)
1083 {
1084 if(field == SHORTCUT_VIEW_DESCRIPTION)
1085 field_text = g_strdup(_(category_label[GPOINTER_TO_INT(data_ptr)]));
1086 }
1087 else
1088 {
1089 const dt_action_element_def_t *elements = NULL;
1090 dt_shortcut_t *s = g_sequence_get(data_ptr);
1091 switch(field)
1092 {
1093 case SHORTCUT_VIEW_DESCRIPTION:
1094 field_text = g_strdup(_shortcut_description(s));
1095 break;
1096 case SHORTCUT_VIEW_ACTION:
1097 if(s->action)
1098 field_text = _action_full_label(s->action);
1099 break;
1100 case SHORTCUT_VIEW_ELEMENT:
1101 elements = _action_find_elements(s->action);
1102 if(elements && elements->name)
1103 {
1104 if(s->element || s->action->type != DT_ACTION_TYPE_FALLBACK)
1105 field_text = g_strdup(_(elements[s->element].name));
1106 if(s->element == 0) weight = PANGO_WEIGHT_LIGHT;
1107 editable = TRUE;
1108 }
1109 break;
1110 case SHORTCUT_VIEW_EFFECT:
1111 elements = _action_find_elements(s->action);
1112 if(elements)
1113 {
1114 if(elements[s->element].effects == dt_action_effect_selection
1115 && s->effect > DT_ACTION_EFFECT_COMBO_SEPARATOR)
1116 {
1117 dt_introspection_type_enum_tuple_t *values
1118 = g_hash_table_lookup(darktable.control->combo_introspection, s->action);
1119 if(values)
1120 field_text = g_strdup(_(values[s->effect - DT_ACTION_EFFECT_COMBO_SEPARATOR - 1].description));
1121 else
1122 {
1123 gchar **strings
1124 = g_hash_table_lookup(darktable.control->combo_list, s->action);
1125 if(strings)
1126 field_text = g_strdup(_(strings[s->effect - DT_ACTION_EFFECT_COMBO_SEPARATOR - 1]));
1127 }
1128 }
1129 else if(s->effect > 0 || s->action->type != DT_ACTION_TYPE_FALLBACK)
1130 field_text = g_strdup(_(elements[s->element].effects[s->effect]));
1131 if(s->effect == 0) weight = PANGO_WEIGHT_LIGHT;
1132 editable = TRUE;
1133 }
1134 break;
1135 case SHORTCUT_VIEW_SPEED:
1136 elements = _action_find_elements(s->action);
1137 if(s->speed != 1.0
1138 || (elements && elements[s->element].effects == dt_action_effect_value
1139 && (s->effect == DT_ACTION_EFFECT_DEFAULT_MOVE
1140 || s->effect == DT_ACTION_EFFECT_DEFAULT_KEY
1141 || s->effect == DT_ACTION_EFFECT_DEFAULT_UP
1142 || s->effect == DT_ACTION_EFFECT_DEFAULT_DOWN
1143 || s->effect == DT_ACTION_EFFECT_SET
1144 || (!s->effect && s->action->type == DT_ACTION_TYPE_FALLBACK))))
1145 {
1146 field_text = g_strdup_printf("%.3f", s->speed);
1147 if(s->speed == 1.0) weight = PANGO_WEIGHT_LIGHT;
1148 }
1149 editable = TRUE;
1150 break;
1151 case SHORTCUT_VIEW_INSTANCE:
1152 for(dt_action_t *owner = s->action; owner; owner = owner->owner)
1153 {
1154 if(owner->type == DT_ACTION_TYPE_IOP)
1155 {
1156 dt_iop_module_so_t *iop = (dt_iop_module_so_t *)owner;
1157
1158 if(!(iop->flags() & IOP_FLAGS_ONE_INSTANCE))
1159 {
1160 field_text = abs(s->instance) <= (NUM_INSTANCES - 1) /2
1161 ? g_strdup(_(instance_label[abs(s->instance)*2 - (s->instance > 0)]))
1162 : g_strdup_printf("%+d", s->instance);
1163 if(s->instance == 0) weight = PANGO_WEIGHT_LIGHT;
1164 editable = TRUE;
1165 }
1166 break;
1167 }
1168 }
1169 break;
1170 default:
1171 break;
1172 }
1173 }
1174 g_object_set(cell, "text", field_text, "editable", editable, "underline", underline, "weight", weight, NULL);
1175 g_free(field_text);
1176 }
1177
_add_prefs_column(GtkTreeView * tree,GtkCellRenderer * renderer,char * name,int position)1178 static void _add_prefs_column(GtkTreeView *tree, GtkCellRenderer *renderer, char *name, int position)
1179 {
1180 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(name, renderer, NULL);
1181 gtk_tree_view_column_set_cell_data_func(column, renderer, _fill_shortcut_fields, GINT_TO_POINTER(position), NULL);
1182 gtk_tree_view_column_set_resizable(column, TRUE);
1183 gtk_tree_view_append_column(tree, column);
1184 }
1185
_find_edited_shortcut(GtkTreeModel * model,const gchar * path_string)1186 static dt_shortcut_t *_find_edited_shortcut(GtkTreeModel *model, const gchar *path_string)
1187 {
1188 GtkTreePath *path = gtk_tree_path_new_from_string(path_string);
1189 GtkTreeIter iter;
1190 gtk_tree_model_get_iter(model, &iter, path);
1191 gtk_tree_path_free(path);
1192
1193 void *data_ptr = NULL;
1194 gtk_tree_model_get(model, &iter, 0, &data_ptr, -1);
1195
1196 return g_sequence_get(data_ptr);
1197 }
1198
_element_editing_started(GtkCellRenderer * renderer,GtkCellEditable * editable,char * path,gpointer data)1199 static void _element_editing_started(GtkCellRenderer *renderer, GtkCellEditable *editable, char *path, gpointer data)
1200 {
1201 dt_shortcut_t *s = _find_edited_shortcut(data, path);
1202
1203 GtkComboBox *combo_box = GTK_COMBO_BOX(editable);
1204 GtkListStore *store = GTK_LIST_STORE(gtk_combo_box_get_model(combo_box));
1205 gtk_list_store_clear(store);
1206
1207 int show_all = s->action->type != DT_ACTION_TYPE_FALLBACK;
1208 for(const dt_action_element_def_t *element = _action_find_elements(s->action); element && element->name ; element++)
1209 gtk_list_store_insert_with_values(store, NULL, -1, 0, show_all++ ? _(element->name) : _("(unchanged)"), -1);
1210
1211 gtk_combo_box_set_active(combo_box, s->element);
1212 }
1213
_element_changed(GtkCellRendererCombo * combo,char * path_string,GtkTreeIter * new_iter,gpointer data)1214 static void _element_changed(GtkCellRendererCombo *combo, char *path_string, GtkTreeIter *new_iter, gpointer data)
1215 {
1216 dt_shortcut_t *s = _find_edited_shortcut(data, path_string);
1217
1218 GtkTreeModel *model = NULL;
1219 g_object_get(combo, "model", &model, NULL);
1220 GtkTreePath *path = gtk_tree_model_get_path(model, new_iter);
1221 const gint new_index = gtk_tree_path_get_indices(path)[0];
1222 gtk_tree_path_free(path);
1223
1224 const dt_action_element_def_t *elements = _action_find_elements(s->action);
1225 if(elements[s->element].effects != elements[new_index].effects)
1226 {
1227 s->effect = _shortcut_is_move(s) ? DT_ACTION_EFFECT_DEFAULT_MOVE : DT_ACTION_EFFECT_DEFAULT_KEY;
1228 }
1229 s->element = new_index;
1230
1231 dt_shortcuts_save(NULL, FALSE);
1232 }
1233
1234 enum
1235 {
1236 DT_ACTION_EFFECT_COLUMN_NAME,
1237 DT_ACTION_EFFECT_COLUMN_SEPARATOR,
1238 DT_ACTION_EFFECT_COLUMN_WEIGHT,
1239 };
1240
_effects_separator_func(GtkTreeModel * model,GtkTreeIter * iter,gpointer data)1241 static gboolean _effects_separator_func(GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
1242 {
1243 gboolean is_separator;
1244 gtk_tree_model_get(model, iter, DT_ACTION_EFFECT_COLUMN_SEPARATOR, &is_separator, -1);
1245 return is_separator;
1246 }
1247
_effect_editing_started(GtkCellRenderer * renderer,GtkCellEditable * editable,char * path,gpointer data)1248 static void _effect_editing_started(GtkCellRenderer *renderer, GtkCellEditable *editable, char *path, gpointer data)
1249 {
1250 dt_shortcut_t *s = _find_edited_shortcut(data, path);
1251
1252 GtkComboBox *combo_box = GTK_COMBO_BOX(editable);
1253 GtkListStore *store = GTK_LIST_STORE(gtk_combo_box_get_model(combo_box));
1254 gtk_list_store_clear(store);
1255
1256 const dt_action_element_def_t *elements = _action_find_elements(s->action);
1257 int show_all = s->action->type != DT_ACTION_TYPE_FALLBACK;
1258 int bold_move = _shortcut_is_move(s) ? DT_ACTION_EFFECT_DEFAULT_KEY : DT_ACTION_EFFECT_DEFAULT_DOWN + 1;
1259 if(elements)
1260 for(const gchar **effect = elements[s->element].effects; *effect ; effect++, bold_move++)
1261 {
1262 gtk_list_store_insert_with_values(store, NULL, -1,
1263 DT_ACTION_EFFECT_COLUMN_NAME, show_all++ ? _(*effect) : _("(unchanged)"),
1264 DT_ACTION_EFFECT_COLUMN_WEIGHT, bold_move > DT_ACTION_EFFECT_DEFAULT_KEY
1265 && bold_move <= DT_ACTION_EFFECT_DEFAULT_DOWN
1266 ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
1267 -1);
1268 }
1269
1270 GList *cell = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(combo_box));
1271 gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(combo_box), cell->data, "weight", DT_ACTION_EFFECT_COLUMN_WEIGHT);
1272 g_list_free(cell);
1273
1274 if(elements[s->element].effects == dt_action_effect_selection)
1275 {
1276 gtk_combo_box_set_row_separator_func(combo_box, _effects_separator_func, NULL, NULL);
1277
1278 dt_introspection_type_enum_tuple_t *values
1279 = g_hash_table_lookup(darktable.control->combo_introspection, s->action);
1280 if(values)
1281 {
1282 // insert empty/separator row
1283 gtk_list_store_insert_with_values(store, NULL, -1, DT_ACTION_EFFECT_COLUMN_SEPARATOR, TRUE, -1);
1284
1285 while(values->name)
1286 {
1287 gtk_list_store_insert_with_values(store, NULL, -1,
1288 DT_ACTION_EFFECT_COLUMN_NAME, _((values++)->description),
1289 DT_ACTION_EFFECT_COLUMN_WEIGHT, PANGO_WEIGHT_NORMAL,
1290 -1);
1291 }
1292 }
1293 else
1294 {
1295 gchar **strings
1296 = g_hash_table_lookup(darktable.control->combo_list, s->action);
1297 if(strings)
1298 {
1299 // insert empty/separator row
1300 gtk_list_store_insert_with_values(store, NULL, -1, DT_ACTION_EFFECT_COLUMN_SEPARATOR, TRUE, -1);
1301
1302 while(*strings)
1303 {
1304 gtk_list_store_insert_with_values(store, NULL, -1,
1305 DT_ACTION_EFFECT_COLUMN_NAME, _(*(strings++)),
1306 DT_ACTION_EFFECT_COLUMN_WEIGHT, PANGO_WEIGHT_NORMAL,
1307 -1);
1308 }
1309 }
1310 }
1311 }
1312
1313 gtk_combo_box_set_active(combo_box, s->effect == -1 ? 1 : s->effect);
1314 }
1315
_effect_changed(GtkCellRendererCombo * combo,char * path_string,GtkTreeIter * new_iter,gpointer data)1316 static void _effect_changed(GtkCellRendererCombo *combo, char *path_string, GtkTreeIter *new_iter, gpointer data)
1317 {
1318 dt_shortcut_t *s = _find_edited_shortcut(data, path_string);
1319
1320 GtkTreeModel *model = NULL;
1321 g_object_get(combo, "model", &model, NULL);
1322 GtkTreePath *path = gtk_tree_model_get_path(model, new_iter);
1323 const gint new_index = s->effect = gtk_tree_path_get_indices(path)[0];
1324 gtk_tree_path_free(path);
1325
1326 if(_shortcut_is_move(s) &&
1327 (new_index == DT_ACTION_EFFECT_DEFAULT_UP || new_index == DT_ACTION_EFFECT_DEFAULT_DOWN))
1328 s->effect = DT_ACTION_EFFECT_DEFAULT_MOVE;
1329 else
1330 s->effect = new_index;
1331
1332 dt_shortcuts_save(NULL, FALSE);
1333 }
1334
_speed_edited(GtkCellRendererText * cell,const gchar * path_string,const gchar * new_text,gpointer data)1335 static void _speed_edited(GtkCellRendererText *cell, const gchar *path_string, const gchar *new_text, gpointer data)
1336 {
1337 _find_edited_shortcut(data, path_string)->speed = atof(new_text);
1338
1339 dt_shortcuts_save(NULL, FALSE);
1340 }
1341
_instance_edited(GtkCellRendererText * cell,const gchar * path_string,const gchar * new_text,gpointer data)1342 static void _instance_edited(GtkCellRendererText *cell, const gchar *path_string, const gchar *new_text, gpointer data)
1343 {
1344 dt_shortcut_t *s = _find_edited_shortcut(data, path_string);
1345
1346 if(!(s->instance = atoi(new_text)))
1347 for(int i = 0; i < NUM_INSTANCES; i++)
1348 if(!strcmp(instance_label[i], new_text))
1349 s->instance = (i + 1) / 2 * (i % 2 ? 1 : -1);
1350
1351 dt_shortcuts_save(NULL, FALSE);
1352 }
1353
_grab_in_tree_view(GtkTreeView * tree_view)1354 static void _grab_in_tree_view(GtkTreeView *tree_view)
1355 {
1356 g_set_weak_pointer(&_grab_widget, gtk_widget_get_parent(gtk_widget_get_parent(GTK_WIDGET(tree_view)))); // static
1357 gtk_widget_set_sensitive(_grab_widget, FALSE);
1358 gtk_widget_set_tooltip_text(_grab_widget, _("define a shortcut by pressing a key, optionally combined with modifier keys (ctrl/shift/alt)\n"
1359 "a key can be double or triple pressed, with a long last press\n"
1360 "while the key is held, a combination of mouse buttons can be (double/triple/long) clicked\n"
1361 "still holding the key (and modifiers and/or buttons) a scroll or mouse move can be added\n"
1362 "connected devices can send keys or moves using their physical controllers\n\n"
1363 "right-click to cancel"));
1364 g_set_weak_pointer(&_grab_window, gtk_widget_get_toplevel(_grab_widget));
1365 if(_sc.action && _sc.action->type == DT_ACTION_TYPE_FALLBACK)
1366 dt_shortcut_key_press(DT_SHORTCUT_DEVICE_KEYBOARD_MOUSE, 0, 0);
1367 g_signal_connect(_grab_window, "event", G_CALLBACK(dt_shortcut_dispatcher), NULL);
1368 }
1369
_shortcut_row_activated(GtkTreeView * tree_view,GtkTreePath * path,GtkTreeViewColumn * column,gpointer user_data)1370 static void _shortcut_row_activated(GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data)
1371 {
1372 GtkTreeIter iter;
1373 gtk_tree_model_get_iter(GTK_TREE_MODEL(user_data), &iter, path);
1374
1375 GSequenceIter *shortcut_iter = NULL;
1376 gtk_tree_model_get(GTK_TREE_MODEL(user_data), &iter, 0, &shortcut_iter, -1);
1377
1378 if(GPOINTER_TO_UINT(shortcut_iter) < NUM_CATEGORIES) return;
1379
1380 dt_shortcut_t *s = g_sequence_get(shortcut_iter);
1381 _sc.action = s->action;
1382 _sc.element = s->element;
1383 _sc.instance = s->instance;
1384
1385 _grab_in_tree_view(tree_view);
1386 }
1387
_shortcut_key_pressed(GtkWidget * widget,GdkEventKey * event,gpointer user_data)1388 static gboolean _shortcut_key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
1389 {
1390 // GDK_KEY_BackSpace moves to parent in tree
1391 if(event->keyval == GDK_KEY_Delete || event->keyval == GDK_KEY_KP_Delete)
1392 {
1393 GtkTreeView *view = GTK_TREE_VIEW(widget);
1394 GtkTreeSelection *selection = gtk_tree_view_get_selection(view);
1395
1396 GtkTreeIter iter;
1397 GtkTreeModel *model = NULL;
1398 if(gtk_tree_selection_get_selected(selection, &model, &iter))
1399 {
1400 GSequenceIter *shortcut_iter = NULL;
1401 gtk_tree_model_get(model, &iter, 0, &shortcut_iter, -1);
1402
1403 if(GPOINTER_TO_UINT(shortcut_iter) >= NUM_CATEGORIES)
1404 {
1405 if(_yes_no_dialog(_("removing shortcut"),
1406 _("remove the selected shortcut?")))
1407 {
1408 _remove_shortcut(shortcut_iter);
1409
1410 dt_shortcuts_save(NULL, FALSE);
1411 }
1412 }
1413 }
1414
1415 return TRUE;
1416 }
1417
1418 return FALSE;
1419 }
1420
_add_shortcuts_to_tree()1421 static void _add_shortcuts_to_tree()
1422 {
1423 const dt_view_t *vw = dt_view_manager_get_current_view(darktable.view_manager);
1424 dt_view_type_flags_t view = vw && vw->view ? vw->view(vw) : DT_VIEW_LIGHTTABLE;
1425
1426 for(gint i = 0; i < NUM_CATEGORIES; i++)
1427 gtk_tree_store_insert_with_values(_shortcuts_store, NULL, NULL, -1, 0, GINT_TO_POINTER(i), -1);
1428
1429 for(GSequenceIter *iter = g_sequence_get_begin_iter(darktable.control->shortcuts);
1430 !g_sequence_iter_is_end(iter);
1431 iter = g_sequence_iter_next(iter))
1432 {
1433 dt_shortcut_t *s = g_sequence_get(iter);
1434 GtkTreeIter category;
1435 _shortcuts_store_category(&category, s, view);
1436
1437 gtk_tree_store_insert_with_values(_shortcuts_store, NULL, &category, -1, 0, iter, -1);
1438 }
1439 }
1440
_add_actions_to_tree(GtkTreeIter * parent,dt_action_t * action,dt_action_t * find,GtkTreeIter * found)1441 static gboolean _add_actions_to_tree(GtkTreeIter *parent, dt_action_t *action,
1442 dt_action_t *find, GtkTreeIter *found)
1443 {
1444 gboolean any_leaves = FALSE;
1445
1446 GtkTreeIter iter;
1447 while(action)
1448 {
1449 gtk_tree_store_insert_with_values(_actions_store, &iter, parent, -1, 0, action, -1);
1450
1451 gboolean module_is_needed = FALSE;
1452 if(action->type == DT_ACTION_TYPE_IOP)
1453 {
1454 const dt_iop_module_so_t *module = (dt_iop_module_so_t *)action;
1455 module_is_needed = !(module->flags() & (IOP_FLAGS_HIDDEN | IOP_FLAGS_DEPRECATED));
1456 }
1457 else if(action->type == DT_ACTION_TYPE_LIB)
1458 {
1459 dt_lib_module_t *module = (dt_lib_module_t *)action;
1460 module_is_needed = module->gui_reset || module->get_params || module->expandable(module);
1461 }
1462
1463 if(action->type <= DT_ACTION_TYPE_SECTION &&
1464 !_add_actions_to_tree(&iter, action->target, find, found) &&
1465 !module_is_needed)
1466 gtk_tree_store_remove(_actions_store, &iter);
1467 else
1468 {
1469 any_leaves = TRUE;
1470 if(action == find) *found = iter;
1471 }
1472
1473 action = action->next;
1474 }
1475
1476 return any_leaves;
1477 }
1478
_fill_action_fields(GtkTreeViewColumn * column,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,gpointer data)1479 static void _fill_action_fields(GtkTreeViewColumn *column, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
1480 {
1481 dt_action_t *action = NULL;
1482 gtk_tree_model_get(model, iter, 0, &action, -1);
1483 if(data)
1484 g_object_set(cell, "text", action->label, NULL);
1485 else
1486 {
1487 const dt_action_def_t *def = _action_find_definition(action);
1488 g_object_set(cell, "text", def ? _(def->name) : "", NULL);
1489 }
1490 }
1491
_action_row_activated(GtkTreeView * tree_view,GtkTreePath * path,GtkTreeViewColumn * column,gpointer user_data)1492 static void _action_row_activated(GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data)
1493 {
1494 GtkTreeIter iter;
1495 gtk_tree_model_get_iter(GTK_TREE_MODEL(user_data), &iter, path);
1496
1497 gtk_tree_model_get(GTK_TREE_MODEL(user_data), &iter, 0, &_sc.action, -1);
1498 _sc.element = DT_ACTION_ELEMENT_DEFAULT;
1499 _sc.instance = 0;
1500
1501 if(_action_find_definition(_sc.action)
1502 || (_sc.action->type > DT_ACTION_TYPE_SECTION && _sc.action->type < DT_ACTION_TYPE_WIDGET))
1503
1504 _grab_in_tree_view(tree_view);
1505 else
1506 _sc.action = NULL;
1507 }
1508
_shortcut_selection_function(GtkTreeSelection * selection,GtkTreeModel * model,GtkTreePath * path,gboolean path_currently_selected,gpointer data)1509 static gboolean _shortcut_selection_function(GtkTreeSelection *selection,
1510 GtkTreeModel *model, GtkTreePath *path,
1511 gboolean path_currently_selected, gpointer data)
1512 {
1513 GtkTreeIter iter;
1514 gtk_tree_model_get_iter(model, &iter, path);
1515
1516 void *data_ptr = NULL;
1517 gtk_tree_model_get(model, &iter, 0, &data_ptr, -1);
1518
1519 if(GPOINTER_TO_UINT(data_ptr) < NUM_CATEGORIES)
1520 {
1521 GtkTreeView *view = gtk_tree_selection_get_tree_view(selection);
1522 if(gtk_tree_view_row_expanded(view, path))
1523 gtk_tree_view_collapse_row(view, path);
1524 else
1525 gtk_tree_view_expand_row(view, path, FALSE);
1526
1527 return FALSE;
1528 }
1529 else
1530 return TRUE;
1531 }
1532
1533 static dt_action_t *_selected_action = NULL;
1534
_action_view_click(GtkWidget * widget,GdkEventButton * event,gpointer data)1535 static gboolean _action_view_click(GtkWidget *widget, GdkEventButton *event, gpointer data)
1536 {
1537 GtkTreeView *view = GTK_TREE_VIEW(widget);
1538
1539 if(event->button == GDK_BUTTON_PRIMARY)
1540 {
1541 GtkTreeSelection *selection = gtk_tree_view_get_selection(view);
1542
1543 GtkTreePath *path = NULL;
1544 if(gtk_tree_view_get_path_at_pos(view, (gint)event->x, (gint)event->y, &path, NULL, NULL, NULL))
1545 {
1546 if(event->type == GDK_DOUBLE_BUTTON_PRESS)
1547 {
1548 gtk_tree_selection_select_path(selection, path);
1549 _action_row_activated(view, path, NULL, gtk_tree_view_get_model(view));
1550 }
1551 else if(gtk_tree_selection_path_is_selected(selection, path))
1552 {
1553 gtk_tree_selection_unselect_path(selection, path);
1554 gtk_tree_view_collapse_row(view, path);
1555 }
1556 else
1557 {
1558 gtk_tree_selection_select_path(selection, path);
1559 gtk_tree_view_set_cursor(view, path, NULL, FALSE);
1560 }
1561
1562 gtk_widget_grab_focus(widget);
1563 }
1564 else
1565 gtk_tree_selection_unselect_all(selection);
1566 }
1567
1568 return TRUE;
1569 }
1570
_action_view_map(GtkTreeView * view,GdkEvent * event,gpointer found_iter)1571 static gboolean _action_view_map(GtkTreeView *view, GdkEvent *event, gpointer found_iter)
1572 {
1573 GtkTreePath *path = gtk_tree_model_get_path(gtk_tree_view_get_model(view), found_iter);
1574 gtk_tree_view_expand_to_path(view, path);
1575 gtk_tree_view_scroll_to_cell(view, path, NULL, TRUE, 0.5, 0);
1576 gtk_tree_view_set_cursor(view, path, NULL, FALSE);
1577 gtk_tree_path_free(path);
1578
1579 gtk_tree_selection_select_iter(gtk_tree_view_get_selection(view), found_iter);
1580
1581 return FALSE;
1582 }
1583
_action_selection_changed(GtkTreeSelection * selection,gpointer data)1584 static void _action_selection_changed(GtkTreeSelection *selection, gpointer data)
1585 {
1586 GtkTreeIter iter;
1587 GtkTreeModel *model = NULL;
1588
1589 if(!gtk_tree_selection_get_selected(selection, &model, &iter))
1590 _selected_action = NULL;
1591 else
1592 {
1593 gtk_tree_model_get(model, &iter, 0, &_selected_action, -1);
1594
1595 GtkTreeView *view = gtk_tree_selection_get_tree_view(selection);
1596 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
1597 gtk_tree_view_expand_row(view, path, FALSE);
1598 gtk_tree_path_free(path);
1599 }
1600
1601 GtkTreeView *shortcuts_view = GTK_TREE_VIEW(data);
1602 gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(gtk_tree_view_get_model(shortcuts_view)));
1603 gtk_tree_view_expand_all(shortcuts_view);
1604 }
1605
_search_func(GtkTreeModel * model,gint column,const gchar * key,GtkTreeIter * iter,gpointer search_data)1606 static gboolean _search_func(GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer search_data)
1607 {
1608 gchar *key_case = g_utf8_casefold(key, -1), *label_case = NULL;
1609 if(column == 1)
1610 {
1611 dt_action_t *action = NULL;
1612 gtk_tree_model_get(model, iter, 0, &action, -1);
1613 label_case = g_utf8_casefold(action->label, -1);
1614 }
1615 else
1616 {
1617 GSequenceIter *seq_iter = NULL;
1618 gtk_tree_model_get(model, iter, 0, &seq_iter, -1);
1619 if(GPOINTER_TO_UINT(seq_iter) >= NUM_CATEGORIES)
1620 {
1621 dt_shortcut_t *s = g_sequence_get(seq_iter);
1622 if(s->action)
1623 {
1624 gchar *label = _action_full_label(s->action);
1625 label_case = g_utf8_casefold(label, -1);
1626 g_free(label);
1627 }
1628 }
1629 }
1630 const gboolean different = label_case ? !strstr(label_case, key_case) : TRUE;
1631 g_free(key_case);
1632 g_free(label_case);
1633 if(!different)
1634 {
1635 GtkTreePath *path = gtk_tree_model_get_path(model, iter);
1636 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(search_data), path);
1637 gtk_tree_path_free(path);
1638
1639 return FALSE;
1640 }
1641
1642 GtkTreeIter child;
1643 if(gtk_tree_model_iter_children(model, &child, iter))
1644 {
1645 do
1646 {
1647 _search_func(model, column, key, &child, search_data);
1648 }
1649 while(gtk_tree_model_iter_next(model, &child));
1650 }
1651
1652 return TRUE;
1653 }
1654
_fallback_type_is_relevant(dt_action_t * ac,dt_action_type_t type)1655 static gboolean _fallback_type_is_relevant(dt_action_t *ac, dt_action_type_t type)
1656 {
1657 if(!ac) return FALSE;
1658
1659 if(ac->type == type) return TRUE;
1660
1661 if(ac->type >= DT_ACTION_TYPE_WIDGET)
1662 {
1663 if(type == DT_ACTION_TYPE_VALUE_FALLBACK)
1664 {
1665 const dt_action_def_t *def = _action_find_definition(ac);
1666 if(def && def->elements)
1667 {
1668 const dt_action_element_def_t *el = def->elements;
1669 do
1670 {
1671 if(el->effects == dt_action_effect_value) return TRUE;
1672 el++;
1673 } while (el->name);
1674 }
1675 }
1676 }
1677 else if(ac->type <= DT_ACTION_TYPE_SECTION)
1678 for(ac = ac->target; ac; ac = ac->next)
1679 if(_fallback_type_is_relevant(ac, type)) return TRUE;
1680
1681 return FALSE;
1682 }
1683
_visible_shortcuts(GtkTreeModel * model,GtkTreeIter * iter,gpointer data)1684 static gboolean _visible_shortcuts(GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
1685 {
1686 void *data_ptr = NULL;
1687 gtk_tree_model_get(model, iter, 0, &data_ptr, -1);
1688
1689 if(GPOINTER_TO_UINT(data_ptr) == CATEGORY_FALLBACKS && !darktable.control->enable_fallbacks) return FALSE;
1690
1691 if(!_selected_action || GPOINTER_TO_UINT(data_ptr) < NUM_CATEGORIES) return TRUE;
1692
1693 dt_shortcut_t *s = g_sequence_get(data_ptr);
1694
1695 if(_selected_action->type == DT_ACTION_TYPE_FALLBACK &&
1696 s->action->type == GPOINTER_TO_INT(_selected_action->target))
1697 return TRUE;
1698
1699 for(dt_action_t *ac = s->action; ac; ac = ac->owner)
1700 if(ac == _selected_action)
1701 return TRUE;
1702
1703 if(s->action->type == DT_ACTION_TYPE_FALLBACK)
1704 return _fallback_type_is_relevant(_selected_action, GPOINTER_TO_INT(s->action->target));
1705
1706 return FALSE;
1707 }
1708
_resize_shortcuts_view(GtkWidget * view,GdkRectangle * allocation,gpointer data)1709 static void _resize_shortcuts_view(GtkWidget *view, GdkRectangle *allocation, gpointer data)
1710 {
1711 dt_conf_set_int("shortcuts/window_split", gtk_paned_get_position(GTK_PANED(data)));
1712 }
1713
1714 const dt_input_device_t DT_ALL_DEVICES = UINT8_MAX;
1715 static void _shortcuts_save(const gchar *shortcuts_file, const dt_input_device_t device);
1716 static void _shortcuts_load(const gchar *shortcuts_file, const dt_input_device_t file_dev, const dt_input_device_t load_dev, const gboolean clear);
1717
_fallbacks_toggled(GtkToggleButton * button,gpointer data)1718 static void _fallbacks_toggled(GtkToggleButton *button, gpointer data)
1719 {
1720 dt_conf_set_bool("accel/enable_fallbacks",
1721 (darktable.control->enable_fallbacks = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))));
1722
1723 GtkTreeView *shortcuts_view = GTK_TREE_VIEW(data);
1724 gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(gtk_tree_view_get_model(shortcuts_view)));
1725 }
1726
_restore_clicked(GtkButton * button,gpointer user_data)1727 static void _restore_clicked(GtkButton *button, gpointer user_data)
1728 {
1729 enum
1730 {
1731 _DEFAULTS = 1,
1732 _STARTUP,
1733 _EDITS,
1734 };
1735
1736 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("restore shortcuts"),
1737 GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button))),
1738 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
1739 _("_cancel"), GTK_RESPONSE_REJECT,
1740 _("_defaults"), _DEFAULTS,
1741 _("_startup"), _STARTUP,
1742 _("_edits"), _EDITS,
1743 NULL);
1744 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_REJECT);
1745
1746 GtkContainer *content_area = GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG (dialog)));
1747 GtkWidget *label = gtk_label_new(_("restore default shortcuts\n or as at startup\n or when the configuration dialog was opened\n"));
1748 gtk_widget_set_halign(label, GTK_ALIGN_START);
1749 gtk_container_add(content_area, label);
1750 GtkWidget *clear = gtk_check_button_new_with_label(_("clear all newer shortcuts\n(instead of just restoring changed ones)"));
1751 gtk_container_add(content_area, clear);
1752
1753 gtk_widget_show_all(GTK_WIDGET(content_area));
1754
1755 const int resp = gtk_dialog_run(GTK_DIALOG(dialog));
1756 const gboolean wipe = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(clear));
1757
1758 gtk_widget_destroy(dialog);
1759
1760 switch(resp)
1761 {
1762 case _DEFAULTS:
1763 dt_shortcuts_load(".defaults", wipe);
1764 break;
1765 case _STARTUP:
1766 dt_shortcuts_load(".backup", wipe);
1767 break;
1768 case _EDITS:
1769 dt_shortcuts_load(".edit", wipe);
1770 break;
1771 }
1772 }
1773
_import_export_dev_changed(GtkComboBox * widget,gpointer user_data)1774 static void _import_export_dev_changed(GtkComboBox *widget, gpointer user_data)
1775 {
1776 gint dev = gtk_combo_box_get_active(GTK_COMBO_BOX(widget));
1777 g_object_set_data(G_OBJECT(user_data), "device", GINT_TO_POINTER(dev));
1778 gtk_combo_box_set_active(GTK_COMBO_BOX(user_data), 1); // make sure changed triggered
1779 gtk_combo_box_set_active(GTK_COMBO_BOX(user_data), dev > 1 ? 0 : -1);
1780 gtk_widget_set_visible(gtk_widget_get_parent(GTK_WIDGET(user_data)), dev > 1);
1781 }
1782
_export_id_changed(GtkComboBox * widget,gpointer user_data)1783 static void _export_id_changed(GtkComboBox *widget, gpointer user_data)
1784 {
1785 gint dev = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "device"));
1786 gint id = dev <= 1 ? 0 :
1787 gtk_combo_box_get_active(GTK_COMBO_BOX(widget)) + (dev-1) * 10;
1788
1789 gint count = 0;
1790
1791 for(GSequenceIter *iter = g_sequence_get_begin_iter(darktable.control->shortcuts);
1792 !g_sequence_iter_is_end(iter);
1793 iter = g_sequence_iter_next(iter))
1794 {
1795 const dt_shortcut_t *s = g_sequence_get(iter);
1796 if(dev == 0 ||
1797 (id == 0 && s->key_device == id && s->move_device == id) ||
1798 (id != 0 && (s->key_device == id || s->move_device == id)))
1799 count++;
1800 }
1801
1802 gchar *text = g_strdup_printf("%d %s", count, _("shortcuts"));
1803 gtk_label_set_text(GTK_LABEL(user_data), text);
1804 g_free(text);
1805 }
1806
_export_clicked(GtkButton * button,gpointer user_data)1807 static void _export_clicked(GtkButton *button, gpointer user_data)
1808 {
1809 GtkWindow *win = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button)));
1810
1811 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("export shortcuts"),
1812 win, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
1813 _("_cancel"), GTK_RESPONSE_REJECT,
1814 _("_ok"), GTK_RESPONSE_OK,
1815 NULL);
1816 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_REJECT);
1817
1818 GtkContainer *content_area = GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG (dialog)));
1819 GtkWidget *label = gtk_label_new(_("export all shortcuts to a file\nor just for one selected device\n"));
1820 gtk_widget_set_halign(label, GTK_ALIGN_START);
1821 gtk_container_add(content_area, label);
1822
1823 GtkWidget *combo_dev = gtk_combo_box_text_new();
1824 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo_dev), _("all"));
1825 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo_dev), _("keyboard"));
1826 for(GSList *driver = darktable.control->input_drivers; driver; driver = driver->next)
1827 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo_dev),
1828 ((dt_input_driver_definition_t *)driver->data)->name);
1829 gtk_container_add(content_area, combo_dev);
1830
1831 GtkWidget *device_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
1832
1833 GtkWidget *combo_id = gtk_combo_box_text_new();
1834 for(gchar num[] = "0"; *num <= '9'; (*num)++)
1835 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo_id), num);
1836 gtk_container_add(GTK_CONTAINER(device_box), combo_id);
1837 gtk_container_add(GTK_CONTAINER(device_box), dt_ui_label_new(_("device id")));
1838
1839 gtk_container_add(content_area, device_box);
1840
1841 GtkWidget *count = gtk_label_new("");
1842 gtk_container_add(content_area, count);
1843
1844 g_signal_connect(combo_dev, "changed", G_CALLBACK(_import_export_dev_changed), combo_id);
1845 g_signal_connect(combo_id, "changed", G_CALLBACK(_export_id_changed), count);
1846
1847 gtk_widget_show_all(GTK_WIDGET(content_area));
1848
1849 gtk_combo_box_set_active(GTK_COMBO_BOX(combo_dev), 0);
1850
1851 const int resp = gtk_dialog_run(GTK_DIALOG(dialog));
1852
1853 const gint dev = gtk_combo_box_get_active(GTK_COMBO_BOX(combo_dev));
1854 const gint id = dev == 0 ? DT_ALL_DEVICES :
1855 dev == 1 ? 0 :
1856 gtk_combo_box_get_active(GTK_COMBO_BOX(combo_id)) + (dev-1) * 10;
1857
1858 gtk_widget_destroy(dialog);
1859
1860 if(resp != GTK_RESPONSE_OK) return;
1861
1862 GtkFileChooserNative *chooser = gtk_file_chooser_native_new(
1863 _("select file to export"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_SAVE,
1864 _("_export"), _("_cancel"));
1865
1866 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(chooser), TRUE);
1867 dt_conf_get_folder_to_file_chooser("ui_last/export_path", GTK_FILE_CHOOSER(chooser));
1868 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(chooser), "shortcutsrc");
1869 if(gtk_native_dialog_run(GTK_NATIVE_DIALOG(chooser)) == GTK_RESPONSE_ACCEPT)
1870 {
1871 gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(chooser));
1872
1873 _shortcuts_save(filename, id);
1874 g_free(filename);
1875 dt_conf_set_folder_from_file_chooser("ui_last/export_path", GTK_FILE_CHOOSER(chooser));
1876 }
1877 g_object_unref(chooser);
1878 }
1879
_import_id_changed(GtkComboBox * widget,gpointer user_data)1880 static void _import_id_changed(GtkComboBox *widget, gpointer user_data)
1881 {
1882 gint id = gtk_combo_box_get_active(widget);
1883 gtk_combo_box_set_active(GTK_COMBO_BOX(user_data), id);
1884 }
1885
_import_clicked(GtkButton * button,gpointer user_data)1886 static void _import_clicked(GtkButton *button, gpointer user_data)
1887 {
1888 GtkWindow *win = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button)));
1889
1890 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("import shortcuts"),
1891 win, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
1892 _("_cancel"), GTK_RESPONSE_REJECT,
1893 _("_ok"), GTK_RESPONSE_OK,
1894 NULL);
1895 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_REJECT);
1896
1897 GtkContainer *content_area = GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG (dialog)));
1898 GtkWidget *label = gtk_label_new(_("import all shortcuts from a file\nor just for one selected device\n"));
1899 gtk_widget_set_halign(label, GTK_ALIGN_START);
1900 gtk_container_add(content_area, label);
1901
1902 GtkWidget *combo_dev = gtk_combo_box_text_new();
1903 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo_dev), _("all"));
1904 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo_dev), _("keyboard"));
1905 for(GSList *driver = darktable.control->input_drivers; driver; driver = driver->next)
1906 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo_dev),
1907 ((dt_input_driver_definition_t *)driver->data)->name);
1908 gtk_container_add(content_area, combo_dev);
1909
1910 GtkWidget *device_grid = gtk_grid_new();
1911
1912 GtkWidget *combo_from_id = gtk_combo_box_text_new();
1913 for(gchar num[] = "0"; *num <= '9'; (*num)++)
1914 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo_from_id), num);
1915 gtk_grid_attach(GTK_GRID(device_grid), combo_from_id, 0, 0, 1, 1);
1916 gtk_grid_attach(GTK_GRID(device_grid), dt_ui_label_new(_("id in file")), 1, 0, 1, 1);
1917
1918 GtkWidget *combo_to_id = gtk_combo_box_text_new();
1919 for(gchar num[] = "0"; *num <= '9'; (*num)++)
1920 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo_to_id), num);
1921 gtk_grid_attach(GTK_GRID(device_grid), combo_to_id, 0, 1, 1, 1);
1922 gtk_grid_attach(GTK_GRID(device_grid), dt_ui_label_new(_("id when loaded")), 1, 1, 1, 1);
1923
1924 gtk_container_add(content_area, device_grid);
1925
1926 GtkWidget *clear = gtk_check_button_new_with_label(_("clear device first"));
1927 gtk_container_add(content_area, clear);
1928
1929 g_signal_connect(combo_dev, "changed", G_CALLBACK(_import_export_dev_changed), combo_from_id);
1930 g_signal_connect(combo_from_id, "changed", G_CALLBACK(_import_id_changed), combo_to_id);
1931
1932 gtk_widget_show_all(GTK_WIDGET(content_area));
1933
1934 gtk_combo_box_set_active(GTK_COMBO_BOX(combo_dev), 0);
1935
1936 const int resp = gtk_dialog_run(GTK_DIALOG(dialog));
1937 const gint dev = gtk_combo_box_get_active(GTK_COMBO_BOX(combo_dev));
1938 const gint from_id = dev == 0 ? DT_ALL_DEVICES :
1939 dev == 1 ? 0 :
1940 gtk_combo_box_get_active(GTK_COMBO_BOX(combo_from_id)) + (dev-1) * 10;
1941 const gint to_id = dev == 1 ? 0 :
1942 gtk_combo_box_get_active(GTK_COMBO_BOX(combo_to_id)) + (dev-1) * 10;
1943 const gboolean wipe = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(clear));
1944
1945 gtk_widget_destroy(dialog);
1946
1947 if(resp != GTK_RESPONSE_OK) return;
1948
1949 GtkFileChooserNative *chooser = gtk_file_chooser_native_new(
1950 _("select file to import"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_OPEN,
1951 _("_import"), _("_cancel"));
1952 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(chooser), FALSE);
1953
1954 dt_conf_get_folder_to_file_chooser("ui_last/import_path", GTK_FILE_CHOOSER(chooser));
1955 if(gtk_native_dialog_run(GTK_NATIVE_DIALOG(chooser)) == GTK_RESPONSE_ACCEPT)
1956 {
1957 gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(chooser));
1958
1959 if(wipe && from_id != DT_ALL_DEVICES)
1960 {
1961 GtkTreeModel *model = GTK_TREE_MODEL(_shortcuts_store);
1962 GtkTreeIter category;
1963 gboolean valid_category = gtk_tree_model_get_iter_first(model, &category);
1964 while(valid_category)
1965 {
1966 GtkTreeIter child;
1967 gboolean valid_child = gtk_tree_model_iter_children(model, &child, &category);
1968 while(valid_child)
1969 {
1970 gpointer child_data;
1971 gtk_tree_model_get(model, &child, 0, &child_data, -1);
1972
1973 dt_shortcut_t *s = g_sequence_get(child_data);
1974 if((to_id == 0 && s->key_device == to_id && s->move_device == to_id) ||
1975 (to_id != 0 && (s->key_device == to_id || s->move_device == to_id)))
1976 {
1977 g_sequence_remove(child_data);
1978 valid_child = gtk_tree_store_remove(GTK_TREE_STORE(model), &child);
1979 }
1980 else
1981 valid_child = gtk_tree_model_iter_next(model, &child);
1982 }
1983 valid_category = gtk_tree_model_iter_next(model, &category);
1984 };
1985 }
1986
1987 _shortcuts_load(filename, from_id, to_id, wipe && from_id == DT_ALL_DEVICES);
1988
1989 g_free(filename);
1990 dt_conf_set_folder_from_file_chooser("ui_last/import_path", GTK_FILE_CHOOSER(chooser));
1991 }
1992 g_object_unref(chooser);
1993
1994 dt_shortcuts_save(NULL, FALSE);
1995 }
1996
dt_shortcuts_prefs(GtkWidget * widget)1997 GtkWidget *dt_shortcuts_prefs(GtkWidget *widget)
1998 {
1999 // Save the shortcuts before editing
2000 dt_shortcuts_save(".edit", FALSE);
2001
2002 _selected_action = g_hash_table_lookup(darktable.control->widgets, widget);
2003 if(!_selected_action && widget)
2004 _selected_action = g_hash_table_lookup(darktable.control->widgets, gtk_widget_get_parent(widget));
2005 darktable.control->element = -1;
2006
2007 GtkWidget *container = gtk_paned_new(GTK_ORIENTATION_VERTICAL);
2008
2009 // Building the shortcut treeview
2010 g_set_weak_pointer(&_shortcuts_store, gtk_tree_store_new(1, G_TYPE_POINTER)); // static
2011
2012 _add_shortcuts_to_tree();
2013
2014 GtkTreeModel *filtered_shortcuts = gtk_tree_model_filter_new(GTK_TREE_MODEL(_shortcuts_store), NULL);
2015 g_object_unref(G_OBJECT(_shortcuts_store));
2016
2017 gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(filtered_shortcuts), _visible_shortcuts, NULL, NULL);
2018
2019 GtkTreeView *shortcuts_view = GTK_TREE_VIEW(gtk_tree_view_new_with_model(filtered_shortcuts));
2020 g_object_unref(G_OBJECT(filtered_shortcuts));
2021 gtk_tree_view_set_search_column(shortcuts_view, 0); // fake column for _search_func
2022 gtk_tree_view_set_search_equal_func(shortcuts_view, _search_func, shortcuts_view, NULL);
2023 GtkWidget *search_shortcuts = gtk_search_entry_new();
2024 gtk_entry_set_placeholder_text(GTK_ENTRY(search_shortcuts), _("search shortcuts list"));
2025 gtk_widget_set_tooltip_text(GTK_WIDGET(search_shortcuts), _("incrementally search the list of shortcuts\npress up or down keys to cycle through matches"));
2026 g_signal_connect(G_OBJECT(search_shortcuts), "activate", G_CALLBACK(dt_gui_search_stop), shortcuts_view);
2027 g_signal_connect(G_OBJECT(search_shortcuts), "stop-search", G_CALLBACK(dt_gui_search_stop), shortcuts_view);
2028 gtk_tree_view_set_search_entry(shortcuts_view, GTK_ENTRY(search_shortcuts));
2029
2030 gtk_tree_selection_set_select_function(gtk_tree_view_get_selection(shortcuts_view),
2031 _shortcut_selection_function, NULL, NULL);
2032 g_object_set(shortcuts_view, "has-tooltip", TRUE, NULL);
2033 g_signal_connect(G_OBJECT(shortcuts_view), "query-tooltip", G_CALLBACK(_shortcut_tooltip_callback), GINT_TO_POINTER(TRUE));
2034 g_signal_connect(G_OBJECT(shortcuts_view), "row-activated", G_CALLBACK(_shortcut_row_activated), filtered_shortcuts);
2035 g_signal_connect(G_OBJECT(shortcuts_view), "key-press-event", G_CALLBACK(_shortcut_key_pressed), NULL);
2036 g_signal_connect(G_OBJECT(shortcuts_view), "key-press-event", G_CALLBACK(dt_gui_search_start), search_shortcuts);
2037 g_signal_connect(G_OBJECT(_shortcuts_store), "row-inserted", G_CALLBACK(_shortcut_row_inserted), shortcuts_view);
2038
2039 // Setting up the cell renderers
2040 _add_prefs_column(shortcuts_view, gtk_cell_renderer_text_new(), _("shortcut"), SHORTCUT_VIEW_DESCRIPTION);
2041
2042 _add_prefs_column(shortcuts_view, gtk_cell_renderer_text_new(), _("action"), SHORTCUT_VIEW_ACTION);
2043
2044 GtkCellRenderer *renderer = NULL;
2045
2046 renderer = gtk_cell_renderer_combo_new();
2047 GtkListStore *elements = gtk_list_store_new(1, G_TYPE_STRING);
2048 g_object_set(renderer, "model", elements, "text-column", 0, "has-entry", FALSE, NULL);
2049 g_signal_connect(renderer, "editing-started" , G_CALLBACK(_element_editing_started), filtered_shortcuts);
2050 g_signal_connect(renderer, "changed", G_CALLBACK(_element_changed), filtered_shortcuts);
2051 _add_prefs_column(shortcuts_view, renderer, _("element"), SHORTCUT_VIEW_ELEMENT);
2052
2053 renderer = gtk_cell_renderer_combo_new();
2054 GtkListStore *effects = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_INT);
2055 g_object_set(renderer, "model", effects, "text-column", 0, "has-entry", FALSE, NULL);
2056 g_signal_connect(renderer, "editing-started" , G_CALLBACK(_effect_editing_started), filtered_shortcuts);
2057 g_signal_connect(renderer, "changed", G_CALLBACK(_effect_changed), filtered_shortcuts);
2058 _add_prefs_column(shortcuts_view, renderer, _("effect"), SHORTCUT_VIEW_EFFECT);
2059
2060 renderer = gtk_cell_renderer_spin_new();
2061 g_object_set(renderer, "adjustment", gtk_adjustment_new(1, -1000, 1000, .01, 1, 10),
2062 "digits", 3, "xalign", 1.0, NULL);
2063 g_signal_connect(renderer, "edited", G_CALLBACK(_speed_edited), filtered_shortcuts);
2064 _add_prefs_column(shortcuts_view, renderer, _("speed"), SHORTCUT_VIEW_SPEED);
2065
2066 renderer = gtk_cell_renderer_combo_new();
2067 GtkListStore *instances = gtk_list_store_new(1, G_TYPE_STRING);
2068 for(int i = 0; i < NUM_INSTANCES; i++)
2069 gtk_list_store_insert_with_values(instances, NULL, -1, 0, _(instance_label[i]), -1);
2070 for(char relative[] = "-2"; (relative[0] ^= '+' ^ '-') == '-' || ++relative[1] <= '9'; )
2071 gtk_list_store_insert_with_values(instances, NULL, -1, 0, relative, -1);
2072 g_object_set(renderer, "model", instances, "text-column", 0, "has-entry", FALSE, NULL);
2073 g_signal_connect(renderer, "edited", G_CALLBACK(_instance_edited), filtered_shortcuts);
2074 _add_prefs_column(shortcuts_view, renderer, _("instance"), SHORTCUT_VIEW_INSTANCE);
2075
2076 // Adding the shortcuts treeview to its containers
2077 GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
2078 gtk_widget_set_size_request(scroll, -1, 100);
2079 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2080 gtk_container_add(GTK_CONTAINER(scroll), GTK_WIDGET(shortcuts_view));
2081 gtk_paned_pack2(GTK_PANED(container), scroll, TRUE, FALSE);
2082
2083 // Creating the action selection treeview
2084 g_set_weak_pointer(&_actions_store, gtk_tree_store_new(1, G_TYPE_POINTER)); // static
2085 GtkTreeIter found_iter = {};
2086 if(widget && !_selected_action)
2087 {
2088 const dt_view_t *active_view = dt_view_manager_get_current_view(darktable.view_manager);
2089 if(gtk_widget_is_ancestor(widget, dt_ui_center_base(darktable.gui->ui)) ||
2090 dt_ui_panel_ancestor(darktable.gui->ui, DT_UI_PANEL_CENTER_TOP, widget) ||
2091 dt_ui_panel_ancestor(darktable.gui->ui, DT_UI_PANEL_CENTER_BOTTOM, widget) ||
2092 gtk_widget_is_ancestor(widget, GTK_WIDGET(dt_ui_get_container(darktable.gui->ui,
2093 DT_UI_CONTAINER_PANEL_LEFT_TOP))) ||
2094 gtk_widget_is_ancestor(widget, GTK_WIDGET(dt_ui_get_container(darktable.gui->ui,
2095 DT_UI_CONTAINER_PANEL_RIGHT_TOP))))
2096 _selected_action = (dt_action_t*)active_view;
2097 else if(dt_ui_panel_ancestor(darktable.gui->ui, DT_UI_PANEL_BOTTOM, widget))
2098 _selected_action = &darktable.control->actions_thumb;
2099 else if(dt_ui_panel_ancestor(darktable.gui->ui, DT_UI_PANEL_RIGHT, widget))
2100 _selected_action = active_view->view(active_view) == DT_VIEW_DARKROOM
2101 ? &darktable.control->actions_iops
2102 : &darktable.control->actions_libs;
2103 else if(dt_ui_panel_ancestor(darktable.gui->ui, DT_UI_PANEL_LEFT, widget))
2104 _selected_action = &darktable.control->actions_libs;
2105 else
2106 _selected_action = &darktable.control->actions_global;
2107 }
2108 _add_actions_to_tree(NULL, darktable.control->actions, _selected_action, &found_iter);
2109
2110 GtkTreeView *actions_view = GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL(_actions_store)));
2111 g_object_unref(_actions_store);
2112 gtk_tree_view_set_search_column(actions_view, 1); // fake column for _search_func
2113 gtk_tree_view_set_search_equal_func(actions_view, _search_func, actions_view, NULL);
2114 GtkWidget *search_actions = gtk_search_entry_new();
2115 gtk_entry_set_placeholder_text(GTK_ENTRY(search_actions), _("search actions list"));
2116 gtk_widget_set_tooltip_text(GTK_WIDGET(search_actions), _("incrementally search the list of actions\npress up or down keys to cycle through matches"));
2117 g_signal_connect(G_OBJECT(search_actions), "activate", G_CALLBACK(dt_gui_search_stop), actions_view);
2118 g_signal_connect(G_OBJECT(search_actions), "stop-search", G_CALLBACK(dt_gui_search_stop), actions_view);
2119 gtk_tree_view_set_search_entry(actions_view, GTK_ENTRY(search_actions));
2120
2121 g_object_set(actions_view, "has-tooltip", TRUE, NULL);
2122 g_signal_connect(G_OBJECT(actions_view), "query-tooltip", G_CALLBACK(_shortcut_tooltip_callback), NULL);
2123 g_signal_connect(G_OBJECT(actions_view), "row-activated", G_CALLBACK(_action_row_activated), _actions_store);
2124 g_signal_connect(G_OBJECT(actions_view), "button-press-event", G_CALLBACK(_action_view_click), _actions_store);
2125 g_signal_connect(G_OBJECT(actions_view), "key-press-event", G_CALLBACK(dt_gui_search_start), search_actions);
2126 g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(actions_view)), "changed",
2127 G_CALLBACK(_action_selection_changed), shortcuts_view);
2128
2129 renderer = gtk_cell_renderer_text_new();
2130 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(_("action"), renderer, NULL);
2131 gtk_tree_view_column_set_expand(column, TRUE);
2132 gtk_tree_view_column_set_cell_data_func(column, renderer, _fill_action_fields, GINT_TO_POINTER(TRUE), NULL);
2133 gtk_tree_view_append_column(GTK_TREE_VIEW(actions_view), column);
2134
2135 renderer = gtk_cell_renderer_text_new();
2136 column = gtk_tree_view_column_new_with_attributes(_("type"), renderer, NULL);
2137 gtk_tree_view_column_set_alignment(column, 1.0);
2138 gtk_cell_renderer_set_alignment(renderer, 1.0, 0.0);
2139 gtk_tree_view_column_set_cell_data_func(column, renderer, _fill_action_fields, NULL, NULL);
2140 gtk_tree_view_append_column(GTK_TREE_VIEW(actions_view), column);
2141
2142 // Adding the action treeview to its containers
2143 scroll = gtk_scrolled_window_new(NULL, NULL);
2144 gtk_widget_set_size_request(scroll, -1, 100);
2145 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2146 gtk_container_add(GTK_CONTAINER(scroll), GTK_WIDGET(actions_view));
2147 gtk_paned_pack1(GTK_PANED(container), scroll, TRUE, FALSE);
2148
2149 if(found_iter.user_data)
2150 {
2151 GtkTreeIter *send_iter = calloc(1, sizeof(GtkTreeIter));
2152 *send_iter = found_iter;
2153 gtk_widget_add_events(GTK_WIDGET(actions_view), GDK_STRUCTURE_MASK);
2154 g_signal_connect_data(G_OBJECT(actions_view), "map-event", G_CALLBACK(_action_view_map),
2155 send_iter, (GClosureNotify)g_free, G_CONNECT_AFTER);
2156 }
2157
2158 GtkTreePath *path = gtk_tree_path_new_first();
2159 gtk_tree_view_set_cursor(shortcuts_view, path, NULL, FALSE);
2160 gtk_tree_path_free(path);
2161
2162 const int split_position = dt_conf_get_int("shortcuts/window_split");
2163 if(split_position) gtk_paned_set_position(GTK_PANED(container), split_position);
2164 g_signal_connect(G_OBJECT(shortcuts_view), "size-allocate", G_CALLBACK(_resize_shortcuts_view), container);
2165
2166 GtkWidget *button_bar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0), *button = NULL;
2167 gtk_widget_set_name(button_bar, "shortcut_controls");
2168 gtk_box_pack_start(GTK_BOX(button_bar), search_actions, FALSE, FALSE, 0);
2169 gtk_box_pack_start(GTK_BOX(button_bar), search_shortcuts, FALSE, FALSE, 0);
2170
2171 button = gtk_check_button_new_with_label(_("enable fallbacks"));
2172 gtk_widget_set_tooltip_text(button, _("enables default meanings for additional buttons, modifiers or moves\n"
2173 "when used in combination with a base shortcut"));
2174 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), darktable.control->enable_fallbacks);
2175 g_signal_connect(button, "toggled", G_CALLBACK(_fallbacks_toggled), shortcuts_view);
2176 gtk_box_pack_start(GTK_BOX(button_bar), button, TRUE, FALSE, 0);
2177
2178 button = gtk_button_new_with_label(_("restore..."));
2179 gtk_widget_set_tooltip_text(button, _("restore default shortcuts or previous state"));
2180 g_signal_connect(button, "clicked", G_CALLBACK(_restore_clicked), NULL);
2181 gtk_box_pack_end(GTK_BOX(button_bar), button, FALSE, FALSE, 0);
2182
2183 button = gtk_button_new_with_label(_("import..."));
2184 gtk_widget_set_tooltip_text(button, _("fully or partially import shortcuts from file"));
2185 g_signal_connect(button, "clicked", G_CALLBACK(_import_clicked), NULL);
2186 gtk_box_pack_end(GTK_BOX(button_bar), button, FALSE, FALSE, 0);
2187
2188 button = gtk_button_new_with_label(_("export..."));
2189 gtk_widget_set_tooltip_text(button, _("fully or partially export shortcuts to file"));
2190 g_signal_connect(button, "clicked", G_CALLBACK(_export_clicked), NULL);
2191 gtk_box_pack_end(GTK_BOX(button_bar), button, FALSE, FALSE, 0);
2192
2193 GtkWidget *top_level = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
2194 gtk_box_pack_start(GTK_BOX(top_level), container, TRUE, TRUE, 0);
2195 gtk_box_pack_start(GTK_BOX(top_level), button_bar, FALSE, FALSE, 0);
2196
2197 return top_level;
2198 }
2199
_shortcuts_save(const gchar * shortcuts_file,const dt_input_device_t device)2200 static void _shortcuts_save(const gchar *shortcuts_file, const dt_input_device_t device)
2201 {
2202 FILE *f = g_fopen(shortcuts_file, "wb");
2203 if(f)
2204 {
2205 for(GSequenceIter *i = g_sequence_get_begin_iter(darktable.control->shortcuts);
2206 !g_sequence_iter_is_end(i);
2207 i = g_sequence_iter_next(i))
2208 {
2209 dt_shortcut_t *s = g_sequence_get(i);
2210
2211 if(device != DT_ALL_DEVICES &&
2212 (device != 0 || s->key_device != device || s->move_device != device) &&
2213 (device == 0 || (s->key_device != device && s->move_device != device)))
2214 continue;
2215
2216 gchar *key_name = _shortcut_key_move_name(s->key_device, s->key, s->mods, FALSE);
2217 fprintf(f, "%s", key_name);
2218 g_free(key_name);
2219
2220 if(s->move_device || s->move)
2221 {
2222 gchar *move_name = _shortcut_key_move_name(s->move_device, s->move, DT_MOVE_NAME, FALSE);
2223 fprintf(f, ";%s", move_name);
2224 g_free(move_name);
2225 if(s->direction)
2226 fprintf(f, ";%s", s->direction & DT_SHORTCUT_UP ? "up" : "down");
2227 }
2228
2229 if(s->press & DT_SHORTCUT_DOUBLE ) fprintf(f, ";%s", "double");
2230 if(s->press & DT_SHORTCUT_TRIPLE ) fprintf(f, ";%s", "triple");
2231 if(s->press & DT_SHORTCUT_LONG ) fprintf(f, ";%s", "long");
2232 if(s->button & DT_SHORTCUT_LEFT ) fprintf(f, ";%s", "left");
2233 if(s->button & DT_SHORTCUT_MIDDLE ) fprintf(f, ";%s", "middle");
2234 if(s->button & DT_SHORTCUT_RIGHT ) fprintf(f, ";%s", "right");
2235 if(s->click & DT_SHORTCUT_DOUBLE ) fprintf(f, ";%s", "double");
2236 if(s->click & DT_SHORTCUT_TRIPLE ) fprintf(f, ";%s", "triple");
2237 if(s->click & DT_SHORTCUT_LONG ) fprintf(f, ";%s", "long");
2238
2239 fprintf(f, "=");
2240
2241 gchar *action_label = _action_full_id(s->action);
2242 fprintf(f, "%s", action_label);
2243 g_free(action_label);
2244
2245 const dt_action_element_def_t *elements = _action_find_elements(s->action);
2246 if(s->element)
2247 fprintf(f, ";%s", elements[s->element].name);
2248 if(s->effect > (_shortcut_is_move(s) ? DT_ACTION_EFFECT_DEFAULT_MOVE
2249 : DT_ACTION_EFFECT_DEFAULT_KEY))
2250 {
2251 if(elements[s->element].effects == dt_action_effect_selection
2252 && s->effect > DT_ACTION_EFFECT_COMBO_SEPARATOR)
2253 {
2254 dt_introspection_type_enum_tuple_t *values
2255 = g_hash_table_lookup(darktable.control->combo_introspection, s->action);
2256 if(values)
2257 fprintf(f, ";item:%s", values[s->effect - DT_ACTION_EFFECT_COMBO_SEPARATOR - 1].description);
2258 else
2259 {
2260 gchar **strings
2261 = g_hash_table_lookup(darktable.control->combo_list, s->action);
2262 if(strings)
2263 fprintf(f, ";item:%s", strings[s->effect - DT_ACTION_EFFECT_COMBO_SEPARATOR - 1]);
2264 }
2265 }
2266 else
2267 fprintf(f, ";%s", elements[s->element].effects[s->effect]);
2268 }
2269
2270 if(s->instance == -1) fprintf(f, ";last");
2271 if(s->instance == +1) fprintf(f, ";first");
2272 if(abs(s->instance) > 1) fprintf(f, ";%+d", s->instance);
2273 if(s->speed != 1.0) fprintf(f, ";*%g", s->speed);
2274
2275 fprintf(f, "\n");
2276 }
2277
2278 fclose(f);
2279 }
2280 }
2281
dt_shortcuts_save(const gchar * ext,const gboolean backup)2282 void dt_shortcuts_save(const gchar *ext, const gboolean backup)
2283 {
2284 char shortcuts_file[PATH_MAX] = { 0 };
2285 dt_loc_get_user_config_dir(shortcuts_file, sizeof(shortcuts_file));
2286 g_strlcat(shortcuts_file, "/shortcutsrc", PATH_MAX);
2287 if(ext) g_strlcat(shortcuts_file, ext, PATH_MAX);
2288 if(backup)
2289 {
2290 gchar *backup_file = g_strdup_printf("%s.backup", shortcuts_file);
2291 g_rename(shortcuts_file, backup_file);
2292 g_free(backup_file);
2293 }
2294
2295 _shortcuts_save(shortcuts_file, DT_ALL_DEVICES);
2296 }
2297
_find_combo_effect(const gchar ** effects,const gchar * token,dt_action_t * ac,gint * ef)2298 static gboolean _find_combo_effect(const gchar **effects, const gchar *token, dt_action_t *ac, gint *ef)
2299 {
2300 if(effects == dt_action_effect_selection && g_strstr_len(token, 5, "item:"))
2301 {
2302 int effect = -1;
2303 const char *entry = NULL;
2304
2305 dt_introspection_type_enum_tuple_t *values
2306 = g_hash_table_lookup(darktable.control->combo_introspection, ac);
2307 if(values)
2308 {
2309 while((entry = values[++effect].description))
2310 if(!strcmp(token + 5, entry)) break;
2311 }
2312 else
2313 {
2314 gchar **strings
2315 = g_hash_table_lookup(darktable.control->combo_list, ac);
2316 if(strings)
2317 {
2318 while((entry = strings[++effect]))
2319 if(!strcmp(token + 5, entry)) break;
2320 }
2321 }
2322 if(entry)
2323 {
2324 *ef = effect + DT_ACTION_EFFECT_COMBO_SEPARATOR + 1;
2325 return TRUE;
2326 }
2327 }
2328
2329 return FALSE;
2330 }
2331
_shortcuts_load(const gchar * shortcuts_file,dt_input_device_t file_dev,const dt_input_device_t load_dev,const gboolean clear)2332 static void _shortcuts_load(const gchar *shortcuts_file, dt_input_device_t file_dev, const dt_input_device_t load_dev, const gboolean clear)
2333 {
2334 // start with an empty shortcuts collection
2335 if(clear && darktable.control->shortcuts)
2336 {
2337 if(_shortcuts_store) gtk_tree_store_clear(_shortcuts_store);
2338
2339 g_sequence_free(darktable.control->shortcuts);
2340 darktable.control->shortcuts = g_sequence_new(g_free);
2341
2342 if(_shortcuts_store) _add_shortcuts_to_tree();
2343 }
2344
2345 FILE *f = g_fopen(shortcuts_file, "rb");
2346 if(f)
2347 {
2348 while(!feof(f))
2349 {
2350 char line[1024];
2351 char *read = fgets(line, sizeof(line), f);
2352 if(read > 0)
2353 {
2354 line[strcspn(line, "\r\n")] = '\0';
2355
2356 char *act_start = strchr(line, '=');
2357 if(!act_start)
2358 {
2359 fprintf(stderr, "[dt_shortcuts_load] line '%s' is not an assignment\n", line);
2360 continue;
2361 }
2362
2363 dt_shortcut_t s = { .speed = 1 };
2364
2365 char *token = strtok(line, "=;");
2366 if(strcmp(token, "None"))
2367 {
2368 char *colon = strchr(token, ':');
2369 if(!colon)
2370 {
2371 gtk_accelerator_parse(token, &s.key, &s.mods);
2372 if(s.mods) fprintf(stderr, "[dt_shortcuts_load] unexpected modifiers found in %s\n", token);
2373 if(!s.key) fprintf(stderr, "[dt_shortcuts_load] no key name found in %s\n", token);
2374 }
2375 else
2376 {
2377 char *key_start = colon + 1;
2378 *colon-- = 0;
2379 if(colon == token)
2380 {
2381 fprintf(stderr, "[dt_shortcuts_load] missing driver name in %s\n", token);
2382 continue;
2383 }
2384 dt_input_device_t id = *colon - '0';
2385 if(id > 9 )
2386 id = 0;
2387 else
2388 *colon-- = 0;
2389
2390 GSList *driver = darktable.control->input_drivers;
2391 while(driver)
2392 {
2393 id += 10;
2394 dt_input_driver_definition_t *callbacks = driver->data;
2395 if(!strcmp(token, callbacks->name))
2396 {
2397 if(!callbacks->string_to_key(key_start, &s.key))
2398 fprintf(stderr, "[dt_shortcuts_load] key not recognised in %s\n", key_start);
2399
2400 s.key_device = id;
2401 break;
2402 }
2403 driver = driver->next;
2404 }
2405 if(!driver)
2406 {
2407 fprintf(stderr, "[dt_shortcuts_load] '%s' is not a valid driver\n", token);
2408 continue;
2409 }
2410 }
2411 }
2412
2413 while((token = strtok(NULL, "=;")) && token < act_start)
2414 {
2415 char *colon = strchr(token, ':');
2416 if(!colon)
2417 {
2418 int mod = -1;
2419 while(modifier_string[++mod].modifier)
2420 if(!strcmp(token, modifier_string[mod].name)) break;
2421 if(modifier_string[mod].modifier)
2422 {
2423 s.mods |= modifier_string[mod].modifier;
2424 continue;
2425 }
2426
2427 if(!strcmp(token, "left" )) { s.button |= DT_SHORTCUT_LEFT ; continue; }
2428 if(!strcmp(token, "middle")) { s.button |= DT_SHORTCUT_MIDDLE; continue; }
2429 if(!strcmp(token, "right" )) { s.button |= DT_SHORTCUT_RIGHT ; continue; }
2430
2431 if(s.button)
2432 {
2433 if(!strcmp(token, "double")) { s.click |= DT_SHORTCUT_DOUBLE; continue; }
2434 if(!strcmp(token, "triple")) { s.click |= DT_SHORTCUT_TRIPLE; continue; }
2435 if(!strcmp(token, "long" )) { s.click |= DT_SHORTCUT_LONG ; continue; }
2436 }
2437 else
2438 {
2439 if(!strcmp(token, "double")) { s.press |= DT_SHORTCUT_DOUBLE; continue; }
2440 if(!strcmp(token, "triple")) { s.press |= DT_SHORTCUT_TRIPLE; continue; }
2441 if(!strcmp(token, "long" )) { s.press |= DT_SHORTCUT_LONG ; continue; }
2442 }
2443
2444 int move = 0;
2445 while(move_string[++move])
2446 if(!strcmp(token, move_string[move])) break;
2447 if(move_string[move])
2448 {
2449 s.move = move;
2450 continue;
2451 }
2452
2453 if(!strcmp(token, "up" )) { s.direction = DT_SHORTCUT_UP ; continue; }
2454 if(!strcmp(token, "down")) { s.direction= DT_SHORTCUT_DOWN; continue; }
2455
2456 fprintf(stderr, "[dt_shortcuts_load] token '%s' not recognised\n", token);
2457 }
2458 else
2459 {
2460 char *move_start = colon + 1;
2461 *colon-- = 0;
2462 if(colon == token)
2463 {
2464 fprintf(stderr, "[dt_shortcuts_load] missing driver name in %s\n", token);
2465 continue;
2466 }
2467 dt_input_device_t id = *colon - '0';
2468 if(id > 9 )
2469 id = 0;
2470 else
2471 *colon-- = 0;
2472
2473 GSList *driver = darktable.control->input_drivers;
2474 while(driver)
2475 {
2476 id += 10;
2477 const dt_input_driver_definition_t *callbacks = driver->data;
2478 if(!strcmp(token, callbacks->name))
2479 {
2480 if(!callbacks->string_to_move(move_start, &s.move))
2481 fprintf(stderr, "[dt_shortcuts_load] move not recognised in %s\n", move_start);
2482
2483 s.move_device = id;
2484 break;
2485 }
2486 driver = driver->next;
2487 }
2488 if(!driver)
2489 {
2490 fprintf(stderr, "[dt_shortcuts_load] '%s' is not a valid driver\n", token);
2491 continue;
2492 }
2493 }
2494 }
2495
2496 // find action
2497 gchar **path = g_strsplit(token, "/", 0);
2498 s.action = dt_action_locate(NULL, path, FALSE);
2499 g_strfreev(path);
2500
2501 if(!s.action)
2502 {
2503 fprintf(stderr, "[dt_shortcuts_load] action path '%s' not found\n", token);
2504 continue;
2505 }
2506
2507 const dt_action_element_def_t *elements = _action_find_elements(s.action);
2508 const gchar **effects = NULL;
2509 const gint default_effect = s.effect = _shortcut_is_move(&s)
2510 ? DT_ACTION_EFFECT_DEFAULT_MOVE
2511 : DT_ACTION_EFFECT_DEFAULT_KEY;
2512
2513 while((token = strtok(NULL, ";")))
2514 {
2515 if(elements)
2516 {
2517 int element = -1;
2518 while(elements[++element].name)
2519 if(!strcmp(token, elements[element].name)) break;
2520 if(elements[element].name)
2521 {
2522 s.element = element;
2523 s.effect = default_effect; // reset if an effect for a different element was found first
2524 continue;
2525 }
2526
2527 effects = elements[s.element].effects;
2528
2529 if(_find_combo_effect(effects, token, s.action, &s.effect)) continue;
2530
2531 int effect = -1;
2532 while(effects[++effect])
2533 if(!strcmp(token, effects[effect])) break;
2534 if(effects[effect])
2535 {
2536 s.effect = effect;
2537 continue;
2538 }
2539 }
2540
2541 if(!strcmp(token, "first")) s.instance = 1; else
2542 if(!strcmp(token, "last" )) s.instance = -1; else
2543 if(*token == '+' || *token == '-') sscanf(token, "%d", &s.instance); else
2544 if(*token == '*') sscanf(token, "*%g", &s.speed); else
2545 fprintf(stderr, "[dt_shortcuts_load] token '%s' not recognised\n", token);
2546 }
2547
2548 if(file_dev == DT_ALL_DEVICES ||
2549 (file_dev == 0 && s.key_device == file_dev && s.move_device == file_dev) ||
2550 (file_dev != 0 && (s.key_device == file_dev || s.move_device == file_dev)))
2551 {
2552 if(file_dev != 0)
2553 {
2554 if(s.key_device == file_dev) s.key_device = load_dev;
2555 if(s.move_device == file_dev) s.move_device = load_dev;
2556 }
2557
2558 _insert_shortcut(&s, FALSE);
2559 }
2560 }
2561 }
2562 fclose(f);
2563 }
2564 }
2565
dt_shortcuts_load(const gchar * ext,const gboolean clear)2566 void dt_shortcuts_load(const gchar *ext, const gboolean clear)
2567 {
2568 char shortcuts_file[PATH_MAX] = { 0 };
2569 dt_loc_get_user_config_dir(shortcuts_file, sizeof(shortcuts_file));
2570 g_strlcat(shortcuts_file, "/shortcutsrc", PATH_MAX);
2571 if(ext) g_strlcat(shortcuts_file, ext, PATH_MAX);
2572 if(!g_file_test(shortcuts_file, G_FILE_TEST_EXISTS))
2573 return;
2574
2575 _shortcuts_load(shortcuts_file, DT_ALL_DEVICES, DT_ALL_DEVICES, clear);
2576 }
2577
dt_shortcuts_reinitialise()2578 void dt_shortcuts_reinitialise()
2579 {
2580 for(GSList *d = darktable.control->input_drivers; d; d = d->next)
2581 {
2582 const dt_input_driver_definition_t *driver = d->data;
2583 driver->module->gui_cleanup(driver->module);
2584 driver->module->gui_init(driver->module);
2585 }
2586
2587 // reload shortcuts
2588 dt_shortcuts_load(NULL, TRUE);
2589
2590 char actions_file[PATH_MAX] = { 0 };
2591 dt_loc_get_user_config_dir(actions_file, sizeof(actions_file));
2592 g_strlcat(actions_file, "/all_actions", PATH_MAX);
2593 FILE *f = g_fopen(actions_file, "wb");
2594 _dump_actions(f, darktable.control->actions);
2595 fclose(f);
2596
2597 dt_control_log(_("input devices reinitialised"));
2598 }
2599
dt_shortcuts_select_view(dt_view_type_flags_t view)2600 void dt_shortcuts_select_view(dt_view_type_flags_t view)
2601 {
2602 g_sequence_sort(darktable.control->shortcuts, _shortcut_compare_func, GINT_TO_POINTER(view));
2603 }
2604
2605 static GSList *_pressed_keys = NULL, *_hold_keys = NULL; // lists of currently pressed and held keys
2606 static guint _pressed_button = 0;
2607 static guint _last_time = 0, _last_mapping_time = 0;
2608 static guint _timeout_source = 0;
2609 static guint _focus_loss_key = 0;
2610 static guint _focus_loss_press = 0;
2611
_lookup_mapping_widget()2612 static void _lookup_mapping_widget()
2613 {
2614 if(_sc.action) return;
2615 _sc.action = g_hash_table_lookup(darktable.control->widgets, darktable.control->mapping_widget);
2616 if(!_sc.action) return;
2617
2618 _sc.instance = 0;
2619 if(dt_conf_get_bool("accel/assign_instance") && _sc.action->target != darktable.control->mapping_widget)
2620 {
2621 // find relative module instance
2622 dt_action_t *owner = _sc.action;
2623 while(owner && owner->type != DT_ACTION_TYPE_IOP) owner = owner->owner;
2624 if(owner)
2625 {
2626 GtkWidget *expander = gtk_widget_get_ancestor(darktable.control->mapping_widget, DTGTK_TYPE_EXPANDER);
2627
2628 dt_iop_module_so_t *module = (dt_iop_module_so_t *)owner;
2629
2630 dt_iop_module_t *preferred = dt_iop_get_module_preferred_instance(module);
2631
2632 if(expander != preferred->expander)
2633 {
2634 int current_instance = 0;
2635 for(GList *iop_mods = darktable.develop->iop;
2636 iop_mods;
2637 iop_mods = g_list_next(iop_mods))
2638 {
2639 const dt_iop_module_t *mod = (dt_iop_module_t *)iop_mods->data;
2640
2641 if(mod->so == module && mod->iop_order != INT_MAX)
2642 {
2643 current_instance++;
2644
2645 if(mod->expander == expander)
2646 _sc.instance = current_instance; // and continue counting
2647 }
2648 }
2649
2650 if(current_instance + 1 - _sc.instance < _sc.instance) _sc.instance -= current_instance + 1;
2651 }
2652 }
2653 }
2654
2655 _sc.element = 0;
2656 const dt_action_def_t *def = _action_find_definition(_sc.action);
2657 if(def && def->elements && def->elements[0].name)
2658 _sc.element = darktable.control->element;
2659 }
2660
_widget_invisible(GtkWidget * w)2661 static gboolean _widget_invisible(GtkWidget *w)
2662 {
2663 GtkWidget *p = gtk_widget_get_parent(w);
2664 return (!GTK_IS_WIDGET(w) ||
2665 !gtk_widget_get_visible(w) ||
2666 (g_strcmp0(gtk_widget_get_name(p), "lib-plugin-ui-main") && !gtk_widget_get_visible(p)));
2667 }
2668
_shortcut_closest_match(GSequenceIter ** current,dt_shortcut_t * s,gboolean * fully_matched,const dt_action_def_t * def,char ** fb_log)2669 gboolean _shortcut_closest_match(GSequenceIter **current, dt_shortcut_t *s, gboolean *fully_matched, const dt_action_def_t *def, char **fb_log)
2670 {
2671 *current = g_sequence_iter_prev(*current);
2672 dt_shortcut_t *c = g_sequence_get(*current);
2673 //dt_print(DT_DEBUG_INPUT, " [_shortcut_closest_match] shortcut considered: %s\n", _shortcut_description(c));
2674
2675 gboolean applicable;
2676 while((applicable =
2677 (c->key_device == s->key_device && c->key == s->key && c->press >= (s->press & ~DT_SHORTCUT_LONG) &&
2678 ((!c->move_device && !c->move) ||
2679 (c->move_device == s->move_device && c->move == s->move)) &&
2680 (!s->action || s->action->type != DT_ACTION_TYPE_FALLBACK ||
2681 s->action->target == c->action->target))) &&
2682 !g_sequence_iter_is_begin(*current) &&
2683 (((c->button || c->click) && (c->button != s->button || c->click != s->click)) ||
2684 (c->mods && c->mods != s->mods ) ||
2685 (c->direction & ~s->direction ) ||
2686 (c->element && s->element ) ||
2687 (c->effect > 0 && s->effect > 0 ) ||
2688 (c->instance && s->instance ) ||
2689 (c->element && s->effect > 0 && def &&
2690 def->elements[c->element].effects != def->elements[s->element].effects ) ))
2691 {
2692 *current = g_sequence_iter_prev(*current);
2693 c = g_sequence_get(*current);
2694 //dt_print(DT_DEBUG_INPUT, " [_shortcut_closest_match] shortcut considered: %s\n", _shortcut_description(c));
2695 }
2696
2697 if(applicable)
2698 {
2699 s->key_device = 0;
2700 s->key = 0;
2701 s->mods &= ~c->mods;
2702 s->press -= c->press;
2703 s->button &= ~c->button;
2704 s->click -= c->click;
2705 s->direction &= ~c->direction;
2706 s->move_device -= c->move_device;
2707 s->move -= c->move;
2708
2709 if(c->element) s->element = c->element;
2710 if(c->effect > DT_ACTION_EFFECT_DEFAULT_KEY) s->effect = c->effect;
2711 if(c->instance) s->instance = c->instance;
2712
2713 s->speed *= c->speed;
2714 s->action = c->action;
2715
2716 *fully_matched = !(s->mods || s->press || s->button || s->click || s->move_device || s->move);
2717
2718 if(*fb_log)
2719 *fb_log = dt_util_dstrcat(*fb_log, "\n%s \u2192 %s", _shortcut_description(c), _action_description(c, 2));
2720
2721 return TRUE;
2722 }
2723 else
2724 {
2725 *fully_matched = FALSE;
2726 return FALSE;
2727 }
2728 }
2729
_shortcut_match(dt_shortcut_t * f,gchar ** fb_log)2730 static gboolean _shortcut_match(dt_shortcut_t *f, gchar **fb_log)
2731 {
2732 f->views = darktable.view_manager->current_view->view(darktable.view_manager->current_view);
2733 gpointer v = GINT_TO_POINTER(f->views);
2734
2735 GSequenceIter *existing = g_sequence_search(darktable.control->shortcuts, f, _shortcut_compare_func, v);
2736
2737 gboolean matched = FALSE;
2738
2739 if(!_shortcut_closest_match(&existing, f, &matched, NULL, fb_log))
2740 {
2741 // see if there is a fallback from midi knob press to knob turn
2742 if(!f->key_device || f->move_device || f->move)
2743 return FALSE;
2744
2745 dt_input_device_t id = f->key_device;
2746 GSList *driver = darktable.control->input_drivers;
2747 while(driver && (id -= 10) >= 10)
2748 driver = driver->next;
2749
2750 if(!driver)
2751 return FALSE;
2752 else
2753 {
2754 dt_input_driver_definition_t *callbacks = driver->data;
2755
2756 if(callbacks->key_to_move &&
2757 callbacks->key_to_move(callbacks->module, f->key_device, f->key, &f->move))
2758 {
2759 f->move_device = f->key_device;
2760 f->key_device = 0;
2761 f->key = 0;
2762
2763 existing = g_sequence_search(darktable.control->shortcuts, f, _shortcut_compare_func, v);
2764 if(!_shortcut_closest_match(&existing, f, &matched, NULL, fb_log) && !f->action)
2765 return FALSE;
2766 else
2767 {
2768 const dt_action_def_t *def = _action_find_definition(f->action);
2769
2770 if(def && def->elements
2771 && (def->elements[f->element].effects == dt_action_effect_value
2772 || def->elements[f->element].effects == dt_action_effect_selection))
2773 f->effect = DT_ACTION_EFFECT_RESET;
2774 }
2775 }
2776 }
2777 }
2778
2779 if(!matched && f->action && darktable.control->enable_fallbacks)
2780 {
2781 // try to add fallbacks
2782 f->views = 0;
2783
2784 dt_action_t *matched_action = f->action;
2785 dt_action_t fallback_action = { .type = DT_ACTION_TYPE_FALLBACK,
2786 .target = GINT_TO_POINTER(matched_action->type) };
2787 f->action = &fallback_action;
2788
2789 const dt_action_def_t *def = _action_find_definition(matched_action);
2790
2791 existing = g_sequence_search(darktable.control->shortcuts, f, _shortcut_compare_func, v);
2792 while(_shortcut_closest_match(&existing, f, &matched, def, fb_log) && !matched) {};
2793
2794 if(!matched && def && def->elements[f->element].effects == dt_action_effect_value)
2795 {
2796 static dt_action_t value_action = { .type = DT_ACTION_TYPE_FALLBACK,
2797 .target = GINT_TO_POINTER(DT_ACTION_TYPE_VALUE_FALLBACK) };
2798 f->action = &value_action;
2799 existing = g_sequence_search(darktable.control->shortcuts, f, _shortcut_compare_func, v);
2800 while(_shortcut_closest_match(&existing, f, &matched, def, fb_log) && !matched) {};
2801 }
2802
2803 if(f->move && !f->move_device && !(f->mods || f->press || f->button || f->click))
2804 {
2805 if(*fb_log)
2806 *fb_log = dt_util_dstrcat(*fb_log, "\n%s \u2192 %s", _shortcut_description(f), _("fallback to move"));
2807
2808 f->effect = DT_ACTION_EFFECT_DEFAULT_MOVE;
2809 f->move = 0;
2810 }
2811
2812 f->action = matched_action;
2813 }
2814
2815 return f->action != NULL && !f->move;
2816 }
2817
2818
_process_action(dt_action_t * action,int instance,dt_action_element_t element,dt_action_effect_t effect,float move_size)2819 static float _process_action(dt_action_t *action, int instance,
2820 dt_action_element_t element, dt_action_effect_t effect, float move_size)
2821 {
2822 float return_value = NAN;
2823
2824 dt_action_t *owner = action;
2825 while(owner && owner->type >= DT_ACTION_TYPE_SECTION) owner = owner->owner;
2826
2827 gpointer action_target = action->type == DT_ACTION_TYPE_LIB ? action : action->target;
2828
2829 if(owner && owner->type == DT_ACTION_TYPE_IOP)
2830 {
2831 // find module instance
2832 dt_iop_module_so_t *module = (dt_iop_module_so_t *)owner;
2833
2834 if(instance)
2835 {
2836 int current_instance = abs(instance);
2837
2838 dt_iop_module_t *mod = NULL;
2839
2840 for(GList *iop_mods = instance >= 0
2841 ? darktable.develop->iop
2842 : g_list_last(darktable.develop->iop);
2843 iop_mods;
2844 iop_mods = instance >= 0
2845 ? g_list_next(iop_mods)
2846 : g_list_previous(iop_mods))
2847 {
2848 mod = (dt_iop_module_t *)iop_mods->data;
2849
2850 if(mod->so == module
2851 && mod->iop_order != INT_MAX
2852 && !--current_instance)
2853 break;
2854 }
2855
2856 // find module instance widget
2857 if(mod && action->type >= DT_ACTION_TYPE_PER_INSTANCE)
2858 {
2859 for(GSList *w = mod->widget_list; w; w = w->next)
2860 {
2861 const dt_action_target_t *referral = w->data;
2862 if(referral->action == action)
2863 {
2864 action_target = referral->target;
2865 break;
2866 }
2867 }
2868 }
2869 else
2870 action_target = mod;
2871 }
2872 else if(action->type == DT_ACTION_TYPE_IOP
2873 || action->type == DT_ACTION_TYPE_PRESET)
2874 {
2875 action_target = dt_iop_get_module_preferred_instance((dt_iop_module_so_t *)owner);
2876 }
2877 }
2878
2879 if(action->type == DT_ACTION_TYPE_CLOSURE && action->target && !isnan(move_size))
2880 {
2881 typedef gboolean (*accel_callback)(GtkAccelGroup *accel_group, GObject *acceleratable,
2882 guint keyval, GdkModifierType modifier, gpointer p);
2883 ((accel_callback)((GCClosure*)action_target)->callback)(NULL, NULL, _sc.key, _sc.mods,
2884 ((GClosure*)action_target)->data);
2885 }
2886 else if(action->type == DT_ACTION_TYPE_PRESET && owner && !isnan(move_size))
2887 {
2888 if(owner->type == DT_ACTION_TYPE_LIB)
2889 {
2890 const dt_lib_module_t *lib = (dt_lib_module_t *)owner;
2891 dt_lib_presets_apply(action->label, lib->plugin_name, lib->version());
2892 }
2893 else if(owner->type == DT_ACTION_TYPE_IOP)
2894 {
2895 gchar *text = g_strdup_printf("\napplying preset '%s'", action->label);
2896 dt_action_widget_toast(action_target, NULL, text);
2897 g_free(text);
2898
2899 dt_gui_presets_apply_preset(action->label, action_target);
2900 }
2901 else
2902 fprintf(stderr, "[process_action] preset '%s' has unsupported type\n", action->label);
2903 }
2904 else
2905 {
2906 const dt_action_def_t *definition = _action_find_definition(action);
2907
2908 if(definition && definition->process
2909 && (action->type < DT_ACTION_TYPE_WIDGET
2910 || definition->no_widget
2911 || !_widget_invisible(action_target)))
2912 return_value = definition->process(action_target, element, effect, move_size);
2913 else if(!isnan(move_size))
2914 dt_action_widget_toast(action, action_target, "not active");
2915 }
2916
2917 return return_value;
2918 }
2919
_ungrab_grab_widget()2920 static void _ungrab_grab_widget()
2921 {
2922 gdk_seat_ungrab(gdk_display_get_default_seat(gdk_display_get_default()));
2923
2924 g_slist_free_full(_pressed_keys, g_free);
2925 _pressed_keys = NULL;
2926
2927 if(_grab_widget)
2928 {
2929 gtk_widget_set_sensitive(_grab_widget, TRUE);
2930 gtk_widget_set_tooltip_text(_grab_widget, NULL);
2931 g_signal_handlers_disconnect_by_func(gtk_widget_get_toplevel(_grab_widget), G_CALLBACK(dt_shortcut_dispatcher), NULL);
2932 _grab_widget = NULL;
2933 }
2934 }
2935
_ungrab_at_focus_loss()2936 static void _ungrab_at_focus_loss()
2937 {
2938 _grab_window = NULL;
2939 _focus_loss_key = _sc.key;
2940 _focus_loss_press = _sc.press;
2941 _ungrab_grab_widget();
2942 _sc = (dt_shortcut_t) { 0 };
2943 }
2944
_process_shortcut(float move_size)2945 static float _process_shortcut(float move_size)
2946 {
2947 float return_value = NAN;
2948
2949 dt_shortcut_t fsc = _sc;
2950 fsc.action = NULL;
2951 fsc.element = 0;
2952
2953 static int consecutive_unmatched = 0;
2954
2955 gchar *fb_log = darktable.control->mapping_widget && !isnan(move_size)
2956 ? g_strdup_printf("[ %s ]", _shortcut_description(&fsc))
2957 : NULL;
2958
2959 if(_shortcut_match(&fsc, &fb_log))
2960 {
2961 consecutive_unmatched = 0;
2962
2963 move_size *= fsc.speed;
2964
2965 if(fsc.effect == DT_ACTION_EFFECT_DEFAULT_MOVE)
2966 {
2967 if(move_size < .0f)
2968 {
2969 fsc.effect = DT_ACTION_EFFECT_DEFAULT_DOWN;
2970 move_size *= -1;
2971 }
2972 else
2973 fsc.effect = DT_ACTION_EFFECT_DEFAULT_UP;
2974 }
2975
2976 return_value = _process_action(fsc.action, fsc.instance, fsc.element, fsc.effect, move_size);
2977 }
2978 else if(!isnan(move_size) && !fsc.action)
2979 {
2980 dt_toast_log(_("%s not assigned"), _shortcut_description(&_sc));
2981
2982 if(++consecutive_unmatched >= 3)
2983 {
2984 _ungrab_at_focus_loss();
2985 consecutive_unmatched = 0;
2986 }
2987 }
2988
2989 if(fb_log)
2990 {
2991 dt_control_log("%s", fb_log);
2992 g_free(fb_log);
2993 }
2994
2995 return return_value;
2996 }
2997
dt_action_process(const gchar * action,int instance,const gchar * element,const gchar * effect,float move_size)2998 float dt_action_process(const gchar *action, int instance, const gchar *element, const gchar *effect, float move_size)
2999 {
3000 gchar **path = g_strsplit(action, "/", 0);
3001 dt_action_t *ac = dt_action_locate(NULL, path, FALSE);
3002 g_strfreev(path);
3003
3004 if(!ac)
3005 {
3006 fprintf(stderr, "[dt_action_process] action path '%s' not found\n", action);
3007 return NAN;;
3008 }
3009
3010 const dt_view_type_flags_t vws = _find_views(ac);
3011 if(!(vws & darktable.view_manager->current_view->view(darktable.view_manager->current_view)))
3012 {
3013 fprintf(stderr, "[dt_action_process] action '%s' not valid for current view\n", action);
3014 return NAN;;
3015 }
3016
3017 dt_action_element_t el = DT_ACTION_ELEMENT_DEFAULT;
3018 dt_action_effect_t ef = DT_ACTION_EFFECT_DEFAULT_KEY;
3019 if((element && *element) || (effect && *effect))
3020 {
3021 const dt_action_element_def_t *elements = _action_find_elements(ac);
3022 if(elements)
3023 {
3024 if(element && *element)
3025 {
3026 while(elements[el].name && strcmp(elements[el].name, element)) el++;
3027
3028 if(!elements[el].name)
3029 {
3030 fprintf(stderr, "[dt_action_process] element '%s' not valid for action '%s'\n", element, action);
3031 return NAN;;
3032 }
3033 }
3034
3035 const gchar **effects = elements[el].effects;
3036 if(effect && *effect && !_find_combo_effect(effects, effect, ac, &ef))
3037 {
3038 while(effects[ef] && strcmp(effects[ef], effect)) ef++;
3039
3040 if(!effects[ef])
3041 {
3042 fprintf(stderr, "[dt_action_process] effect '%s' not valid for action '%s'\n", effect, action);
3043 return NAN;
3044 }
3045 }
3046 }
3047 }
3048
3049 return _process_action(ac, instance, el, ef, move_size);
3050 }
3051
_cmp_key(const gconstpointer a,const gconstpointer b)3052 static gint _cmp_key(const gconstpointer a, const gconstpointer b)
3053 {
3054 const dt_device_key_t *key_a = a;
3055 const dt_device_key_t *key_b = b;
3056 return key_a->key_device != key_b->key_device || key_a->key != key_b->key;
3057 }
3058
_cancel_delayed_release(void)3059 static inline void _cancel_delayed_release(void)
3060 {
3061 if(_timeout_source)
3062 {
3063 g_source_remove(_timeout_source);
3064 _timeout_source = 0;
3065
3066 _sc.button = _pressed_button;
3067 _sc.click = 0;
3068 }
3069 }
3070
_key_modifiers_clean(guint mods)3071 static guint _key_modifiers_clean(guint mods)
3072 {
3073 GdkKeymap *keymap = gdk_keymap_get_for_display(gdk_display_get_default());
3074 mods &= GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_MOD5_MASK |
3075 gdk_keymap_get_modifier_mask(keymap, GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR);
3076 return mods | dt_modifier_shortcuts;
3077 }
3078
dt_shortcut_move(dt_input_device_t id,guint time,guint move,double size)3079 float dt_shortcut_move(dt_input_device_t id, guint time, guint move, double size)
3080 {
3081 _sc.move_device = id;
3082 _sc.move = move;
3083 _sc.speed = 1.0;
3084 _sc.direction = 0;
3085
3086 if(_shortcut_is_move(&_sc))
3087 {
3088 _sc.effect = DT_ACTION_EFFECT_DEFAULT_MOVE;
3089 _sc.direction = size > 0 ? DT_SHORTCUT_UP : DT_SHORTCUT_DOWN;
3090 }
3091 else
3092 _sc.effect = DT_ACTION_EFFECT_DEFAULT_KEY;
3093
3094 if(id) _sc.mods = _key_modifiers_clean(dt_key_modifier_state());
3095
3096 float return_value = 0;
3097 if(isnan(size))
3098 return_value = _process_shortcut(size);
3099 else
3100 {
3101 _cancel_delayed_release();
3102 _last_time = 0;
3103 _previous_move = move;
3104
3105 if(_grab_widget)
3106 _ungrab_grab_widget();
3107
3108 dt_print(DT_DEBUG_INPUT,
3109 " [dt_shortcut_move] shortcut received: %s\n",
3110 _shortcut_description(&_sc));
3111
3112 _lookup_mapping_widget();
3113
3114 if(_sc.action)
3115 {
3116 if(!time
3117 || time > _last_mapping_time + 1000
3118 || time < _last_mapping_time)
3119 {
3120 _last_mapping_time = time;
3121
3122 // mapping_widget gets cleared by confirmation dialog focus loss
3123 GtkWidget *mapped_widget = darktable.control->mapping_widget;
3124
3125 dt_shortcut_t s = _sc;
3126 if(_insert_shortcut(&s, TRUE))
3127 {
3128 dt_control_log(_("%s assigned to %s"),
3129 _shortcut_description(&s), _action_description(&s, 2));
3130
3131 if(mapped_widget)
3132 gtk_widget_trigger_tooltip_query(mapped_widget);
3133 }
3134
3135 dt_shortcuts_save(NULL, FALSE);
3136 }
3137
3138 _sc.action = NULL;
3139 _sc.instance = 0;
3140 }
3141 else
3142 {
3143 if(!_pressed_keys)
3144 return_value = _process_shortcut(size);
3145 else
3146 {
3147 // pressed_keys can be emptied if losing grab during processing
3148 for(GSList *k = _pressed_keys; k; k = _pressed_keys ? k->next : NULL)
3149 {
3150 const dt_device_key_t *device_key = k->data;
3151 _sc.key_device = device_key->key_device;
3152 _sc.key = device_key->key;
3153
3154 return_value = _process_shortcut(size);
3155 }
3156 }
3157 }
3158 }
3159
3160 _sc.move_device = 0;
3161 _sc.move = DT_SHORTCUT_MOVE_NONE;
3162 _sc.direction = 0;
3163
3164 return return_value;
3165 }
3166
_key_release_delayed(gpointer timed_out)3167 static gboolean _key_release_delayed(gpointer timed_out)
3168 {
3169 _timeout_source = 0;
3170 _last_time = 0;
3171
3172 _ungrab_grab_widget();
3173
3174 if(!timed_out)
3175 dt_shortcut_move(DT_SHORTCUT_DEVICE_KEYBOARD_MOUSE, 0, DT_SHORTCUT_MOVE_NONE, 1);
3176
3177 _sc = (dt_shortcut_t) { 0 };
3178
3179 return FALSE;
3180 }
3181
_button_release_delayed(gpointer timed_out)3182 static gboolean _button_release_delayed(gpointer timed_out)
3183 {
3184 _timeout_source = 0;
3185 _last_time = 0;
3186
3187 if(!timed_out)
3188 dt_shortcut_move(DT_SHORTCUT_DEVICE_KEYBOARD_MOUSE, 0, DT_SHORTCUT_MOVE_NONE, 1);
3189
3190 _sc.button = _pressed_button;
3191 _sc.click = 0;
3192
3193 return FALSE;
3194 }
3195
dt_shortcut_key_press(dt_input_device_t id,guint time,guint key)3196 void dt_shortcut_key_press(dt_input_device_t id, guint time, guint key)
3197 {
3198 dt_device_key_t this_key = { id, key };
3199
3200 if(g_slist_find_custom(_pressed_keys, &this_key, _cmp_key))
3201 {
3202 // if key is still repeating (after return from popup menu) then restore double/triple press state
3203 if(id == DT_SHORTCUT_DEVICE_KEYBOARD_MOUSE && key == _focus_loss_key && time < _last_time + 50)
3204 _sc.press = _focus_loss_press;
3205 _focus_loss_key = 0;
3206 }
3207 else if(g_slist_find_custom(_hold_keys, &this_key, _cmp_key))
3208 {} // ignore repeating hold key
3209 else
3210 {
3211 if(id) _sc.mods = _key_modifiers_clean(dt_key_modifier_state());
3212
3213 dt_shortcut_t just_key
3214 = { .key_device = id,
3215 .key = key,
3216 .mods = _sc.mods,
3217 .views = darktable.view_manager->current_view->view(darktable.view_manager->current_view) };
3218
3219 dt_shortcut_t *s = NULL;
3220 GSequenceIter *existing = g_sequence_lookup(darktable.control->shortcuts, &just_key,
3221 _shortcut_compare_func, GINT_TO_POINTER(just_key.views));
3222 if(existing)
3223 s = g_sequence_get(existing);
3224 else
3225 {
3226 just_key.mods = 0; // fall back to key without modifiers (for multiple emulated modifiers)
3227 existing = g_sequence_lookup(darktable.control->shortcuts, &just_key,
3228 _shortcut_compare_func, GINT_TO_POINTER(just_key.views));
3229 if(existing && (s = g_sequence_get(existing)) &&
3230 (s->action != darktable.control->actions_modifiers || s->effect != DT_ACTION_EFFECT_HOLD))
3231 s = NULL;
3232 }
3233 if(s
3234 && !_sc.action
3235 && s->effect == DT_ACTION_EFFECT_HOLD
3236 && s->action
3237 && s->action->type >= DT_ACTION_TYPE_WIDGET
3238 && !g_hash_table_lookup(darktable.control->widgets, darktable.control->mapping_widget))
3239 {
3240 const dt_action_def_t *definition = _action_find_definition(s->action);
3241 if(definition && definition->process
3242 && definition->elements[s->element].effects == dt_action_effect_hold)
3243 {
3244 if(darktable.control->mapping_widget)
3245 {
3246 dt_control_log("[ %s ]\n%s \u2192 %s", _shortcut_description(&_sc),
3247 _shortcut_description(s), _action_description(s, 2));
3248 }
3249
3250 definition->process(NULL, s->element, DT_ACTION_EFFECT_ON, 1);
3251
3252 this_key.hold_def = definition;
3253 this_key.hold_element = s->element;
3254
3255 dt_device_key_t *new_key = calloc(1, sizeof(dt_device_key_t));
3256 *new_key = this_key;
3257 _hold_keys = g_slist_prepend(_hold_keys, new_key);
3258
3259 return;
3260 }
3261 }
3262
3263 _cancel_delayed_release();
3264
3265 int delay = 0;
3266 g_object_get(gtk_settings_get_default(), "gtk-double-click-time", &delay, NULL);
3267
3268 if(!_pressed_keys)
3269 {
3270 if((id || key)
3271 && id == _sc.key_device
3272 && key == _sc.key
3273 && time < _last_time + delay
3274 && !(_sc.press & DT_SHORTCUT_TRIPLE))
3275 {
3276 _sc.press += DT_SHORTCUT_DOUBLE;
3277 }
3278 else
3279 {
3280 _sc.press = 0;
3281
3282 _lookup_mapping_widget();
3283 }
3284
3285 gdk_seat_grab(gdk_display_get_default_seat(gdk_display_get_default()),
3286 gtk_widget_get_window(_grab_window ? _grab_window
3287 : dt_ui_main_window(darktable.gui->ui)),
3288 GDK_SEAT_CAPABILITY_ALL, FALSE, NULL, NULL, NULL, NULL);
3289
3290 _last_time = time;
3291 }
3292 else
3293 {
3294 if(_sc.action)
3295 {
3296 // only one key press allowed while defining shortcut
3297 _ungrab_grab_widget();
3298 _sc = (dt_shortcut_t) { 0 };
3299 return;
3300 }
3301 }
3302
3303 _sc.key_device = id;
3304 _sc.key = key;
3305 _sc.button = _pressed_button = 0;
3306 _sc.click = 0;
3307 _sc.direction = 0;
3308
3309 dt_device_key_t *new_key = calloc(1, sizeof(dt_device_key_t));
3310 *new_key = this_key;
3311 _pressed_keys = g_slist_prepend(_pressed_keys, new_key);
3312
3313 // FIXME: make arrow keys repeat; eventually treat up/down and left/right as move
3314 if(key == GDK_KEY_Left
3315 || key == GDK_KEY_Right
3316 || key == GDK_KEY_Up
3317 || key == GDK_KEY_Down)
3318 {
3319 dt_shortcut_key_release(DT_SHORTCUT_DEVICE_KEYBOARD_MOUSE, time, key);
3320 }
3321 }
3322 }
3323
dt_shortcut_key_release(dt_input_device_t id,guint time,guint key)3324 void dt_shortcut_key_release(dt_input_device_t id, guint time, guint key)
3325 {
3326 dt_device_key_t this_key = { id, key };
3327
3328 GSList *held_key = g_slist_find_custom(_hold_keys, &this_key, _cmp_key);
3329 if(held_key)
3330 {
3331 dt_device_key_t *held_data = held_key->data;
3332 held_data->hold_def->process(NULL, held_data->hold_element, DT_ACTION_EFFECT_OFF, 1);
3333 g_free(held_data);
3334 _hold_keys = g_slist_delete_link(_hold_keys, held_key);
3335 return;
3336 }
3337
3338 GSList *stored_key = g_slist_find_custom(_pressed_keys, &this_key, _cmp_key);
3339 if(stored_key)
3340 {
3341 _cancel_delayed_release();
3342
3343 g_free(stored_key->data);
3344 _pressed_keys = g_slist_delete_link(_pressed_keys, stored_key);
3345
3346 if(!_pressed_keys)
3347 {
3348 // is this sequence of same key, without mouse buttons
3349 if(_sc.key_device == id
3350 && _sc.key == key
3351 && !_sc.button)
3352 {
3353 int delay = 0;
3354 g_object_get(gtk_settings_get_default(), "gtk-double-click-time", &delay, NULL);
3355
3356 guint passed_time = time - _last_time;
3357
3358 if(passed_time > delay) _sc.press |= DT_SHORTCUT_LONG;
3359
3360 if(!_sc.press && !_sc.action)
3361 {
3362 // detect if any double or triple press shortcuts exist for this key; otherwise skip delay
3363 dt_shortcut_t key_23press =
3364 { .key_device = id,
3365 .key = key,
3366 .press = DT_SHORTCUT_DOUBLE,
3367 .views = darktable.view_manager->current_view->view(darktable.view_manager->current_view) };
3368 GSequenceIter *double_press = g_sequence_search(darktable.control->shortcuts, &key_23press, _shortcut_compare_func,
3369 GINT_TO_POINTER(key_23press.views));
3370 if(double_press)
3371 {
3372 dt_shortcut_t *dp = g_sequence_get(double_press);
3373 if(!dp || dp->key_device != id || dp->key != key || dp->press <= DT_SHORTCUT_LONG)
3374 {
3375 dp = g_sequence_get(g_sequence_iter_prev(double_press));
3376 if(!dp || dp->key_device != id || dp->key != key || dp->press <= DT_SHORTCUT_LONG)
3377 passed_time = delay;
3378 }
3379 }
3380 }
3381
3382 if(passed_time < delay && !(_sc.press & DT_SHORTCUT_TRIPLE))
3383 _timeout_source = g_timeout_add(delay - passed_time, _key_release_delayed, NULL);
3384 else
3385 _key_release_delayed(GINT_TO_POINTER(passed_time > 2 * delay)); // call immediately
3386 }
3387 else
3388 {
3389 _key_release_delayed(GINT_TO_POINTER(TRUE)); // not sequence of same key
3390 }
3391 }
3392 }
3393 }
3394
dt_shortcut_key_active(dt_input_device_t id,guint key)3395 gboolean dt_shortcut_key_active(dt_input_device_t id, guint key)
3396 {
3397 dt_shortcut_t base_key
3398 = { .key_device = id,
3399 .key = key,
3400 .views = darktable.view_manager->current_view->view(darktable.view_manager->current_view) };
3401
3402 GSequenceIter *existing = g_sequence_lookup(darktable.control->shortcuts, &base_key,
3403 _shortcut_compare_func, GINT_TO_POINTER(base_key.views));
3404 if(existing)
3405 {
3406 dt_shortcut_t *s = g_sequence_get(existing);
3407
3408 if(s && s->action && s->action->type >= DT_ACTION_TYPE_WIDGET)
3409 {
3410 const dt_action_def_t *definition = _action_find_definition(s->action);
3411 if(definition && definition->process)
3412 {
3413 float value = definition->process(s->action->target, s->element, s->effect, NAN);
3414 return fmodf(value, 1) <= DT_VALUE_PATTERN_ACTIVE || fmodf(value, 2) > .5;
3415 }
3416 }
3417 }
3418
3419 return FALSE;
3420 }
3421
_fix_keyval(GdkEvent * event)3422 static guint _fix_keyval(GdkEvent *event)
3423 {
3424 guint keyval = 0;
3425 GdkKeymap *keymap = gdk_keymap_get_for_display(gdk_display_get_default());
3426 gdk_keymap_translate_keyboard_state(keymap, event->key.hardware_keycode, 0, 0,
3427 &keyval, NULL, NULL, NULL);
3428 return keyval;
3429 }
3430
dt_shortcut_dispatcher(GtkWidget * w,GdkEvent * event,gpointer user_data)3431 gboolean dt_shortcut_dispatcher(GtkWidget *w, GdkEvent *event, gpointer user_data)
3432 {
3433 // dt_print(DT_DEBUG_INPUT, " [shortcut_dispatcher] %d\n", event->type);
3434
3435 if(!darktable.control->key_accelerators_on) return FALSE; // FIXME should eventually no longer be needed
3436
3437 if(_pressed_keys == NULL)
3438 {
3439 if(_grab_widget && event->type == GDK_BUTTON_PRESS)
3440 {
3441 _ungrab_grab_widget();
3442 _sc = (dt_shortcut_t) { 0 };
3443 return TRUE;
3444 }
3445
3446 if(event->type != GDK_KEY_PRESS && event->type != GDK_KEY_RELEASE &&
3447 event->type != GDK_FOCUS_CHANGE)
3448 return FALSE;
3449
3450 if(GTK_IS_WINDOW(w) &&
3451 (event->type == GDK_KEY_PRESS || event->type == GDK_KEY_RELEASE))
3452 {
3453 GtkWidget *focused_widget = gtk_window_get_focus(GTK_WINDOW(w));
3454 if(focused_widget)
3455 {
3456 if(gtk_widget_event(focused_widget, event))
3457 return TRUE;
3458
3459 if(GTK_IS_ENTRY(focused_widget) &&
3460 (event->key.keyval == GDK_KEY_Tab ||
3461 event->key.keyval == GDK_KEY_KP_Tab ||
3462 event->key.keyval == GDK_KEY_ISO_Left_Tab))
3463 return FALSE;
3464 }
3465 }
3466 }
3467
3468 int delay = 0;
3469 g_object_get(gtk_settings_get_default(), "gtk-double-click-time", &delay, NULL);
3470
3471 switch(event->type)
3472 {
3473 case GDK_KEY_PRESS:
3474 if(event->key.is_modifier
3475 || event->key.keyval == GDK_KEY_VoidSymbol
3476 || event->key.keyval == GDK_KEY_Meta_L
3477 || event->key.keyval == GDK_KEY_Meta_R
3478 || event->key.keyval == GDK_KEY_ISO_Level3_Shift)
3479 return FALSE;
3480
3481 _sc.mods = _key_modifiers_clean(event->key.state);
3482
3483 // FIXME: eventually clean up per-view and global key_pressed handlers
3484 if(!_grab_widget && !darktable.control->mapping_widget &&
3485 dt_control_key_pressed_override(event->key.keyval, dt_gui_translated_key_state(&event->key))) return TRUE;
3486
3487 dt_shortcut_key_press(DT_SHORTCUT_DEVICE_KEYBOARD_MOUSE, event->key.time, _fix_keyval(event));
3488 break;
3489 case GDK_KEY_RELEASE:
3490 if(event->key.is_modifier || event->key.keyval == GDK_KEY_ISO_Level3_Shift)
3491 {
3492 if(_sc.action)
3493 {
3494 // we may interrupt a delayed release, so do the ungrab here if needed
3495 if(!_pressed_keys) _ungrab_grab_widget();
3496
3497 _sc.mods = _key_modifiers_clean(event->key.state);
3498 dt_shortcut_move(DT_SHORTCUT_DEVICE_KEYBOARD_MOUSE, 0, DT_SHORTCUT_MOVE_NONE, 1);
3499 }
3500 return FALSE;
3501 }
3502
3503 dt_shortcut_key_release(DT_SHORTCUT_DEVICE_KEYBOARD_MOUSE, event->key.time, _fix_keyval(event));
3504 break;
3505 case GDK_GRAB_BROKEN:
3506 if(!event->grab_broken.implicit)
3507 _ungrab_at_focus_loss();
3508 return FALSE;
3509 case GDK_WINDOW_STATE:
3510 if(!(event->window_state.new_window_state & GDK_WINDOW_STATE_FOCUSED))
3511 _ungrab_at_focus_loss();
3512 return FALSE;
3513 case GDK_FOCUS_CHANGE: // dialog boxes and switch to other app release grab
3514 if(event->focus_change.in)
3515 g_set_weak_pointer(&_grab_window, w);
3516 else
3517 _ungrab_at_focus_loss();
3518 return FALSE;
3519 case GDK_SCROLL:
3520 _sc.mods = _key_modifiers_clean(event->scroll.state);
3521
3522 int delta_x, delta_y;
3523 if(dt_gui_get_scroll_unit_deltas((GdkEventScroll *)event, &delta_x, &delta_y))
3524 {
3525 if(delta_x)
3526 dt_shortcut_move(DT_SHORTCUT_DEVICE_KEYBOARD_MOUSE, event->scroll.time, DT_SHORTCUT_MOVE_PAN, -delta_x);
3527 if(delta_y)
3528 dt_shortcut_move(DT_SHORTCUT_DEVICE_KEYBOARD_MOUSE, event->scroll.time, DT_SHORTCUT_MOVE_SCROLL, -delta_y);
3529 }
3530 break;
3531 case GDK_MOTION_NOTIFY:
3532 _sc.mods = _key_modifiers_clean(event->motion.state);
3533
3534 static gdouble move_start_x = 0, move_start_y = 0, last_distance = 0;
3535
3536 const gdouble x_move = event->motion.x - move_start_x;
3537 const gdouble y_move = event->motion.y - move_start_y;
3538 const gdouble new_distance = x_move * x_move + y_move * y_move;
3539
3540 static int move_last_time = 0;
3541 if(move_last_time != _last_time || new_distance < last_distance)
3542 {
3543 move_start_x = event->motion.x;
3544 move_start_y = event->motion.y;
3545 move_last_time = _last_time;
3546 last_distance = 0;
3547 break;
3548 }
3549
3550 // might just be an accidental move during a key press or button click
3551 // possibly different time sources from midi or other devices
3552 if(event->motion.time > _last_time && event->motion.time < _last_time + delay) break;
3553
3554 const gdouble step_size = 10;
3555
3556 const gdouble angle = x_move / (0.001 + y_move);
3557 gdouble size = trunc(x_move / step_size);
3558 gdouble y_size = - trunc(y_move / step_size);
3559
3560 if(size != 0 || y_size != 0)
3561 {
3562 guint move = DT_SHORTCUT_MOVE_HORIZONTAL;
3563 if(fabs(angle) >= 2)
3564 {
3565 move_start_x += size * step_size;
3566 move_start_y = event->motion.y;
3567 }
3568 else
3569 {
3570 size = y_size;
3571 move_start_y -= size * step_size;
3572 if(fabs(angle) < .5)
3573 {
3574 move_start_x = event->motion.x;
3575 move = DT_SHORTCUT_MOVE_VERTICAL;
3576 }
3577 else
3578 {
3579 move_start_x -= size * step_size * angle;
3580 move = angle < 0 ? DT_SHORTCUT_MOVE_SKEW : DT_SHORTCUT_MOVE_DIAGONAL;
3581 }
3582 }
3583
3584 if(_previous_move == move || _previous_move == DT_SHORTCUT_MOVE_NONE)
3585 dt_shortcut_move(DT_SHORTCUT_DEVICE_KEYBOARD_MOUSE, event->motion.time, move, size);
3586 else
3587 _previous_move = move;
3588 }
3589 break;
3590 case GDK_BUTTON_PRESS:
3591 _sc.mods = _key_modifiers_clean(event->button.state);
3592
3593 _cancel_delayed_release();
3594 _pressed_button |= 1 << (event->button.button - 1);
3595 _sc.button = _pressed_button;
3596 _sc.click = 0;
3597 _last_time = event->button.time;
3598 break;
3599 case GDK_DOUBLE_BUTTON_PRESS:
3600 _sc.click |= DT_SHORTCUT_DOUBLE;
3601 break;
3602 case GDK_TRIPLE_BUTTON_PRESS:
3603 _sc.click |= DT_SHORTCUT_TRIPLE;
3604 break;
3605 case GDK_BUTTON_RELEASE:
3606 // FIXME; check if there's a shortcut defined for double/triple (could be fallback?); if not -> no delay
3607 // maybe even action on PRESS rather than RELEASE
3608 // FIXME be careful!!; we seem to be receiving presses and releases twice!?!
3609 _pressed_button &= ~(1 << (event->button.button - 1));
3610
3611 const guint passed_time = event->button.time - _last_time;
3612 if(passed_time < delay
3613 && !(_sc.click & DT_SHORTCUT_TRIPLE))
3614 {
3615 if(!_timeout_source)
3616 _timeout_source = g_timeout_add(delay - passed_time, _button_release_delayed, NULL);
3617 }
3618 else
3619 {
3620 if(passed_time > delay)
3621 _sc.click |= DT_SHORTCUT_LONG;
3622 _button_release_delayed(GINT_TO_POINTER(passed_time > 2 * delay)); // call immediately
3623 }
3624 break;
3625 default:
3626 return FALSE;
3627 }
3628
3629 return TRUE;
3630 }
3631
_remove_widget_from_hashtable(GtkWidget * widget,gpointer user_data)3632 static void _remove_widget_from_hashtable(GtkWidget *widget, gpointer user_data)
3633 {
3634 dt_action_t *action = g_hash_table_lookup(darktable.control->widgets, widget);
3635 if(action)
3636 {
3637 if(action->target == widget) action->target = NULL;
3638
3639 g_hash_table_remove(darktable.control->widgets, widget);
3640 }
3641 }
3642
path_without_symbols(const gchar * path)3643 static inline gchar *path_without_symbols(const gchar *path)
3644 {
3645 return g_strdelimit(g_strdup(path), "=,/.", '-');
3646 }
3647
dt_action_insert_sorted(dt_action_t * owner,dt_action_t * new_action)3648 void dt_action_insert_sorted(dt_action_t *owner, dt_action_t *new_action)
3649 {
3650 dt_action_t **insertion_point = (dt_action_t **)&owner->target;
3651
3652 while(*insertion_point
3653 && strcmp(new_action->id, "preset")
3654 && (!strcmp((*insertion_point)->id, "preset")
3655 || g_utf8_collate((*insertion_point)->label, new_action->label) < 0))
3656 {
3657 insertion_point = &(*insertion_point)->next;
3658 }
3659 new_action->next = *insertion_point;
3660 *insertion_point = new_action;
3661 }
3662
dt_action_locate(dt_action_t * owner,gchar ** path,gboolean create)3663 dt_action_t *dt_action_locate(dt_action_t *owner, gchar **path, gboolean create)
3664 {
3665 gchar *clean_path = NULL;
3666
3667 dt_action_t *action = owner ? owner->target : darktable.control->actions;
3668 while(*path)
3669 {
3670 if(owner == &darktable.control->actions_lua) create = TRUE;
3671
3672 if(!clean_path) clean_path = path_without_symbols(*path);
3673
3674 if(!action)
3675 {
3676 if(!owner || !create)
3677 {
3678 fprintf(stderr, "[dt_action_locate] action '%s' %s\n", *path,
3679 !owner ? "not valid base node" : "doesn't exist");
3680 g_free(clean_path);
3681 return NULL;
3682 }
3683
3684 dt_action_t *new_action = calloc(1, sizeof(dt_action_t));
3685 new_action->id = clean_path;
3686 new_action->label = g_strdup(Q_(*path));
3687 new_action->type = DT_ACTION_TYPE_SECTION;
3688 new_action->owner = owner;
3689
3690 dt_action_insert_sorted(owner, new_action);
3691
3692 owner = new_action;
3693 action = NULL;
3694 }
3695 else if(!strcmp(action->id, clean_path))
3696 {
3697 g_free(clean_path);
3698 owner = action;
3699 action = action->target;
3700 }
3701 else
3702 {
3703 action = action->next;
3704 continue;
3705 }
3706 clean_path = NULL; // now owned by action or freed
3707 path++;
3708 }
3709
3710 if(owner->type <= DT_ACTION_TYPE_VIEW)
3711 {
3712 fprintf(stderr, "[dt_action_locate] found action '%s' internal node\n", owner->id);
3713 return NULL;
3714 }
3715 else if(owner->type == DT_ACTION_TYPE_SECTION)
3716 owner->type = DT_ACTION_TYPE_CLOSURE; // mark newly created leaf as closure
3717
3718 return owner;
3719 }
3720
dt_action_define(dt_action_t * owner,const gchar * section,const gchar * label,GtkWidget * widget,const dt_action_def_t * action_def)3721 dt_action_t *dt_action_define(dt_action_t *owner, const gchar *section, const gchar *label, GtkWidget *widget, const dt_action_def_t *action_def)
3722 {
3723 if(owner->type == DT_ACTION_TYPE_IOP_INSTANCE)
3724 {
3725 dt_action_define_iop((dt_iop_module_t *)owner, section, label, widget, action_def);
3726 return owner;
3727 }
3728
3729 dt_action_t *ac = owner;
3730
3731 if(label)
3732 {
3733 const gchar *path[] = { section, label, NULL };
3734 ac = dt_action_locate(owner, (gchar**)&path[section ? 0 : 1], TRUE);
3735 }
3736
3737 if(ac)
3738 {
3739 if(label)
3740 {
3741 if(ac->type == DT_ACTION_TYPE_CLOSURE && ac->target && action_def)
3742 g_closure_unref(ac->target);
3743
3744 guint index = 0;
3745 if(g_ptr_array_find(darktable.control->widget_definitions, action_def, &index))
3746 ac->type = DT_ACTION_TYPE_WIDGET + index + 1;
3747 else if(action_def == &_action_def_dummy)
3748 ac->type = DT_ACTION_TYPE_WIDGET;
3749 else if(action_def)
3750 {
3751 ac->type = DT_ACTION_TYPE_WIDGET + darktable.control->widget_definitions->len + 1;
3752 g_ptr_array_add(darktable.control->widget_definitions, (gpointer)action_def);
3753
3754 dt_action_define_fallback(ac->type, action_def);
3755 }
3756 }
3757
3758 if(action_def && action_def->no_widget)
3759 {
3760 ac->target = widget;
3761 }
3762 else if(!darktable.control->accel_initialising)
3763 {
3764 if(label && action_def && !ac->target) ac->target = widget;
3765 g_hash_table_insert(darktable.control->widgets, widget, ac);
3766
3767 gtk_widget_set_has_tooltip(widget, TRUE);
3768 g_signal_connect(G_OBJECT(widget), "query-tooltip", G_CALLBACK(_shortcut_tooltip_callback), NULL);
3769 g_signal_connect(G_OBJECT(widget), "destroy", G_CALLBACK(_remove_widget_from_hashtable), NULL);
3770 }
3771 }
3772
3773 return ac;
3774 }
3775
dt_action_define_iop(dt_iop_module_t * self,const gchar * section,const gchar * label,GtkWidget * widget,const dt_action_def_t * action_def)3776 void dt_action_define_iop(dt_iop_module_t *self, const gchar *section, const gchar *label, GtkWidget *widget, const dt_action_def_t *action_def)
3777 {
3778 // add to module_so or blending actions list
3779 dt_action_t *ac = NULL;
3780 if(section && g_str_has_prefix(section, "blend"))
3781 {
3782 const char *subsection = section[strlen("blend")] ? section + strlen("blend") + 1 : NULL;
3783 ac = dt_action_define(&darktable.control->actions_blend, subsection, label, widget, action_def);
3784 }
3785 else
3786 {
3787 ac = dt_action_define(&self->so->actions, section, label, widget, action_def ? action_def : &_action_def_dummy);
3788 }
3789
3790 // to support multi-instance, also save in per instance widget list
3791 dt_action_target_t *referral = g_malloc0(sizeof(dt_action_target_t));
3792 referral->action = ac;
3793 referral->target = widget;
3794 self->widget_list = g_slist_prepend(self->widget_list, referral);
3795 }
3796
_mods_fix_primary(GdkModifierType mods)3797 static GdkModifierType _mods_fix_primary(GdkModifierType mods)
3798 {
3799 // FIXME move to darktable.h (?) and use there too in dt_modifier_is and dt_modifiers_include
3800 // use global variable?
3801 GdkKeymap *keymap = gdk_keymap_get_for_display(gdk_display_get_default());
3802 if(mods & GDK_CONTROL_MASK)
3803 return (mods & ~GDK_CONTROL_MASK)
3804 | gdk_keymap_get_modifier_mask(keymap, GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR);
3805 else
3806 return mods;
3807 }
3808
dt_action_define_fallback(dt_action_type_t type,const dt_action_def_t * action_def)3809 void dt_action_define_fallback(dt_action_type_t type, const dt_action_def_t *action_def)
3810 {
3811 const dt_shortcut_fallback_t *f = action_def->fallbacks;
3812 if(f)
3813 {
3814 const gchar *fallback_path[] = { action_def->name, NULL };
3815 dt_action_t *fb = dt_action_locate(&darktable.control->actions_fallbacks, (gchar**)fallback_path, TRUE);
3816 fb->type = DT_ACTION_TYPE_FALLBACK;
3817 fb->target = GINT_TO_POINTER(type);
3818
3819 while(f->mods || f->press || f->button || f->click || f->direction || f->move)
3820 {
3821 dt_shortcut_t s = { .mods = _mods_fix_primary(f->mods),
3822 .press = f->press,
3823 .button = f->button,
3824 .click = f->click,
3825 .direction = f->direction,
3826 .move = f->move,
3827 .element = f->element,
3828 .effect = f->effect,
3829 .action = fb,
3830 .speed = f->speed ? f->speed : 1.0 };
3831
3832 _insert_shortcut(&s, FALSE);
3833
3834 f++;
3835 }
3836 }
3837 }
3838
dt_accel_register_shortcut(dt_action_t * owner,const gchar * path_string,guint element,guint effect,guint accel_key,GdkModifierType mods)3839 void dt_accel_register_shortcut(dt_action_t *owner, const gchar *path_string, guint element, guint effect, guint accel_key, GdkModifierType mods)
3840 {
3841 if(path_string)
3842 {
3843 gchar **split_path = g_strsplit(path_string, "/", 0);
3844 gchar **split_trans = g_strsplit(g_dpgettext2(NULL, "accel", path_string), "/", g_strv_length(split_path));
3845
3846 gchar **path = split_path;
3847 gchar **trans = split_trans;
3848
3849 gchar *clean_path = NULL;
3850
3851 dt_action_t *action = owner->target;
3852 while(*path)
3853 {
3854 if(!clean_path) clean_path = path_without_symbols(*path);
3855
3856 if(!action)
3857 {
3858 dt_action_t *new_action = calloc(1, sizeof(dt_action_t));
3859 new_action->id = clean_path;
3860 new_action->label = g_strdup(*trans ? *trans : *path);
3861 new_action->type = DT_ACTION_TYPE_SECTION;
3862 new_action->owner = owner;
3863
3864 dt_action_insert_sorted(owner, new_action);
3865
3866 owner = new_action;
3867 action = NULL;
3868 }
3869 else if(!strcmp(action->id, clean_path))
3870 {
3871 g_free(clean_path);
3872 owner = action;
3873 action = action->target;
3874 }
3875 else
3876 {
3877 action = action->next;
3878 continue;
3879 }
3880 clean_path = NULL; // now owned by action or freed
3881 path++;
3882 if(*trans) trans++;
3883 }
3884
3885 g_strfreev(split_path);
3886 g_strfreev(split_trans);
3887 }
3888
3889 if(accel_key != 0)
3890 {
3891 GdkKeymap *keymap = gdk_keymap_get_for_display(gdk_display_get_default());
3892
3893 GdkKeymapKey *keys;
3894 gint n_keys, i = 0;
3895
3896 if(!gdk_keymap_get_entries_for_keyval(keymap, accel_key, &keys, &n_keys)) return;
3897
3898 // find the first key in group 0, if any
3899 while(i < n_keys - 1 && (keys[i].group > 0 || keys[i].level > 1)) i++;
3900
3901 if(keys[i].level & 1) mods |= GDK_SHIFT_MASK;
3902 if(keys[i].level & 2) mods |= GDK_MOD5_MASK;
3903
3904 mods = _mods_fix_primary(mods);
3905
3906 dt_shortcut_t s = { .key_device = DT_SHORTCUT_DEVICE_KEYBOARD_MOUSE,
3907 .mods = mods,
3908 .speed = 1.0,
3909 .action = owner,
3910 .element = element,
3911 .effect = effect };
3912
3913 gdk_keymap_translate_keyboard_state(keymap, keys[i].keycode, 0, 0, &s.key, NULL, NULL, NULL);
3914
3915 _insert_shortcut(&s, FALSE);
3916
3917 g_free(keys);
3918 }
3919 }
3920
dt_accel_connect_shortcut(dt_action_t * owner,const gchar * path_string,GClosure * closure)3921 void dt_accel_connect_shortcut(dt_action_t *owner, const gchar *path_string, GClosure *closure)
3922 {
3923 gchar **split_path = g_strsplit(path_string, "/", 0);
3924 gchar **path = split_path;
3925
3926 while(*path && (owner = owner->target))
3927 {
3928 gchar *clean_path = path_without_symbols(*path);
3929
3930 while(owner)
3931 {
3932 if(!strcmp(owner->id, clean_path))
3933 break;
3934 else
3935 owner = owner->next;
3936 }
3937
3938 g_free(clean_path);
3939 path++;
3940 }
3941
3942 if(!*path && owner)
3943 {
3944 if(owner->type == DT_ACTION_TYPE_CLOSURE && owner->target)
3945 g_closure_unref(owner->target);
3946
3947 owner->type = DT_ACTION_TYPE_CLOSURE;
3948 owner->target = closure;
3949 g_closure_ref(closure);
3950 g_closure_sink(closure);
3951 }
3952 else
3953 {
3954 fprintf(stderr, "[dt_accel_connect_shortcut] '%s' not found\n", path_string);
3955 }
3956
3957 g_strfreev(split_path);
3958 }
3959
dt_accel_register_global(const gchar * path,guint accel_key,GdkModifierType mods)3960 void dt_accel_register_global(const gchar *path, guint accel_key, GdkModifierType mods)
3961 {
3962 dt_accel_register_shortcut(&darktable.control->actions_global, path, 0, 0, accel_key, mods);
3963 }
3964
dt_accel_register_view(dt_view_t * self,const gchar * path,guint accel_key,GdkModifierType mods)3965 void dt_accel_register_view(dt_view_t *self, const gchar *path, guint accel_key, GdkModifierType mods)
3966 {
3967 dt_accel_register_shortcut(&self->actions, path, 0, 0, accel_key, mods);
3968 }
3969
dt_accel_register_iop(dt_iop_module_so_t * so,gboolean local,const gchar * path,guint accel_key,GdkModifierType mods)3970 void dt_accel_register_iop(dt_iop_module_so_t *so, gboolean local, const gchar *path, guint accel_key,
3971 GdkModifierType mods)
3972 {
3973 dt_accel_register_shortcut(&so->actions, path, 0, 0, accel_key, mods);
3974 }
3975
dt_action_define_preset(dt_action_t * action,const gchar * name)3976 void dt_action_define_preset(dt_action_t *action, const gchar *name)
3977 {
3978 gchar *path[3] = { "preset", (gchar *)name, NULL };
3979 dt_action_t *p = dt_action_locate(action, path, TRUE);
3980 if(p)
3981 {
3982 p->type = DT_ACTION_TYPE_PRESET;
3983 p->target = (gpointer)TRUE;
3984 }
3985 }
3986
dt_action_rename(dt_action_t * action,const gchar * new_name)3987 void dt_action_rename(dt_action_t *action, const gchar *new_name)
3988 {
3989 g_free((char*)action->id);
3990 g_free((char*)action->label);
3991
3992 dt_action_t **previous = (dt_action_t **)&action->owner->target;
3993 while(*previous)
3994 {
3995 if(*previous == action)
3996 {
3997 *previous = action->next;
3998 break;
3999 }
4000 previous = &(*previous)->next;
4001 }
4002
4003 if(new_name)
4004 {
4005 action->id = path_without_symbols(new_name);
4006 action->label = g_strdup(_(new_name));
4007
4008 dt_action_insert_sorted(action->owner, action);
4009 }
4010 else
4011 {
4012 GSequenceIter *iter = g_sequence_get_begin_iter(darktable.control->shortcuts);
4013 while(!g_sequence_iter_is_end(iter))
4014 {
4015 GSequenceIter *current = iter;
4016 iter = g_sequence_iter_next(iter); // remove will invalidate
4017
4018 dt_shortcut_t *s = g_sequence_get(current);
4019 if(s->action == action)
4020 _remove_shortcut(current);
4021 }
4022
4023 if(action->type == DT_ACTION_TYPE_CLOSURE)
4024 g_closure_unref(action->target);
4025
4026 g_free(action);
4027 }
4028
4029 dt_shortcuts_save(NULL, FALSE);
4030 }
4031
dt_action_rename_preset(dt_action_t * action,const gchar * old_name,const gchar * new_name)4032 void dt_action_rename_preset(dt_action_t *action, const gchar *old_name, const gchar *new_name)
4033 {
4034 gchar *path[3] = { "preset", (gchar *)old_name, NULL };
4035 dt_action_t *p = dt_action_locate(action, path, FALSE);
4036 if(p)
4037 {
4038 if(!new_name)
4039 {
4040 if(_actions_store)
4041 gtk_tree_model_foreach(GTK_TREE_MODEL(_actions_store), _remove_shortcut_from_store, p);
4042 }
4043
4044 dt_action_rename(p, new_name);
4045 }
4046 }
4047
dt_accel_register_lib_as_view(gchar * view_name,const gchar * path,guint accel_key,GdkModifierType mods)4048 void dt_accel_register_lib_as_view(gchar *view_name, const gchar *path, guint accel_key, GdkModifierType mods)
4049 {
4050 //register a lib shortcut but place it in the path of a view
4051 dt_action_t *a = darktable.control->actions_views.target;
4052 while(a)
4053 {
4054 if(!strcmp(a->id, view_name))
4055 break;
4056 else
4057 a = a->next;
4058 }
4059 if(a)
4060 {
4061 dt_accel_register_shortcut(a, path, 0, 0, accel_key, mods);
4062 }
4063 else
4064 {
4065 fprintf(stderr, "[dt_accel_register_lib_as_view] '%s' not found\n", view_name);
4066 }
4067 }
4068
dt_accel_register_lib(dt_lib_module_t * self,const gchar * path,guint accel_key,GdkModifierType mods)4069 void dt_accel_register_lib(dt_lib_module_t *self, const gchar *path, guint accel_key, GdkModifierType mods)
4070 {
4071 dt_accel_register_shortcut(&self->actions, path, 0, 0, accel_key, mods);
4072 }
4073
dt_accel_register_lua(const gchar * path,guint accel_key,GdkModifierType mods)4074 void dt_accel_register_lua(const gchar *path, guint accel_key, GdkModifierType mods)
4075 {
4076 dt_accel_register_shortcut(&darktable.control->actions_lua, path, 0, 0, accel_key, mods);
4077 }
4078
dt_accel_connect_global(const gchar * path,GClosure * closure)4079 void dt_accel_connect_global(const gchar *path, GClosure *closure)
4080 {
4081 dt_accel_connect_shortcut(&darktable.control->actions_global, path, closure);
4082 }
4083
dt_accel_connect_view(dt_view_t * self,const gchar * path,GClosure * closure)4084 void dt_accel_connect_view(dt_view_t *self, const gchar *path, GClosure *closure)
4085 {
4086 dt_accel_connect_shortcut(&self->actions, path, closure);
4087 }
4088
dt_accel_connect_lib_as_view(dt_lib_module_t * module,gchar * view_name,const gchar * path,GClosure * closure)4089 void dt_accel_connect_lib_as_view(dt_lib_module_t *module, gchar *view_name, const gchar *path, GClosure *closure)
4090 {
4091 dt_action_t *a = darktable.control->actions_views.target;
4092 while(a)
4093 {
4094 if(!strcmp(a->id, view_name))
4095 break;
4096 else
4097 a = a->next;
4098 }
4099 if(a)
4100 {
4101 dt_accel_connect_shortcut(a, path, closure);
4102 }
4103 else
4104 {
4105 fprintf(stderr, "[dt_accel_register_lib_as_view] '%s' not found\n", view_name);
4106 }
4107 }
4108
dt_accel_connect_lib_as_global(dt_lib_module_t * module,const gchar * path,GClosure * closure)4109 void dt_accel_connect_lib_as_global(dt_lib_module_t *module, const gchar *path, GClosure *closure)
4110 {
4111 dt_accel_connect_shortcut(&darktable.control->actions_global, path, closure);
4112 }
4113
dt_accel_connect_iop(dt_iop_module_t * module,const gchar * path,GClosure * closure)4114 void dt_accel_connect_iop(dt_iop_module_t *module, const gchar *path, GClosure *closure)
4115 {
4116 gchar **split_path = g_strsplit(path, "`", 6);
4117 dt_action_t *ac = dt_action_locate(&module->so->actions, split_path, FALSE);
4118 g_strfreev(split_path);
4119
4120 if(ac)
4121 {
4122 ac->type = DT_ACTION_TYPE_CLOSURE;
4123
4124 // to support multi-instance, save in and own by per instance widget list
4125 dt_action_target_t *referral = g_malloc0(sizeof(dt_action_target_t));
4126 referral->action = ac;
4127 referral->target = closure;
4128 g_closure_ref(closure);
4129 g_closure_sink(closure);
4130 module->widget_list = g_slist_prepend(module->widget_list, referral);
4131 }
4132 }
4133
dt_accel_connect_lib(dt_lib_module_t * module,const gchar * path,GClosure * closure)4134 void dt_accel_connect_lib(dt_lib_module_t *module, const gchar *path, GClosure *closure)
4135 {
4136 dt_accel_connect_shortcut(&module->actions, path, closure);
4137 }
4138
dt_accel_connect_lua(const gchar * path,GClosure * closure)4139 void dt_accel_connect_lua(const gchar *path, GClosure *closure)
4140 {
4141 dt_accel_connect_shortcut(&darktable.control->actions_lua, path, closure);
4142 }
4143
dt_accel_connect_button_iop(dt_iop_module_t * module,const gchar * path,GtkWidget * button)4144 void dt_accel_connect_button_iop(dt_iop_module_t *module, const gchar *path, GtkWidget *button)
4145 {
4146 dt_action_define_iop(module, NULL, path, button, &dt_action_def_button);
4147 }
4148
dt_accel_connect_button_lib(dt_lib_module_t * module,const gchar * path,GtkWidget * button)4149 void dt_accel_connect_button_lib(dt_lib_module_t *module, const gchar *path, GtkWidget *button)
4150 {
4151 dt_action_define(DT_ACTION(module), NULL, path, button, &dt_action_def_button);
4152 }
4153
dt_accel_connect_button_lib_as_global(dt_lib_module_t * module,const gchar * path,GtkWidget * button)4154 void dt_accel_connect_button_lib_as_global(dt_lib_module_t *module, const gchar *path, GtkWidget *button)
4155 {
4156 dt_action_define(&darktable.control->actions_global, NULL, path, button, &dt_action_def_button);
4157 }
4158
dt_action_widget_toast(dt_action_t * action,GtkWidget * widget,const gchar * text)4159 void dt_action_widget_toast(dt_action_t *action, GtkWidget *widget, const gchar *text)
4160 {
4161 if(!darktable.gui->reset)
4162 {
4163 if(!action)
4164 action = g_hash_table_lookup(darktable.control->widgets, widget);
4165 if(action)
4166 {
4167 gchar *instance_name = "";
4168 gchar *label = NULL;
4169
4170 if(action->type == DT_ACTION_TYPE_IOP_INSTANCE)
4171 {
4172 dt_iop_module_t *module = (dt_iop_module_t *)action;
4173
4174 action = DT_ACTION(module->so);
4175 instance_name = module->multi_name;
4176
4177 for(GSList *w = module->widget_list; w; w = w->next)
4178 {
4179 dt_action_target_t *referral = w->data;
4180 if(referral->target == widget)
4181 {
4182 if(referral->action->owner == &darktable.control->actions_blend)
4183 {
4184 _action_distinct_label(&label, referral->action, NULL);
4185 }
4186 else
4187 action = referral->action;
4188 break;
4189 }
4190 }
4191 }
4192
4193 _action_distinct_label(&label, action, instance_name);
4194 dt_toast_log("%s : %s", label, text);
4195 g_free(label);
4196 }
4197 else
4198 dt_toast_log("%s", text);
4199 }
4200 }
4201
dt_accel_get_slider_scale_multiplier()4202 float dt_accel_get_slider_scale_multiplier()
4203 {
4204 const int slider_precision = dt_conf_get_int("accel/slider_precision");
4205
4206 if(slider_precision == DT_IOP_PRECISION_COARSE)
4207 {
4208 return dt_conf_get_float("darkroom/ui/scale_rough_step_multiplier");
4209 }
4210 else if(slider_precision == DT_IOP_PRECISION_FINE)
4211 {
4212 return dt_conf_get_float("darkroom/ui/scale_precise_step_multiplier");
4213 }
4214
4215 return dt_conf_get_float("darkroom/ui/scale_step_multiplier");
4216 }
4217
dt_accel_connect_instance_iop(dt_iop_module_t * module)4218 void dt_accel_connect_instance_iop(dt_iop_module_t *module)
4219 {
4220 for(GSList *w = module->widget_list; w; w = w->next)
4221 {
4222 dt_action_target_t *referral = w->data;
4223 referral->action->target = referral->target;
4224 }
4225 }
4226
_destroy_referral(gpointer data)4227 static void _destroy_referral(gpointer data)
4228 {
4229 dt_action_target_t *referral = data;
4230 if(referral->action && referral->action->type == DT_ACTION_TYPE_CLOSURE)
4231 {
4232 if(referral->action->target == referral->target)
4233 referral->action->target = NULL;
4234 g_closure_unref(referral->target);
4235 }
4236
4237 g_free(referral);
4238 }
4239
dt_action_cleanup_instance_iop(dt_iop_module_t * module)4240 void dt_action_cleanup_instance_iop(dt_iop_module_t *module)
4241 {
4242 g_slist_free_full(module->widget_list, _destroy_referral);
4243 }
4244
dt_accel_rename_global(const gchar * path,const gchar * new_name)4245 void dt_accel_rename_global(const gchar *path, const gchar *new_name)
4246 {
4247 gchar **split_path = g_strsplit(path, "/", 6);
4248 dt_action_t *p = dt_action_locate(&darktable.control->actions_global, split_path, FALSE);
4249 g_strfreev(split_path);
4250
4251 if(p) dt_action_rename(p, new_name);
4252 }
4253
dt_accel_rename_lua(const gchar * path,const gchar * new_name)4254 void dt_accel_rename_lua(const gchar *path, const gchar *new_name)
4255 {
4256 gchar **split_path = g_strsplit(path, "/", 6);
4257 dt_action_t *p = dt_action_locate(&darktable.control->actions_lua, split_path, FALSE);
4258 g_strfreev(split_path);
4259
4260 if(p) dt_action_rename(p, new_name);
4261 }
4262
4263 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
4264 // vim: shiftwidth=2 expandtab tabstop=2 cindent
4265 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
4266