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;
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 
name(dt_lib_module_t * self)56 const char *name(dt_lib_module_t *self)
57 {
58   return _("preferences");
59 }
60 
views(dt_lib_module_t * self)61 const char **views(dt_lib_module_t *self)
62 {
63   static const char *v[] = {"*", NULL};
64   return v;
65 }
66 
container(dt_lib_module_t * self)67 uint32_t container(dt_lib_module_t *self)
68 {
69   return DT_UI_CONTAINER_PANEL_CENTER_TOP_RIGHT;
70 }
71 
expandable(dt_lib_module_t * self)72 int expandable(dt_lib_module_t *self)
73 {
74   return 0;
75 }
76 
position()77 int position()
78 {
79   return 1001;
80 }
81 
_overlays_accels_callback(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,gpointer data)82 static void _overlays_accels_callback(GtkAccelGroup *accel_group, GObject *acceleratable, guint keyval,
83                                       GdkModifierType modifier, gpointer data)
84 {
85   dt_thumbnail_overlay_t over = (dt_thumbnail_overlay_t)GPOINTER_TO_INT(data);
86   dt_thumbtable_set_overlays_mode(dt_ui_thumbtable(darktable.gui->ui), over);
87 }
88 
_overlays_toggle_button(GtkWidget * w,gpointer user_data)89 static void _overlays_toggle_button(GtkWidget *w, gpointer user_data)
90 {
91   dt_lib_module_t *self = (dt_lib_module_t *)user_data;
92   dt_lib_tool_preferences_t *d = (dt_lib_tool_preferences_t *)self->data;
93 
94   if(d->disable_over_events) return;
95 
96   dt_thumbnail_overlay_t over = DT_THUMBNAIL_OVERLAYS_HOVER_NORMAL;
97   if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->over_r0)))
98     over = DT_THUMBNAIL_OVERLAYS_NONE;
99   else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->over_r2)))
100     over = DT_THUMBNAIL_OVERLAYS_HOVER_EXTENDED;
101   else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->over_r3)))
102     over = DT_THUMBNAIL_OVERLAYS_ALWAYS_NORMAL;
103   else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->over_r4)))
104     over = DT_THUMBNAIL_OVERLAYS_ALWAYS_EXTENDED;
105   else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->over_r5)))
106     over = DT_THUMBNAIL_OVERLAYS_MIXED;
107   else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->over_r6)))
108     over = DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK;
109 
110   dt_ui_thumbtable(darktable.gui->ui)->show_tooltips = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->over_tt));
111   dt_thumbtable_set_overlays_mode(dt_ui_thumbtable(darktable.gui->ui), over);
112 
113   gtk_widget_set_sensitive(d->over_timeout, (over == DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK));
114 
115   // we don't hide the popup in case of block overlay, as the user may want to tweak the duration
116   if(over != DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK) gtk_widget_hide(d->over_popup);
117 
118 #ifdef USE_LUA
119   gboolean show = (over == DT_THUMBNAIL_OVERLAYS_ALWAYS_NORMAL || over == DT_THUMBNAIL_OVERLAYS_ALWAYS_EXTENDED);
120   dt_lua_async_call_alien(dt_lua_event_trigger_wrapper, 0, NULL, NULL, LUA_ASYNC_TYPENAME, "const char*",
121                           "global_toolbox-overlay_toggle", LUA_ASYNC_TYPENAME, "bool", show, LUA_ASYNC_DONE);
122 #endif // USE_LUA
123 }
124 
_overlays_toggle_culling_button(GtkWidget * w,gpointer user_data)125 static void _overlays_toggle_culling_button(GtkWidget *w, gpointer user_data)
126 {
127   dt_lib_module_t *self = (dt_lib_module_t *)user_data;
128   dt_lib_tool_preferences_t *d = (dt_lib_tool_preferences_t *)self->data;
129 
130   if(d->disable_over_events) return;
131 
132   dt_thumbnail_overlay_t over = DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK;
133   if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->over_culling_r0)))
134     over = DT_THUMBNAIL_OVERLAYS_NONE;
135   else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->over_culling_r3)))
136     over = DT_THUMBNAIL_OVERLAYS_ALWAYS_NORMAL;
137   else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->over_culling_r4)))
138     over = DT_THUMBNAIL_OVERLAYS_ALWAYS_EXTENDED;
139 
140   dt_culling_mode_t cmode = DT_CULLING_MODE_CULLING;
141   if(dt_view_lighttable_preview_state(darktable.view_manager)) cmode = DT_CULLING_MODE_PREVIEW;
142   gchar *txt = dt_util_dstrcat(NULL, "plugins/lighttable/overlays/culling/%d", cmode);
143   dt_conf_set_int(txt, over);
144   g_free(txt);
145   txt = dt_util_dstrcat(NULL, "plugins/lighttable/tooltips/culling/%d", cmode);
146   dt_conf_set_bool(txt, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->over_culling_tt)));
147   g_free(txt);
148   dt_view_lighttable_culling_preview_reload_overlays(darktable.view_manager);
149 
150   gtk_widget_set_sensitive(d->over_culling_timeout, (over == DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK));
151 
152   // we don't hide the popup in case of block overlay, as the user may want to tweak the duration
153   if(over != DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK) gtk_widget_hide(d->over_popup);
154 
155 #ifdef USE_LUA
156   gboolean show = (over == DT_THUMBNAIL_OVERLAYS_ALWAYS_NORMAL || over == DT_THUMBNAIL_OVERLAYS_ALWAYS_EXTENDED);
157   dt_lua_async_call_alien(dt_lua_event_trigger_wrapper, 0, NULL, NULL, LUA_ASYNC_TYPENAME, "const char*",
158                           "global_toolbox-overlay_toggle", LUA_ASYNC_TYPENAME, "bool", show, LUA_ASYNC_DONE);
159 #endif // USE_LUA
160 }
161 
_overlays_timeout_changed(GtkWidget * w,gpointer user_data)162 static void _overlays_timeout_changed(GtkWidget *w, gpointer user_data)
163 {
164   dt_lib_module_t *self = (dt_lib_module_t *)user_data;
165   dt_lib_tool_preferences_t *d = (dt_lib_tool_preferences_t *)self->data;
166 
167   const int val = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(w));
168 
169   if(w == d->over_timeout)
170   {
171     dt_thumbtable_set_overlays_block_timeout(dt_ui_thumbtable(darktable.gui->ui), val);
172   }
173   else if(w == d->over_culling_timeout)
174   {
175     dt_culling_mode_t cmode = DT_CULLING_MODE_CULLING;
176     if(dt_view_lighttable_preview_state(darktable.view_manager)) cmode = DT_CULLING_MODE_PREVIEW;
177     gchar *txt = dt_util_dstrcat(NULL, "plugins/lighttable/overlays/culling_block_timeout/%d", cmode);
178     dt_conf_set_int(txt, val);
179     g_free(txt);
180 
181     dt_view_lighttable_culling_preview_reload_overlays(darktable.view_manager);
182   }
183 }
184 
_overlays_show_popup(dt_lib_module_t * self)185 static void _overlays_show_popup(dt_lib_module_t *self)
186 {
187   dt_lib_tool_preferences_t *d = (dt_lib_tool_preferences_t *)self->data;
188 
189   d->disable_over_events = TRUE;
190 
191   gboolean show = FALSE;
192 
193   // thumbnails part
194   const dt_view_t *cv = dt_view_manager_get_current_view(darktable.view_manager);
195   gboolean thumbs_state;
196   if(g_strcmp0(cv->module_name, "slideshow") == 0)
197   {
198     thumbs_state = FALSE;
199   }
200   else if(g_strcmp0(cv->module_name, "lighttable") == 0)
201   {
202     if(dt_view_lighttable_preview_state(darktable.view_manager)
203        || dt_view_lighttable_get_layout(darktable.view_manager) == DT_LIGHTTABLE_LAYOUT_CULLING)
204     {
205       thumbs_state = dt_ui_panel_visible(darktable.gui->ui, DT_UI_PANEL_BOTTOM);
206     }
207     else
208     {
209       thumbs_state = TRUE;
210     }
211   }
212   else
213   {
214     thumbs_state = dt_ui_panel_visible(darktable.gui->ui, DT_UI_PANEL_BOTTOM);
215   }
216 
217 
218   if(thumbs_state)
219   {
220     // we write the label with the size category
221     gchar *txt = dt_util_dstrcat(NULL, "%s %d (%d %s)", _("thumbnails overlays for size"),
222                                  dt_ui_thumbtable(darktable.gui->ui)->prefs_size,
223                                  dt_ui_thumbtable(darktable.gui->ui)->thumb_size, _("px"));
224     gtk_label_set_text(GTK_LABEL(d->over_label), txt);
225     g_free(txt);
226 
227     // we get and set the current value
228     dt_thumbnail_overlay_t mode = dt_ui_thumbtable(darktable.gui->ui)->overlays;
229 
230     gtk_spin_button_set_value(GTK_SPIN_BUTTON(d->over_timeout),
231                               dt_ui_thumbtable(darktable.gui->ui)->overlays_block_timeout);
232     gtk_widget_set_sensitive(d->over_timeout, FALSE);
233 
234     if(mode == DT_THUMBNAIL_OVERLAYS_NONE)
235       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_r0), TRUE);
236     else if(mode == DT_THUMBNAIL_OVERLAYS_HOVER_EXTENDED)
237       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_r2), TRUE);
238     else if(mode == DT_THUMBNAIL_OVERLAYS_ALWAYS_NORMAL)
239       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_r3), TRUE);
240     else if(mode == DT_THUMBNAIL_OVERLAYS_ALWAYS_EXTENDED)
241       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_r4), TRUE);
242     else if(mode == DT_THUMBNAIL_OVERLAYS_MIXED)
243       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_r5), TRUE);
244     else if(mode == DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK)
245     {
246       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_r6), TRUE);
247       gtk_widget_set_sensitive(d->over_timeout, TRUE);
248     }
249     else
250       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_r1), TRUE);
251 
252     if(mode == DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK)
253     {
254       gtk_widget_set_tooltip_text(d->over_timeout,
255                                   _("duration before the block overlay is hidden after each mouse movement on the "
256                                     "image\nset -1 to never hide the overlay"));
257     }
258     else
259     {
260       gtk_widget_set_tooltip_text(d->over_timeout, _("timeout only available for block overlay"));
261     }
262 
263     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_tt), dt_ui_thumbtable(darktable.gui->ui)->show_tooltips);
264 
265     gtk_widget_show_all(d->thumbnails_box);
266     show = TRUE;
267   }
268   else
269   {
270     gtk_widget_hide(d->thumbnails_box);
271   }
272 
273   // and we do the same for culling/preview if needed
274   if(g_strcmp0(cv->module_name, "lighttable") == 0
275      && (dt_view_lighttable_preview_state(darktable.view_manager)
276          || dt_view_lighttable_get_layout(darktable.view_manager) == DT_LIGHTTABLE_LAYOUT_CULLING))
277   {
278     dt_culling_mode_t cmode = DT_CULLING_MODE_CULLING;
279     if(dt_view_lighttable_preview_state(darktable.view_manager)) cmode = DT_CULLING_MODE_PREVIEW;
280 
281     // we write the label text
282     if(cmode == DT_CULLING_MODE_CULLING)
283       gtk_label_set_text(GTK_LABEL(d->over_culling_label), _("culling overlays"));
284     else
285       gtk_label_set_text(GTK_LABEL(d->over_culling_label), _("preview overlays"));
286 
287     // we get and set the current value
288     gchar *otxt = dt_util_dstrcat(NULL, "plugins/lighttable/overlays/culling/%d", cmode);
289     dt_thumbnail_overlay_t mode = dt_conf_get_int(otxt);
290     g_free(otxt);
291 
292     otxt = dt_util_dstrcat(NULL, "plugins/lighttable/overlays/culling_block_timeout/%d", cmode);
293     int timeout = 2;
294     if(!dt_conf_key_exists(otxt))
295       timeout = dt_conf_get_int("plugins/lighttable/overlay_timeout");
296     else
297       timeout = dt_conf_get_int(otxt);
298     g_free(otxt);
299 
300     gtk_spin_button_set_value(GTK_SPIN_BUTTON(d->over_culling_timeout), timeout);
301     gtk_widget_set_sensitive(d->over_culling_timeout, FALSE);
302 
303     if(mode == DT_THUMBNAIL_OVERLAYS_NONE)
304       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_culling_r0), TRUE);
305     else if(mode == DT_THUMBNAIL_OVERLAYS_ALWAYS_NORMAL)
306       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_culling_r3), TRUE);
307     else if(mode == DT_THUMBNAIL_OVERLAYS_ALWAYS_EXTENDED)
308       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_culling_r4), TRUE);
309     else
310     {
311       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_culling_r6), TRUE);
312       gtk_widget_set_sensitive(d->over_culling_timeout, TRUE);
313     }
314 
315     if(mode == DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK)
316     {
317       gtk_widget_set_tooltip_text(d->over_culling_timeout,
318                                   _("duration before the block overlay is hidden after each mouse movement on the "
319                                     "image\nset -1 to never hide the overlay"));
320     }
321     else
322     {
323       gtk_widget_set_tooltip_text(d->over_culling_timeout, _("timeout only available for block overlay"));
324     }
325 
326     otxt = dt_util_dstrcat(NULL, "plugins/lighttable/tooltips/culling/%d", cmode);
327     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->over_culling_tt), dt_conf_get_bool(otxt));
328     g_free(otxt);
329 
330     gtk_widget_show_all(d->culling_box);
331     show = TRUE;
332   }
333   else
334   {
335     gtk_widget_hide(d->culling_box);
336   }
337 
338 
339   if(show) gtk_widget_show(d->over_popup);
340   else
341     dt_control_log(_("overlays not available here..."));
342 
343   d->disable_over_events = FALSE;
344 }
345 
_main_icons_register_size(GtkWidget * widget,GdkRectangle * allocation,gpointer user_data)346 static void _main_icons_register_size(GtkWidget *widget, GdkRectangle *allocation, gpointer user_data)
347 {
348 
349   GtkStateFlags state = gtk_widget_get_state_flags(widget);
350   GtkStyleContext *context = gtk_widget_get_style_context(widget);
351 
352   /* get the css geometry properties */
353   GtkBorder margin, border, padding;
354   gtk_style_context_get_margin(context, state, &margin);
355   gtk_style_context_get_border(context, state, &border);
356   gtk_style_context_get_padding(context, state, &padding);
357 
358   /* we first remove css margin border and padding from allocation */
359   int width = allocation->width - margin.left - margin.right - border.left - border.right - padding.left - padding.right;
360 
361   GtkStyleContext *ccontext = gtk_widget_get_style_context(DTGTK_BUTTON(widget)->canvas);
362   GtkBorder cmargin;
363   gtk_style_context_get_margin(ccontext, state, &cmargin);
364 
365   /* we remove the extra room for optical alignment */
366   width = round((float)width * (1.0 - (cmargin.left + cmargin.right) / 100.0f));
367 
368   // we store the icon size in order to keep in sync thumbtable overlays
369   darktable.gui->icon_size = width;
370 }
371 
gui_init(dt_lib_module_t * self)372 void gui_init(dt_lib_module_t *self)
373 {
374   /* initialize ui widgets */
375   dt_lib_tool_preferences_t *d = (dt_lib_tool_preferences_t *)g_malloc0(sizeof(dt_lib_tool_preferences_t));
376   self->data = (void *)d;
377 
378   self->widget = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
379 
380   /* create the grouping button */
381   d->grouping_button = dtgtk_togglebutton_new(dtgtk_cairo_paint_grouping, CPF_STYLE_FLAT, NULL);
382   gtk_box_pack_start(GTK_BOX(self->widget), d->grouping_button, FALSE, FALSE, 0);
383   if(darktable.gui->grouping)
384     gtk_widget_set_tooltip_text(d->grouping_button, _("expand grouped images"));
385   else
386     gtk_widget_set_tooltip_text(d->grouping_button, _("collapse grouped images"));
387   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->grouping_button), darktable.gui->grouping);
388   g_signal_connect(G_OBJECT(d->grouping_button), "clicked", G_CALLBACK(_lib_filter_grouping_button_clicked),
389                    NULL);
390 
391   /* create the "show/hide overlays" button */
392   d->overlays_button = dtgtk_button_new(dtgtk_cairo_paint_overlays, CPF_STYLE_FLAT, NULL);
393   gtk_widget_set_tooltip_text(d->overlays_button, _("click to change the type of overlays shown on thumbnails"));
394   gtk_box_pack_start(GTK_BOX(self->widget), d->overlays_button, FALSE, FALSE, 0);
395   d->over_popup = gtk_popover_new(d->overlays_button);
396   gtk_widget_set_size_request(d->over_popup, 350, -1);
397 #if GTK_CHECK_VERSION(3, 16, 0)
398   g_object_set(G_OBJECT(d->over_popup), "transitions-enabled", FALSE, NULL);
399 #endif
400   g_signal_connect_swapped(G_OBJECT(d->overlays_button), "button-press-event", G_CALLBACK(_overlays_show_popup),
401                            self);
402   // we register size of overlay icon to keep in sync thumbtable overlays
403   g_signal_connect(G_OBJECT(d->overlays_button), "size-allocate", G_CALLBACK(_main_icons_register_size), NULL);
404 
405   GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
406 
407   gtk_container_add(GTK_CONTAINER(d->over_popup), vbox);
408 
409   // thumbnails overlays
410   d->thumbnails_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
411 
412   d->over_label = gtk_label_new(_("overlay mode for size"));
413   gtk_widget_set_name(d->over_label, "overlays_label");
414   gtk_box_pack_start(GTK_BOX(d->thumbnails_box), d->over_label, TRUE, TRUE, 0);
415   d->over_r0 = gtk_radio_button_new_with_label(NULL, _("no overlays"));
416   g_signal_connect(G_OBJECT(d->over_r0), "toggled", G_CALLBACK(_overlays_toggle_button), self);
417   gtk_box_pack_start(GTK_BOX(d->thumbnails_box), d->over_r0, TRUE, TRUE, 0);
418   d->over_r1
419       = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(d->over_r0), _("overlays on mouse hover"));
420   g_signal_connect(G_OBJECT(d->over_r1), "toggled", G_CALLBACK(_overlays_toggle_button), self);
421   gtk_box_pack_start(GTK_BOX(d->thumbnails_box), d->over_r1, TRUE, TRUE, 0);
422   d->over_r2 = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(d->over_r0),
423                                                            _("extended overlays on mouse hover"));
424   g_signal_connect(G_OBJECT(d->over_r2), "toggled", G_CALLBACK(_overlays_toggle_button), self);
425   gtk_box_pack_start(GTK_BOX(d->thumbnails_box), d->over_r2, TRUE, TRUE, 0);
426   d->over_r3 = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(d->over_r0), _("permanent overlays"));
427   g_signal_connect(G_OBJECT(d->over_r3), "toggled", G_CALLBACK(_overlays_toggle_button), self);
428   gtk_box_pack_start(GTK_BOX(d->thumbnails_box), d->over_r3, TRUE, TRUE, 0);
429   d->over_r4 = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(d->over_r0),
430                                                            _("permanent extended overlays"));
431   g_signal_connect(G_OBJECT(d->over_r4), "toggled", G_CALLBACK(_overlays_toggle_button), self);
432   gtk_box_pack_start(GTK_BOX(d->thumbnails_box), d->over_r4, TRUE, TRUE, 0);
433   d->over_r5 = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(d->over_r0),
434                                                            _("permanent overlays extended on mouse hover"));
435   g_signal_connect(G_OBJECT(d->over_r5), "toggled", G_CALLBACK(_overlays_toggle_button), self);
436   gtk_box_pack_start(GTK_BOX(d->thumbnails_box), d->over_r5, TRUE, TRUE, 0);
437   GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
438   d->over_r6 = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(d->over_r0),
439                                                            _("overlays block on mouse hover during (s) "));
440   g_signal_connect(G_OBJECT(d->over_r6), "toggled", G_CALLBACK(_overlays_toggle_button), self);
441   gtk_box_pack_start(GTK_BOX(hbox), d->over_r6, TRUE, TRUE, 0);
442   d->over_timeout = gtk_spin_button_new_with_range(-1, 99, 1);
443   g_signal_connect(G_OBJECT(d->over_timeout), "value-changed", G_CALLBACK(_overlays_timeout_changed), self);
444   gtk_box_pack_start(GTK_BOX(hbox), d->over_timeout, TRUE, TRUE, 0);
445   gtk_box_pack_start(GTK_BOX(d->thumbnails_box), hbox, TRUE, TRUE, 0);
446   d->over_tt = gtk_check_button_new_with_label(_("show tooltip"));
447   g_signal_connect(G_OBJECT(d->over_tt), "toggled", G_CALLBACK(_overlays_toggle_button), self);
448   gtk_widget_set_name(d->over_tt, "show-tooltip");
449   gtk_box_pack_start(GTK_BOX(d->thumbnails_box), d->over_tt, TRUE, TRUE, 0);
450 
451   gtk_box_pack_start(GTK_BOX(vbox), d->thumbnails_box, TRUE, TRUE, 0);
452 
453   // culling/preview overlays
454   d->culling_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
455 
456   d->over_culling_label = gtk_label_new(_("overlay mode for size"));
457   gtk_widget_set_name(d->over_culling_label, "overlays_label");
458   gtk_box_pack_start(GTK_BOX(d->culling_box), d->over_culling_label, TRUE, TRUE, 0);
459   d->over_culling_r0 = gtk_radio_button_new_with_label(NULL, _("no overlays"));
460   g_signal_connect(G_OBJECT(d->over_culling_r0), "toggled", G_CALLBACK(_overlays_toggle_culling_button), self);
461   gtk_box_pack_start(GTK_BOX(d->culling_box), d->over_culling_r0, TRUE, TRUE, 0);
462   d->over_culling_r3
463       = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(d->over_culling_r0), _("permanent overlays"));
464   g_signal_connect(G_OBJECT(d->over_culling_r3), "toggled", G_CALLBACK(_overlays_toggle_culling_button), self);
465   gtk_box_pack_start(GTK_BOX(d->culling_box), d->over_culling_r3, TRUE, TRUE, 0);
466   d->over_culling_r4 = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(d->over_culling_r0),
467                                                                    _("permanent extended overlays"));
468   g_signal_connect(G_OBJECT(d->over_culling_r4), "toggled", G_CALLBACK(_overlays_toggle_culling_button), self);
469   gtk_box_pack_start(GTK_BOX(d->culling_box), d->over_culling_r4, TRUE, TRUE, 0);
470   hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
471   d->over_culling_r6 = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(d->over_culling_r0),
472                                                                    _("overlays block on mouse hover during (s) "));
473   g_signal_connect(G_OBJECT(d->over_culling_r6), "toggled", G_CALLBACK(_overlays_toggle_culling_button), self);
474   gtk_box_pack_start(GTK_BOX(hbox), d->over_culling_r6, TRUE, TRUE, 0);
475   d->over_culling_timeout = gtk_spin_button_new_with_range(-1, 99, 1);
476   g_signal_connect(G_OBJECT(d->over_culling_timeout), "value-changed", G_CALLBACK(_overlays_timeout_changed), self);
477   gtk_box_pack_start(GTK_BOX(hbox), d->over_culling_timeout, TRUE, TRUE, 0);
478   gtk_box_pack_start(GTK_BOX(d->culling_box), hbox, TRUE, TRUE, 0);
479   d->over_culling_tt = gtk_check_button_new_with_label(_("show tooltip"));
480   g_signal_connect(G_OBJECT(d->over_culling_tt), "toggled", G_CALLBACK(_overlays_toggle_culling_button), self);
481   gtk_widget_set_name(d->over_culling_tt, "show-tooltip");
482   gtk_box_pack_start(GTK_BOX(d->culling_box), d->over_culling_tt, TRUE, TRUE, 0);
483 
484   gtk_box_pack_start(GTK_BOX(vbox), d->culling_box, TRUE, TRUE, 0);
485   gtk_widget_show(vbox);
486 
487   /* create the widget help button */
488   d->help_button = dtgtk_togglebutton_new(dtgtk_cairo_paint_help, CPF_STYLE_FLAT, NULL);
489   gtk_box_pack_start(GTK_BOX(self->widget), d->help_button, FALSE, FALSE, 0);
490   gtk_widget_set_tooltip_text(d->help_button, _("enable this, then click on a control element to see its online help"));
491   g_signal_connect(G_OBJECT(d->help_button), "clicked", G_CALLBACK(_lib_help_button_clicked), d);
492   dt_gui_add_help_link(d->help_button, dt_get_help_url("global_toolbox_help"));
493 
494   // the rest of these is added in reverse order as they are always put at the end of the container.
495   // that's done so that buttons added via Lua will come first.
496 
497   /* create the preference button */
498   d->preferences_button = dtgtk_button_new(dtgtk_cairo_paint_preferences, CPF_STYLE_FLAT, NULL);
499   gtk_box_pack_end(GTK_BOX(self->widget), d->preferences_button, FALSE, FALSE, 0);
500   gtk_widget_set_tooltip_text(d->preferences_button, _("show global preferences"));
501   g_signal_connect(G_OBJECT(d->preferences_button), "clicked", G_CALLBACK(_lib_preferences_button_clicked),
502                    NULL);
503   dt_gui_add_help_link(d->preferences_button, dt_get_help_url("global_toolbox_preferences"));
504 }
505 
gui_cleanup(dt_lib_module_t * self)506 void gui_cleanup(dt_lib_module_t *self)
507 {
508   g_free(self->data);
509   self->data = NULL;
510 }
511 
_lib_preferences_button_clicked(GtkWidget * widget,gpointer user_data)512 void _lib_preferences_button_clicked(GtkWidget *widget, gpointer user_data)
513 {
514   dt_gui_preferences_show();
515 }
516 
_lib_filter_grouping_button_clicked(GtkWidget * widget,gpointer user_data)517 static void _lib_filter_grouping_button_clicked(GtkWidget *widget, gpointer user_data)
518 {
519 
520   darktable.gui->grouping = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
521   if(darktable.gui->grouping)
522     gtk_widget_set_tooltip_text(widget, _("expand grouped images"));
523   else
524     gtk_widget_set_tooltip_text(widget, _("collapse grouped images"));
525   dt_conf_set_bool("ui_last/grouping", darktable.gui->grouping);
526   darktable.gui->expanded_group_id = -1;
527   dt_collection_update_query(darktable.collection, DT_COLLECTION_CHANGE_RELOAD, DT_COLLECTION_PROP_GROUPING, NULL);
528 
529 #ifdef USE_LUA
530   dt_lua_async_call_alien(dt_lua_event_trigger_wrapper,
531       0,NULL,NULL,
532       LUA_ASYNC_TYPENAME,"const char*","global_toolbox-grouping_toggle",
533       LUA_ASYNC_TYPENAME,"bool",darktable.gui->grouping,
534       LUA_ASYNC_DONE);
535 #endif // USE_LUA
536 }
537 
538 // 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)539 static char *get_help_url(GtkWidget *widget)
540 {
541   while(widget)
542   {
543     // if the widget doesn't have a help url set go up the widget hierarchy to find a parent that has an url
544     gchar *help_url = g_object_get_data(G_OBJECT(widget), "dt-help-url");
545 
546     if(help_url)
547       return help_url;
548 
549     // TODO: shall we cross from libs/iops to the core gui? if not, here is the place to break out of the loop
550 
551     widget = gtk_widget_get_parent(widget);
552   }
553 
554   return NULL;
555 }
556 
_get_base_url()557 static char *_get_base_url()
558 {
559   const gboolean use_default_url =
560     dt_conf_get_bool("context_help/use_default_url");
561   const char *c_base_url = dt_confgen_get
562     (dt_is_dev_version() ? "context_help/dev_url" : "context_help/url",
563      DT_DEFAULT);
564   char *base_url = dt_conf_get_string
565     (dt_is_dev_version() ? "context_help/dev_url" : "context_help/url");
566 
567   if(use_default_url)
568   {
569     // want to use default URL, reset darktablerc
570     dt_conf_set_string
571       (dt_is_dev_version() ? "context_help/dev_url" : "context_help/url",
572        c_base_url);
573     return g_strdup(c_base_url);
574   }
575   else
576     return base_url;
577 }
578 
_main_do_event(GdkEvent * event,gpointer data)579 static void _main_do_event(GdkEvent *event, gpointer data)
580 {
581   dt_lib_tool_preferences_t *d = (dt_lib_tool_preferences_t *)data;
582 
583   gboolean handled = FALSE;
584 
585   switch(event->type)
586   {
587     case GDK_BUTTON_PRESS:
588     {
589       GtkWidget *event_widget = gtk_get_event_widget(event);
590       if(event_widget)
591       {
592         // TODO: When the widget doesn't have a help url set we should probably look at the parent(s)
593         gchar *help_url = get_help_url(event_widget);
594         if(help_url && *help_url)
595         {
596           GtkWidget *win = dt_ui_main_window(darktable.gui->ui);
597           dt_print(DT_DEBUG_CONTROL, "[context help] opening `%s'\n", help_url);
598           char *base_url = _get_base_url();
599 
600           // in case of a standard release, happend the dt version to the url
601           if(!dt_is_dev_version())
602           {
603             char *ver = dt_version_major_minor();
604             base_url = dt_util_dstrcat(base_url, "%s/", ver);
605             g_free(ver);
606           }
607 
608           char *last_base_url = dt_conf_get_string("context_help/last_url");
609 
610           // if url is https://www.darktable.org/usermanual/,
611           // it is the old deprecated url and we need to update it
612           if(!last_base_url
613              || !*last_base_url
614              || (strcmp(base_url, last_base_url) != 0))
615           {
616             g_free(last_base_url);
617             last_base_url = base_url;
618 
619             // ask the user if darktable.org may be accessed
620             GtkWidget *dialog = gtk_message_dialog_new
621               (GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
622                GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
623                _("do you want to access `%s'?"), last_base_url);
624 #ifdef GDK_WINDOWING_QUARTZ
625             dt_osx_disallow_fullscreen(dialog);
626 #endif
627 
628             gtk_window_set_title(GTK_WINDOW(dialog), _("access the online usermanual?"));
629             const gint res = gtk_dialog_run(GTK_DIALOG(dialog));
630             gtk_widget_destroy(dialog);
631             if(res == GTK_RESPONSE_YES)
632             {
633               dt_conf_set_string("context_help/last_url", last_base_url);
634             }
635             else
636             {
637               g_free(base_url);
638               base_url = NULL;
639             }
640           }
641           if(base_url)
642           {
643             gboolean is_language_supported = FALSE;
644             char *lang = "en";
645             GError *error = NULL;
646 
647             if(darktable.l10n!=NULL)
648             {
649               dt_l10n_language_t *language = NULL;
650               if(darktable.l10n->selected!=-1)
651                   language = (dt_l10n_language_t *)g_list_nth(darktable.l10n->languages, darktable.l10n->selected)->data;
652               if (language != NULL)
653                 lang = language->code;
654               // array of languages the usermanual supports.
655               // NULL MUST remain the last element of the array
656               const char *supported_languages[] = { "en", NULL }; // "fr", "it", "es", "de", "pl", NULL };
657               int i = 0;
658               while(supported_languages[i])
659               {
660                 if(!strcmp(lang, supported_languages[i]))
661                 {
662                   is_language_supported = TRUE;
663                   break;
664                 }
665                 i++;
666               }
667             }
668             if(!is_language_supported) lang = "en";
669             char *url = dt_is_dev_version()
670               ? g_build_path("/", base_url, help_url, NULL)
671               : g_build_path("/", base_url, lang, help_url, NULL);
672 
673             // TODO: call the web browser directly so that file:// style base for local installs works
674             const gboolean uri_success = gtk_show_uri_on_window(GTK_WINDOW(win), url, gtk_get_current_event_time(), &error);
675             g_free(base_url);
676             g_free(url);
677             if(uri_success)
678             {
679               dt_control_log(_("help url opened in web browser"));
680             }
681             else
682             {
683               dt_control_log(_("error while opening help url in web browser"));
684               if (error != NULL) // uri_success being FALSE should guarantee that
685               {
686                 fprintf (stderr, "unable to read file: %s\n", error->message);
687                 g_error_free (error);
688               }
689             }
690           }
691         }
692         else
693         {
694           dt_control_log(_("there is no help available for this element"));
695         }
696       }
697       handled = TRUE;
698       break;
699     }
700 
701     case GDK_BUTTON_RELEASE:
702     {
703       // reset GTK to normal behaviour
704 
705       g_signal_handlers_block_by_func(d->help_button, _lib_help_button_clicked, d);
706       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->help_button), FALSE);
707       g_signal_handlers_unblock_by_func(d->help_button, _lib_help_button_clicked, d);
708 
709       dt_control_allow_change_cursor();
710       dt_control_change_cursor(GDK_LEFT_PTR);
711       gdk_event_handler_set((GdkEventFunc)gtk_main_do_event, NULL, NULL);
712 
713       handled = TRUE;
714     }
715     break;
716 
717     case GDK_ENTER_NOTIFY:
718     case GDK_LEAVE_NOTIFY:
719     {
720       GtkWidget *event_widget = gtk_get_event_widget(event);
721       if(event_widget)
722       {
723         gchar *help_url = get_help_url(event_widget);
724         if(help_url)
725         {
726           // TODO: find a better way to tell the user that the hovered widget has a help link
727           dt_cursor_t cursor = event->type == GDK_ENTER_NOTIFY ? GDK_QUESTION_ARROW : GDK_X_CURSOR;
728           dt_control_allow_change_cursor();
729           dt_control_change_cursor(cursor);
730           dt_control_forbid_change_cursor();
731         }
732       }
733       break;
734     }
735     default:
736       break;
737   }
738 
739   if(!handled) gtk_main_do_event(event);
740 }
741 
_lib_help_button_clicked(GtkWidget * widget,gpointer user_data)742 static void _lib_help_button_clicked(GtkWidget *widget, gpointer user_data)
743 {
744   dt_control_change_cursor(GDK_X_CURSOR);
745   dt_control_forbid_change_cursor();
746   gdk_event_handler_set(_main_do_event, user_data, NULL);
747 }
748 
749 
750 
init_key_accels(dt_lib_module_t * self)751 void init_key_accels(dt_lib_module_t *self)
752 {
753   dt_accel_register_global(NC_("accel", "grouping"), 0, 0);
754   dt_accel_register_global(NC_("accel", "preferences"), 0, 0);
755 
756   dt_accel_register_global(NC_("accel", "thumbnail overlays/no overlays"), 0, 0);
757   dt_accel_register_global(NC_("accel", "thumbnail overlays/overlays on mouse hover"), 0, 0);
758   dt_accel_register_global(NC_("accel", "thumbnail overlays/extended overlays on mouse hover"), 0, 0);
759   dt_accel_register_global(NC_("accel", "thumbnail overlays/permanent overlays"), 0, 0);
760   dt_accel_register_global(NC_("accel", "thumbnail overlays/permanent extended overlays"), 0, 0);
761   dt_accel_register_global(NC_("accel", "thumbnail overlays/permanent overlays extended on mouse hover"), 0, 0);
762   dt_accel_register_global(NC_("accel", "thumbnail overlays/overlays block on mouse hover"), 0, 0);
763 }
764 
connect_key_accels(dt_lib_module_t * self)765 void connect_key_accels(dt_lib_module_t *self)
766 {
767   dt_lib_tool_preferences_t *d = (dt_lib_tool_preferences_t *)self->data;
768 
769   dt_accel_connect_button_lib_as_global(self, "grouping", d->grouping_button);
770   dt_accel_connect_button_lib_as_global(self, "preferences", d->preferences_button);
771 
772   dt_accel_connect_lib_as_global( self, "thumbnail overlays/no overlays",
773       g_cclosure_new(G_CALLBACK(_overlays_accels_callback), GINT_TO_POINTER(DT_THUMBNAIL_OVERLAYS_NONE), NULL));
774   dt_accel_connect_lib_as_global(self, "thumbnail overlays/overlays on mouse hover",
775                        g_cclosure_new(G_CALLBACK(_overlays_accels_callback),
776                                       GINT_TO_POINTER(DT_THUMBNAIL_OVERLAYS_HOVER_NORMAL), NULL));
777   dt_accel_connect_lib_as_global(self, "thumbnail overlays/extended overlays on mouse hover",
778                        g_cclosure_new(G_CALLBACK(_overlays_accels_callback),
779                                       GINT_TO_POINTER(DT_THUMBNAIL_OVERLAYS_HOVER_EXTENDED), NULL));
780   dt_accel_connect_lib_as_global(self, "thumbnail overlays/permanent overlays",
781                        g_cclosure_new(G_CALLBACK(_overlays_accels_callback),
782                                       GINT_TO_POINTER(DT_THUMBNAIL_OVERLAYS_ALWAYS_NORMAL), NULL));
783   dt_accel_connect_lib_as_global(self, "thumbnail overlays/permanent extended overlays",
784                        g_cclosure_new(G_CALLBACK(_overlays_accels_callback),
785                                       GINT_TO_POINTER(DT_THUMBNAIL_OVERLAYS_ALWAYS_EXTENDED), NULL));
786   dt_accel_connect_lib_as_global(
787       self, "thumbnail overlays/permanent overlays extended on mouse hover",
788       g_cclosure_new(G_CALLBACK(_overlays_accels_callback), GINT_TO_POINTER(DT_THUMBNAIL_OVERLAYS_MIXED), NULL));
789   dt_accel_connect_lib_as_global(self, "thumbnail overlays/overlays block on mouse hover",
790                        g_cclosure_new(G_CALLBACK(_overlays_accels_callback),
791                                       GINT_TO_POINTER(DT_THUMBNAIL_OVERLAYS_HOVER_BLOCK), NULL));
792 }
793 
794 #ifdef USE_LUA
795 
grouping_member(lua_State * L)796 static int grouping_member(lua_State *L)
797 {
798   dt_lib_module_t *self = *(dt_lib_module_t **)lua_touserdata(L, 1);
799   dt_lib_tool_preferences_t *d = (dt_lib_tool_preferences_t *)self->data;
800   if(lua_gettop(L) != 3)
801   {
802     lua_pushboolean(L, darktable.gui->grouping);
803     return 1;
804   }
805   else
806   {
807     gboolean value = lua_toboolean(L, 3);
808     if(darktable.gui->grouping != value)
809     {
810       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->grouping_button), value);
811     }
812   }
813   return 0;
814 }
815 
show_overlays_member(lua_State * L)816 static int show_overlays_member(lua_State *L)
817 {
818   dt_lib_module_t *self = *(dt_lib_module_t **)lua_touserdata(L, 1);
819   dt_lib_tool_preferences_t *d = (dt_lib_tool_preferences_t *)self->data;
820   if(lua_gettop(L) != 3)
821   {
822     lua_pushboolean(L, darktable.gui->show_overlays);
823     return 1;
824   }
825   else
826   {
827     gboolean value = lua_toboolean(L, 3);
828     if(darktable.gui->show_overlays != value)
829     {
830       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->overlays_button), value);
831     }
832   }
833   return 0;
834 }
835 
init(struct dt_lib_module_t * self)836 void init(struct dt_lib_module_t *self)
837 {
838   lua_State *L = darktable.lua_state.state;
839   int my_type = dt_lua_module_entry_get_type(L, "lib", self->plugin_name);
840 
841   lua_pushcfunction(L, grouping_member);
842   dt_lua_gtk_wrap(L);
843   dt_lua_type_register_type(L, my_type, "grouping");
844   lua_pushcfunction(L, show_overlays_member);
845   dt_lua_gtk_wrap(L);
846   dt_lua_type_register_type(L, my_type, "show_overlays");
847 
848   lua_pushcfunction(L, dt_lua_event_multiinstance_register);
849   lua_pushcfunction(L, dt_lua_event_multiinstance_destroy);
850   lua_pushcfunction(L, dt_lua_event_multiinstance_trigger);
851   dt_lua_event_add(L, "global_toolbox-grouping_toggle");
852 
853   lua_pushcfunction(L, dt_lua_event_multiinstance_register);
854   lua_pushcfunction(L, dt_lua_event_multiinstance_destroy);
855   lua_pushcfunction(L, dt_lua_event_multiinstance_trigger);
856   dt_lua_event_add(L, "global_toolbox-overlay_toggle");
857 }
858 
859 #endif // USE_LUA
860 
861 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
862 // vim: shiftwidth=2 expandtab tabstop=2 cindent
863 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
864