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 "common/collection.h"
20 #include "common/darktable.h"
21 #include "common/l10n.h"
22 #include "control/conf.h"
23 #include "control/control.h"
24 #include "dtgtk/button.h"
25 #include "dtgtk/culling.h"
26 #include "dtgtk/thumbtable.h"
27 #include "dtgtk/togglebutton.h"
28 #include "gui/accelerators.h"
29 #include "gui/preferences.h"
30 #include "libs/lib.h"
31 #include "libs/lib_api.h"
32 #ifdef GDK_WINDOWING_QUARTZ
33 #include "osx/osx.h"
34 #endif
35 
36 DT_MODULE(1)
37 
38 typedef struct dt_lib_tool_preferences_t
39 {
40   GtkWidget *preferences_button, *grouping_button, *overlays_button, *help_button, *keymap_button;
41   GtkWidget *over_popup, *thumbnails_box, *culling_box;
42   GtkWidget *over_label, *over_r0, *over_r1, *over_r2, *over_r3, *over_r4, *over_r5, *over_r6, *over_timeout,
43       *over_tt;
44   GtkWidget *over_culling_label, *over_culling_r0, *over_culling_r3, *over_culling_r4, *over_culling_r6,
45       *over_culling_timeout, *over_culling_tt;
46   gboolean disable_over_events;
47 } dt_lib_tool_preferences_t;
48 
49 /* callback for grouping button */
50 static void _lib_filter_grouping_button_clicked(GtkWidget *widget, gpointer user_data);
51 /* callback for preference button */
52 static void _lib_preferences_button_clicked(GtkWidget *widget, gpointer user_data);
53 /* callback for help button */
54 static void _lib_help_button_clicked(GtkWidget *widget, gpointer user_data);
55 /* callbacks for key mapping button */
56 static void _lib_keymap_button_clicked(GtkWidget *widget, gpointer user_data);
57 static gboolean _lib_keymap_button_press_release(GtkWidget *button, GdkEventButton *event, gpointer user_data);
58 
name(dt_lib_module_t * self)59 const char *name(dt_lib_module_t *self)
60 {
61   return _("preferences");
62 }
63 
views(dt_lib_module_t * self)64 const char **views(dt_lib_module_t *self)
65 {
66   static const char *v[] = {"*", NULL};
67   return v;
68 }
69 
container(dt_lib_module_t * self)70 uint32_t container(dt_lib_module_t *self)
71 {
72   return DT_UI_CONTAINER_PANEL_CENTER_TOP_RIGHT;
73 }
74 
expandable(dt_lib_module_t * self)75 int expandable(dt_lib_module_t *self)
76 {
77   return 0;
78 }
79 
position()80 int position()
81 {
82   return 1001;
83 }
84 
_overlays_accels_callback(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)85 static void _overlays_accels_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
86                                       GdkModifierType modifier, gpointer data)
87 {
88   dt_thumbnail_overlay_t over = (dt_thumbnail_overlay_t)GPOINTER_TO_INT(data);
89   dt_thumbtable_set_overlays_mode(dt_ui_thumbtable(darktable.gui->ui), over);
90 }
91 
_overlays_toggle_button(GtkWidget * w,gpointer user_data)92 static void _overlays_toggle_button(GtkWidget *w, gpointer user_data)
93 {
94   dt_lib_module_t *self = (dt_lib_module_t *)user_data;
95   dt_lib_tool_preferences_t *d = (dt_lib_tool_preferences_t *)self->data;
96 
97   if(d->disable_over_events) return;
98 
99   dt_thumbnail_overlay_t over = DT_THUMBNAIL_OVERLAYS_HOVER_NORMAL;
100   if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->over_r0)))
101     over = DT_THUMBNAIL_OVERLAYS_NONE;
102   else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->over_r2)))
103     over = DT_THUMBNAIL_OVERLAYS_HOVER_EXTENDED;
104   else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->over_r3)))
105     over = DT_THUMBNAIL_OVERLAYS_ALWAYS_NORMAL;
106   else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->over_r4)))
107     over = DT_THUMBNAIL_OVERLAYS_ALWAYS_EXTENDED;
108   else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->over_r5)))
109     over = DT_THUMBNAIL_OVERLAYS_MIXED;
110   else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->over_r6)))
111     over = DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK;
112 
113   dt_ui_thumbtable(darktable.gui->ui)->show_tooltips = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->over_tt));
114   dt_thumbtable_set_overlays_mode(dt_ui_thumbtable(darktable.gui->ui), over);
115 
116   gtk_widget_set_sensitive(d->over_timeout, (over == DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK));
117 
118   // we don't hide the popup in case of block overlay, as the user may want to tweak the duration
119   if(over != DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK) gtk_widget_hide(d->over_popup);
120 
121 #ifdef USE_LUA
122   gboolean show = (over == DT_THUMBNAIL_OVERLAYS_ALWAYS_NORMAL || over == DT_THUMBNAIL_OVERLAYS_ALWAYS_EXTENDED);
123   dt_lua_async_call_alien(dt_lua_event_trigger_wrapper, 0, NULL, NULL, LUA_ASYNC_TYPENAME, "const char*",
124                           "global_toolbox-overlay_toggle", LUA_ASYNC_TYPENAME, "bool", show, LUA_ASYNC_DONE);
125 #endif // USE_LUA
126 }
127 
_overlays_toggle_culling_button(GtkWidget * w,gpointer user_data)128 static void _overlays_toggle_culling_button(GtkWidget *w, gpointer user_data)
129 {
130   dt_lib_module_t *self = (dt_lib_module_t *)user_data;
131   dt_lib_tool_preferences_t *d = (dt_lib_tool_preferences_t *)self->data;
132 
133   if(d->disable_over_events) return;
134 
135   dt_thumbnail_overlay_t over = DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK;
136   if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->over_culling_r0)))
137     over = DT_THUMBNAIL_OVERLAYS_NONE;
138   else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->over_culling_r3)))
139     over = DT_THUMBNAIL_OVERLAYS_ALWAYS_NORMAL;
140   else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->over_culling_r4)))
141     over = DT_THUMBNAIL_OVERLAYS_ALWAYS_EXTENDED;
142 
143   dt_culling_mode_t cmode = DT_CULLING_MODE_CULLING;
144   if(dt_view_lighttable_preview_state(darktable.view_manager)) cmode = DT_CULLING_MODE_PREVIEW;
145   gchar *txt = g_strdup_printf("plugins/lighttable/overlays/culling/%d", cmode);
146   dt_conf_set_int(txt, over);
147   g_free(txt);
148   txt = g_strdup_printf("plugins/lighttable/tooltips/culling/%d", cmode);
149   dt_conf_set_bool(txt, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->over_culling_tt)));
150   g_free(txt);
151   dt_view_lighttable_culling_preview_reload_overlays(darktable.view_manager);
152 
153   gtk_widget_set_sensitive(d->over_culling_timeout, (over == DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK));
154 
155   // we don't hide the popup in case of block overlay, as the user may want to tweak the duration
156   if(over != DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK) gtk_widget_hide(d->over_popup);
157 
158 #ifdef USE_LUA
159   gboolean show = (over == DT_THUMBNAIL_OVERLAYS_ALWAYS_NORMAL || over == DT_THUMBNAIL_OVERLAYS_ALWAYS_EXTENDED);
160   dt_lua_async_call_alien(dt_lua_event_trigger_wrapper, 0, NULL, NULL, LUA_ASYNC_TYPENAME, "const char*",
161                           "global_toolbox-overlay_toggle", LUA_ASYNC_TYPENAME, "bool", show, LUA_ASYNC_DONE);
162 #endif // USE_LUA
163 }
164 
_overlays_timeout_changed(GtkWidget * w,gpointer user_data)165 static void _overlays_timeout_changed(GtkWidget *w, gpointer user_data)
166 {
167   dt_lib_module_t *self = (dt_lib_module_t *)user_data;
168   dt_lib_tool_preferences_t *d = (dt_lib_tool_preferences_t *)self->data;
169 
170   const int val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(w));
171 
172   if(w == d->over_timeout)
173   {
174     dt_thumbtable_set_overlays_block_timeout(dt_ui_thumbtable(darktable.gui->ui), val);
175   }
176   else if(w == d->over_culling_timeout)
177   {
178     dt_culling_mode_t cmode = DT_CULLING_MODE_CULLING;
179     if(dt_view_lighttable_preview_state(darktable.view_manager)) cmode = DT_CULLING_MODE_PREVIEW;
180     gchar *txt = g_strdup_printf("plugins/lighttable/overlays/culling_block_timeout/%d", cmode);
181     dt_conf_set_int(txt, val);
182     g_free(txt);
183 
184     dt_view_lighttable_culling_preview_reload_overlays(darktable.view_manager);
185   }
186 }
187 
_overlays_show_popup(GtkWidget * button,dt_lib_module_t * self)188 static void _overlays_show_popup(GtkWidget *button, dt_lib_module_t *self)
189 {
190   dt_lib_tool_preferences_t *d = (dt_lib_tool_preferences_t *)self->data;
191 
192   d->disable_over_events = TRUE;
193 
194   gboolean show = FALSE;
195 
196   // thumbnails part
197   const dt_view_t *cv = dt_view_manager_get_current_view(darktable.view_manager);
198   gboolean thumbs_state;
199   if(g_strcmp0(cv->module_name, "slideshow") == 0)
200   {
201     thumbs_state = FALSE;
202   }
203   else if(g_strcmp0(cv->module_name, "lighttable") == 0)
204   {
205     if(dt_view_lighttable_preview_state(darktable.view_manager)
206        || dt_view_lighttable_get_layout(darktable.view_manager) == DT_LIGHTTABLE_LAYOUT_CULLING)
207     {
208       thumbs_state = dt_ui_panel_visible(darktable.gui->ui, DT_UI_PANEL_BOTTOM);
209     }
210     else
211     {
212       thumbs_state = TRUE;
213     }
214   }
215   else
216   {
217     thumbs_state = dt_ui_panel_visible(darktable.gui->ui, DT_UI_PANEL_BOTTOM);
218   }
219 
220 
221   if(thumbs_state)
222   {
223     // we write the label with the size category
224     gchar *txt = g_strdup_printf("%s %d (%d %s)", _("thumbnails overlays for size"),
225                                  dt_ui_thumbtable(darktable.gui->ui)->prefs_size,
226                                  dt_ui_thumbtable(darktable.gui->ui)->thumb_size, _("px"));
227     gtk_label_set_text(GTK_LABEL(d->over_label), txt);
228     g_free(txt);
229 
230     // we get and set the current value
231     dt_thumbnail_overlay_t mode = dt_ui_thumbtable(darktable.gui->ui)->overlays;
232 
233     gtk_spin_button_set_value(GTK_SPIN_BUTTON(d->over_timeout),
234                               dt_ui_thumbtable(darktable.gui->ui)->overlays_block_timeout);
235     gtk_widget_set_sensitive(d->over_timeout, FALSE);
236 
237     if(mode == DT_THUMBNAIL_OVERLAYS_NONE)
238       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_r0), TRUE);
239     else if(mode == DT_THUMBNAIL_OVERLAYS_HOVER_EXTENDED)
240       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_r2), TRUE);
241     else if(mode == DT_THUMBNAIL_OVERLAYS_ALWAYS_NORMAL)
242       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_r3), TRUE);
243     else if(mode == DT_THUMBNAIL_OVERLAYS_ALWAYS_EXTENDED)
244       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_r4), TRUE);
245     else if(mode == DT_THUMBNAIL_OVERLAYS_MIXED)
246       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_r5), TRUE);
247     else if(mode == DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK)
248     {
249       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_r6), TRUE);
250       gtk_widget_set_sensitive(d->over_timeout, TRUE);
251     }
252     else
253       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_r1), TRUE);
254 
255     if(mode == DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK)
256     {
257       gtk_widget_set_tooltip_text(d->over_timeout,
258                                   _("duration before the block overlay is hidden after each mouse movement on the "
259                                     "image\nset -1 to never hide the overlay"));
260     }
261     else
262     {
263       gtk_widget_set_tooltip_text(d->over_timeout, _("timeout only available for block overlay"));
264     }
265 
266     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_tt), dt_ui_thumbtable(darktable.gui->ui)->show_tooltips);
267 
268     gtk_widget_show_all(d->thumbnails_box);
269     show = TRUE;
270   }
271   else
272   {
273     gtk_widget_hide(d->thumbnails_box);
274   }
275 
276   // and we do the same for culling/preview if needed
277   if(g_strcmp0(cv->module_name, "lighttable") == 0
278      && (dt_view_lighttable_preview_state(darktable.view_manager)
279          || dt_view_lighttable_get_layout(darktable.view_manager) == DT_LIGHTTABLE_LAYOUT_CULLING))
280   {
281     dt_culling_mode_t cmode = DT_CULLING_MODE_CULLING;
282     if(dt_view_lighttable_preview_state(darktable.view_manager)) cmode = DT_CULLING_MODE_PREVIEW;
283 
284     // we write the label text
285     if(cmode == DT_CULLING_MODE_CULLING)
286       gtk_label_set_text(GTK_LABEL(d->over_culling_label), _("culling overlays"));
287     else
288       gtk_label_set_text(GTK_LABEL(d->over_culling_label), _("preview overlays"));
289 
290     // we get and set the current value
291     gchar *otxt = g_strdup_printf("plugins/lighttable/overlays/culling/%d", cmode);
292     dt_thumbnail_overlay_t mode = dt_conf_get_int(otxt);
293     g_free(otxt);
294 
295     otxt = g_strdup_printf("plugins/lighttable/overlays/culling_block_timeout/%d", cmode);
296     int timeout = 2;
297     if(!dt_conf_key_exists(otxt))
298       timeout = dt_conf_get_int("plugins/lighttable/overlay_timeout");
299     else
300       timeout = dt_conf_get_int(otxt);
301     g_free(otxt);
302 
303     gtk_spin_button_set_value(GTK_SPIN_BUTTON(d->over_culling_timeout), timeout);
304     gtk_widget_set_sensitive(d->over_culling_timeout, FALSE);
305 
306     if(mode == DT_THUMBNAIL_OVERLAYS_NONE)
307       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_culling_r0), TRUE);
308     else if(mode == DT_THUMBNAIL_OVERLAYS_ALWAYS_NORMAL)
309       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_culling_r3), TRUE);
310     else if(mode == DT_THUMBNAIL_OVERLAYS_ALWAYS_EXTENDED)
311       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_culling_r4), TRUE);
312     else
313     {
314       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_culling_r6), TRUE);
315       gtk_widget_set_sensitive(d->over_culling_timeout, TRUE);
316     }
317 
318     if(mode == DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK)
319     {
320       gtk_widget_set_tooltip_text(d->over_culling_timeout,
321                                   _("duration before the block overlay is hidden after each mouse movement on the "
322                                     "image\nset -1 to never hide the overlay"));
323     }
324     else
325     {
326       gtk_widget_set_tooltip_text(d->over_culling_timeout, _("timeout only available for block overlay"));
327     }
328 
329     otxt = g_strdup_printf("plugins/lighttable/tooltips/culling/%d", cmode);
330     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_culling_tt), dt_conf_get_bool(otxt));
331     g_free(otxt);
332 
333     gtk_widget_show_all(d->culling_box);
334     show = TRUE;
335   }
336   else
337   {
338     gtk_widget_hide(d->culling_box);
339   }
340 
341   if(show)
342   {
343     GdkDevice *pointer = gdk_seat_get_pointer(gdk_display_get_default_seat(gdk_display_get_default()));
344 
345     int x, y;
346     GdkWindow *pointer_window = gdk_device_get_window_at_position(pointer, &x, &y);
347     gpointer   pointer_widget = NULL;
348     if(pointer_window)
349       gdk_window_get_user_data(pointer_window, &pointer_widget);
350 
351     GdkRectangle rect = { gtk_widget_get_allocated_width(button) / 2,
352                           gtk_widget_get_allocated_height(button), 1, 1 };
353 
354     if(pointer_widget && button != pointer_widget)
355       gtk_widget_translate_coordinates(pointer_widget, button, x, y, &rect.x, &rect.y);
356 
357     gtk_popover_set_pointing_to(GTK_POPOVER(d->over_popup), &rect);
358 
359     gtk_widget_show(d->over_popup);
360   }
361   else
362     dt_control_log(_("overlays not available here..."));
363 
364   d->disable_over_events = FALSE;
365 }
366 
_main_icons_register_size(GtkWidget * widget,GdkRectangle * allocation,gpointer user_data)367 static void _main_icons_register_size(GtkWidget *widget, GdkRectangle *allocation, gpointer user_data)
368 {
369 
370   GtkStateFlags state = gtk_widget_get_state_flags(widget);
371   GtkStyleContext *context = gtk_widget_get_style_context(widget);
372 
373   /* get the css geometry properties */
374   GtkBorder margin, border, padding;
375   gtk_style_context_get_margin(context, state, &margin);
376   gtk_style_context_get_border(context, state, &border);
377   gtk_style_context_get_padding(context, state, &padding);
378 
379   /* we first remove css margin border and padding from allocation */
380   int width = allocation->width - margin.left - margin.right - border.left - border.right - padding.left - padding.right;
381 
382   GtkStyleContext *ccontext = gtk_widget_get_style_context(DTGTK_BUTTON(widget)->canvas);
383   GtkBorder cmargin;
384   gtk_style_context_get_margin(ccontext, state, &cmargin);
385 
386   /* we remove the extra room for optical alignment */
387   width = round((float)width * (1.0 - (cmargin.left + cmargin.right) / 100.0f));
388 
389   // we store the icon size in order to keep in sync thumbtable overlays
390   darktable.gui->icon_size = width;
391 }
392 
gui_init(dt_lib_module_t * self)393 void gui_init(dt_lib_module_t *self)
394 {
395   /* initialize ui widgets */
396   dt_lib_tool_preferences_t *d = (dt_lib_tool_preferences_t *)g_malloc0(sizeof(dt_lib_tool_preferences_t));
397   self->data = (void *)d;
398 
399   self->widget = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
400 
401   /* create the grouping button */
402   d->grouping_button = dtgtk_togglebutton_new(dtgtk_cairo_paint_grouping, CPF_STYLE_FLAT, NULL);
403   dt_action_define(&darktable.control->actions_global, NULL, "grouping", d->grouping_button, &dt_action_def_toggle);
404   gtk_box_pack_start(GTK_BOX(self->widget), d->grouping_button, FALSE, FALSE, 0);
405   if(darktable.gui->grouping)
406     gtk_widget_set_tooltip_text(d->grouping_button, _("expand grouped images"));
407   else
408     gtk_widget_set_tooltip_text(d->grouping_button, _("collapse grouped images"));
409   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->grouping_button), darktable.gui->grouping);
410   g_signal_connect(G_OBJECT(d->grouping_button), "clicked", G_CALLBACK(_lib_filter_grouping_button_clicked),
411                    NULL);
412 
413   /* create the "show/hide overlays" button */
414   d->overlays_button = dtgtk_button_new(dtgtk_cairo_paint_overlays, CPF_STYLE_FLAT, NULL);
415   gtk_widget_set_tooltip_text(d->overlays_button, _("click to change the type of overlays shown on thumbnails"));
416   gtk_box_pack_start(GTK_BOX(self->widget), d->overlays_button, FALSE, FALSE, 0);
417   d->over_popup = gtk_popover_new(d->overlays_button);
418   gtk_widget_set_size_request(d->over_popup, 350, -1);
419   g_object_set(G_OBJECT(d->over_popup), "transitions-enabled", FALSE, NULL);
420   g_signal_connect(G_OBJECT(d->overlays_button), "clicked", G_CALLBACK(_overlays_show_popup), self);
421   // we register size of overlay icon to keep in sync thumbtable overlays
422   g_signal_connect(G_OBJECT(d->overlays_button), "size-allocate", G_CALLBACK(_main_icons_register_size), NULL);
423 
424   GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
425 
426   gtk_container_add(GTK_CONTAINER(d->over_popup), vbox);
427 
428   // thumbnails overlays
429   d->thumbnails_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
430 
431   d->over_label = gtk_label_new(_("overlay mode for size"));
432   gtk_widget_set_name(d->over_label, "overlays_label");
433   gtk_box_pack_start(GTK_BOX(d->thumbnails_box), d->over_label, TRUE, TRUE, 0);
434   d->over_r0 = gtk_radio_button_new_with_label(NULL, _("no overlays"));
435   g_signal_connect(G_OBJECT(d->over_r0), "toggled", G_CALLBACK(_overlays_toggle_button), self);
436   gtk_box_pack_start(GTK_BOX(d->thumbnails_box), d->over_r0, TRUE, TRUE, 0);
437   d->over_r1
438       = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(d->over_r0), _("overlays on mouse hover"));
439   g_signal_connect(G_OBJECT(d->over_r1), "toggled", G_CALLBACK(_overlays_toggle_button), self);
440   gtk_box_pack_start(GTK_BOX(d->thumbnails_box), d->over_r1, TRUE, TRUE, 0);
441   d->over_r2 = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(d->over_r0),
442                                                            _("extended overlays on mouse hover"));
443   g_signal_connect(G_OBJECT(d->over_r2), "toggled", G_CALLBACK(_overlays_toggle_button), self);
444   gtk_box_pack_start(GTK_BOX(d->thumbnails_box), d->over_r2, TRUE, TRUE, 0);
445   d->over_r3 = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(d->over_r0), _("permanent overlays"));
446   g_signal_connect(G_OBJECT(d->over_r3), "toggled", G_CALLBACK(_overlays_toggle_button), self);
447   gtk_box_pack_start(GTK_BOX(d->thumbnails_box), d->over_r3, TRUE, TRUE, 0);
448   d->over_r4 = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(d->over_r0),
449                                                            _("permanent extended overlays"));
450   g_signal_connect(G_OBJECT(d->over_r4), "toggled", G_CALLBACK(_overlays_toggle_button), self);
451   gtk_box_pack_start(GTK_BOX(d->thumbnails_box), d->over_r4, TRUE, TRUE, 0);
452   d->over_r5 = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(d->over_r0),
453                                                            _("permanent overlays extended on mouse hover"));
454   g_signal_connect(G_OBJECT(d->over_r5), "toggled", G_CALLBACK(_overlays_toggle_button), self);
455   gtk_box_pack_start(GTK_BOX(d->thumbnails_box), d->over_r5, TRUE, TRUE, 0);
456   GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
457   d->over_r6 = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(d->over_r0),
458                                                            _("overlays block on mouse hover during (s)"));
459   g_signal_connect(G_OBJECT(d->over_r6), "toggled", G_CALLBACK(_overlays_toggle_button), self);
460   gtk_box_pack_start(GTK_BOX(hbox), d->over_r6, TRUE, TRUE, 0);
461   d->over_timeout = gtk_spin_button_new_with_range(-1, 99, 1);
462   g_signal_connect(G_OBJECT(d->over_timeout), "value-changed", G_CALLBACK(_overlays_timeout_changed), self);
463   gtk_box_pack_start(GTK_BOX(hbox), d->over_timeout, TRUE, TRUE, 0);
464   gtk_box_pack_start(GTK_BOX(d->thumbnails_box), hbox, TRUE, TRUE, 0);
465   d->over_tt = gtk_check_button_new_with_label(_("show tooltip"));
466   g_signal_connect(G_OBJECT(d->over_tt), "toggled", G_CALLBACK(_overlays_toggle_button), self);
467   gtk_widget_set_name(d->over_tt, "show-tooltip");
468   gtk_box_pack_start(GTK_BOX(d->thumbnails_box), d->over_tt, TRUE, TRUE, 0);
469 
470   gtk_box_pack_start(GTK_BOX(vbox), d->thumbnails_box, TRUE, TRUE, 0);
471 
472   // culling/preview overlays
473   d->culling_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
474 
475   d->over_culling_label = gtk_label_new(_("overlay mode for size"));
476   gtk_widget_set_name(d->over_culling_label, "overlays_label");
477   gtk_box_pack_start(GTK_BOX(d->culling_box), d->over_culling_label, TRUE, TRUE, 0);
478   d->over_culling_r0 = gtk_radio_button_new_with_label(NULL, _("no overlays"));
479   g_signal_connect(G_OBJECT(d->over_culling_r0), "toggled", G_CALLBACK(_overlays_toggle_culling_button), self);
480   gtk_box_pack_start(GTK_BOX(d->culling_box), d->over_culling_r0, TRUE, TRUE, 0);
481   d->over_culling_r3
482       = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(d->over_culling_r0), _("permanent overlays"));
483   g_signal_connect(G_OBJECT(d->over_culling_r3), "toggled", G_CALLBACK(_overlays_toggle_culling_button), self);
484   gtk_box_pack_start(GTK_BOX(d->culling_box), d->over_culling_r3, TRUE, TRUE, 0);
485   d->over_culling_r4 = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(d->over_culling_r0),
486                                                                    _("permanent extended overlays"));
487   g_signal_connect(G_OBJECT(d->over_culling_r4), "toggled", G_CALLBACK(_overlays_toggle_culling_button), self);
488   gtk_box_pack_start(GTK_BOX(d->culling_box), d->over_culling_r4, TRUE, TRUE, 0);
489   hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
490   d->over_culling_r6 = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(d->over_culling_r0),
491                                                                    _("overlays block on mouse hover during (s)"));
492   g_signal_connect(G_OBJECT(d->over_culling_r6), "toggled", G_CALLBACK(_overlays_toggle_culling_button), self);
493   gtk_box_pack_start(GTK_BOX(hbox), d->over_culling_r6, TRUE, TRUE, 0);
494   d->over_culling_timeout = gtk_spin_button_new_with_range(-1, 99, 1);
495   g_signal_connect(G_OBJECT(d->over_culling_timeout), "value-changed", G_CALLBACK(_overlays_timeout_changed), self);
496   gtk_box_pack_start(GTK_BOX(hbox), d->over_culling_timeout, TRUE, TRUE, 0);
497   gtk_box_pack_start(GTK_BOX(d->culling_box), hbox, TRUE, TRUE, 0);
498   d->over_culling_tt = gtk_check_button_new_with_label(_("show tooltip"));
499   g_signal_connect(G_OBJECT(d->over_culling_tt), "toggled", G_CALLBACK(_overlays_toggle_culling_button), self);
500   gtk_widget_set_name(d->over_culling_tt, "show-tooltip");
501   gtk_box_pack_start(GTK_BOX(d->culling_box), d->over_culling_tt, TRUE, TRUE, 0);
502 
503   gtk_box_pack_start(GTK_BOX(vbox), d->culling_box, TRUE, TRUE, 0);
504   gtk_widget_show(vbox);
505 
506   /* create the widget help button */
507   d->help_button = dtgtk_togglebutton_new(dtgtk_cairo_paint_help, CPF_STYLE_FLAT, NULL);
508   gtk_box_pack_start(GTK_BOX(self->widget), d->help_button, FALSE, FALSE, 0);
509   gtk_widget_set_tooltip_text(d->help_button, _("enable this, then click on a control element to see its online help"));
510   g_signal_connect(G_OBJECT(d->help_button), "clicked", G_CALLBACK(_lib_help_button_clicked), d);
511   dt_gui_add_help_link(d->help_button, dt_get_help_url("global_toolbox_help"));
512 
513   /* create the shortcuts button */
514   d->keymap_button = dtgtk_togglebutton_new(dtgtk_cairo_paint_shortcut, CPF_STYLE_FLAT, NULL);
515   dt_action_define(&darktable.control->actions_global, NULL, "shortcuts", d->keymap_button, &dt_action_def_toggle);
516   gtk_box_pack_start(GTK_BOX(self->widget), d->keymap_button, FALSE, FALSE, 0);
517   gtk_widget_set_tooltip_text(d->keymap_button, _("define shortcuts\n"
518                                                   "hover over a widget and press keys with mouse click and scroll or move combinations\n"
519                                                   "repeat same combination again to delete mapping\n"
520                                                   "click on a widget, module or screen area to open the dialog for further configuration"));
521   g_signal_connect(G_OBJECT(d->keymap_button), "clicked", G_CALLBACK(_lib_keymap_button_clicked), d);
522   g_signal_connect(G_OBJECT(d->keymap_button), "button-press-event", G_CALLBACK(_lib_keymap_button_press_release), d);
523   g_signal_connect(G_OBJECT(d->keymap_button), "button-release-event", G_CALLBACK(_lib_keymap_button_press_release), d);
524   dt_gui_add_help_link(d->keymap_button, dt_get_help_url("global_toolbox_keymap"));
525 
526   // the rest of these is added in reverse order as they are always put at the end of the container.
527   // that's done so that buttons added via Lua will come first.
528 
529   /* create the preference button */
530   d->preferences_button = dtgtk_button_new(dtgtk_cairo_paint_preferences, CPF_STYLE_FLAT, NULL);
531   gtk_box_pack_end(GTK_BOX(self->widget), d->preferences_button, FALSE, FALSE, 0);
532   gtk_widget_set_tooltip_text(d->preferences_button, _("show global preferences"));
533   g_signal_connect(G_OBJECT(d->preferences_button), "clicked", G_CALLBACK(_lib_preferences_button_clicked),
534                    NULL);
535   dt_gui_add_help_link(d->preferences_button, dt_get_help_url("global_toolbox_preferences"));
536 
537 }
538 
gui_cleanup(dt_lib_module_t * self)539 void gui_cleanup(dt_lib_module_t *self)
540 {
541   g_free(self->data);
542   self->data = NULL;
543 }
544 
_lib_preferences_button_clicked(GtkWidget * widget,gpointer user_data)545 void _lib_preferences_button_clicked(GtkWidget *widget, gpointer user_data)
546 {
547   dt_gui_preferences_show();
548 }
549 
_lib_filter_grouping_button_clicked(GtkWidget * widget,gpointer user_data)550 static void _lib_filter_grouping_button_clicked(GtkWidget *widget, gpointer user_data)
551 {
552 
553   darktable.gui->grouping = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
554   if(darktable.gui->grouping)
555     gtk_widget_set_tooltip_text(widget, _("expand grouped images"));
556   else
557     gtk_widget_set_tooltip_text(widget, _("collapse grouped images"));
558   dt_conf_set_bool("ui_last/grouping", darktable.gui->grouping);
559   darktable.gui->expanded_group_id = -1;
560   dt_collection_update_query(darktable.collection, DT_COLLECTION_CHANGE_RELOAD, DT_COLLECTION_PROP_GROUPING, NULL);
561 
562 #ifdef USE_LUA
563   dt_lua_async_call_alien(dt_lua_event_trigger_wrapper,
564       0,NULL,NULL,
565       LUA_ASYNC_TYPENAME,"const char*","global_toolbox-grouping_toggle",
566       LUA_ASYNC_TYPENAME,"bool",darktable.gui->grouping,
567       LUA_ASYNC_DONE);
568 #endif // USE_LUA
569 }
570 
571 // TODO: this doesn't work for all widgets. the reason being that the GtkEventBox we put libs/iops into catches events.
get_help_url(GtkWidget * widget)572 static char *get_help_url(GtkWidget *widget)
573 {
574   while(widget)
575   {
576     // if the widget doesn't have a help url set go up the widget hierarchy to find a parent that has an url
577     gchar *help_url = g_object_get_data(G_OBJECT(widget), "dt-help-url");
578 
579     if(help_url)
580       return help_url;
581 
582     // TODO: shall we cross from libs/iops to the core gui? if not, here is the place to break out of the loop
583 
584     widget = gtk_widget_get_parent(widget);
585   }
586 
587   return NULL;
588 }
589 
_get_base_url()590 static char *_get_base_url()
591 {
592   const gboolean use_default_url =
593     dt_conf_get_bool("context_help/use_default_url");
594   const char *c_base_url = dt_confgen_get("context_help/url", DT_DEFAULT);
595   char *base_url = dt_conf_get_string("context_help/url");
596 
597   if(use_default_url)
598   {
599     // want to use default URL, reset darktablerc
600     dt_conf_set_string("context_help/url", c_base_url);
601     return g_strdup(c_base_url);
602   }
603   else
604     return base_url;
605 }
606 
_main_do_event_help(GdkEvent * event,gpointer data)607 static void _main_do_event_help(GdkEvent *event, gpointer data)
608 {
609   dt_lib_tool_preferences_t *d = (dt_lib_tool_preferences_t *)data;
610 
611   gboolean handled = FALSE;
612 
613   switch(event->type)
614   {
615     case GDK_BUTTON_PRESS:
616     {
617       GtkWidget *event_widget = gtk_get_event_widget(event);
618       if(event_widget)
619       {
620         // TODO: When the widget doesn't have a help url set we should probably look at the parent(s)
621         gchar *help_url = get_help_url(event_widget);
622         if(help_url && *help_url)
623         {
624           GtkWidget *win = dt_ui_main_window(darktable.gui->ui);
625           dt_print(DT_DEBUG_CONTROL, "[context help] opening `%s'\n", help_url);
626           char *base_url = _get_base_url();
627 
628           // The base_url is: docs.darktable.org/usermanual
629           // The full format for the documentation pages is:
630           //    <base-url>/<ver>/<lang>[/path/to/page]
631           // Where:
632           //   <ver>  = development | 3.6 | 3.8 ...
633           //   <lang> = en / fr ...              (default = en)
634 
635           // in case of a standard release, append the dt version to the url
636           if(dt_is_dev_version())
637           {
638             base_url = dt_util_dstrcat(base_url, "development/");
639           }
640           else
641           {
642             char *ver = dt_version_major_minor();
643             base_url = dt_util_dstrcat(base_url, "%s/", ver);
644             g_free(ver);
645           }
646 
647           char *last_base_url = dt_conf_get_string("context_help/last_url");
648 
649           // if url is https://www.darktable.org/usermanual/,
650           // it is the old deprecated url and we need to update it
651           if(!last_base_url
652              || !*last_base_url
653              || (strcmp(base_url, last_base_url) != 0))
654           {
655             g_free(last_base_url);
656             last_base_url = base_url;
657 
658             // ask the user if darktable.org may be accessed
659             GtkWidget *dialog = gtk_message_dialog_new
660               (GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
661                GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
662                _("do you want to access `%s'?"), last_base_url);
663 #ifdef GDK_WINDOWING_QUARTZ
664             dt_osx_disallow_fullscreen(dialog);
665 #endif
666 
667             gtk_window_set_title(GTK_WINDOW(dialog), _("access the online usermanual?"));
668             const gint res = gtk_dialog_run(GTK_DIALOG(dialog));
669             gtk_widget_destroy(dialog);
670             if(res == GTK_RESPONSE_YES)
671             {
672               dt_conf_set_string("context_help/last_url", last_base_url);
673             }
674             else
675             {
676               g_free(base_url);
677               base_url = NULL;
678             }
679           }
680           if(base_url)
681           {
682             char *lang = "en";
683             GError *error = NULL;
684 
685             // array of languages the usermanual supports.
686             // NULL MUST remain the last element of the array
687             const char *supported_languages[] =
688               { "en", "fr", "de", "eo", "es", "gl", "it", "pl", "pt-br", "uk", NULL };
689             int lang_index = 0;
690             gboolean is_language_supported = FALSE;
691 
692             if(darktable.l10n != NULL)
693             {
694               dt_l10n_language_t *language = NULL;
695               if(darktable.l10n->selected != -1)
696                   language = (dt_l10n_language_t *)g_list_nth(darktable.l10n->languages, darktable.l10n->selected)->data;
697               if (language != NULL)
698                 lang = language->code;
699               while(supported_languages[lang_index])
700               {
701                 gchar *nlang = g_strdup(lang);
702 
703                 // try lang as-is
704                 if(!g_ascii_strcasecmp(nlang, supported_languages[lang_index]))
705                 {
706                   is_language_supported = TRUE;
707                 }
708 
709                 if(!is_language_supported)
710                 {
711                   // keep only first part up to _
712                   for(gchar *p = nlang; *p; p++)
713                     if(*p == '_') *p = '\0';
714 
715                   if(!g_ascii_strcasecmp(nlang, supported_languages[lang_index]))
716                   {
717                     is_language_supported = TRUE;
718                   }
719                 }
720 
721                 g_free(nlang);
722                 if(is_language_supported) break;
723 
724                 lang_index++;
725               }
726             }
727 
728             // language not found, default to EN
729             if(!is_language_supported) lang_index = 0;
730 
731             char *url = g_build_path("/", base_url, supported_languages[lang_index], help_url, NULL);
732 
733             // TODO: call the web browser directly so that file:// style base for local installs works
734             const gboolean uri_success = gtk_show_uri_on_window(GTK_WINDOW(win), url, gtk_get_current_event_time(), &error);
735             g_free(base_url);
736             g_free(url);
737             if(uri_success)
738             {
739               dt_control_log(_("help url opened in web browser"));
740             }
741             else
742             {
743               dt_control_log(_("error while opening help url in web browser"));
744               if (error != NULL) // uri_success being FALSE should guarantee that
745               {
746                 fprintf (stderr, "unable to read file: %s\n", error->message);
747                 g_error_free (error);
748               }
749             }
750           }
751         }
752         else
753         {
754           dt_control_log(_("there is no help available for this element"));
755         }
756       }
757       handled = TRUE;
758       break;
759     }
760 
761     case GDK_BUTTON_RELEASE:
762     {
763       // reset GTK to normal behaviour
764 
765       g_signal_handlers_block_by_func(d->help_button, _lib_help_button_clicked, d);
766       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->help_button), FALSE);
767       g_signal_handlers_unblock_by_func(d->help_button, _lib_help_button_clicked, d);
768 
769       dt_control_allow_change_cursor();
770       dt_control_change_cursor(GDK_LEFT_PTR);
771       gdk_event_handler_set((GdkEventFunc)gtk_main_do_event, NULL, NULL);
772 
773       handled = TRUE;
774     }
775     break;
776 
777     case GDK_ENTER_NOTIFY:
778     case GDK_LEAVE_NOTIFY:
779     {
780       GtkWidget *event_widget = gtk_get_event_widget(event);
781       if(event_widget)
782       {
783         gchar *help_url = get_help_url(event_widget);
784         if(help_url)
785         {
786           // TODO: find a better way to tell the user that the hovered widget has a help link
787           dt_cursor_t cursor = event->type == GDK_ENTER_NOTIFY ? GDK_QUESTION_ARROW : GDK_X_CURSOR;
788           dt_control_allow_change_cursor();
789           dt_control_change_cursor(cursor);
790           dt_control_forbid_change_cursor();
791         }
792       }
793       break;
794     }
795     default:
796       break;
797   }
798 
799   if(!handled) gtk_main_do_event(event);
800 }
801 
802 // Don't save across sessions (window managers role)
803 static struct { gint x, y, w, h; } _shortcuts_dialog_posize = {};
804 
_resize_shortcuts_dialog(GtkWidget * widget,GdkEvent * event,gpointer user_data)805 static gboolean _resize_shortcuts_dialog(GtkWidget *widget, GdkEvent *event, gpointer user_data)
806 {
807   gtk_window_get_position(GTK_WINDOW(widget), &_shortcuts_dialog_posize.x, &_shortcuts_dialog_posize.y);
808   gtk_window_get_size(GTK_WINDOW(widget), &_shortcuts_dialog_posize.w, &_shortcuts_dialog_posize.h);
809 
810   dt_conf_set_int("ui_last/shortcuts_dialog_width", _shortcuts_dialog_posize.w);
811   dt_conf_set_int("ui_last/shortcuts_dialog_height", _shortcuts_dialog_posize.h);
812 
813   return FALSE;
814 }
815 
_set_mapping_mode_cursor(GtkWidget * widget)816 static void _set_mapping_mode_cursor(GtkWidget *widget)
817 {
818   GdkCursorType cursor = GDK_DIAMOND_CROSS;
819 
820   if(GTK_IS_EVENT_BOX(widget)) widget = gtk_bin_get_child(GTK_BIN(widget));
821 
822   if(widget && !strcmp(gtk_widget_get_name(widget), "module-header"))
823     cursor = GDK_BASED_ARROW_DOWN;
824   else if(g_hash_table_lookup(darktable.control->widgets, darktable.control->mapping_widget)
825           && darktable.develop)
826   {
827     switch(dt_dev_modulegroups_basics_module_toggle(darktable.develop, widget, FALSE))
828     {
829     case  1: cursor = GDK_SB_UP_ARROW; break;
830     case -1: cursor = GDK_SB_DOWN_ARROW; break;
831     default: cursor = GDK_BOX_SPIRAL;
832     }
833   }
834 
835   dt_control_allow_change_cursor();
836   dt_control_change_cursor(cursor);
837   dt_control_forbid_change_cursor();
838 }
839 
_show_shortcuts_prefs(GtkWidget * w)840 static void _show_shortcuts_prefs(GtkWidget *w)
841 {
842   GtkWidget *shortcuts_dialog = gtk_dialog_new_with_buttons(_("shortcuts"), GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)),
843                                                             GTK_DIALOG_DESTROY_WITH_PARENT, NULL, NULL);
844   if(!_shortcuts_dialog_posize.w)
845     gtk_window_set_default_size(GTK_WINDOW(shortcuts_dialog),
846                                 DT_PIXEL_APPLY_DPI(dt_conf_get_int("ui_last/shortcuts_dialog_width")),
847                                 DT_PIXEL_APPLY_DPI(dt_conf_get_int("ui_last/shortcuts_dialog_height")));
848   else
849   {
850     gtk_window_move(GTK_WINDOW(shortcuts_dialog), _shortcuts_dialog_posize.x, _shortcuts_dialog_posize.y);
851     gtk_window_resize(GTK_WINDOW(shortcuts_dialog), _shortcuts_dialog_posize.w, _shortcuts_dialog_posize.h);
852   }
853   g_signal_connect(G_OBJECT(shortcuts_dialog), "configure-event", G_CALLBACK(_resize_shortcuts_dialog), NULL);
854 
855   //grab the content area of the dialog
856   GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(shortcuts_dialog));
857   gtk_box_pack_start(GTK_BOX(content), dt_shortcuts_prefs(w), TRUE, TRUE, 0);
858 
859   gtk_widget_show_all(shortcuts_dialog);
860   gtk_dialog_run(GTK_DIALOG(shortcuts_dialog));
861   gtk_widget_destroy(shortcuts_dialog);
862 }
863 
_main_do_event_keymap(GdkEvent * event,gpointer data)864 static void _main_do_event_keymap(GdkEvent *event, gpointer data)
865 {
866   GtkWidget *event_widget = gtk_get_event_widget(event);
867 
868   switch(event->type)
869   {
870   case GDK_LEAVE_NOTIFY:
871   case GDK_ENTER_NOTIFY:
872     if(darktable.control->mapping_widget
873        && event->crossing.mode == GDK_CROSSING_UNGRAB)
874       break;
875   case GDK_GRAB_BROKEN:
876   case GDK_FOCUS_CHANGE:
877     darktable.control->mapping_widget = event_widget;
878     _set_mapping_mode_cursor(event_widget);
879     break;
880   case GDK_BUTTON_PRESS:
881     if(gdk_display_device_is_grabbed(gdk_window_get_display(event->button.window), event->button.device))
882       break;
883 
884     GtkWidget *main_window = dt_ui_main_window(darktable.gui->ui);
885     if(gtk_widget_get_toplevel(event_widget) != main_window)
886       break;
887 
888     if(!gtk_window_is_active(GTK_WINDOW(main_window)))
889       break;
890 
891     dt_lib_tool_preferences_t *d = (dt_lib_tool_preferences_t *)data;
892     if(event_widget == d->keymap_button)
893       break;
894 
895     if(GTK_IS_ENTRY(event_widget))
896       break;
897 
898     if(event->button.button != GDK_BUTTON_PRIMARY)
899       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->keymap_button), FALSE);
900     else if(dt_modifier_is(event->button.state, GDK_CONTROL_MASK))
901     {
902       if(darktable.develop)
903       {
904         dt_dev_modulegroups_basics_module_toggle(darktable.develop, event_widget, TRUE);
905         _set_mapping_mode_cursor(event_widget);
906       }
907     }
908     else
909     {
910       // allow opening modules to map widgets inside
911       if(GTK_IS_EVENT_BOX(event_widget)) event_widget = gtk_bin_get_child(GTK_BIN(event_widget));
912       if(event_widget && !strcmp(gtk_widget_get_name(event_widget), "module-header"))
913         break;
914 
915       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->keymap_button), FALSE);
916       _show_shortcuts_prefs(event_widget);
917     }
918 
919     return;
920   default:
921     break;
922   }
923 
924   gtk_main_do_event(event);
925 }
926 
_lib_help_button_clicked(GtkWidget * widget,gpointer user_data)927 static void _lib_help_button_clicked(GtkWidget *widget, gpointer user_data)
928 {
929   dt_control_change_cursor(GDK_X_CURSOR);
930   dt_control_forbid_change_cursor();
931   gdk_event_handler_set(_main_do_event_help, user_data, NULL);
932 }
933 
_lib_keymap_button_clicked(GtkWidget * widget,gpointer user_data)934 static void _lib_keymap_button_clicked(GtkWidget *widget, gpointer user_data)
935 {
936   if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
937   {
938     gdk_event_handler_set(_main_do_event_keymap, user_data, NULL);
939   }
940   else
941   {
942     darktable.control->mapping_widget = NULL;
943     dt_control_allow_change_cursor();
944     dt_control_change_cursor(GDK_LEFT_PTR);
945     gdk_event_handler_set((GdkEventFunc)gtk_main_do_event, NULL, NULL);
946   }
947 }
948 
_lib_keymap_button_press_release(GtkWidget * button,GdkEventButton * event,gpointer user_data)949 static gboolean _lib_keymap_button_press_release(GtkWidget *button, GdkEventButton *event, gpointer user_data)
950 {
951   static guint start_time = 0;
952 
953   int delay = 0;
954   g_object_get(gtk_settings_get_default(), "gtk-long-press-time", &delay, NULL);
955 
956   if((event->type == GDK_BUTTON_PRESS && event->button == 3) ||
957      (event->type == GDK_BUTTON_RELEASE && event->time - start_time > delay))
958   {
959     _show_shortcuts_prefs(NULL);
960     return TRUE;
961   }
962   else
963   {
964     start_time = event->time;
965     return FALSE;
966   }
967 }
968 
init_key_accels(dt_lib_module_t * self)969 void init_key_accels(dt_lib_module_t *self)
970 {
971   dt_accel_register_global(NC_("accel", "grouping"), 0, 0);
972   dt_accel_register_global(NC_("accel", "thumbnail overlays options"), 0, 0);
973   dt_accel_register_global(NC_("accel", "preferences"), 0, 0);
974   dt_accel_register_global(NC_("accel", "shortcuts"), 0, 0);
975 
976   dt_accel_register_global(NC_("accel", "thumbnail overlays/no overlays"), 0, 0);
977   dt_accel_register_global(NC_("accel", "thumbnail overlays/overlays on mouse hover"), 0, 0);
978   dt_accel_register_global(NC_("accel", "thumbnail overlays/extended overlays on mouse hover"), 0, 0);
979   dt_accel_register_global(NC_("accel", "thumbnail overlays/permanent overlays"), 0, 0);
980   dt_accel_register_global(NC_("accel", "thumbnail overlays/permanent extended overlays"), 0, 0);
981   dt_accel_register_global(NC_("accel", "thumbnail overlays/permanent overlays extended on mouse hover"), 0, 0);
982   dt_accel_register_global(NC_("accel", "thumbnail overlays/overlays block on mouse hover"), 0, 0);
983 }
984 
connect_key_accels(dt_lib_module_t * self)985 void connect_key_accels(dt_lib_module_t *self)
986 {
987   dt_lib_tool_preferences_t *d = (dt_lib_tool_preferences_t *)self->data;
988 
989   dt_accel_connect_button_lib_as_global(self, "thumbnail overlays options", d->overlays_button);
990   dt_accel_connect_button_lib_as_global(self, "preferences", d->preferences_button);
991 
992   dt_accel_connect_lib_as_global( self, "thumbnail overlays/no overlays",
993       g_cclosure_new(G_CALLBACK(_overlays_accels_callback), GINT_TO_POINTER(DT_THUMBNAIL_OVERLAYS_NONE), NULL));
994   dt_accel_connect_lib_as_global(self, "thumbnail overlays/overlays on mouse hover",
995                        g_cclosure_new(G_CALLBACK(_overlays_accels_callback),
996                                       GINT_TO_POINTER(DT_THUMBNAIL_OVERLAYS_HOVER_NORMAL), NULL));
997   dt_accel_connect_lib_as_global(self, "thumbnail overlays/extended overlays on mouse hover",
998                        g_cclosure_new(G_CALLBACK(_overlays_accels_callback),
999                                       GINT_TO_POINTER(DT_THUMBNAIL_OVERLAYS_HOVER_EXTENDED), NULL));
1000   dt_accel_connect_lib_as_global(self, "thumbnail overlays/permanent overlays",
1001                        g_cclosure_new(G_CALLBACK(_overlays_accels_callback),
1002                                       GINT_TO_POINTER(DT_THUMBNAIL_OVERLAYS_ALWAYS_NORMAL), NULL));
1003   dt_accel_connect_lib_as_global(self, "thumbnail overlays/permanent extended overlays",
1004                        g_cclosure_new(G_CALLBACK(_overlays_accels_callback),
1005                                       GINT_TO_POINTER(DT_THUMBNAIL_OVERLAYS_ALWAYS_EXTENDED), NULL));
1006   dt_accel_connect_lib_as_global(
1007       self, "thumbnail overlays/permanent overlays extended on mouse hover",
1008       g_cclosure_new(G_CALLBACK(_overlays_accels_callback), GINT_TO_POINTER(DT_THUMBNAIL_OVERLAYS_MIXED), NULL));
1009   dt_accel_connect_lib_as_global(self, "thumbnail overlays/overlays block on mouse hover",
1010                        g_cclosure_new(G_CALLBACK(_overlays_accels_callback),
1011                                       GINT_TO_POINTER(DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK), NULL));
1012 }
1013 
1014 #ifdef USE_LUA
1015 
grouping_member(lua_State * L)1016 static int grouping_member(lua_State *L)
1017 {
1018   dt_lib_module_t *self = *(dt_lib_module_t **)lua_touserdata(L, 1);
1019   dt_lib_tool_preferences_t *d = (dt_lib_tool_preferences_t *)self->data;
1020   if(lua_gettop(L) != 3)
1021   {
1022     lua_pushboolean(L, darktable.gui->grouping);
1023     return 1;
1024   }
1025   else
1026   {
1027     gboolean value = lua_toboolean(L, 3);
1028     if(darktable.gui->grouping != value)
1029     {
1030       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->grouping_button), value);
1031     }
1032   }
1033   return 0;
1034 }
1035 
show_overlays_member(lua_State * L)1036 static int show_overlays_member(lua_State *L)
1037 {
1038   dt_lib_module_t *self = *(dt_lib_module_t **)lua_touserdata(L, 1);
1039   dt_lib_tool_preferences_t *d = (dt_lib_tool_preferences_t *)self->data;
1040   if(lua_gettop(L) != 3)
1041   {
1042     lua_pushboolean(L, darktable.gui->show_overlays);
1043     return 1;
1044   }
1045   else
1046   {
1047     gboolean value = lua_toboolean(L, 3);
1048     if(darktable.gui->show_overlays != value)
1049     {
1050       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->overlays_button), value);
1051     }
1052   }
1053   return 0;
1054 }
1055 
init(struct dt_lib_module_t * self)1056 void init(struct dt_lib_module_t *self)
1057 {
1058   lua_State *L = darktable.lua_state.state;
1059   int my_type = dt_lua_module_entry_get_type(L, "lib", self->plugin_name);
1060 
1061   lua_pushcfunction(L, grouping_member);
1062   dt_lua_gtk_wrap(L);
1063   dt_lua_type_register_type(L, my_type, "grouping");
1064   lua_pushcfunction(L, show_overlays_member);
1065   dt_lua_gtk_wrap(L);
1066   dt_lua_type_register_type(L, my_type, "show_overlays");
1067 
1068   lua_pushcfunction(L, dt_lua_event_multiinstance_register);
1069   lua_pushcfunction(L, dt_lua_event_multiinstance_destroy);
1070   lua_pushcfunction(L, dt_lua_event_multiinstance_trigger);
1071   dt_lua_event_add(L, "global_toolbox-grouping_toggle");
1072 
1073   lua_pushcfunction(L, dt_lua_event_multiinstance_register);
1074   lua_pushcfunction(L, dt_lua_event_multiinstance_destroy);
1075   lua_pushcfunction(L, dt_lua_event_multiinstance_trigger);
1076   dt_lua_event_add(L, "global_toolbox-overlay_toggle");
1077 }
1078 
1079 #endif // USE_LUA
1080 
1081 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
1082 // vim: shiftwidth=2 expandtab tabstop=2 cindent
1083 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1084