1 /*
2 This file is part of darktable,
3 Copyright (C) 2009-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 "views/view.h"
20 #include "bauhaus/bauhaus.h"
21 #include "common/collection.h"
22 #include "common/darktable.h"
23 #include "common/debug.h"
24 #include "common/focus_peaking.h"
25 #include "common/image_cache.h"
26 #include "common/mipmap_cache.h"
27 #include "common/module.h"
28 #include "common/selection.h"
29 #include "common/undo.h"
30 #include "common/usermanual_url.h"
31 #include "control/conf.h"
32 #include "control/control.h"
33 #include "develop/develop.h"
34 #include "dtgtk/button.h"
35 #include "dtgtk/expander.h"
36 #include "dtgtk/thumbtable.h"
37 #include "gui/accelerators.h"
38 #include "gui/draw.h"
39 #include "gui/gtk.h"
40 #include "libs/lib.h"
41 #ifdef GDK_WINDOWING_QUARTZ
42 #include "osx/osx.h"
43 #endif
44
45 #include <glib.h>
46 #include <math.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <strings.h>
51
52 #define DECORATION_SIZE_LIMIT 40
53
54 static void dt_view_manager_load_modules(dt_view_manager_t *vm);
55 static int dt_view_load_module(void *v, const char *libname, const char *module_name);
56 static void dt_view_unload_module(dt_view_t *view);
57
dt_view_manager_init(dt_view_manager_t * vm)58 void dt_view_manager_init(dt_view_manager_t *vm)
59 {
60 /* prepare statements */
61 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT imgid FROM main.selected_images "
62 "WHERE imgid = ?1", -1, &vm->statements.is_selected, NULL);
63 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "DELETE FROM main.selected_images WHERE imgid = ?1",
64 -1, &vm->statements.delete_from_selected, NULL);
65 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
66 "INSERT OR IGNORE INTO main.selected_images VALUES (?1)", -1,
67 &vm->statements.make_selected, NULL);
68 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT num FROM main.history WHERE imgid = ?1", -1,
69 &vm->statements.have_history, NULL);
70 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT color FROM main.color_labels WHERE imgid=?1",
71 -1, &vm->statements.get_color, NULL);
72 DT_DEBUG_SQLITE3_PREPARE_V2(
73 dt_database_get(darktable.db),
74 "SELECT id FROM main.images WHERE group_id = (SELECT group_id FROM main.images WHERE id=?1) AND id != ?2",
75 -1, &vm->statements.get_grouped, NULL);
76
77 dt_view_manager_load_modules(vm);
78
79 // Modules loaded, let's handle specific cases
80 for(GList *iter = vm->views; iter; iter = g_list_next(iter))
81 {
82 dt_view_t *view = (dt_view_t *)iter->data;
83 if(!strcmp(view->module_name, "darkroom"))
84 {
85 darktable.develop = (dt_develop_t *)view->data;
86 break;
87 }
88 }
89
90 vm->current_view = NULL;
91 vm->audio.audio_player_id = -1;
92 }
93
dt_view_manager_gui_init(dt_view_manager_t * vm)94 void dt_view_manager_gui_init(dt_view_manager_t *vm)
95 {
96 for(GList *iter = vm->views; iter; iter = g_list_next(iter))
97 {
98 dt_view_t *view = (dt_view_t *)iter->data;
99 if(view->gui_init) view->gui_init(view);
100 }
101 }
102
dt_view_manager_cleanup(dt_view_manager_t * vm)103 void dt_view_manager_cleanup(dt_view_manager_t *vm)
104 {
105 for(GList *iter = vm->views; iter; iter = g_list_next(iter)) dt_view_unload_module((dt_view_t *)iter->data);
106 g_list_free_full(vm->views, free);
107 vm->views = NULL;
108 }
109
dt_view_manager_get_current_view(dt_view_manager_t * vm)110 const dt_view_t *dt_view_manager_get_current_view(dt_view_manager_t *vm)
111 {
112 return vm->current_view;
113 }
114
115 // we want a stable order of views, for example for viewswitcher.
116 // anything not hardcoded will be put alphabetically wrt. localised names.
sort_views(gconstpointer a,gconstpointer b)117 static gint sort_views(gconstpointer a, gconstpointer b)
118 {
119 static const char *view_order[] = {"lighttable", "darkroom"};
120 static const int n_view_order = G_N_ELEMENTS(view_order);
121
122 dt_view_t *av = (dt_view_t *)a;
123 dt_view_t *bv = (dt_view_t *)b;
124 const char *aname = av->name(av);
125 const char *bname = bv->name(bv);
126 int apos = n_view_order;
127 int bpos = n_view_order;
128
129 for(int i = 0; i < n_view_order; i++)
130 {
131 if(!strcmp(av->module_name, view_order[i])) apos = i;
132 if(!strcmp(bv->module_name, view_order[i])) bpos = i;
133 }
134
135 // order will be zero iff apos == bpos which can only happen when both views are not in view_order
136 const int order = apos - bpos;
137 return order ? order : strcmp(aname, bname);
138 }
139
dt_view_manager_load_modules(dt_view_manager_t * vm)140 static void dt_view_manager_load_modules(dt_view_manager_t *vm)
141 {
142 vm->views = dt_module_load_modules("/views", sizeof(dt_view_t), dt_view_load_module, NULL, sort_views);
143 }
144
145 /* default flags for view which does not implement the flags() function */
default_flags()146 static uint32_t default_flags()
147 {
148 return 0;
149 }
150
151 /** load a view module */
dt_view_load_module(void * v,const char * libname,const char * module_name)152 static int dt_view_load_module(void *v, const char *libname, const char *module_name)
153 {
154 dt_view_t *module = (dt_view_t *)v;
155 g_strlcpy(module->module_name, module_name, sizeof(module->module_name));
156
157 #define INCLUDE_API_FROM_MODULE_LOAD "view_load_module"
158 #include "views/view_api.h"
159
160 module->data = NULL;
161 module->vscroll_size = module->vscroll_viewport_size = 1.0;
162 module->hscroll_size = module->hscroll_viewport_size = 1.0;
163 module->vscroll_pos = module->hscroll_pos = 0.0;
164 module->height = module->width = 100; // set to non-insane defaults before first expose/configure.
165 module->accel_closures = NULL;
166
167 if(!strcmp(module->module_name, "darkroom")) darktable.develop = (dt_develop_t *)module->data;
168
169 #ifdef USE_LUA
170 dt_lua_register_view(darktable.lua_state.state, module);
171 #endif
172
173 if(module->init) module->init(module);
174 if(darktable.gui && module->init_key_accels) module->init_key_accels(module);
175
176 return 0;
177 }
178
179 /** unload, cleanup */
dt_view_unload_module(dt_view_t * view)180 static void dt_view_unload_module(dt_view_t *view)
181 {
182 if(view->cleanup) view->cleanup(view);
183
184 g_slist_free(view->accel_closures);
185
186 if(view->module) g_module_close(view->module);
187 }
188
dt_vm_remove_child(GtkWidget * widget,gpointer data)189 void dt_vm_remove_child(GtkWidget *widget, gpointer data)
190 {
191 gtk_container_remove(GTK_CONTAINER(data), widget);
192 }
193
194 /*
195 When expanders get destroyed, they destroy the child
196 so remove the child before that
197 */
_remove_child(GtkWidget * child,GtkContainer * container)198 static void _remove_child(GtkWidget *child,GtkContainer *container)
199 {
200 if(DTGTK_IS_EXPANDER(child))
201 {
202 GtkWidget * evb = dtgtk_expander_get_body_event_box(DTGTK_EXPANDER(child));
203 gtk_container_remove(GTK_CONTAINER(evb),dtgtk_expander_get_body(DTGTK_EXPANDER(child)));
204 gtk_widget_destroy(child);
205 }
206 else
207 {
208 gtk_container_remove(container,child);
209 }
210 }
211
dt_view_manager_switch(dt_view_manager_t * vm,const char * view_name)212 int dt_view_manager_switch(dt_view_manager_t *vm, const char *view_name)
213 {
214 gboolean switching_to_none = *view_name == '\0';
215 dt_view_t *new_view = NULL;
216
217 if(!switching_to_none)
218 {
219 for(GList *iter = vm->views; iter; iter = g_list_next(iter))
220 {
221 dt_view_t *v = (dt_view_t *)iter->data;
222 if(!strcmp(v->module_name, view_name))
223 {
224 new_view = v;
225 break;
226 }
227 }
228 if(!new_view) return 1; // the requested view doesn't exist
229 }
230
231 return dt_view_manager_switch_by_view(vm, new_view);
232 }
233
dt_view_manager_switch_by_view(dt_view_manager_t * vm,const dt_view_t * nv)234 int dt_view_manager_switch_by_view(dt_view_manager_t *vm, const dt_view_t *nv)
235 {
236 dt_view_t *old_view = vm->current_view;
237 dt_view_t *new_view = (dt_view_t *)nv; // views belong to us, we can de-const them :-)
238
239 // Before switching views, restore accelerators if disabled
240 if(!darktable.control->key_accelerators_on) dt_control_key_accelerators_on(darktable.control);
241
242 // reset the cursor to the default one
243 dt_control_change_cursor(GDK_LEFT_PTR);
244
245 // also ignore what scrolling there was previously happening
246 memset(darktable.gui->scroll_to, 0, sizeof(darktable.gui->scroll_to));
247
248 // destroy old module list
249
250 /* clear the undo list, for now we do this unconditionally. At some point we will probably want to clear
251 only part
252 of the undo list. This should probably done with a view proxy routine returning the type of undo to
253 remove. */
254 dt_undo_clear(darktable.undo, DT_UNDO_ALL);
255
256 /* Special case when entering nothing (just before leaving dt) */
257 if(!new_view)
258 {
259 if(old_view)
260 {
261 /* leave the current view*/
262 if(old_view->leave) old_view->leave(old_view);
263
264 /* iterator plugins and cleanup plugins in current view */
265 for(GList *iter = darktable.lib->plugins; iter; iter = g_list_next(iter))
266 {
267 dt_lib_module_t *plugin = (dt_lib_module_t *)(iter->data);
268
269 /* does this module belong to current view ?*/
270 if(dt_lib_is_visible_in_view(plugin, old_view))
271 {
272 if(plugin->view_leave) plugin->view_leave(plugin, old_view, NULL);
273 plugin->gui_cleanup(plugin);
274 plugin->data = NULL;
275 dt_accel_disconnect_list(&plugin->accel_closures);
276 plugin->widget = NULL;
277 }
278 }
279 }
280
281 /* remove all widgets in all containers */
282 for(int l = 0; l < DT_UI_CONTAINER_SIZE; l++)
283 dt_ui_container_destroy_children(darktable.gui->ui, l);
284 vm->current_view = NULL;
285
286 /* remove sticky accels window */
287 if(vm->accels_window.window) dt_view_accels_hide(vm);
288 return 0;
289 }
290
291 // invariant: new_view != NULL after this point
292 assert(new_view != NULL);
293
294 if(new_view->try_enter)
295 {
296 int error = new_view->try_enter(new_view);
297 if(error) return error;
298 }
299
300 /* cleanup current view before initialization of new */
301 if(old_view)
302 {
303 /* leave current view */
304 if(old_view->leave) old_view->leave(old_view);
305 dt_accel_disconnect_list(&old_view->accel_closures);
306
307 /* iterator plugins and cleanup plugins in current view */
308 for(GList *iter = darktable.lib->plugins; iter; iter = g_list_next(iter))
309 {
310 dt_lib_module_t *plugin = (dt_lib_module_t *)(iter->data);
311
312 /* does this module belong to current view ?*/
313 if(dt_lib_is_visible_in_view(plugin, old_view))
314 {
315 if(plugin->view_leave) plugin->view_leave(plugin, old_view, new_view);
316 dt_accel_disconnect_list(&plugin->accel_closures);
317 }
318 }
319
320 /* remove all widets in all containers */
321 for(int l = 0; l < DT_UI_CONTAINER_SIZE; l++)
322 dt_ui_container_foreach(darktable.gui->ui, l,(GtkCallback)_remove_child);
323 }
324
325 /* change current view to the new view */
326 vm->current_view = new_view;
327
328 /* update thumbtable accels */
329 dt_thumbtable_update_accels_connection(dt_ui_thumbtable(darktable.gui->ui), new_view->view(new_view));
330
331 /* restore visible stat of panels for the new view */
332 dt_ui_restore_panels(darktable.gui->ui);
333
334 /* lets add plugins related to new view into panels.
335 * this has to be done in reverse order to have the lowest position at the bottom! */
336 for(GList *iter = g_list_last(darktable.lib->plugins); iter; iter = g_list_previous(iter))
337 {
338 dt_lib_module_t *plugin = (dt_lib_module_t *)(iter->data);
339 if(dt_lib_is_visible_in_view(plugin, new_view))
340 {
341
342 /* try get the module expander */
343 GtkWidget *w = dt_lib_gui_get_expander(plugin);
344
345 if(plugin->connect_key_accels) plugin->connect_key_accels(plugin);
346 dt_lib_connect_common_accels(plugin);
347
348 /* if we didn't get an expander let's add the widget */
349 if(!w) w = plugin->widget;
350
351 dt_gui_add_help_link(w, dt_get_help_url(plugin->plugin_name));
352 // some plugins help links depend on the view
353 if(!strcmp(plugin->plugin_name,"module_toolbox")
354 || !strcmp(plugin->plugin_name,"view_toolbox"))
355 {
356 dt_view_type_flags_t view_type = new_view->view(new_view);
357 if(view_type == DT_VIEW_LIGHTTABLE)
358 dt_gui_add_help_link(w, dt_get_help_url("lighttable_mode"));
359 if(view_type == DT_VIEW_DARKROOM)
360 dt_gui_add_help_link(w, dt_get_help_url("darkroom_bottom_panel"));
361 }
362
363
364 /* add module to its container */
365 dt_ui_container_add_widget(darktable.gui->ui, plugin->container(plugin), w);
366 }
367 }
368
369 /* hide/show modules as last config */
370 for(GList *iter = darktable.lib->plugins; iter; iter = g_list_next(iter))
371 {
372 dt_lib_module_t *plugin = (dt_lib_module_t *)(iter->data);
373 if(dt_lib_is_visible_in_view(plugin, new_view))
374 {
375 /* set expanded if last mode was that */
376 char var[1024];
377 gboolean expanded = FALSE;
378 gboolean visible = dt_lib_is_visible(plugin);
379 if(plugin->expandable(plugin))
380 {
381 snprintf(var, sizeof(var), "plugins/%s/%s/expanded", new_view->module_name, plugin->plugin_name);
382 expanded = dt_conf_get_bool(var);
383 dt_lib_gui_set_expanded(plugin, expanded);
384 dt_lib_set_visible(plugin, visible);
385 }
386 else
387 {
388 /* show/hide plugin widget depending on expanded flag or if plugin
389 not is expandeable() */
390 if(visible)
391 gtk_widget_show_all(plugin->widget);
392 else
393 gtk_widget_hide(plugin->widget);
394 }
395 if(plugin->view_enter) plugin->view_enter(plugin, old_view, new_view);
396 }
397 }
398
399 /* enter view. crucially, do this before initing the plugins below,
400 as e.g. modulegroups requires the dr stuff to be inited. */
401 if(new_view->enter) new_view->enter(new_view);
402 if(new_view->connect_key_accels) new_view->connect_key_accels(new_view);
403
404 /* update the scrollbars */
405 dt_ui_update_scrollbars(darktable.gui->ui);
406
407 /* update sticky accels window */
408 if(vm->accels_window.window && vm->accels_window.sticky) dt_view_accels_refresh(vm);
409
410 /* raise view changed signal */
411 DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_VIEWMANAGER_VIEW_CHANGED, old_view, new_view);
412
413 // update log visibility
414 DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_CONTROL_LOG_REDRAW);
415
416 // update toast visibility
417 DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_CONTROL_TOAST_REDRAW);
418 return 0;
419 }
420
dt_view_manager_name(dt_view_manager_t * vm)421 const char *dt_view_manager_name(dt_view_manager_t *vm)
422 {
423 if(!vm->current_view) return "";
424 if(vm->current_view->name)
425 return vm->current_view->name(vm->current_view);
426 else
427 return vm->current_view->module_name;
428 }
429
dt_view_manager_expose(dt_view_manager_t * vm,cairo_t * cr,int32_t width,int32_t height,int32_t pointerx,int32_t pointery)430 void dt_view_manager_expose(dt_view_manager_t *vm, cairo_t *cr, int32_t width, int32_t height,
431 int32_t pointerx, int32_t pointery)
432 {
433 if(!vm->current_view)
434 {
435 dt_gui_gtk_set_source_rgb(cr, DT_GUI_COLOR_BG);
436 cairo_paint(cr);
437 return;
438 }
439 vm->current_view->width = width;
440 vm->current_view->height = height;
441
442 if(vm->current_view->expose)
443 {
444 /* expose the view */
445 cairo_rectangle(cr, 0, 0, vm->current_view->width, vm->current_view->height);
446 cairo_clip(cr);
447 cairo_new_path(cr);
448 cairo_save(cr);
449 float px = pointerx, py = pointery;
450 if(pointery > vm->current_view->height)
451 {
452 px = 10000.0;
453 py = -1.0;
454 }
455 vm->current_view->expose(vm->current_view, cr, vm->current_view->width, vm->current_view->height, px, py);
456
457 cairo_restore(cr);
458 /* expose plugins */
459 for(const GList *plugins = g_list_last(darktable.lib->plugins); plugins; plugins = g_list_previous(plugins))
460 {
461 dt_lib_module_t *plugin = (dt_lib_module_t *)(plugins->data);
462
463 /* does this module belong to current view ?*/
464 if(plugin->gui_post_expose
465 && dt_lib_is_visible_in_view(plugin, vm->current_view))
466 plugin->gui_post_expose(plugin, cr, vm->current_view->width, vm->current_view->height, px, py);
467 }
468 }
469 }
470
dt_view_manager_reset(dt_view_manager_t * vm)471 void dt_view_manager_reset(dt_view_manager_t *vm)
472 {
473 if(!vm->current_view) return;
474 if(vm->current_view->reset) vm->current_view->reset(vm->current_view);
475 }
476
dt_view_manager_mouse_leave(dt_view_manager_t * vm)477 void dt_view_manager_mouse_leave(dt_view_manager_t *vm)
478 {
479 if(!vm->current_view) return;
480 dt_view_t *v = vm->current_view;
481
482 /* lets check if any plugins want to handle mouse move */
483 gboolean handled = FALSE;
484 for(const GList *plugins = g_list_last(darktable.lib->plugins);
485 plugins;
486 plugins = g_list_previous(plugins))
487 {
488 dt_lib_module_t *plugin = (dt_lib_module_t *)(plugins->data);
489
490 /* does this module belong to current view ?*/
491 if(plugin->mouse_leave && dt_lib_is_visible_in_view(plugin, v))
492 if(plugin->mouse_leave(plugin)) handled = TRUE;
493 }
494
495 /* if not handled by any plugin let pass to view handler*/
496 if(!handled && v->mouse_leave) v->mouse_leave(v);
497 }
498
dt_view_manager_mouse_enter(dt_view_manager_t * vm)499 void dt_view_manager_mouse_enter(dt_view_manager_t *vm)
500 {
501 if(!vm->current_view) return;
502 if(vm->current_view->mouse_enter) vm->current_view->mouse_enter(vm->current_view);
503 }
504
dt_view_manager_mouse_moved(dt_view_manager_t * vm,double x,double y,double pressure,int which)505 void dt_view_manager_mouse_moved(dt_view_manager_t *vm, double x, double y, double pressure, int which)
506 {
507 if(!vm->current_view) return;
508 dt_view_t *v = vm->current_view;
509
510 /* lets check if any plugins want to handle mouse move */
511 gboolean handled = FALSE;
512 for(const GList *plugins = g_list_last(darktable.lib->plugins);
513 plugins;
514 plugins = g_list_previous(plugins))
515 {
516 dt_lib_module_t *plugin = (dt_lib_module_t *)(plugins->data);
517
518 /* does this module belong to current view ?*/
519 if(plugin->mouse_moved && dt_lib_is_visible_in_view(plugin, v))
520 if(plugin->mouse_moved(plugin, x, y, pressure, which)) handled = TRUE;
521 }
522
523 /* if not handled by any plugin let pass to view handler*/
524 if(!handled && v->mouse_moved) v->mouse_moved(v, x, y, pressure, which);
525 }
526
dt_view_manager_button_released(dt_view_manager_t * vm,double x,double y,int which,uint32_t state)527 int dt_view_manager_button_released(dt_view_manager_t *vm, double x, double y, int which, uint32_t state)
528 {
529 if(!vm->current_view) return 0;
530 dt_view_t *v = vm->current_view;
531
532 /* lets check if any plugins want to handle button press */
533 gboolean handled = FALSE;
534 for(const GList *plugins = g_list_last(darktable.lib->plugins);
535 plugins;
536 plugins = g_list_previous(plugins))
537 {
538 dt_lib_module_t *plugin = (dt_lib_module_t *)(plugins->data);
539
540 /* does this module belong to current view ?*/
541 if(plugin->button_released && dt_lib_is_visible_in_view(plugin, v))
542 if(plugin->button_released(plugin, x, y, which, state)) handled = TRUE;
543 }
544
545 if(handled)
546 return 1;
547 /* if not handled by any plugin let pass to view handler*/
548 else if(v->button_released)
549 v->button_released(v, x, y, which, state);
550
551 return 0;
552 }
553
dt_view_manager_button_pressed(dt_view_manager_t * vm,double x,double y,double pressure,int which,int type,uint32_t state)554 int dt_view_manager_button_pressed(dt_view_manager_t *vm, double x, double y, double pressure, int which,
555 int type, uint32_t state)
556 {
557 if(!vm->current_view) return 0;
558 dt_view_t *v = vm->current_view;
559
560 /* lets check if any plugins want to handle button press */
561 gboolean handled = FALSE;
562 for(const GList *plugins = g_list_last(darktable.lib->plugins);
563 plugins && !handled;
564 plugins = g_list_previous(plugins))
565 {
566 dt_lib_module_t *plugin = (dt_lib_module_t *)(plugins->data);
567
568 /* does this module belong to current view ?*/
569 if(plugin->button_pressed && dt_lib_is_visible_in_view(plugin, v))
570 if(plugin->button_pressed(plugin, x, y, pressure, which, type, state)) handled = TRUE;
571 }
572
573 if(handled) return 1;
574 /* if not handled by any plugin let pass to view handler*/
575 else if(v->button_pressed)
576 return v->button_pressed(v, x, y, pressure, which, type, state);
577
578 return 0;
579 }
580
dt_view_manager_key_pressed(dt_view_manager_t * vm,guint key,guint state)581 int dt_view_manager_key_pressed(dt_view_manager_t *vm, guint key, guint state)
582 {
583 if(!vm->current_view) return 0;
584 if(vm->current_view->key_pressed) return vm->current_view->key_pressed(vm->current_view, key, state);
585 return 0;
586 }
587
dt_view_manager_key_released(dt_view_manager_t * vm,guint key,guint state)588 int dt_view_manager_key_released(dt_view_manager_t *vm, guint key, guint state)
589 {
590 if(!vm->current_view) return 0;
591 if(vm->current_view->key_released) return vm->current_view->key_released(vm->current_view, key, state);
592 return 0;
593 }
594
dt_view_manager_configure(dt_view_manager_t * vm,int width,int height)595 void dt_view_manager_configure(dt_view_manager_t *vm, int width, int height)
596 {
597 for(GList *iter = vm->views; iter; iter = g_list_next(iter))
598 {
599 // this is necessary for all
600 dt_view_t *v = (dt_view_t *)iter->data;
601 v->width = width;
602 v->height = height;
603 if(v->configure) v->configure(v, width, height);
604 }
605 }
606
dt_view_manager_scrolled(dt_view_manager_t * vm,double x,double y,int up,int state)607 void dt_view_manager_scrolled(dt_view_manager_t *vm, double x, double y, int up, int state)
608 {
609 if(!vm->current_view) return;
610 if(vm->current_view->scrolled) vm->current_view->scrolled(vm->current_view, x, y, up, state);
611 }
612
dt_view_manager_scrollbar_changed(dt_view_manager_t * vm,double x,double y)613 void dt_view_manager_scrollbar_changed(dt_view_manager_t *vm, double x, double y)
614 {
615 if(!vm->current_view) return;
616 if(vm->current_view->scrollbar_changed)
617 vm->current_view->scrollbar_changed(vm->current_view, x, y);
618 }
619
dt_view_set_scrollbar(dt_view_t * view,float hpos,float hlower,float hsize,float hwinsize,float vpos,float vlower,float vsize,float vwinsize)620 void dt_view_set_scrollbar(dt_view_t *view, float hpos, float hlower, float hsize, float hwinsize, float vpos,
621 float vlower, float vsize, float vwinsize)
622 {
623 if (view->vscroll_pos == vpos
624 && view->vscroll_lower == vlower
625 && view->vscroll_size == vsize
626 && view->vscroll_viewport_size == vwinsize
627 && view->hscroll_pos == hpos
628 && view->hscroll_lower == hlower
629 && view->hscroll_size == hsize
630 && view->hscroll_viewport_size == hwinsize)
631 return;
632
633 view->vscroll_pos = vpos;
634 view->vscroll_lower = vlower;
635 view->vscroll_size = vsize;
636 view->vscroll_viewport_size = vwinsize;
637 view->hscroll_pos = hpos;
638 view->hscroll_lower = hlower;
639 view->hscroll_size = hsize;
640 view->hscroll_viewport_size = hwinsize;
641
642 GtkWidget *widget;
643 widget = darktable.gui->widgets.left_border;
644 gtk_widget_queue_draw(widget);
645 widget = darktable.gui->widgets.right_border;
646 gtk_widget_queue_draw(widget);
647 widget = darktable.gui->widgets.bottom_border;
648 gtk_widget_queue_draw(widget);
649 widget = darktable.gui->widgets.top_border;
650 gtk_widget_queue_draw(widget);
651
652 if (!darktable.gui->scrollbars.dragging)
653 dt_ui_update_scrollbars(darktable.gui->ui);
654 }
655
_images_to_act_on_find_custom(gconstpointer a,gconstpointer b)656 static int _images_to_act_on_find_custom(gconstpointer a, gconstpointer b)
657 {
658 return (GPOINTER_TO_INT(a) != GPOINTER_TO_INT(b));
659 }
_images_to_act_on_insert_in_list(GList ** list,const int imgid,gboolean only_visible)660 static void _images_to_act_on_insert_in_list(GList **list, const int imgid, gboolean only_visible)
661 {
662 if(only_visible)
663 {
664 if(!g_list_find_custom(*list, GINT_TO_POINTER(imgid), _images_to_act_on_find_custom))
665 *list = g_list_append(*list, GINT_TO_POINTER(imgid));
666 return;
667 }
668
669 const dt_image_t *image = dt_image_cache_get(darktable.image_cache, imgid, 'r');
670 if(image)
671 {
672 const int img_group_id = image->group_id;
673 dt_image_cache_read_release(darktable.image_cache, image);
674
675 if(!darktable.gui
676 || !darktable.gui->grouping
677 || darktable.gui->expanded_group_id == img_group_id
678 || !dt_selection_get_collection(darktable.selection))
679 {
680 if(!g_list_find_custom(*list, GINT_TO_POINTER(imgid), _images_to_act_on_find_custom))
681 *list = g_list_append(*list, GINT_TO_POINTER(imgid));
682 }
683 else
684 {
685 sqlite3_stmt *stmt;
686 gchar *query = dt_util_dstrcat(
687 NULL,
688 "SELECT id"
689 " FROM main.images"
690 " WHERE group_id = %d AND id IN (%s)",
691 img_group_id, dt_collection_get_query_no_group(dt_selection_get_collection(darktable.selection)));
692 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
693 while(sqlite3_step(stmt) == SQLITE_ROW)
694 {
695 const int imgidg = sqlite3_column_int(stmt, 0);
696 if(!g_list_find_custom(*list, GINT_TO_POINTER(imgidg), _images_to_act_on_find_custom))
697 *list = g_list_append(*list, GINT_TO_POINTER(imgidg));
698 }
699 sqlite3_finalize(stmt);
700 g_free(query);
701 }
702 }
703 }
704
705 // get the list of images to act on during global changes (libs, accels)
dt_view_get_images_to_act_on(const gboolean only_visible,const gboolean force,const gboolean ordered)706 const GList *dt_view_get_images_to_act_on(const gboolean only_visible, const gboolean force,
707 const gboolean ordered)
708 {
709 /** Here's how it works
710 *
711 * mouse over| x | x | x | | |
712 * mouse inside table| x | x | | | |
713 * mouse inside selection| x | | | | |
714 * active images| ? | ? | x | | x |
715 * | | | | | |
716 * | S | O | O | S | A |
717 * S = selection ; O = mouseover ; A = active images
718 * the mouse can be outside thumbtable in case of filmstrip + mouse in center widget
719 *
720 * if only_visible is FALSE, then it will add also not visible images because of grouping
721 * force define if we try to use cache or not
722 * if ordered is TRUE, we return the list in the gui order. Otherwise the order is undefined (but quicker)
723 **/
724
725 const int mouseover = dt_control_get_mouse_over_id();
726
727 // if possible, we return the cached list
728 if(!force
729 && darktable.view_manager->act_on.ok
730 && darktable.view_manager->act_on.image_over == mouseover
731 && darktable.view_manager->act_on.ordered == ordered
732 && darktable.view_manager->act_on.inside_table
733 == dt_ui_thumbtable(darktable.gui->ui)->mouse_inside
734 && g_slist_length(darktable.view_manager->act_on.active_imgs)
735 == g_slist_length(darktable.view_manager->active_images))
736 {
737 // we test active images if mouse outside table
738 gboolean ok = TRUE;
739 if(!dt_ui_thumbtable(darktable.gui->ui)->mouse_inside
740 && darktable.view_manager->act_on.active_imgs)
741 {
742 GSList *l1 = darktable.view_manager->act_on.active_imgs;
743 GSList *l2 = darktable.view_manager->active_images;
744 while(l1 && l2)
745 {
746 if(GPOINTER_TO_INT(l1->data) != GPOINTER_TO_INT(l2->data))
747 {
748 ok = FALSE;
749 break;
750 }
751 l2 = g_slist_next(l2);
752 l1 = g_slist_next(l1);
753 }
754 }
755 if(ok) return darktable.view_manager->act_on.images;
756 }
757
758 GList *l = NULL;
759 gboolean inside_sel = FALSE;
760 if(mouseover > 0)
761 {
762 // column 1,2,3
763 if(dt_ui_thumbtable(darktable.gui->ui)->mouse_inside)
764 {
765 // column 1,2
766 sqlite3_stmt *stmt;
767 gchar *query = dt_util_dstrcat(NULL, "SELECT imgid FROM main.selected_images WHERE imgid=%d", mouseover);
768 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
769 if(stmt != NULL && sqlite3_step(stmt) == SQLITE_ROW)
770 {
771 inside_sel = TRUE;
772 sqlite3_finalize(stmt);
773 }
774 g_free(query);
775
776 if(inside_sel)
777 {
778 // column 1
779
780 // first, we try to return cached list if we were already
781 // inside sel and the selection has not changed
782 if(!force
783 && darktable.view_manager->act_on.ok
784 && darktable.view_manager->act_on.image_over_inside_sel
785 && darktable.view_manager->act_on.inside_table
786 && darktable.view_manager->act_on.ordered == ordered)
787 {
788 return darktable.view_manager->act_on.images;
789 }
790
791 // we return the list of the selection
792 l = dt_selection_get_list(darktable.selection, only_visible, ordered);
793 }
794 else
795 {
796 // column 2
797 _images_to_act_on_insert_in_list(&l, mouseover, only_visible);
798 }
799 }
800 else
801 {
802 // column 3
803 _images_to_act_on_insert_in_list(&l, mouseover, only_visible);
804 // be absolutely sure we have the id in the list (in darkroom,
805 // the active image can be out of collection)
806 if(!only_visible) _images_to_act_on_insert_in_list(&l, mouseover, TRUE);
807 }
808 }
809 else
810 {
811 // column 4,5
812 if(darktable.view_manager->active_images)
813 {
814 // column 5
815 for(GSList *ll = darktable.view_manager->active_images; ll; ll = g_slist_next(ll))
816 {
817 const int id = GPOINTER_TO_INT(ll->data);
818 _images_to_act_on_insert_in_list(&l, id, only_visible);
819 // be absolutely sure we have the id in the list (in darkroom,
820 // the active image can be out of collection)
821 if(!only_visible) _images_to_act_on_insert_in_list(&l, id, TRUE);
822 }
823 }
824 else
825 {
826 // column 4
827 // we return the list of the selection
828 l = dt_selection_get_list(darktable.selection, only_visible, ordered);
829 }
830 }
831
832 // let's register the new list as cached
833 darktable.view_manager->act_on.image_over_inside_sel = inside_sel;
834 darktable.view_manager->act_on.ordered = ordered;
835 darktable.view_manager->act_on.image_over = mouseover;
836 g_list_free(darktable.view_manager->act_on.images);
837 darktable.view_manager->act_on.images = l;
838 g_slist_free(darktable.view_manager->act_on.active_imgs);
839 darktable.view_manager->act_on.active_imgs = g_slist_copy(darktable.view_manager->active_images);
840 darktable.view_manager->act_on.inside_table = dt_ui_thumbtable(darktable.gui->ui)->mouse_inside;
841 darktable.view_manager->act_on.ok = TRUE;
842
843 return darktable.view_manager->act_on.images;
844 }
845
846 // get the query to retrieve images to act on. this is useful to speedup actions if they already use sqlite queries
dt_view_get_images_to_act_on_query(const gboolean only_visible)847 gchar *dt_view_get_images_to_act_on_query(const gboolean only_visible)
848 {
849 /** Here's how it works
850 *
851 * mouse over| x | x | x | | |
852 * mouse inside table| x | x | | | |
853 * mouse inside selection| x | | | | |
854 * active images| ? | ? | x | | x |
855 * | | | | | |
856 * | S | O | O | S | A |
857 * S = selection ; O = mouseover ; A = active images
858 * the mouse can be outside thumbtable in case of filmstrip + mouse in center widget
859 *
860 * if only_visible is FALSE, then it will add also not visible images because of grouping
861 * due to dt_selection_get_list_query limitation, order is always considered as undefined
862 **/
863
864 const int mouseover = dt_control_get_mouse_over_id();
865
866 GList *l = NULL;
867 gboolean inside_sel = FALSE;
868 if(mouseover > 0)
869 {
870 // column 1,2,3
871 if(dt_ui_thumbtable(darktable.gui->ui)->mouse_inside)
872 {
873 // column 1,2
874 sqlite3_stmt *stmt;
875 gchar *query = dt_util_dstrcat(NULL, "SELECT imgid FROM main.selected_images WHERE imgid =%d", mouseover);
876 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
877 if(stmt != NULL && sqlite3_step(stmt) == SQLITE_ROW)
878 {
879 inside_sel = TRUE;
880 sqlite3_finalize(stmt);
881 }
882 g_free(query);
883
884 if(inside_sel)
885 {
886 // column 1
887 return dt_selection_get_list_query(darktable.selection, only_visible, FALSE);
888 }
889 else
890 {
891 // column 2
892 _images_to_act_on_insert_in_list(&l, mouseover, only_visible);
893 }
894 }
895 else
896 {
897 // column 3
898 _images_to_act_on_insert_in_list(&l, mouseover, only_visible);
899 // be absolutely sure we have the id in the list (in darkroom,
900 // the active image can be out of collection)
901 if(!only_visible) _images_to_act_on_insert_in_list(&l, mouseover, TRUE);
902 }
903 }
904 else
905 {
906 // column 4,5
907 if(darktable.view_manager->active_images)
908 {
909 // column 5
910 for(GSList *ll = darktable.view_manager->active_images;
911 ll;
912 ll = g_slist_next(ll))
913 {
914 const int id = GPOINTER_TO_INT(ll->data);
915 _images_to_act_on_insert_in_list(&l, id, only_visible);
916 // be absolutely sure we have the id in the list (in darkroom,
917 // the active image can be out of collection)
918 if(!only_visible) _images_to_act_on_insert_in_list(&l, id, TRUE);
919 }
920 }
921 else
922 {
923 // column 4
924 return dt_selection_get_list_query(darktable.selection, only_visible, FALSE);
925 }
926 }
927
928 // if we don't return the selection, we return the list of imgid separated by comma
929 // in the form it can be used inside queries
930 gchar *images = NULL;
931 for(; l; l = g_list_next(l))
932 {
933 images = dt_util_dstrcat(images, "%d,", GPOINTER_TO_INT(l->data));
934 }
935 if(images)
936 {
937 images[strlen(images) - 1] = '\0';
938 }
939 else
940 images = dt_util_dstrcat(NULL, " ");
941 return images;
942 }
943
944 // get the main image to act on during global changes (libs, accels)
dt_view_get_image_to_act_on()945 int dt_view_get_image_to_act_on()
946 {
947 /** Here's how it works -- same as for list, except we don't care about mouse inside selection or table
948 *
949 * mouse over| x | | |
950 * active images| ? | | x |
951 * | | | |
952 * | O | S | A |
953 * First image of ...
954 * S = selection ; O = mouseover ; A = active images
955 **/
956
957 int ret = -1;
958 const int mouseover = dt_control_get_mouse_over_id();
959
960 if(mouseover > 0)
961 {
962 ret = mouseover;
963 }
964 else
965 {
966 if(darktable.view_manager->active_images)
967 {
968 ret = GPOINTER_TO_INT(darktable.view_manager->active_images->data);
969 }
970 else
971 {
972 sqlite3_stmt *stmt;
973 DT_DEBUG_SQLITE3_PREPARE_V2
974 (dt_database_get(darktable.db),
975 "SELECT s.imgid"
976 " FROM main.selected_images as s, memory.collected_images as c"
977 " WHERE s.imgid=c.imgid"
978 " ORDER BY c.rowid LIMIT 1",
979 -1, &stmt, NULL);
980 if(stmt != NULL && sqlite3_step(stmt) == SQLITE_ROW)
981 {
982 ret = sqlite3_column_int(stmt, 0);
983 }
984 if(stmt) sqlite3_finalize(stmt);
985 }
986 }
987
988 return ret;
989 }
990
dt_view_image_get_surface(int imgid,int width,int height,cairo_surface_t ** surface,const gboolean quality)991 dt_view_surface_value_t dt_view_image_get_surface(int imgid, int width, int height, cairo_surface_t **surface,
992 const gboolean quality)
993 {
994 double tt = 0;
995 if((darktable.unmuted & (DT_DEBUG_LIGHTTABLE | DT_DEBUG_PERF)) == (DT_DEBUG_LIGHTTABLE | DT_DEBUG_PERF))
996 tt = dt_get_wtime();
997
998 dt_view_surface_value_t ret = DT_VIEW_SURFACE_KO;
999 // if surface not null, clean it up
1000 if(*surface
1001 && cairo_surface_get_reference_count(*surface) > 0) cairo_surface_destroy(*surface);
1002 *surface = NULL;
1003
1004 // get mipmap cache image
1005 dt_mipmap_cache_t *cache = darktable.mipmap_cache;
1006 dt_mipmap_size_t mip = dt_mipmap_cache_get_matching_size(cache, width * darktable.gui->ppd, height * darktable.gui->ppd);
1007
1008 // if needed, we load the mimap buffer
1009 dt_mipmap_buffer_t buf;
1010 dt_mipmap_cache_get(cache, &buf, imgid, mip, DT_MIPMAP_BEST_EFFORT, 'r');
1011 const int buf_wd = buf.width;
1012 const int buf_ht = buf.height;
1013
1014 // if we don't get buffer, no image is awailable at the moment
1015 if(!buf.buf)
1016 {
1017 dt_mipmap_cache_release(darktable.mipmap_cache, &buf);
1018 return DT_VIEW_SURFACE_KO;
1019 }
1020
1021 // so we create a new image surface to return
1022 float scale = fminf(width / (float)buf_wd, height / (float)buf_ht) * darktable.gui->ppd_thb;
1023 const int img_width = roundf(buf_wd * scale);
1024 const int img_height = roundf(buf_ht * scale);
1025 // due to the forced rounding above, we need to recompute scaling
1026 scale = fmaxf(img_width / (float)buf_wd, img_height / (float)buf_ht);
1027 *surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, img_width, img_height);
1028
1029 // we transfer cached image on a cairo_surface (with colorspace transform if needed)
1030 cairo_surface_t *tmp_surface = NULL;
1031 uint8_t *rgbbuf = (uint8_t *)calloc((size_t)buf_wd * buf_ht * 4, sizeof(uint8_t));
1032 if(rgbbuf)
1033 {
1034 gboolean have_lock = FALSE;
1035 cmsHTRANSFORM transform = NULL;
1036
1037 if(dt_conf_get_bool("cache_color_managed"))
1038 {
1039 pthread_rwlock_rdlock(&darktable.color_profiles->xprofile_lock);
1040 have_lock = TRUE;
1041
1042 // we only color manage when a thumbnail is sRGB or AdobeRGB. everything else just gets dumped to the
1043 // screen
1044 if(buf.color_space == DT_COLORSPACE_SRGB
1045 && darktable.color_profiles->transform_srgb_to_display)
1046 {
1047 transform = darktable.color_profiles->transform_srgb_to_display;
1048 }
1049 else if(buf.color_space == DT_COLORSPACE_ADOBERGB
1050 && darktable.color_profiles->transform_adobe_rgb_to_display)
1051 {
1052 transform = darktable.color_profiles->transform_adobe_rgb_to_display;
1053 }
1054 else
1055 {
1056 pthread_rwlock_unlock(&darktable.color_profiles->xprofile_lock);
1057 have_lock = FALSE;
1058 if(buf.color_space == DT_COLORSPACE_NONE)
1059 {
1060 fprintf(stderr, "oops, there seems to be a code path not setting the color space of thumbnails!\n");
1061 }
1062 else if(buf.color_space != DT_COLORSPACE_DISPLAY && buf.color_space != DT_COLORSPACE_DISPLAY2)
1063 {
1064 fprintf(stderr,
1065 "oops, there seems to be a code path setting an unhandled color space of thumbnails (%s)!\n",
1066 dt_colorspaces_get_name(buf.color_space, "from file"));
1067 }
1068 }
1069 }
1070
1071 #ifdef _OPENMP
1072 #pragma omp parallel for schedule(static) default(none) shared(buf, rgbbuf, transform)
1073 #endif
1074 for(int i = 0; i < buf.height; i++)
1075 {
1076 const uint8_t *in = buf.buf + i * buf.width * 4;
1077 uint8_t *out = rgbbuf + i * buf.width * 4;
1078
1079 if(transform)
1080 {
1081 cmsDoTransform(transform, in, out, buf.width);
1082 }
1083 else
1084 {
1085 for(int j = 0; j < buf.width; j++, in += 4, out += 4)
1086 {
1087 out[0] = in[2];
1088 out[1] = in[1];
1089 out[2] = in[0];
1090 }
1091 }
1092 }
1093 if(have_lock) pthread_rwlock_unlock(&darktable.color_profiles->xprofile_lock);
1094
1095 const int32_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, buf_wd);
1096 tmp_surface = cairo_image_surface_create_for_data(rgbbuf, CAIRO_FORMAT_RGB24, buf_wd, buf_ht, stride);
1097 }
1098
1099 // draw the image scaled:
1100 if(tmp_surface)
1101 {
1102 cairo_t *cr = cairo_create(*surface);
1103 cairo_scale(cr, scale, scale);
1104
1105 cairo_set_source_surface(cr, tmp_surface, 0, 0);
1106 // set filter no nearest:
1107 // in skull mode, we want to see big pixels.
1108 // in 1 iir mode for the right mip, we want to see exactly what the pipe gave us, 1:1 pixel for pixel.
1109 // in between, filtering just makes stuff go unsharp.
1110 if((buf_wd <= 8 && buf_ht <= 8)
1111 || fabsf(scale - 1.0f) < 0.01f)
1112 cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
1113 else if(mip != buf.size)
1114 cairo_pattern_set_filter(cairo_get_source(cr),
1115 CAIRO_FILTER_FAST); // not the right size, so we scale as fast a possible
1116 else
1117 cairo_pattern_set_filter(cairo_get_source(cr), ((darktable.gui->filter_image == CAIRO_FILTER_FAST) && quality)
1118 ? CAIRO_FILTER_GOOD
1119 : darktable.gui->filter_image);
1120
1121 cairo_paint(cr);
1122 /* from focus_peaking.h
1123 static inline void dt_focuspeaking(cairo_t *cr, int width, int height,
1124 uint8_t *const restrict image,
1125 const int buf_width, const int buf_height)
1126 The current implementation assumes the data at image is organized as a rectangle without a stride,
1127 So we pass the raw data to be processed, this is more data but correct.
1128 */
1129 if(darktable.gui->show_focus_peaking && mip == buf.size)
1130 dt_focuspeaking(cr, img_width, img_height, rgbbuf, buf_wd, buf_ht);
1131
1132 cairo_surface_destroy(tmp_surface);
1133 cairo_destroy(cr);
1134 }
1135
1136 // we consider skull as ok as the image hasn't to be reload
1137 if(buf_wd <= 8 && buf_ht <= 8)
1138 ret = DT_VIEW_SURFACE_OK;
1139 else if(mip != buf.size)
1140 ret = DT_VIEW_SURFACE_SMALLER;
1141 else
1142 ret = DT_VIEW_SURFACE_OK;
1143
1144 dt_mipmap_cache_release(darktable.mipmap_cache, &buf);
1145 if(rgbbuf) free(rgbbuf);
1146
1147 // logs
1148 if((darktable.unmuted & (DT_DEBUG_LIGHTTABLE | DT_DEBUG_PERF)) == (DT_DEBUG_LIGHTTABLE | DT_DEBUG_PERF))
1149 {
1150 dt_print(DT_DEBUG_LIGHTTABLE | DT_DEBUG_PERF,
1151 "[dt_view_image_get_surface] id %i, dots %ix%i, mip %ix%i, surf %ix%i created in %0.04f sec\n",
1152 imgid, width, height, buf_wd, buf_ht, img_width, img_height, dt_get_wtime() - tt);
1153 }
1154 else if(darktable.unmuted & DT_DEBUG_LIGHTTABLE)
1155 {
1156 dt_print(DT_DEBUG_LIGHTTABLE, "[dt_view_image_get_surface] id %i, dots %ix%i, mip %ix%i, surf %ix%i\n", imgid,
1157 width, height, buf_wd, buf_ht, img_width, img_height);
1158 }
1159
1160 // we consider skull as ok as the image hasn't to be reload
1161 return ret;
1162 }
1163
dt_view_extend_modes_str(const char * name,const gboolean is_hdr,const gboolean is_bw,const gboolean is_bw_flow)1164 char* dt_view_extend_modes_str(const char * name, const gboolean is_hdr, const gboolean is_bw, const gboolean is_bw_flow)
1165 {
1166 char* upcase = g_ascii_strup(name, -1); // extension in capital letters to avoid character descenders
1167 // convert to canonical format extension
1168 if(0 == g_ascii_strcasecmp(upcase, "JPG"))
1169 {
1170 gchar* canonical = g_strdup("JPEG");
1171 g_free(upcase);
1172 upcase = canonical;
1173 }
1174 else if(0 == g_ascii_strcasecmp(upcase, "HDR"))
1175 {
1176 gchar* canonical = g_strdup("RGBE");
1177 g_free(upcase);
1178 upcase = canonical;
1179 }
1180 else if(0 == g_ascii_strcasecmp(upcase, "TIF"))
1181 {
1182 gchar* canonical = g_strdup("TIFF");
1183 g_free(upcase);
1184 upcase = canonical;
1185 }
1186
1187 if(is_hdr)
1188 {
1189 gchar* fullname = g_strdup_printf("%s HDR", upcase);
1190 g_free(upcase);
1191 upcase = fullname;
1192 }
1193 if(is_bw)
1194 {
1195 gchar* fullname = g_strdup_printf("%s B&W", upcase);
1196 g_free(upcase);
1197 upcase = fullname;
1198 if(!is_bw_flow)
1199 {
1200 fullname = g_strdup_printf("%s-", upcase);
1201 g_free(upcase);
1202 upcase = fullname;
1203 }
1204 }
1205
1206 return upcase;
1207 }
1208
1209 /**
1210 * \brief Set the selection bit to a given value for the specified image
1211 * \param[in] imgid The image id
1212 * \param[in] value The boolean value for the bit
1213 */
dt_view_set_selection(int imgid,int value)1214 void dt_view_set_selection(int imgid, int value)
1215 {
1216 /* clear and reset statement */
1217 DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.is_selected);
1218 DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.is_selected);
1219
1220 /* setup statement and iterate over rows */
1221 DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.is_selected, 1, imgid);
1222
1223 if(sqlite3_step(darktable.view_manager->statements.is_selected) == SQLITE_ROW)
1224 {
1225 if(!value)
1226 {
1227 /* Value is set and should be unset; get rid of it */
1228
1229 /* clear and reset statement */
1230 DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.delete_from_selected);
1231 DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.delete_from_selected);
1232
1233 /* setup statement and execute */
1234 DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.delete_from_selected, 1, imgid);
1235 sqlite3_step(darktable.view_manager->statements.delete_from_selected);
1236 }
1237 }
1238 else if(value)
1239 {
1240 /* Select bit is unset and should be set; add it */
1241
1242 /* clear and reset statement */
1243 DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.make_selected);
1244 DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.make_selected);
1245
1246 /* setup statement and execute */
1247 DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.make_selected, 1, imgid);
1248 sqlite3_step(darktable.view_manager->statements.make_selected);
1249 }
1250 }
1251
1252 /**
1253 * \brief Toggle the selection bit in the database for the specified image
1254 * \param[in] imgid The image id
1255 */
dt_view_toggle_selection(int imgid)1256 void dt_view_toggle_selection(int imgid)
1257 {
1258 /* clear and reset statement */
1259 DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.is_selected);
1260 DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.is_selected);
1261
1262 /* setup statement and iterate over rows */
1263 DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.is_selected, 1, imgid);
1264 if(sqlite3_step(darktable.view_manager->statements.is_selected) == SQLITE_ROW)
1265 {
1266 /* clear and reset statement */
1267 DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.delete_from_selected);
1268 DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.delete_from_selected);
1269
1270 /* setup statement and execute */
1271 DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.delete_from_selected, 1, imgid);
1272 sqlite3_step(darktable.view_manager->statements.delete_from_selected);
1273 }
1274 else
1275 {
1276 /* clear and reset statement */
1277 DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.make_selected);
1278 DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.make_selected);
1279
1280 /* setup statement and execute */
1281 DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.make_selected, 1, imgid);
1282 sqlite3_step(darktable.view_manager->statements.make_selected);
1283 }
1284 }
1285
1286 /**
1287 * \brief Reset filter
1288 */
dt_view_filter_reset(const dt_view_manager_t * vm,gboolean smart_filter)1289 void dt_view_filter_reset(const dt_view_manager_t *vm, gboolean smart_filter)
1290 {
1291 if(vm->proxy.filter.module && vm->proxy.filter.reset_filter)
1292 vm->proxy.filter.reset_filter(vm->proxy.filter.module, smart_filter);
1293 }
1294
dt_view_active_images_reset(gboolean raise)1295 void dt_view_active_images_reset(gboolean raise)
1296 {
1297 if(!darktable.view_manager->active_images) return;
1298 g_slist_free(darktable.view_manager->active_images);
1299 darktable.view_manager->active_images = NULL;
1300
1301 if(raise) DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_ACTIVE_IMAGES_CHANGE);
1302 }
dt_view_active_images_add(int imgid,gboolean raise)1303 void dt_view_active_images_add(int imgid, gboolean raise)
1304 {
1305 darktable.view_manager->active_images
1306 = g_slist_append(darktable.view_manager->active_images, GINT_TO_POINTER(imgid));
1307 if(raise)
1308 DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_ACTIVE_IMAGES_CHANGE);
1309 }
dt_view_active_images_get()1310 GSList *dt_view_active_images_get()
1311 {
1312 return darktable.view_manager->active_images;
1313 }
1314
dt_view_manager_view_toolbox_add(dt_view_manager_t * vm,GtkWidget * tool,dt_view_type_flags_t views)1315 void dt_view_manager_view_toolbox_add(dt_view_manager_t *vm, GtkWidget *tool, dt_view_type_flags_t views)
1316 {
1317 if(vm->proxy.view_toolbox.module)
1318 vm->proxy.view_toolbox.add(vm->proxy.view_toolbox.module, tool, views);
1319 }
1320
dt_view_manager_module_toolbox_add(dt_view_manager_t * vm,GtkWidget * tool,dt_view_type_flags_t views)1321 void dt_view_manager_module_toolbox_add(dt_view_manager_t *vm, GtkWidget *tool, dt_view_type_flags_t views)
1322 {
1323 if(vm->proxy.module_toolbox.module)
1324 vm->proxy.module_toolbox.add(vm->proxy.module_toolbox.module, tool, views);
1325 }
1326
dt_view_darkroom_get_layout(dt_view_manager_t * vm)1327 dt_darkroom_layout_t dt_view_darkroom_get_layout(dt_view_manager_t *vm)
1328 {
1329 if(vm->proxy.darkroom.view)
1330 return vm->proxy.darkroom.get_layout(vm->proxy.darkroom.view);
1331 else
1332 return DT_DARKROOM_LAYOUT_EDITING;
1333 }
1334
dt_view_lighttable_set_zoom(dt_view_manager_t * vm,gint zoom)1335 void dt_view_lighttable_set_zoom(dt_view_manager_t *vm, gint zoom)
1336 {
1337 if(vm->proxy.lighttable.module)
1338 vm->proxy.lighttable.set_zoom(vm->proxy.lighttable.module, zoom);
1339 }
1340
dt_view_lighttable_get_zoom(dt_view_manager_t * vm)1341 gint dt_view_lighttable_get_zoom(dt_view_manager_t *vm)
1342 {
1343 if(vm->proxy.lighttable.module)
1344 return vm->proxy.lighttable.get_zoom(vm->proxy.lighttable.module);
1345 else
1346 return 10;
1347 }
1348
dt_view_lighttable_culling_init_mode(dt_view_manager_t * vm)1349 void dt_view_lighttable_culling_init_mode(dt_view_manager_t *vm)
1350 {
1351 if(vm->proxy.lighttable.module) vm->proxy.lighttable.culling_init_mode(vm->proxy.lighttable.view);
1352 }
1353
dt_view_lighttable_culling_preview_refresh(dt_view_manager_t * vm)1354 void dt_view_lighttable_culling_preview_refresh(dt_view_manager_t *vm)
1355 {
1356 if(vm->proxy.lighttable.module)
1357 vm->proxy.lighttable.culling_preview_refresh(vm->proxy.lighttable.view);
1358 }
1359
dt_view_lighttable_culling_preview_reload_overlays(dt_view_manager_t * vm)1360 void dt_view_lighttable_culling_preview_reload_overlays(dt_view_manager_t *vm)
1361 {
1362 if(vm->proxy.lighttable.module)
1363 vm->proxy.lighttable.culling_preview_reload_overlays(vm->proxy.lighttable.view);
1364 }
1365
dt_view_lighttable_get_layout(dt_view_manager_t * vm)1366 dt_lighttable_layout_t dt_view_lighttable_get_layout(dt_view_manager_t *vm)
1367 {
1368 if(vm->proxy.lighttable.module)
1369 return vm->proxy.lighttable.get_layout(vm->proxy.lighttable.module);
1370 else
1371 return DT_LIGHTTABLE_LAYOUT_FILEMANAGER;
1372 }
1373
dt_view_lighttable_preview_state(dt_view_manager_t * vm)1374 gboolean dt_view_lighttable_preview_state(dt_view_manager_t *vm)
1375 {
1376 if(vm->proxy.lighttable.module)
1377 return vm->proxy.lighttable.get_preview_state(vm->proxy.lighttable.view);
1378 else
1379 return FALSE;
1380 }
1381
dt_view_lighttable_set_preview_state(dt_view_manager_t * vm,gboolean state,gboolean focus)1382 void dt_view_lighttable_set_preview_state(dt_view_manager_t *vm, gboolean state, gboolean focus)
1383 {
1384 if(vm->proxy.lighttable.module)
1385 vm->proxy.lighttable.set_preview_state(vm->proxy.lighttable.view, state, focus);
1386 }
1387
dt_view_lighttable_change_offset(dt_view_manager_t * vm,gboolean reset,gint imgid)1388 void dt_view_lighttable_change_offset(dt_view_manager_t *vm, gboolean reset, gint imgid)
1389 {
1390 if(vm->proxy.lighttable.module)
1391 vm->proxy.lighttable.change_offset(vm->proxy.lighttable.view, reset, imgid);
1392 }
1393
dt_view_collection_update(const dt_view_manager_t * vm)1394 void dt_view_collection_update(const dt_view_manager_t *vm)
1395 {
1396 if(vm->proxy.module_collect.module)
1397 vm->proxy.module_collect.update(vm->proxy.module_collect.module);
1398 }
1399
1400
dt_view_tethering_get_selected_imgid(const dt_view_manager_t * vm)1401 int32_t dt_view_tethering_get_selected_imgid(const dt_view_manager_t *vm)
1402 {
1403 if(vm->proxy.tethering.view)
1404 return vm->proxy.tethering.get_selected_imgid(vm->proxy.tethering.view);
1405
1406 return -1;
1407 }
1408
dt_view_tethering_set_job_code(const dt_view_manager_t * vm,const char * name)1409 void dt_view_tethering_set_job_code(const dt_view_manager_t *vm, const char *name)
1410 {
1411 if(vm->proxy.tethering.view)
1412 vm->proxy.tethering.set_job_code(vm->proxy.tethering.view, name);
1413 }
1414
dt_view_tethering_get_job_code(const dt_view_manager_t * vm)1415 const char *dt_view_tethering_get_job_code(const dt_view_manager_t *vm)
1416 {
1417 if(vm->proxy.tethering.view)
1418 return vm->proxy.tethering.get_job_code(vm->proxy.tethering.view);
1419 return NULL;
1420 }
1421
1422 #ifdef HAVE_MAP
dt_view_map_center_on_location(const dt_view_manager_t * vm,gdouble lon,gdouble lat,gdouble zoom)1423 void dt_view_map_center_on_location(const dt_view_manager_t *vm, gdouble lon, gdouble lat, gdouble zoom)
1424 {
1425 if(vm->proxy.map.view)
1426 vm->proxy.map.center_on_location(vm->proxy.map.view, lon, lat, zoom);
1427 }
1428
dt_view_map_center_on_bbox(const dt_view_manager_t * vm,gdouble lon1,gdouble lat1,gdouble lon2,gdouble lat2)1429 void dt_view_map_center_on_bbox(const dt_view_manager_t *vm, gdouble lon1, gdouble lat1, gdouble lon2, gdouble lat2)
1430 {
1431 if(vm->proxy.map.view)
1432 vm->proxy.map.center_on_bbox(vm->proxy.map.view, lon1, lat1, lon2, lat2);
1433 }
1434
dt_view_map_show_osd(const dt_view_manager_t * vm)1435 void dt_view_map_show_osd(const dt_view_manager_t *vm)
1436 {
1437 if(vm->proxy.map.view)
1438 vm->proxy.map.show_osd(vm->proxy.map.view);
1439 }
1440
dt_view_map_set_map_source(const dt_view_manager_t * vm,OsmGpsMapSource_t map_source)1441 void dt_view_map_set_map_source(const dt_view_manager_t *vm, OsmGpsMapSource_t map_source)
1442 {
1443 if(vm->proxy.map.view)
1444 vm->proxy.map.set_map_source(vm->proxy.map.view, map_source);
1445 }
1446
dt_view_map_add_marker(const dt_view_manager_t * vm,dt_geo_map_display_t type,GList * points)1447 GObject *dt_view_map_add_marker(const dt_view_manager_t *vm, dt_geo_map_display_t type, GList *points)
1448 {
1449 if(vm->proxy.map.view)
1450 return vm->proxy.map.add_marker(vm->proxy.map.view, type, points);
1451 return NULL;
1452 }
1453
dt_view_map_remove_marker(const dt_view_manager_t * vm,dt_geo_map_display_t type,GObject * marker)1454 gboolean dt_view_map_remove_marker(const dt_view_manager_t *vm, dt_geo_map_display_t type, GObject *marker)
1455 {
1456 if(vm->proxy.map.view)
1457 return vm->proxy.map.remove_marker(vm->proxy.map.view, type, marker);
1458 return FALSE;
1459 }
dt_view_map_add_location(const dt_view_manager_t * vm,dt_map_location_data_t * p,const guint posid)1460 void dt_view_map_add_location(const dt_view_manager_t *vm, dt_map_location_data_t *p, const guint posid)
1461 {
1462 if(vm->proxy.map.view)
1463 vm->proxy.map.add_location(vm->proxy.map.view, p, posid);
1464 }
1465
dt_view_map_location_action(const dt_view_manager_t * vm,const int action)1466 void dt_view_map_location_action(const dt_view_manager_t *vm, const int action)
1467 {
1468 if(vm->proxy.map.view)
1469 vm->proxy.map.location_action(vm->proxy.map.view, action);
1470 }
1471
dt_view_map_drag_set_icon(const dt_view_manager_t * vm,GdkDragContext * context,const int imgid,const int count)1472 void dt_view_map_drag_set_icon(const dt_view_manager_t *vm, GdkDragContext *context, const int imgid, const int count)
1473 {
1474 if(vm->proxy.map.view)
1475 vm->proxy.map.drag_set_icon(vm->proxy.map.view, context, imgid, count);
1476 }
1477
1478 #endif
1479
1480 #ifdef HAVE_PRINT
dt_view_print_settings(const dt_view_manager_t * vm,dt_print_info_t * pinfo)1481 void dt_view_print_settings(const dt_view_manager_t *vm, dt_print_info_t *pinfo)
1482 {
1483 if (vm->proxy.print.view)
1484 vm->proxy.print.print_settings(vm->proxy.print.view, pinfo);
1485 }
1486 #endif
1487
dt_mouse_action_create_simple(GSList * actions,dt_mouse_action_type_t type,GdkModifierType accel,const char * const description)1488 GSList *dt_mouse_action_create_simple(GSList *actions, dt_mouse_action_type_t type, GdkModifierType accel,
1489 const char *const description)
1490 {
1491 dt_mouse_action_t *a = (dt_mouse_action_t *)calloc(1, sizeof(dt_mouse_action_t));
1492 a->action = type;
1493 a->key.accel_mods = accel;
1494 g_strlcpy(a->name, description, sizeof(a->name));
1495 return g_slist_append(actions, a);
1496 }
1497
dt_mouse_action_create_format(GSList * actions,dt_mouse_action_type_t type,GdkModifierType accel,const char * const format_string,const char * const replacement)1498 GSList *dt_mouse_action_create_format(GSList *actions, dt_mouse_action_type_t type, GdkModifierType accel,
1499 const char *const format_string, const char *const replacement)
1500 {
1501 dt_mouse_action_t *a = (dt_mouse_action_t *)calloc(1, sizeof(dt_mouse_action_t));
1502 a->action = type;
1503 a->key.accel_mods = accel;
1504 g_snprintf(a->name, sizeof(a->name), format_string, replacement);
1505 return g_slist_append(actions, a);
1506 }
1507
_mouse_action_get_string(dt_mouse_action_t * ma)1508 static gchar *_mouse_action_get_string(dt_mouse_action_t *ma)
1509 {
1510 gchar *accel_label = gtk_accelerator_get_label(ma->key.accel_key, ma->key.accel_mods);
1511 gchar *atxt = dt_util_dstrcat(NULL, "%s", accel_label);
1512 g_free(accel_label);
1513
1514 if(strcmp(atxt, ""))
1515 atxt = dt_util_dstrcat(atxt, "+");
1516
1517 switch(ma->action)
1518 {
1519 case DT_MOUSE_ACTION_LEFT:
1520 atxt = dt_util_dstrcat(atxt, _("Left click"));
1521 break;
1522 case DT_MOUSE_ACTION_RIGHT:
1523 atxt = dt_util_dstrcat(atxt, _("Right click"));
1524 break;
1525 case DT_MOUSE_ACTION_MIDDLE:
1526 atxt = dt_util_dstrcat(atxt, _("Middle click"));
1527 break;
1528 case DT_MOUSE_ACTION_SCROLL:
1529 atxt = dt_util_dstrcat(atxt, _("Scroll"));
1530 break;
1531 case DT_MOUSE_ACTION_DOUBLE_LEFT:
1532 atxt = dt_util_dstrcat(atxt, _("Left double-click"));
1533 break;
1534 case DT_MOUSE_ACTION_DOUBLE_RIGHT:
1535 atxt = dt_util_dstrcat(atxt, _("Right double-click"));
1536 break;
1537 case DT_MOUSE_ACTION_DRAG_DROP:
1538 atxt = dt_util_dstrcat(atxt, _("Drag and drop"));
1539 break;
1540 case DT_MOUSE_ACTION_LEFT_DRAG:
1541 atxt = dt_util_dstrcat(atxt, _("Left click+Drag"));
1542 break;
1543 case DT_MOUSE_ACTION_RIGHT_DRAG:
1544 atxt = dt_util_dstrcat(atxt, _("Right click+Drag"));
1545 break;
1546 }
1547
1548 return atxt;
1549 }
1550
_accels_window_destroy(GtkWidget * widget,dt_view_manager_t * vm)1551 static void _accels_window_destroy(GtkWidget *widget, dt_view_manager_t *vm)
1552 {
1553 // set to NULL so we can rely on it after
1554 vm->accels_window.window = NULL;
1555 }
1556
_accels_window_sticky(GtkWidget * widget,GdkEventButton * event,dt_view_manager_t * vm)1557 static void _accels_window_sticky(GtkWidget *widget, GdkEventButton *event, dt_view_manager_t *vm)
1558 {
1559 if(!vm->accels_window.window) return;
1560
1561 // creating new window
1562 GtkWindow *win = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
1563 GtkStyleContext *context = gtk_widget_get_style_context(GTK_WIDGET(win));
1564 gtk_style_context_add_class(context, "accels_window");
1565 gtk_window_set_title(win, _("darktable - accels window"));
1566 GtkAllocation alloc;
1567 gtk_widget_get_allocation(dt_ui_main_window(darktable.gui->ui), &alloc);
1568
1569 gtk_window_set_resizable(win, TRUE);
1570 gtk_window_set_icon_name(win, "darktable");
1571 gtk_window_set_default_size(win, alloc.width * 0.7, alloc.height * 0.7);
1572 g_signal_connect(win, "destroy", G_CALLBACK(_accels_window_destroy), vm);
1573
1574 GtkWidget *sw = dt_gui_container_first_child(GTK_CONTAINER(vm->accels_window.window));
1575 g_object_ref(sw);
1576 gtk_container_remove(GTK_CONTAINER(vm->accels_window.window), sw);
1577 gtk_container_add(GTK_CONTAINER(win), sw);
1578 g_object_unref(sw);
1579
1580 gtk_widget_destroy(vm->accels_window.window);
1581 vm->accels_window.window = GTK_WIDGET(win);
1582 gtk_widget_show_all(vm->accels_window.window);
1583 gtk_widget_hide(vm->accels_window.sticky_btn);
1584
1585 vm->accels_window.sticky = TRUE;
1586 }
1587
dt_view_accels_show(dt_view_manager_t * vm)1588 void dt_view_accels_show(dt_view_manager_t *vm)
1589 {
1590 if(vm->accels_window.window) return;
1591
1592 vm->accels_window.sticky = FALSE;
1593 vm->accels_window.prevent_refresh = FALSE;
1594
1595 GtkStyleContext *context;
1596 vm->accels_window.window = gtk_window_new(GTK_WINDOW_POPUP);
1597 #ifdef GDK_WINDOWING_QUARTZ
1598 dt_osx_disallow_fullscreen(vm->accels_window.window);
1599 #endif
1600 context = gtk_widget_get_style_context(vm->accels_window.window);
1601 gtk_style_context_add_class(context, "accels_window");
1602
1603 GtkWidget *sw = gtk_scrolled_window_new(NULL, NULL);
1604 context = gtk_widget_get_style_context(sw);
1605 gtk_style_context_add_class(context, "accels_window_scroll");
1606
1607 GtkWidget *hb = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
1608
1609 vm->accels_window.flow_box = gtk_flow_box_new();
1610 context = gtk_widget_get_style_context(vm->accels_window.flow_box);
1611 gtk_style_context_add_class(context, "accels_window_box");
1612 gtk_orientable_set_orientation(GTK_ORIENTABLE(vm->accels_window.flow_box), GTK_ORIENTATION_HORIZONTAL);
1613
1614 gtk_box_pack_start(GTK_BOX(hb), vm->accels_window.flow_box, TRUE, TRUE, 0);
1615
1616 GtkWidget *vb = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
1617 vm->accels_window.sticky_btn
1618 = dtgtk_button_new(dtgtk_cairo_paint_multiinstance, CPF_STYLE_FLAT, NULL);
1619 g_object_set(G_OBJECT(vm->accels_window.sticky_btn), "tooltip-text",
1620 _("switch to a classic window which will stay open after key release."), (char *)NULL);
1621 g_signal_connect(G_OBJECT(vm->accels_window.sticky_btn), "button-press-event", G_CALLBACK(_accels_window_sticky),
1622 vm);
1623 context = gtk_widget_get_style_context(vm->accels_window.sticky_btn);
1624 gtk_style_context_add_class(context, "accels_window_stick");
1625 gtk_box_pack_start(GTK_BOX(vb), vm->accels_window.sticky_btn, FALSE, FALSE, 0);
1626 gtk_box_pack_start(GTK_BOX(hb), vb, FALSE, FALSE, 0);
1627
1628 dt_view_accels_refresh(vm);
1629
1630 GtkAllocation alloc;
1631 gtk_widget_get_allocation(dt_ui_main_window(darktable.gui->ui), &alloc);
1632 // gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(sw), alloc.height);
1633 gtk_scrolled_window_set_max_content_height(GTK_SCROLLED_WINDOW(sw), alloc.height);
1634 gtk_scrolled_window_set_max_content_width(GTK_SCROLLED_WINDOW(sw), alloc.width);
1635 gtk_container_add(GTK_CONTAINER(sw), hb);
1636 gtk_container_add(GTK_CONTAINER(vm->accels_window.window), sw);
1637
1638 gtk_window_set_resizable(GTK_WINDOW(vm->accels_window.window), FALSE);
1639 gtk_window_set_default_size(GTK_WINDOW(vm->accels_window.window), alloc.width, alloc.height);
1640 gtk_window_set_transient_for(GTK_WINDOW(vm->accels_window.window),
1641 GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)));
1642 gtk_window_set_keep_above(GTK_WINDOW(vm->accels_window.window), TRUE);
1643 // needed on macOS to avoid fullscreening the popup with newer GTK
1644 gtk_window_set_type_hint(GTK_WINDOW(vm->accels_window.window), GDK_WINDOW_TYPE_HINT_POPUP_MENU);
1645
1646 gtk_window_set_gravity(GTK_WINDOW(vm->accels_window.window), GDK_GRAVITY_STATIC);
1647 gtk_window_set_position(GTK_WINDOW(vm->accels_window.window), GTK_WIN_POS_CENTER_ON_PARENT);
1648 gtk_widget_show_all(vm->accels_window.window);
1649 }
1650
dt_view_accels_hide(dt_view_manager_t * vm)1651 void dt_view_accels_hide(dt_view_manager_t *vm)
1652 {
1653 if(vm->accels_window.window && vm->accels_window.sticky) return;
1654 if(vm->accels_window.window) gtk_widget_destroy(vm->accels_window.window);
1655 vm->accels_window.window = NULL;
1656 }
1657
dt_view_accels_refresh(dt_view_manager_t * vm)1658 void dt_view_accels_refresh(dt_view_manager_t *vm)
1659 {
1660 if(!vm->accels_window.window || vm->accels_window.prevent_refresh) return;
1661
1662 // drop all existing tables
1663 GList *lw = gtk_container_get_children(GTK_CONTAINER(vm->accels_window.flow_box));
1664 for(const GList *lw_iter = lw; lw_iter; lw_iter = g_list_next(lw_iter))
1665 {
1666 GtkWidget *w = (GtkWidget *)lw_iter->data;
1667 gtk_widget_destroy(w);
1668 }
1669 g_list_free(lw);
1670
1671 // get the list of valid accel for this view
1672 const dt_view_t *cv = dt_view_manager_get_current_view(vm);
1673 const dt_view_type_flags_t v = cv->view(cv);
1674 GtkStyleContext *context;
1675
1676 typedef struct _bloc_t
1677 {
1678 gchar *base;
1679 gchar *title;
1680 GtkListStore *list_store;
1681 } _bloc_t;
1682
1683 // go through all accels to populate categories with valid ones
1684 GList *blocs = NULL;
1685 GList *bl = NULL;
1686 for(const GList *l = darktable.control->accelerator_list;
1687 l;
1688 l = g_list_next(l))
1689 {
1690 dt_accel_t *da = (dt_accel_t *)l->data;
1691 if(da && (da->views & v) == v)
1692 {
1693 GtkAccelKey ak;
1694 if(gtk_accel_map_lookup_entry(da->path, &ak) && ak.accel_key > 0)
1695 {
1696 // we want the base path
1697 gchar **elems = g_strsplit(da->translated_path, "/", -1);
1698 if(elems[0] && elems[1] && elems[2])
1699 {
1700 // do we already have a category ?
1701 _bloc_t *b = NULL;
1702 for(bl = blocs; bl; bl = g_list_next(bl))
1703 {
1704 _bloc_t *bb = (_bloc_t *)bl->data;
1705 if(strcmp(elems[1], bb->base) == 0)
1706 {
1707 b = bb;
1708 break;
1709 }
1710 }
1711
1712 // if not found, we create it
1713 if(!b)
1714 {
1715 b = (_bloc_t *)calloc(1, sizeof(_bloc_t));
1716 b->base = dt_util_dstrcat(NULL, "%s", elems[1]);
1717
1718 if(g_str_has_prefix(da->path, "<Darktable>/views/"))
1719 b->title = dt_util_dstrcat(NULL, "%s", cv->name(cv));
1720 else
1721 b->title = dt_util_dstrcat(NULL, "%s", elems[1]);
1722
1723 b->list_store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
1724 blocs = g_list_prepend(blocs, b);
1725 }
1726 // we add the new line
1727 GtkTreeIter iter;
1728 gtk_list_store_prepend(b->list_store, &iter);
1729 gchar *txt;
1730 // for views accels, no need to specify the view name, it's in the category title
1731 if(g_str_has_prefix(da->path, "<Darktable>/views/"))
1732 txt = da->translated_path + strlen(elems[0]) + strlen(elems[1]) + strlen(elems[2]) + 3;
1733 else
1734 txt = da->translated_path + strlen(elems[0]) + strlen(elems[1]) + 2;
1735 // for dynamic accel, we need to add the "+scroll"
1736
1737 gchar *accel_label = gtk_accelerator_get_label(ak.accel_key, ak.accel_mods);
1738 gchar *atxt = dt_util_dstrcat(NULL, "%s", accel_label);
1739 g_free(accel_label);
1740 if(g_str_has_prefix(da->path, "<Darktable>/image operations/") && g_str_has_suffix(da->path, "/dynamic"))
1741 atxt = dt_util_dstrcat(atxt, _("+Scroll"));
1742 gtk_list_store_set(b->list_store, &iter, 0, atxt, 1, txt, -1);
1743 g_free(atxt);
1744 g_strfreev(elems);
1745 }
1746 }
1747 }
1748 }
1749
1750 // we add the mouse actions too
1751 if(cv->mouse_actions)
1752 {
1753 _bloc_t *bm = (_bloc_t *)calloc(1, sizeof(_bloc_t));
1754 bm->base = NULL;
1755 bm->title = dt_util_dstrcat(NULL, _("mouse actions"));
1756 bm->list_store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
1757 blocs = g_list_prepend(blocs, bm);
1758
1759 GSList *actions = cv->mouse_actions(cv);
1760 for(GSList *lm = actions; lm; lm = g_slist_next(lm))
1761 {
1762 dt_mouse_action_t *ma = (dt_mouse_action_t *)lm->data;
1763 if(ma)
1764 {
1765 GtkTreeIter iter;
1766 gtk_list_store_append(bm->list_store, &iter);
1767 gchar *atxt = _mouse_action_get_string(ma);
1768 gtk_list_store_set(bm->list_store, &iter, 0, atxt, 1, ma->name, -1);
1769 g_free(atxt);
1770 }
1771 }
1772 g_slist_free(actions); // we've already freed the action records, but still need to free the list itself
1773 }
1774
1775 // now we create and insert the widget to display all accels by categories
1776 for(bl = blocs; bl; bl = g_list_next(bl))
1777 {
1778 const _bloc_t *bb = (_bloc_t *)bl->data;
1779 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
1780 // the title
1781 GtkWidget *lb = gtk_label_new(bb->title);
1782 context = gtk_widget_get_style_context(lb);
1783 gtk_style_context_add_class(context, "accels_window_cat_title");
1784 gtk_box_pack_start(GTK_BOX(box), lb, FALSE, FALSE, 0);
1785
1786 // the list of accels
1787 GtkWidget *list = gtk_tree_view_new_with_model(GTK_TREE_MODEL(bb->list_store));
1788 context = gtk_widget_get_style_context(list);
1789 gtk_style_context_add_class(context, "accels_window_list");
1790 GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
1791 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(_("Accel"), renderer, "text", 0, NULL);
1792 gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
1793 column = gtk_tree_view_column_new_with_attributes(_("Action"), renderer, "text", 1, NULL);
1794 gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
1795
1796 gtk_box_pack_start(GTK_BOX(box), list, FALSE, FALSE, 0);
1797
1798 gtk_flow_box_insert(GTK_FLOW_BOX(vm->accels_window.flow_box), box, -1);
1799 g_free(bb->base);
1800 g_free(bb->title);
1801 }
1802 g_list_free_full(blocs, free);
1803
1804 gtk_widget_show_all(vm->accels_window.flow_box);
1805 }
1806
_audio_child_watch(GPid pid,gint status,gpointer data)1807 static void _audio_child_watch(GPid pid, gint status, gpointer data)
1808 {
1809 dt_view_manager_t *vm = (dt_view_manager_t *)data;
1810 vm->audio.audio_player_id = -1;
1811 g_spawn_close_pid(pid);
1812 }
1813
dt_view_audio_start(dt_view_manager_t * vm,int imgid)1814 void dt_view_audio_start(dt_view_manager_t *vm, int imgid)
1815 {
1816 char *player = dt_conf_get_string("plugins/lighttable/audio_player");
1817 if(player && *player)
1818 {
1819 char *filename = dt_image_get_audio_path(imgid);
1820 if(filename)
1821 {
1822 char *argv[] = { player, filename, NULL };
1823 gboolean ret = g_spawn_async(NULL, argv, NULL,
1824 G_SPAWN_DO_NOT_REAP_CHILD
1825 | G_SPAWN_SEARCH_PATH
1826 | G_SPAWN_STDOUT_TO_DEV_NULL
1827 | G_SPAWN_STDERR_TO_DEV_NULL,
1828 NULL, NULL,
1829 &vm->audio.audio_player_pid, NULL);
1830
1831 if(ret)
1832 {
1833 vm->audio.audio_player_id = imgid;
1834 vm->audio.audio_player_event_source
1835 = g_child_watch_add(vm->audio.audio_player_pid, (GChildWatchFunc)_audio_child_watch, vm);
1836 }
1837 else
1838 vm->audio.audio_player_id = -1;
1839
1840 g_free(filename);
1841 }
1842 }
1843 g_free(player);
1844 }
1845
dt_view_audio_stop(dt_view_manager_t * vm)1846 void dt_view_audio_stop(dt_view_manager_t *vm)
1847 {
1848 // make sure that the process didn't finish yet and that _audio_child_watch() hasn't run
1849 if(vm->audio.audio_player_id == -1)
1850 return;
1851
1852 // we don't want to trigger the callback due to a possible race condition
1853 g_source_remove(vm->audio.audio_player_event_source);
1854 #ifdef _WIN32
1855 // TODO: add Windows code to actually kill the process
1856 #else // _WIN32
1857 if(vm->audio.audio_player_id != -1)
1858 {
1859 if(getpgid(0) != getpgid(vm->audio.audio_player_pid))
1860 kill(-vm->audio.audio_player_pid, SIGKILL);
1861 else
1862 kill(vm->audio.audio_player_pid, SIGKILL);
1863 }
1864 #endif // _WIN32
1865 g_spawn_close_pid(vm->audio.audio_player_pid);
1866 vm->audio.audio_player_id = -1;
1867 }
1868 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
1869 // vim: shiftwidth=2 expandtab tabstop=2 cindent
1870 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1871