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