Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
view.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2009-2015 johannes hanika.
4 Copyright (C) 2010-2012, 2014 Henrik Andersson.
5 Copyright (C) 2010 Richard Hughes.
6 Copyright (C) 2010-2020 Tobias Ellinghaus.
7 Copyright (C) 2011 Antony Dovgal.
8 Copyright (C) 2011 Kanstantsin Shautsou.
9 Copyright (C) 2011 Moritz Lipp.
10 Copyright (C) 2011 Omari Stephens.
11 Copyright (C) 2011 Robert Bieber.
12 Copyright (C) 2011, 2013 Simon Spannagel.
13 Copyright (C) 2012-2014 José Carlos García Sogo.
14 Copyright (C) 2012, 2014-2015 Jérémy Rosen.
15 Copyright (C) 2012 marcel.
16 Copyright (C) 2012 Michal Babej.
17 Copyright (C) 2012 Mika Boström.
18 Copyright (C) 2012 Pascal de Bruijn.
19 Copyright (C) 2012-2021 Pascal Obry.
20 Copyright (C) 2012 Richard Wonka.
21 Copyright (C) 2012, 2014 Ulrich Pegelow.
22 Copyright (C) 2013, 2016, 2019-2022 Aldric Renaudin.
23 Copyright (C) 2013 Benjamin Cahill.
24 Copyright (C) 2013 Gaspard Jankowiak.
25 Copyright (C) 2013 Pierre Le Magourou.
26 Copyright (C) 2013-2016, 2020 Roman Lebedev.
27 Copyright (C) 2013-2014 Ronny Kahl.
28 Copyright (C) 2014 Mikhail Trishchenkov.
29 Copyright (C) 2014 Pedro Côrte-Real.
30 Copyright (C) 2016 Asma.
31 Copyright (C) 2017, 2019, 2021 luzpaz.
32 Copyright (C) 2017, 2019 Marcello Mamino.
33 Copyright (C) 2017-2018 Matthieu Moy.
34 Copyright (C) 2017, 2019-2020 parafin.
35 Copyright (C) 2018 grand-piano.
36 Copyright (C) 2018 rawfiner.
37 Copyright (C) 2018 Rikard Öxler.
38 Copyright (C) 2019-2020, 2022-2023, 2025-2026 Aurélien PIERRE.
39 Copyright (C) 2019 Edgardo Hoszowski.
40 Copyright (C) 2019 Felipe Contreras.
41 Copyright (C) 2019-2020 Heiko Bauke.
42 Copyright (C) 2019 Jacopo Guderzo.
43 Copyright (C) 2020 Chris Elston.
44 Copyright (C) 2020 Dan Torop.
45 Copyright (C) 2020-2022 Diederik Ter Rahe.
46 Copyright (C) 2020 Hanno Schwalm.
47 Copyright (C) 2020-2021 Hubert Kowalski.
48 Copyright (C) 2020 Marco.
49 Copyright (C) 2020 Miloš Komarčević.
50 Copyright (C) 2020-2021 Philippe Weyland.
51 Copyright (C) 2021 Bill Ferguson.
52 Copyright (C) 2021 Ralf Brown.
53 Copyright (C) 2022 Martin Bařinka.
54 Copyright (C) 2022 Nicolas Auffray.
55 Copyright (C) 2022 Sakari Kapanen.
56 Copyright (C) 2023 Maurizio Paglia.
57 Copyright (C) 2025 Guillaume Stutin.
58
59 darktable is free software: you can redistribute it and/or modify
60 it under the terms of the GNU General Public License as published by
61 the Free Software Foundation, either version 3 of the License, or
62 (at your option) any later version.
63
64 darktable is distributed in the hope that it will be useful,
65 but WITHOUT ANY WARRANTY; without even the implied warranty of
66 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
67 GNU General Public License for more details.
68
69 You should have received a copy of the GNU General Public License
70 along with darktable. If not, see <http://www.gnu.org/licenses/>.
71*/
72
73#include "views/view.h"
74#include "bauhaus/bauhaus.h"
75#include "common/collection.h"
76#include "common/darktable.h"
77#include "common/sentry.h"
78#include "common/telemetry.h"
79#include "common/debug.h"
80#include "common/image_cache.h"
81#include "common/mipmap_cache.h"
82#include "common/module.h"
83#include "common/selection.h"
84#include "common/undo.h"
86#include "control/conf.h"
87#include "control/control.h"
88#include "develop/develop.h"
89#include "dtgtk/button.h"
90#include "dtgtk/expander.h"
91#include "dtgtk/thumbtable.h"
92
93#include "gui/draw.h"
94#include "gui/gtk.h"
95#include "libs/lib.h"
96#ifdef GDK_WINDOWING_QUARTZ
97#include "osx/osx.h"
98#endif
99
100#include <glib.h>
101#include <math.h>
102#include <stdio.h>
103#include <stdlib.h>
104#include <string.h>
105#include <strings.h>
106
107#define DECORATION_SIZE_LIMIT 40
108
110static int dt_view_load_module(void *v, const char *libname, const char *module_name);
112static int32_t _view_surface_fetch_job_run(dt_job_t *job);
113
115{
117 vm->current_view = NULL;
118 vm->audio.audio_player_id = -1;
119 vm->active_images = NULL;
120}
121
123{
124 for(GList *iter = vm->views; iter; iter = g_list_next(iter))
125 {
126 dt_view_t *view = (dt_view_t *)iter->data;
127 if(view->gui_init) view->gui_init(view);
128 }
129}
130
132{
133 g_list_free(vm->active_images);
134 vm->active_images = NULL;
135 for(GList *iter = vm->views; iter; iter = g_list_next(iter)) dt_view_unload_module((dt_view_t *)iter->data);
136 g_list_free_full(vm->views, dt_free_gpointer);
137 vm->views = NULL;
138}
139
144
145// we want a stable order of views, for example for viewswitcher.
146// anything not hardcoded will be put alphabetically wrt. localised names.
147static gint sort_views(gconstpointer a, gconstpointer b)
148{
149 static const char *view_order[] = {"lighttable", "darkroom"};
150 static const int n_view_order = G_N_ELEMENTS(view_order);
151
152 dt_view_t *av = (dt_view_t *)a;
153 dt_view_t *bv = (dt_view_t *)b;
154 const char *aname = av->name(av);
155 const char *bname = bv->name(bv);
156 int apos = n_view_order;
157 int bpos = n_view_order;
158
159 for(int i = 0; i < n_view_order; i++)
160 {
161 if(!strcmp(av->module_name, view_order[i])) apos = i;
162 if(!strcmp(bv->module_name, view_order[i])) bpos = i;
163 }
164
165 // order will be zero iff apos == bpos which can only happen when both views are not in view_order
166 const int order = apos - bpos;
167 return order ? order : strcmp(aname, bname);
168}
169
174
175/* default flags for view which does not implement the flags() function */
176static uint32_t default_flags()
177{
178 return 0;
179}
180
182static int dt_view_load_module(void *v, const char *libname, const char *module_name)
183{
184 dt_view_t *module = (dt_view_t *)v;
185 g_strlcpy(module->module_name, module_name, sizeof(module->module_name));
186
187#define INCLUDE_API_FROM_MODULE_LOAD "view_load_module"
188#include "views/view_api.h"
189
190 module->data = NULL;
191 module->vscroll_size = module->vscroll_viewport_size = 1.0;
192 module->hscroll_size = module->hscroll_viewport_size = 1.0;
193 module->vscroll_pos = module->hscroll_pos = 0.0;
194 module->height = module->width = 100; // set to non-insane defaults before first expose/configure.
195
196 if(module->init) module->init(module);
197
198 return 0;
199}
200
203{
204 if(view->cleanup) view->cleanup(view);
205
206 if(view->module) g_module_close(view->module);
207}
208
209void dt_vm_remove_child(GtkWidget *widget, gpointer data)
210{
211 if(GTK_IS_CONTAINER(data))
212 gtk_container_remove(GTK_CONTAINER(data), widget);
213}
214
215/*
216 When expanders get destroyed, they destroy the child
217 so remove the child before that
218 */
219static void _remove_child(GtkWidget *child,GtkContainer *container)
220{
221 // some libs module can be used inside popups and not attached to panels, they have no container.
222 if(DTGTK_IS_EXPANDER(child))
223 {
225 if(GTK_IS_CONTAINER(evb))
226 gtk_container_remove(GTK_CONTAINER(evb), dtgtk_expander_get_body(DTGTK_EXPANDER(child)));
227 gtk_widget_destroy(child);
228 }
229 else if(GTK_IS_CONTAINER(container))
230 {
231 gtk_container_remove(container, child);
232 }
233}
234
235int dt_view_manager_switch(dt_view_manager_t *vm, const char *view_name)
236{
237 gboolean switching_to_none = *view_name == '\0';
238 dt_view_t *new_view = NULL;
239
240 if(!switching_to_none)
241 {
242 for(GList *iter = vm->views; iter; iter = g_list_next(iter))
243 {
244 dt_view_t *v = (dt_view_t *)iter->data;
245 if(!strcmp(v->module_name, view_name))
246 {
247 new_view = v;
248 break;
249 }
250 }
251 if(IS_NULL_PTR(new_view)) return 1; // the requested view doesn't exist
252 }
253
254 return dt_view_manager_switch_by_view(vm, new_view);
255}
256
258{
259 dt_view_t *old_view = vm->current_view;
260 dt_view_t *new_view = (dt_view_t *)nv; // views belong to us, we can de-const them :-)
261
262 // reset the cursor to the default one
263 dt_control_change_cursor(GDK_LEFT_PTR);
264
265 /* Reset Gtk focus */
266 gtk_window_set_focus(GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)), NULL);
268
269 // also ignore what scrolling there was previously happening
270 memset(darktable.gui->scroll_to, 0, sizeof(darktable.gui->scroll_to));
271
272 // destroy old module list
273
274 /* clear the undo list, for now we do this unconditionally. At some point we will probably want to clear
275 only part
276 of the undo list. This should probably done with a view proxy routine returning the type of undo to
277 remove. */
279
280 /* Special case when entering nothing (just before leaving dt) */
281 if(IS_NULL_PTR(new_view))
282 {
283 if(old_view)
284 {
285 /* leave the current view*/
286 if(old_view->leave) old_view->leave(old_view);
287
288 /* iterator plugins and cleanup plugins in current view */
289 for(GList *iter = darktable.lib->plugins; iter; iter = g_list_next(iter))
290 {
291 dt_lib_module_t *plugin = (dt_lib_module_t *)(iter->data);
292
293 /* does this module belong to current view ?*/
294 if(dt_lib_is_visible_in_view(plugin, old_view))
295 {
296 if(plugin->view_leave) plugin->view_leave(plugin, old_view, NULL);
297 plugin->gui_cleanup(plugin);
298 plugin->data = NULL;
299 plugin->widget = NULL;
300 }
301 }
302 }
303
304 /* remove all widgets in all containers */
306 for(int l = 0; l < DT_UI_CONTAINER_SIZE; l++)
308 vm->current_view = NULL;
309
310 return 0;
311 }
312
313 // invariant: !IS_NULL_PTR(new_view) after this point
314 assert(!IS_NULL_PTR(new_view));
315
316 if(new_view->try_enter)
317 {
318 const int error = new_view->try_enter(new_view);
319 if(error)
320 {
322 return error;
323 }
324 }
325
326 /* cleanup current view before initialization of new */
327 if(old_view)
328 {
329 /* leave current view */
330 if(old_view->leave) old_view->leave(old_view);
331
332 /* iterator plugins and cleanup plugins in current view */
333 for(GList *iter = darktable.lib->plugins; iter; iter = g_list_next(iter))
334 {
335 dt_lib_module_t *plugin = (dt_lib_module_t *)(iter->data);
336
337 /* does this module belong to current view ?*/
338 if(dt_lib_is_visible_in_view(plugin, old_view))
339 {
340 if(plugin->view_leave) plugin->view_leave(plugin, old_view, new_view);
341 }
342 }
343
344 /* remove all widets in all containers */
345 for(int l = 0; l < DT_UI_CONTAINER_SIZE; l++)
347 }
348
349 /* change current view to the new view */
350 vm->current_view = new_view;
351
352 /* restore visible stat of panels for the new view */
354
355 /* lets add plugins related to new view into panels.
356 * this has to be done in reverse order to have the lowest position at the bottom! */
357 for(GList *iter = g_list_last(darktable.lib->plugins); iter; iter = g_list_previous(iter))
358 {
359 dt_lib_module_t *plugin = (dt_lib_module_t *)(iter->data);
360 if(dt_lib_is_visible_in_view(plugin, new_view))
361 {
362
363 /* try get the module expander */
365
366 /* if we didn't get an expander let's add the widget */
367 if(IS_NULL_PTR(w)) w = plugin->widget;
368
370 // some plugins help links depend on the view
371 if(!strcmp(plugin->plugin_name,"module_toolbox")
372 || !strcmp(plugin->plugin_name,"view_toolbox"))
373 {
374 dt_view_type_flags_t view_type = new_view->view(new_view);
375 if(view_type == DT_VIEW_LIGHTTABLE)
376 dt_gui_add_help_link(w, dt_get_help_url("lighttable_mode"));
377 if(view_type == DT_VIEW_DARKROOM)
378 dt_gui_add_help_link(w, dt_get_help_url("darkroom_bottom_panel"));
379 }
380
381
382 /* add module to its container */
383 dt_ui_container_add_widget(darktable.gui->ui, plugin->container(plugin), w);
384 }
385 }
386
387 /* hide/show modules as last config */
388 for(GList *iter = darktable.lib->plugins; iter; iter = g_list_next(iter))
389 {
390 dt_lib_module_t *plugin = (dt_lib_module_t *)(iter->data);
391 if(dt_lib_is_visible_in_view(plugin, new_view))
392 {
393 /* set expanded if last mode was that */
394 char var[1024];
395 gboolean expanded = FALSE;
396 gboolean visible = dt_lib_is_visible(plugin);
397 if(plugin->expandable(plugin))
398 {
399 snprintf(var, sizeof(var), "plugins/%s/%s/expanded", new_view->module_name, plugin->plugin_name);
400 expanded = dt_conf_get_bool(var);
401 dt_lib_gui_set_expanded(plugin, expanded);
402 dt_lib_set_visible(plugin, visible);
403 }
404 else
405 {
406 /* show/hide plugin widget depending on expanded flag or if plugin
407 not is expandeable() */
408 if(visible)
409 gtk_widget_show_all(plugin->widget);
410 else
411 gtk_widget_hide(plugin->widget);
412 }
413 if(plugin->view_enter) plugin->view_enter(plugin, old_view, new_view);
414 }
415 }
416
417 /* enter view. crucially, do this before initing the plugins below,
418 as e.g. modulegroups requires the dr stuff to be inited. */
419 if(new_view->enter) new_view->enter(new_view);
420
421 /* record view usage for crash reports and usage analytics */
424
425 /* raise view changed signal */
427
428 // update log visibility
430
431 // update toast visibility
433 return 0;
434}
435
437{
438 if(IS_NULL_PTR(vm->current_view)) return "";
439 if(vm->current_view->name)
440 return vm->current_view->name(vm->current_view);
441 else
442 return vm->current_view->module_name;
443}
444
445void dt_view_manager_expose(dt_view_manager_t *vm, cairo_t *cr, int32_t width, int32_t height,
446 int32_t pointerx, int32_t pointery)
447{
449 {
451 cairo_paint(cr);
452 return;
453 }
454 vm->current_view->width = width;
456
457 if(vm->current_view->expose)
458 {
459 /* expose the view */
460 cairo_rectangle(cr, 0, 0, vm->current_view->width, vm->current_view->height);
461 cairo_clip(cr);
462 cairo_new_path(cr);
463 cairo_save(cr);
464 float px = pointerx, py = pointery;
465 if(pointery > vm->current_view->height)
466 {
467 px = 10000.0;
468 py = -1.0;
469 }
470 vm->current_view->expose(vm->current_view, cr, vm->current_view->width, vm->current_view->height, px, py);
471
472 cairo_restore(cr);
473 /* expose plugins */
474 for(const GList *plugins = g_list_last(darktable.lib->plugins); plugins; plugins = g_list_previous(plugins))
475 {
476 dt_lib_module_t *plugin = (dt_lib_module_t *)(plugins->data);
477
478 /* does this module belong to current view ?*/
479 if(plugin->gui_post_expose
481 plugin->gui_post_expose(plugin, cr, vm->current_view->width, vm->current_view->height, px, py);
482 }
483 }
484}
485
487{
488 if(IS_NULL_PTR(vm->current_view)) return;
489 if(vm->current_view->reset) vm->current_view->reset(vm->current_view);
490}
491
493{
494 if(IS_NULL_PTR(vm->current_view)) return;
495 dt_view_t *v = vm->current_view;
496
497 /* lets check if any plugins want to handle mouse move */
498 gboolean handled = FALSE;
499 for(const GList *plugins = g_list_last(darktable.lib->plugins);
500 plugins;
501 plugins = g_list_previous(plugins))
502 {
503 dt_lib_module_t *plugin = (dt_lib_module_t *)(plugins->data);
504
505 /* does this module belong to current view ?*/
506 if(plugin->mouse_leave && dt_lib_is_visible_in_view(plugin, v))
507 if(plugin->mouse_leave(plugin)) handled = TRUE;
508 }
509
510 /* if not handled by any plugin let pass to view handler*/
511 if(!handled && v->mouse_leave) v->mouse_leave(v);
512}
513
515{
516 if(IS_NULL_PTR(vm->current_view)) return;
517 if(vm->current_view->mouse_enter) vm->current_view->mouse_enter(vm->current_view);
518}
519
520void dt_view_manager_mouse_moved(dt_view_manager_t *vm, double x, double y, double pressure, int which)
521{
522 if(IS_NULL_PTR(vm->current_view)) return;
523 dt_view_t *v = vm->current_view;
524
525 /* lets check if any plugins want to handle mouse move */
526 gboolean handled = FALSE;
527 for(const GList *plugins = g_list_last(darktable.lib->plugins);
528 plugins;
529 plugins = g_list_previous(plugins))
530 {
531 dt_lib_module_t *plugin = (dt_lib_module_t *)(plugins->data);
532
533 /* does this module belong to current view ?*/
534 if(plugin->mouse_moved && dt_lib_is_visible_in_view(plugin, v))
535 if(plugin->mouse_moved(plugin, x, y, pressure, which)) handled = TRUE;
536 }
537
538 /* if not handled by any plugin let pass to view handler*/
539 if(!handled && v->mouse_moved) v->mouse_moved(v, x, y, pressure, which);
540}
541
543{
544 if(IS_NULL_PTR(vm->current_view)) return 0;
545 dt_view_t *v = vm->current_view;
546
547 /* lets check if any plugins want to handle button press */
548 gboolean handled = FALSE;
549 for(const GList *plugins = g_list_last(darktable.lib->plugins);
550 plugins;
551 plugins = g_list_previous(plugins))
552 {
553 dt_lib_module_t *plugin = (dt_lib_module_t *)(plugins->data);
554
555 /* does this module belong to current view ?*/
556 if(plugin->key_pressed && dt_lib_is_visible_in_view(plugin, v))
557 if(plugin->key_pressed(plugin, event)) handled = TRUE;
558 }
559
560 if(handled)
561 return 1;
562 /* if not handled by any plugin let pass to view handler*/
563 else if(v->key_pressed)
564 v->key_pressed(v, event);
565
566 return 0;
567}
568
569int dt_view_manager_button_released(dt_view_manager_t *vm, double x, double y, int which, uint32_t state)
570{
571 if(IS_NULL_PTR(vm->current_view)) return 0;
572 dt_view_t *v = vm->current_view;
573
574 /* lets check if any plugins want to handle button press */
575 gboolean handled = FALSE;
576 for(const GList *plugins = g_list_last(darktable.lib->plugins);
577 plugins;
578 plugins = g_list_previous(plugins))
579 {
580 dt_lib_module_t *plugin = (dt_lib_module_t *)(plugins->data);
581
582 /* does this module belong to current view ?*/
583 if(plugin->button_released && dt_lib_is_visible_in_view(plugin, v))
584 if(plugin->button_released(plugin, x, y, which, state)) handled = TRUE;
585 }
586
587 if(handled)
588 return 1;
589 /* if not handled by any plugin let pass to view handler*/
590 else if(v->button_released)
591 v->button_released(v, x, y, which, state);
592
593 return 0;
594}
595
596int dt_view_manager_button_pressed(dt_view_manager_t *vm, double x, double y, double pressure, int which,
597 int type, uint32_t state)
598{
599 if(IS_NULL_PTR(vm->current_view)) return 0;
600 dt_view_t *v = vm->current_view;
601
602 /* Reset Gtk focus */
603 gtk_window_set_focus(GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)), NULL);
605
606 /* lets check if any plugins want to handle button press */
607 gboolean handled = FALSE;
608
609 for(const GList *plugins = g_list_last(darktable.lib->plugins);
610 plugins && !handled;
611 plugins = g_list_previous(plugins))
612 {
613 dt_lib_module_t *plugin = (dt_lib_module_t *)(plugins->data);
614
615 /* does this module belong to current view ?*/
616 if(plugin->button_pressed && dt_lib_is_visible_in_view(plugin, v))
617 if(plugin->button_pressed(plugin, x, y, pressure, which, type, state)) handled = TRUE;
618 }
619
620 if(handled) return 1;
621 /* if not handled by any plugin let pass to view handler*/
622 else if(v->button_pressed)
623 return v->button_pressed(v, x, y, pressure, which, type, state);
624
625 return 0;
626}
627
629{
630 for(GList *iter = vm->views; iter; iter = g_list_next(iter))
631 {
632 // this is necessary for all
633 dt_view_t *v = (dt_view_t *)iter->data;
634 v->width = width;
635 v->height = height;
636 if(v->configure) v->configure(v, width, height);
637 }
638}
639
640int dt_view_manager_scrolled(dt_view_manager_t *vm, double x, double y, int up, int state, int delta_y)
641{
642 if(IS_NULL_PTR(vm->current_view)) return FALSE;
643 if(vm->current_view->scrolled)
644 return vm->current_view->scrolled(vm->current_view, x, y, up, state, delta_y);
645 return 0;
646}
647
657
669
671 cairo_surface_t **surface, int zoom,
672 dt_atomic_int *shutdown);
673
674static void _destroy_surface(cairo_surface_t **surface)
675{
676 if(surface && *surface && cairo_surface_get_reference_count(*surface) > 0)
677 cairo_surface_destroy(*surface);
678 if(surface) *surface = NULL;
679}
680
681static gboolean _view_surface_matches(const dt_view_image_surface_fetcher_t *fetcher, cairo_surface_t **target,
682 const int32_t imgid, const int width, const int height, const int zoom)
683{
684 return target && *target && fetcher->cached_imgid == imgid && fetcher->cached_width == width
685 && fetcher->cached_height == height && fetcher->cached_zoom == zoom;
686}
687
689{
690 dt_view_surface_fetch_job_t *params = g_malloc0(sizeof(dt_view_surface_fetch_job_t));
691 params->fetcher = fetcher;
692 params->request_id = fetcher->request_id;
693 params->imgid = fetcher->imgid;
694 params->width = fetcher->width;
695 params->height = fetcher->height;
696 params->zoom = fetcher->zoom;
697
698 dt_job_t *job = dt_control_job_create(&_view_surface_fetch_job_run, "fetch image surface %i", params->imgid);
699 if(IS_NULL_PTR(job))
700 {
701 g_free(params);
702 return;
703 }
704
705 dt_atomic_set_int(&fetcher->shutdown, FALSE);
707 fetcher->job_queued = TRUE;
708 fetcher->queued_request_id = fetcher->request_id;
710}
711
712static gboolean _view_surface_commit_main(gpointer user_data)
713{
715 dt_view_image_surface_fetcher_t *fetcher = commit->fetcher;
716
717 gboolean queue_redraw = FALSE;
718 gboolean enqueue_next = FALSE;
719
720 dt_pthread_mutex_lock(&fetcher->lock);
721 fetcher->commit_pending = FALSE;
722 pthread_cond_broadcast(&fetcher->cond);
723
724 if(!fetcher->destroying && commit->request_id == fetcher->request_id && commit->result == DT_VIEW_SURFACE_OK)
725 {
726 _destroy_surface(fetcher->target);
727 if(fetcher->target) *fetcher->target = commit->surface;
728 commit->surface = NULL;
729 fetcher->cached_imgid = commit->imgid;
730 fetcher->cached_width = commit->width;
731 fetcher->cached_height = commit->height;
732 fetcher->cached_zoom = commit->zoom;
733 queue_redraw = TRUE;
734 }
735
736 if(!fetcher->destroying && !fetcher->job_queued && fetcher->request_id != commit->request_id)
737 enqueue_next = TRUE;
738 dt_pthread_mutex_unlock(&fetcher->lock);
739
740 if(commit->surface) cairo_surface_destroy(commit->surface);
741
742 if(queue_redraw)
743 {
744 GtkWidget *widget = g_weak_ref_get(&fetcher->widget_ref);
745 if(widget)
746 {
747 if(widget == dt_ui_center(darktable.gui->ui))
748 {
750 }
751 else
752 {
753 gtk_widget_queue_draw(widget);
754 GdkWindow *window = gtk_widget_get_window(widget);
755 if(window)
756 {
757 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
758 gdk_window_process_updates(window, TRUE);
759 G_GNUC_END_IGNORE_DEPRECATIONS
760 }
761 }
762 g_object_unref(widget);
763 }
764 }
765
766 if(enqueue_next)
767 {
768 dt_pthread_mutex_lock(&fetcher->lock);
769 if(!fetcher->destroying && !fetcher->job_queued && !fetcher->commit_pending)
770 _enqueue_surface_fetch(fetcher);
771 dt_pthread_mutex_unlock(&fetcher->lock);
772 }
773
774 g_free(commit);
775 return G_SOURCE_REMOVE;
776}
777
779{
781 dt_view_image_surface_fetcher_t *fetcher = params->fetcher;
782
783 dt_pthread_mutex_lock(&fetcher->lock);
784 const gboolean stale = fetcher->destroying || params->request_id != fetcher->request_id;
785 const gboolean cancelled = dt_control_job_get_state(job) == DT_JOB_STATE_CANCELLED;
786 if(stale || cancelled)
787 {
788 if(fetcher->job_queued && fetcher->queued_request_id == params->request_id) fetcher->job_queued = FALSE;
789
790 // A stale request means the widget parameters changed while the current
791 // job was queued or running. If no newer request is already tracked
792 // locally, start the latest one immediately from the current fetcher
793 // state instead of trying to reason about queue-owned job objects.
794 if(!fetcher->destroying && params->request_id != fetcher->request_id && !fetcher->job_queued
795 && !fetcher->commit_pending)
796 _enqueue_surface_fetch(fetcher);
797
798 pthread_cond_broadcast(&fetcher->cond);
799 dt_pthread_mutex_unlock(&fetcher->lock);
800 return 0;
801 }
802 dt_pthread_mutex_unlock(&fetcher->lock);
803
804 cairo_surface_t *surface = NULL;
805 const dt_view_surface_value_t result =
806 _view_image_get_surface_internal(params->imgid, params->width, params->height, &surface, params->zoom,
807 &fetcher->shutdown);
808
810 commit->fetcher = fetcher;
811 commit->request_id = params->request_id;
812 commit->imgid = params->imgid;
813 commit->width = params->width;
814 commit->height = params->height;
815 commit->zoom = params->zoom;
816 commit->surface = surface;
817 commit->result = result;
818
819 dt_pthread_mutex_lock(&fetcher->lock);
820 if(fetcher->job_queued && fetcher->queued_request_id == params->request_id) fetcher->job_queued = FALSE;
821 fetcher->commit_pending = TRUE;
822 pthread_cond_broadcast(&fetcher->cond);
823 dt_pthread_mutex_unlock(&fetcher->lock);
824
825 g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT, _view_surface_commit_main,
826 commit, NULL);
827 g_main_context_wakeup(g_main_context_default());
828 return 0;
829}
830
832{
833 memset(fetcher, 0, sizeof(dt_view_image_surface_fetcher_t));
834 dt_pthread_mutex_init(&fetcher->lock, NULL);
835 pthread_cond_init(&fetcher->cond, NULL);
836 g_weak_ref_init(&fetcher->widget_ref, NULL);
837 fetcher->cached_imgid = UNKNOWN_IMAGE;
838 fetcher->imgid = UNKNOWN_IMAGE;
839 dt_atomic_set_int(&fetcher->shutdown, FALSE);
840}
841
843{
844 dt_pthread_mutex_lock(&fetcher->lock);
845 fetcher->destroying = TRUE;
846 dt_atomic_set_int(&fetcher->shutdown, TRUE);
847 while(fetcher->job_queued)
848 dt_pthread_cond_wait(&fetcher->cond, &fetcher->lock);
849 dt_pthread_mutex_unlock(&fetcher->lock);
850
851 for(;;)
852 {
853 dt_pthread_mutex_lock(&fetcher->lock);
854 const gboolean pending = fetcher->commit_pending;
855 dt_pthread_mutex_unlock(&fetcher->lock);
856 if(!pending) break;
857 g_main_context_iteration(g_main_context_default(), TRUE);
858 }
859
860 _destroy_surface(fetcher->target);
861 g_weak_ref_clear(&fetcher->widget_ref);
862 pthread_cond_destroy(&fetcher->cond);
864}
865
867{
868 dt_pthread_mutex_lock(&fetcher->lock);
869 fetcher->target = target;
870 fetcher->request_id++;
871 dt_atomic_set_int(&fetcher->shutdown, TRUE);
872 fetcher->cached_imgid = UNKNOWN_IMAGE;
873 fetcher->cached_width = 0;
874 fetcher->cached_height = 0;
875 fetcher->cached_zoom = 0;
876 dt_pthread_mutex_unlock(&fetcher->lock);
877
878 _destroy_surface(target);
879}
880
882 int width, int height, cairo_surface_t **target,
883 GtkWidget *widget, int zoom)
884{
885 if(IS_NULL_PTR(fetcher) || !target || !widget || width < 2 || height < 2 || imgid <= UNKNOWN_IMAGE)
886 return DT_VIEW_SURFACE_KO;
887
889
890 dt_pthread_mutex_lock(&fetcher->lock);
891 const gboolean changed = fetcher->target != target || fetcher->imgid != imgid || fetcher->width != width
892 || fetcher->height != height || fetcher->zoom != zoom;
893 const gboolean exact_match = _view_surface_matches(fetcher, target, imgid, width, height, zoom);
894 const gboolean fallback_match = target && *target && fetcher->cached_imgid == imgid
895 && fetcher->cached_zoom == zoom;
896 fetcher->target = target;
897 fetcher->imgid = imgid;
898 fetcher->width = width;
899 fetcher->height = height;
900 fetcher->zoom = zoom;
901 g_weak_ref_set(&fetcher->widget_ref, widget);
902
903 if(exact_match || fallback_match)
904 ret = DT_VIEW_SURFACE_OK;
905
906 if(changed && !exact_match && !fetcher->destroying && !fetcher->job_queued
907 && !fetcher->commit_pending)
908 {
909 fetcher->request_id++;
910 _enqueue_surface_fetch(fetcher);
911 }
912 else if(changed && !exact_match && !fetcher->destroying)
913 {
914 fetcher->request_id++;
915 dt_atomic_set_int(&fetcher->shutdown, TRUE);
916 }
917 dt_pthread_mutex_unlock(&fetcher->lock);
918
919 return ret;
920}
921
922cairo_surface_t *dt_cairo_rescale_surface(cairo_surface_t *src, int dst_w, int dst_h)
923{
924 if(!src || dst_w <= 0 || dst_h <= 0)
925 return NULL;
926
927 const int src_w = cairo_image_surface_get_width(src);
928 const int src_h = cairo_image_surface_get_height(src);
929
930 cairo_surface_t *dst =
931 cairo_image_surface_create(CAIRO_FORMAT_RGB24, dst_w, dst_h);
932
933 cairo_t *cr = cairo_create(dst);
934
935 // clear (important if aspect ratio leaves borders)
936 cairo_set_source_rgba(cr, 0, 0, 0, 0);
937 cairo_paint(cr);
938
939 double scale_x = (double)dst_w / src_w;
940 double scale_y = (double)dst_h / src_h;
941
942 double sx = scale_x;
943 double sy = scale_y;
944 double tx = 0.0;
945 double ty = 0.0;
946
947
948 double s = MIN(scale_x, scale_y);
949 sx = sy = s;
950
951 tx = (dst_w - src_w * s) * 0.5;
952 ty = (dst_h - src_h * s) * 0.5;
953
954
955 cairo_translate(cr, tx, ty);
956 cairo_scale(cr, sx, sy);
957
958 cairo_set_source_surface(cr, src, 0, 0);
959
960 cairo_pattern_t *pat = cairo_get_source(cr);
961 cairo_pattern_set_filter(pat, CAIRO_FILTER_BEST);
962 cairo_pattern_set_extend(pat, CAIRO_EXTEND_PAD);
963
964 cairo_paint(cr);
965
966 cairo_destroy(cr);
967
968 return dst;
969}
970
971void dt_cairo_sharpen_surface_rgb24(cairo_surface_t *surface)
972{
973 if(!surface) return;
974
975 cairo_surface_flush(surface);
976
977 unsigned char *data = cairo_image_surface_get_data(surface);
978 int width = cairo_image_surface_get_width(surface);
979 int height = cairo_image_surface_get_height(surface);
980 int stride = cairo_image_surface_get_stride(surface);
981
982 // Copy original buffer
983 unsigned char *copy = dt_alloc_align(stride * height);
984 memcpy(copy, data, stride * height);
985
986 // Kernel weights
987 const float k_center = 4.0f;
988 const float k_edge = -0.5f;
989 const float k_corner = -0.25f;
990
991 // Unsharp mask coeffs
992 const float amount = 0.05f;
993 const float amount_inv = 1.f - amount;
994
995 __OMP_PARALLEL_FOR__(collapse(2))
996 for(int y = 1; y < height - 1; y++)
997 for(int x = 1; x < width - 1; x++)
998 {
999 int idx = y * stride + x * 4;
1000
1001 for(int c = 0; c < 3; c++) // B, G, R
1002 {
1003 int i = idx + c;
1004
1005 float v =
1006 k_center * copy[i]
1007
1008 + k_edge * (
1009 copy[i - 4] + // left
1010 copy[i + 4] + // right
1011 copy[i - stride] + // top
1012 copy[i + stride] // bottom
1013 )
1014
1015 + k_corner * (
1016 copy[i - stride - 4] + // top-left
1017 copy[i - stride + 4] + // top-right
1018 copy[i + stride - 4] + // bottom-left
1019 copy[i + stride + 4] // bottom-right
1020 );
1021
1022 // Unsharp-style recombination
1023 const float out = amount_inv * copy[i] + amount * v;
1024 data[i] = (unsigned char)(CLAMP(roundf(out), 0.f, 255.f));
1025 }
1026
1027 data[idx + 3] = copy[idx + 3];
1028 }
1029
1030 dt_free(copy);
1031 cairo_surface_mark_dirty(surface);
1032}
1033
1035 cairo_surface_t **surface, int zoom,
1036 dt_atomic_int *shutdown)
1037{
1038 double tt = 0;
1040 tt = dt_get_wtime();
1041
1043
1044 // if surface not null, clean it up
1045 if(*surface && cairo_surface_get_reference_count(*surface) > 0)
1046 cairo_surface_destroy(*surface);
1047 *surface = NULL;
1048
1049 // get mipmap cache image
1052
1053 if(zoom == DT_THUMBTABLE_ZOOM_FIT)
1054 {
1055 mip = dt_mipmap_cache_get_matching_size(cache, ceilf(width * darktable.gui->ppd), ceilf(height * darktable.gui->ppd), imgid);
1056 }
1057 else
1058 {
1059 const dt_image_t *image = dt_image_cache_get(darktable.image_cache, imgid, 'r');
1060 const int full_width = image->width;
1061 const int full_height = image->height;
1063
1064 if(zoom == DT_THUMBTABLE_ZOOM_HALF)
1065 mip = dt_mipmap_cache_get_matching_size(cache, ceilf(full_width / 2.f ), ceilf(full_height / 2.f), imgid);
1066 else if(zoom >= DT_THUMBTABLE_ZOOM_FULL)
1067 mip = dt_mipmap_cache_get_matching_size(cache, full_width, full_height, imgid);
1068 }
1069
1070 // Can't have float32 types here
1071 if(mip >= DT_MIPMAP_F) return DT_VIEW_SURFACE_KO;
1072
1073 // if needed, we load the mimap buffer
1075 dt_mipmap_cache_get_with_shutdown(cache, &buf, imgid, mip, DT_MIPMAP_BLOCKING, 'r', shutdown);
1076 const int buf_wd = buf.width;
1077 const int buf_ht = buf.height;
1078
1079 // if we don't get buffer, no image is available at the moment
1080 if(IS_NULL_PTR(buf.buf))
1081 {
1083 return DT_VIEW_SURFACE_KO;
1084 }
1085
1086 // so we create a new image surface to return
1087 float scale = 1.f;
1088 int img_width = buf_wd;
1089 int img_height = buf_ht;
1090
1091 if(zoom == DT_THUMBTABLE_ZOOM_FIT)
1092 {
1093 scale = fminf((float)width / (float)buf_wd, (float)height / (float)buf_ht) * darktable.gui->ppd;
1094 img_width = roundf(buf_wd * scale);
1095 img_height = roundf(buf_ht * scale);
1096
1097 // due to the forced rounding above, we need to recompute scaling
1098 scale = fmaxf((float)img_width / (float)buf_wd, (float)img_height / (float)buf_ht);
1099 }
1100 else if(zoom == DT_THUMBTABLE_ZOOM_TWICE)
1101 {
1102 // NOTE: we upscale the image surface, which means we will oversample
1103 // the full-res input buffer
1104 scale = 2.f;
1105 img_width = roundf(buf_wd * scale);
1106 img_height = roundf(buf_ht * scale);
1107 }
1108
1109 *surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, img_width, img_height);
1110
1111 // we transfer cached image on a cairo_surface (with colorspace transform if needed)
1112 uint8_t *rgbbuf = (uint8_t *)calloc((size_t)buf_wd * buf_ht * 4, sizeof(uint8_t));
1113 if(IS_NULL_PTR(rgbbuf))
1114 {
1116 return ret;
1117 }
1118
1119 cmsHTRANSFORM transform = NULL;
1120 pthread_rwlock_rdlock(&darktable.color_profiles->xprofile_lock);
1121 gboolean alloc = FALSE;
1122
1123 // we only color manage when a thumbnail is sRGB or AdobeRGB. everything else just gets dumped to the
1124 // screen
1126 {
1128 }
1129 else if(buf.color_space == DT_COLORSPACE_ADOBERGB)
1130 {
1132 }
1133 else if(buf.color_space == DT_COLORSPACE_DISPLAY)
1134 {
1135 // no-op, buffer is already in display space, pass pixels through
1136 // and simply swap R <-> B, which happens because transform = NULL
1137 }
1138 else
1139 {
1140 alloc = TRUE;
1141 transform = cmsCreateTransform(
1144 INTENT_PERCEPTUAL, 0);
1145 }
1146
1148 if(alloc) cmsDeleteTransform(transform);
1149 pthread_rwlock_unlock(&darktable.color_profiles->xprofile_lock);
1151
1152 const int32_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, buf_wd);
1153 cairo_surface_t *tmp_surface = cairo_image_surface_create_for_data(rgbbuf, CAIRO_FORMAT_RGB24, buf_wd, buf_ht, stride);
1154 if(IS_NULL_PTR(tmp_surface))
1155 {
1156 dt_free(rgbbuf);
1157 return ret;
1158 }
1159
1160 // draw the image scaled:
1161 cairo_t *cr = cairo_create(*surface);
1162 cairo_scale(cr, scale, scale);
1163 cairo_set_source_surface(cr, tmp_surface, 0, 0);
1164
1165 // set filter no nearest:
1166 // in skull mode, we want to see big pixels.
1167 // in 1 iir mode for the right mip, we want to see exactly what the pipe gave us, 1:1 pixel for pixel.
1168 // in between, filtering just makes stuff go unsharp.
1169 if((buf_wd <= 8 && buf_ht <= 8)
1170 || fabsf(scale - 1.0f) < 0.01f
1171 || zoom == DT_THUMBTABLE_ZOOM_TWICE)
1172 cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
1173 else
1174 cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BEST);
1175
1176 cairo_paint(cr);
1177 cairo_surface_destroy(tmp_surface);
1178 cairo_destroy(cr);
1179
1180 /* The async/shared surface path returns pixel-sized Cairo image surfaces.
1181 * Publish the widget PPD on the finished surface so GUI callers can place it
1182 * in logical coordinates without re-deriving HiDPI scaling on every draw. */
1183 cairo_surface_set_device_scale(*surface, darktable.gui->ppd, darktable.gui->ppd);
1184
1185 // we consider skull as ok as the image hasn't to be reloaded
1186 if(buf_wd <= 8 && buf_ht <= 8)
1187 {
1188 ret = DT_VIEW_SURFACE_OK;
1189 }
1190 else
1191 {
1192 ret = DT_VIEW_SURFACE_OK;
1193 //dt_cairo_sharpen_surface_rgb24(*surface);
1194 }
1195
1196 dt_free(rgbbuf);
1197
1198 // logs
1200 {
1202 "[dt_view_image_get_surface] id %i, mip code %i, dots %ix%i, mip %ix%i, surf %ix%i created in %0.04f sec\n",
1203 imgid, mip, width, height, buf_wd, buf_ht, img_width, img_height, dt_get_wtime() - tt);
1204 }
1206 {
1207 dt_print(DT_DEBUG_IMAGEIO, "[dt_view_image_get_surface] id %i, mip code %i, dots %ix%i, mip %ix%i, surf %ix%i\n", imgid, mip,
1208 width, height, buf_wd, buf_ht, img_width, img_height);
1209 }
1210
1211 // we consider skull as ok as the image hasn't to be reload
1212 return ret;
1213}
1214
1215dt_view_surface_value_t dt_view_image_get_surface(int32_t imgid, int width, int height, cairo_surface_t **surface,
1216 int zoom)
1217{
1218 return _view_image_get_surface_internal(imgid, width, height, surface, zoom, NULL);
1219}
1220
1221char* dt_view_extend_modes_str(const char * name, const gboolean is_hdr, const gboolean is_bw, const gboolean is_bw_flow)
1222{
1223 char* upcase = g_ascii_strup(name, -1); // extension in capital letters to avoid character descenders
1224 // convert to canonical format extension
1225 if(0 == g_ascii_strcasecmp(upcase, "JPG"))
1226 {
1227 gchar* canonical = g_strdup("JPEG");
1228 dt_free(upcase);
1229 upcase = canonical;
1230 }
1231 else if(0 == g_ascii_strcasecmp(upcase, "HDR"))
1232 {
1233 gchar* canonical = g_strdup("RGBE");
1234 dt_free(upcase);
1235 upcase = canonical;
1236 }
1237 else if(0 == g_ascii_strcasecmp(upcase, "TIF"))
1238 {
1239 gchar* canonical = g_strdup("TIFF");
1240 dt_free(upcase);
1241 upcase = canonical;
1242 }
1243
1244 if(is_hdr)
1245 {
1246 gchar* fullname = g_strdup_printf("%s HDR", upcase);
1247 dt_free(upcase);
1248 upcase = fullname;
1249 }
1250 if(is_bw)
1251 {
1252 gchar* fullname = g_strdup_printf("%s B&W", upcase);
1253 dt_free(upcase);
1254 upcase = fullname;
1255 if(!is_bw_flow)
1256 {
1257 fullname = g_strdup_printf("%s-", upcase);
1258 dt_free(upcase);
1259 upcase = fullname;
1260 }
1261 }
1262
1263 return upcase;
1264}
1265
1266
1275
1276void dt_view_active_images_add(int32_t imgid, gboolean raise)
1277{
1279 = g_list_append(darktable.view_manager->active_images, GINT_TO_POINTER(imgid));
1280 if(raise)
1282}
1283
1284void dt_view_active_images_remove(int32_t imgid, gboolean raise)
1285{
1286 GList *link = g_list_find(darktable.view_manager->active_images, GINT_TO_POINTER(imgid));
1287 if(link)
1288 {
1290
1291 if(raise)
1293 }
1294}
1295
1296gboolean dt_view_active_images_has_imgid(int32_t imgid)
1297{
1298 return g_list_find(dt_view_active_images_get_all(), GINT_TO_POINTER(imgid)) != NULL;
1299}
1300
1305
1307{
1309 return GPOINTER_TO_INT(darktable.view_manager->active_images->data);
1310}
1311
1312void dt_view_active_images_set(GList *images, gboolean raise)
1313{
1315 && darktable.view_manager->active_images != images)
1316 {
1317 g_list_free(darktable.view_manager->active_images);
1318 }
1319
1321
1322 if(raise)
1324}
1325
1331
1339
1340#ifdef HAVE_MAP
1341void dt_view_map_center_on_location(const dt_view_manager_t *vm, gdouble lon, gdouble lat, gdouble zoom)
1342{
1343 if(vm->proxy.map.view)
1344 vm->proxy.map.center_on_location(vm->proxy.map.view, lon, lat, zoom);
1345}
1346
1347void dt_view_map_center_on_bbox(const dt_view_manager_t *vm, gdouble lon1, gdouble lat1, gdouble lon2, gdouble lat2)
1348{
1349 if(vm->proxy.map.view)
1350 vm->proxy.map.center_on_bbox(vm->proxy.map.view, lon1, lat1, lon2, lat2);
1351}
1352
1353void dt_view_map_show_osd(const dt_view_manager_t *vm)
1354{
1355 if(vm->proxy.map.view)
1356 vm->proxy.map.show_osd(vm->proxy.map.view);
1357}
1358
1359void dt_view_map_set_map_source(const dt_view_manager_t *vm, OsmGpsMapSource_t map_source)
1360{
1361 if(vm->proxy.map.view)
1362 vm->proxy.map.set_map_source(vm->proxy.map.view, map_source);
1363}
1364
1365GObject *dt_view_map_add_marker(const dt_view_manager_t *vm, dt_geo_map_display_t type, GList *points)
1366{
1367 if(vm->proxy.map.view)
1368 return vm->proxy.map.add_marker(vm->proxy.map.view, type, points);
1369 return NULL;
1370}
1371
1372gboolean dt_view_map_remove_marker(const dt_view_manager_t *vm, dt_geo_map_display_t type, GObject *marker)
1373{
1374 if(vm->proxy.map.view)
1375 return vm->proxy.map.remove_marker(vm->proxy.map.view, type, marker);
1376 return FALSE;
1377}
1378void dt_view_map_add_location(const dt_view_manager_t *vm, dt_map_location_data_t *p, const guint posid)
1379{
1380 if(vm->proxy.map.view)
1381 vm->proxy.map.add_location(vm->proxy.map.view, p, posid);
1382}
1383
1384void dt_view_map_location_action(const dt_view_manager_t *vm, const int action)
1385{
1386 if(vm->proxy.map.view)
1387 vm->proxy.map.location_action(vm->proxy.map.view, action);
1388}
1389
1390void dt_view_map_redraw(const dt_view_manager_t *vm)
1391{
1392 if(vm->proxy.map.view)
1393 vm->proxy.map.redraw(vm->proxy.map.view);
1394}
1395
1396#endif
1397
1398#ifdef HAVE_PRINT
1399void dt_view_print_settings(const dt_view_manager_t *vm, dt_print_info_t *pinfo, dt_images_box *imgs)
1400{
1401 if (vm->proxy.print.view)
1402 vm->proxy.print.print_settings(vm->proxy.print.view, pinfo, imgs);
1403}
1404#endif
1405
1406
1407static void _audio_child_watch(GPid pid, gint status, gpointer data)
1408{
1410 vm->audio.audio_player_id = -1;
1411 g_spawn_close_pid(pid);
1412}
1413
1415{
1416 char *player = dt_conf_get_string("plugins/lighttable/audio_player");
1417 if(player && *player)
1418 {
1419 char *filename = dt_image_get_audio_path(imgid);
1420 if(filename)
1421 {
1422 char *argv[] = { player, filename, NULL };
1423 gboolean ret = g_spawn_async(NULL, argv, NULL,
1424 G_SPAWN_DO_NOT_REAP_CHILD
1425 | G_SPAWN_SEARCH_PATH
1426 | G_SPAWN_STDOUT_TO_DEV_NULL
1427 | G_SPAWN_STDERR_TO_DEV_NULL,
1428 NULL, NULL,
1429 &vm->audio.audio_player_pid, NULL);
1430
1431 if(ret)
1432 {
1433 vm->audio.audio_player_id = imgid;
1435 = g_child_watch_add(vm->audio.audio_player_pid, (GChildWatchFunc)_audio_child_watch, vm);
1436 }
1437 else
1438 vm->audio.audio_player_id = -1;
1439
1440 dt_free(filename);
1441 }
1442 }
1443 dt_free(player);
1444}
1445
1447{
1448 // make sure that the process didn't finish yet and that _audio_child_watch() hasn't run
1449 if(vm->audio.audio_player_id == -1)
1450 return;
1451
1452 // we don't want to trigger the callback due to a possible race condition
1453 g_source_remove(vm->audio.audio_player_event_source);
1454#ifdef _WIN32
1455// TODO: add Windows code to actually kill the process
1456#else // _WIN32
1457 if(vm->audio.audio_player_id != -1)
1458 {
1459 if(getpgid(0) != getpgid(vm->audio.audio_player_pid))
1460 kill(-vm->audio.audio_player_pid, SIGKILL);
1461 else
1462 kill(vm->audio.audio_player_pid, SIGKILL);
1463 }
1464#endif // _WIN32
1465 g_spawn_close_pid(vm->audio.audio_player_pid);
1466 vm->audio.audio_player_id = -1;
1467}
1468
1469
1470void dt_view_image_info_update(int32_t imgid)
1471{
1473
1474 if(imgid == UNKNOWN_IMAGE)
1475 {
1477 return;
1478 }
1479
1480 char input_dir[512] = { 0 };
1481 gboolean from_cache = TRUE;
1482 dt_image_full_path(imgid, input_dir, sizeof(input_dir), &from_cache, __FUNCTION__);
1483
1486
1487 vp->filename = input_dir;
1488 vp->jobcode = "infos";
1489 vp->imgid = imgid;
1490 vp->sequence = 0;
1491 vp->escape_markup = TRUE;
1492
1493 gchar *pattern = dt_conf_get_string("plugins/darkroom/image_infos_pattern");
1494 gchar *msg = dt_variables_expand(vp, pattern, TRUE);
1495 dt_free(pattern);
1497
1499
1500 dt_free(msg);
1501}
1502
1503
1504// clang-format off
1505// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1506// vim: shiftwidth=2 expandtab tabstop=2 cindent
1507// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1508// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_atomic_set_int(dt_atomic_int *var, int value)
atomic_int dt_atomic_int
Definition atomic.h:66
uint32_t container(dt_lib_module_t *self)
const char ** views(dt_lib_module_t *self)
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
static void transform(float *x, float *o, const float *m, const float t_h, const float t_v)
Definition clipping.c:482
const dt_colorspaces_color_profile_t * dt_colorspaces_get_profile(dt_colorspaces_color_profile_type_t type, const char *filename, dt_colorspaces_profile_direction_t direction)
void dt_colorspaces_transform_rgba8_to_bgra8(const cmsHTRANSFORM transform, const uint8_t *image_in, uint8_t *image_out, const int width, const int height)
@ DT_COLORSPACE_ADOBERGB
Definition colorspaces.h:85
@ DT_COLORSPACE_DISPLAY
Definition colorspaces.h:91
@ DT_COLORSPACE_SRGB
Definition colorspaces.h:84
@ DT_PROFILE_DIRECTION_DISPLAY
const dt_colormatrix_t dt_aligned_pixel_t out
char * dt_image_get_audio_path(const int32_t imgid)
void dt_image_full_path(const int32_t imgid, char *pathname, size_t pathname_len, gboolean *from_cache, const char *calling_func)
Get the full path of an image out of the database.
int type
char * name
int dt_conf_get_bool(const char *name)
gchar * dt_conf_get_string(const char *name)
void dt_control_queue_redraw_center()
request redraw of center window. This redraws the center view within a gdk critical section to preven...
Definition control.c:861
#define dt_control_change_cursor(cursor)
Definition control.h:116
uint32_t view(const dt_view_t *self)
Definition darkroom.c:227
darktable_t darktable
Definition darktable.c:181
void * dt_alloc_align(size_t size)
Definition darktable.c:446
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
#define UNKNOWN_IMAGE
Definition darktable.h:182
@ DT_DEBUG_LIGHTTABLE
Definition darktable.h:725
@ DT_DEBUG_PERF
Definition darktable.h:719
@ DT_DEBUG_IMAGEIO
Definition darktable.h:733
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:463
#define dt_free(ptr)
Definition darktable.h:456
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
static double dt_get_wtime(void)
Definition darktable.h:914
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:374
static int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Definition dtpthread.h:359
static int dt_pthread_mutex_destroy(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:379
static int dt_pthread_cond_wait(pthread_cond_t *cond, dt_pthread_mutex_t *mutex)
Definition dtpthread.h:384
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:364
GtkWidget * dtgtk_expander_get_body_event_box(GtkDarktableExpander *expander)
Definition expander.c:63
GtkWidget * dtgtk_expander_get_body(GtkDarktableExpander *expander)
Definition expander.c:56
#define DTGTK_EXPANDER(obj)
Definition expander.h:30
#define DTGTK_IS_EXPANDER(obj)
Definition expander.h:31
dt_geo_map_display_t
Definition geo.h:25
void dt_ui_container_foreach(dt_ui_t *ui, const dt_ui_container_t c, GtkCallback callback)
calls a callback on all children widgets from container
Definition gtk.c:1733
void dt_ui_container_destroy_children(dt_ui_t *ui, const dt_ui_container_t c)
destroy all child widgets from container
Definition gtk.c:1739
void dt_gui_gtk_set_source_rgb(cairo_t *cr, dt_gui_color_t color)
Definition gtk.c:442
void dt_gui_add_help_link(GtkWidget *widget, char *link)
Definition gtk.c:2022
GtkWidget * dt_ui_center(dt_ui_t *ui)
get the center drawable widget
@ DT_GUI_COLOR_BG
Definition gtk.h:127
GtkWidget * dt_ui_main_window(dt_ui_t *ui)
get the main window widget
void dt_image_cache_read_release(dt_image_cache_t *cache, const dt_image_t *img)
dt_image_t * dt_image_cache_get(dt_image_cache_t *cache, const int32_t imgid, char mode)
static const float x
const float v
dt_job_state_t dt_control_job_get_state(_dt_job_t *job)
Definition jobs.c:103
dt_job_t * dt_control_job_create(dt_job_execute_callback execute, const char *msg,...)
Definition jobs.c:135
int dt_control_add_job(dt_control_t *control, dt_job_queue_t queue_id, _dt_job_t *job)
Definition jobs.c:405
void * dt_control_job_get_params(const _dt_job_t *job)
Definition jobs.c:129
void dt_control_job_set_params_with_size(dt_job_t *job, void *params, size_t params_size, dt_job_destroy_callback callback)
Definition jobs.c:120
@ DT_JOB_QUEUE_SYSTEM_FG
Definition jobs.h:54
@ DT_JOB_STATE_CANCELLED
Definition jobs.h:46
gboolean dt_lib_is_visible_in_view(dt_lib_module_t *module, const dt_view_t *view)
Definition lib.c:136
gboolean dt_lib_is_visible(dt_lib_module_t *module)
Definition lib.c:1405
void dt_lib_gui_set_expanded(dt_lib_module_t *module, gboolean expanded)
Definition lib.c:1064
void dt_lib_set_visible(dt_lib_module_t *module, gboolean visible)
Definition lib.c:1415
GtkWidget * dt_lib_gui_get_expander(dt_lib_module_t *module)
Definition lib.c:1240
float lat
Definition location.c:3
float lon
Definition location.c:2
dt_mipmap_size_t dt_mipmap_cache_get_matching_size(const dt_mipmap_cache_t *cache, const int32_t width, const int32_t height, const uint32_t imgid)
#define dt_mipmap_cache_get_with_shutdown(A, B, C, D, E, F, G)
@ DT_MIPMAP_BLOCKING
#define dt_mipmap_cache_release(A, B)
dt_mipmap_size_t
@ DT_MIPMAP_F
@ DT_MIPMAP_NONE
GList * dt_module_load_modules(const char *subdir, size_t module_size, int(*load_module_so)(void *module, const char *libname, const char *plugin_name), void(*init_module)(void *module), gint(*sort_modules)(gconstpointer a, gconstpointer b))
Definition module.c:36
void copy(double *dest, double *source, size_t num_el)
Copy a flat buffer.
void dt_sentry_record_module_usage(const char *category, const char *name)
Definition sentry.c:492
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:347
@ DT_SIGNAL_ACTIVE_IMAGES_CHANGE
This signal is raised when image shown in the main view change no param, no returned value.
Definition signal.h:64
@ DT_SIGNAL_CONTROL_TOAST_REDRAW
This signal is raised when dt_control_toast_redraw() is called. no param, no returned value.
Definition signal.h:288
@ DT_SIGNAL_VIEWMANAGER_VIEW_CANNOT_CHANGE
This signal is raised by viewmanager when a view has changed. 1 : dt_view_t * the old view 2 : dt_vie...
Definition signal.h:88
@ DT_SIGNAL_CONTROL_LOG_REDRAW
This signal is raised when dt_control_log_redraw() is called. no param, no returned value.
Definition signal.h:283
@ DT_SIGNAL_VIEWMANAGER_VIEW_CHANGED
This signal is raised by viewmanager when a view has changed. 1 : dt_view_t * the old view 2 : dt_vie...
Definition signal.h:81
struct _GtkWidget GtkWidget
Definition splash.h:29
char * dt_variables_expand(dt_variables_params_t *params, gchar *source, gboolean iterate)
void dt_variables_params_destroy(dt_variables_params_t *params)
void dt_variables_params_init(dt_variables_params_t **params)
const float uint32_t state[4]
struct dt_undo_t * undo
Definition darktable.h:787
struct dt_lib_t * lib
Definition darktable.h:771
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_colorspaces_t * color_profiles
Definition darktable.h:788
struct dt_mipmap_cache_t * mipmap_cache
Definition darktable.h:776
struct dt_control_signal_t * signals
Definition darktable.h:774
int32_t unmuted
Definition darktable.h:760
struct dt_image_cache_t * image_cache
Definition darktable.h:777
struct dt_view_manager_t * view_manager
Definition darktable.h:772
struct dt_control_t * control
Definition darktable.h:773
cmsHTRANSFORM transform_adobe_rgb_to_display
cmsHTRANSFORM transform_srgb_to_display
pthread_rwlock_t xprofile_lock
double ppd
Definition gtk.h:200
GtkWidget * has_scroll_focus
Definition gtk.h:228
dt_ui_t * ui
Definition gtk.h:164
GtkWidget * scroll_to[2]
Definition gtk.h:221
int32_t height
Definition image.h:315
int32_t width
Definition image.h:315
char plugin_name[128]
Definition lib.h:82
GModule *void * data
Definition lib.h:80
GtkWidget * widget
Definition lib.h:84
GList * plugins
Definition lib.h:55
dt_colorspaces_color_profile_type_t color_space
const gchar * filename
Definition variables.h:47
const gchar * jobcode
Definition variables.h:50
Track one asynchronous Cairo surface fetch request for a GUI widget.
Definition view.h:117
dt_pthread_mutex_t lock
Definition view.h:118
cairo_surface_t ** target
Definition view.h:121
GPid audio_player_pid
Definition view.h:214
guint audio_player_event_source
Definition view.h:216
struct dt_view_manager_t::@67 proxy
dt_view_t * current_view
Definition view.h:201
int32_t audio_player_id
Definition view.h:215
int32_t image_info_id
Definition view.h:210
dt_darkroom_layout_t(* get_layout)(struct dt_view_t *view)
Definition view.h:245
GList * views
Definition view.h:200
struct dt_view_manager_t::@66 audio
struct dt_view_t * view
Definition view.h:244
struct dt_view_manager_t::@67::@68 module_toolbox
struct dt_view_manager_t::@67::@70 darkroom
struct dt_lib_module_t *void(* add)(struct dt_lib_module_t *, GtkWidget *, dt_view_type_flags_t)
Definition view.h:231
GList * active_images
Definition view.h:204
dt_view_image_surface_fetcher_t * fetcher
Definition view.c:660
cairo_surface_t * surface
Definition view.c:666
dt_view_surface_value_t result
Definition view.c:667
dt_view_image_surface_fetcher_t * fetcher
Definition view.c:650
uint32_t height
Definition view.h:159
GModule *void * data
Definition view.h:157
uint32_t width
Definition view.h:159
char module_name[64]
Definition view.h:153
void dt_telemetry_record_module_usage(const char *category, const char *name)
Definition telemetry.c:422
typedef double((*spd)(unsigned long int wavelength, double TempK))
#define MIN(a, b)
Definition thinplate.c:32
A widget to manage and display image thumbnails in Ansel's lighttable and filmstrip views.
@ DT_THUMBTABLE_ZOOM_TWICE
Definition thumbtable.h:79
@ DT_THUMBTABLE_ZOOM_FULL
Definition thumbtable.h:78
@ DT_THUMBTABLE_ZOOM_HALF
Definition thumbtable.h:77
@ DT_THUMBTABLE_ZOOM_FIT
Definition thumbtable.h:76
void dt_undo_clear(dt_undo_t *self, uint32_t filter)
Definition undo.c:337
@ DT_UNDO_ALL
Definition undo.h:58
char * dt_get_help_url(char *name)
void dt_view_image_surface_fetcher_invalidate(dt_view_image_surface_fetcher_t *fetcher, cairo_surface_t **target)
Definition view.c:866
void dt_view_manager_cleanup(dt_view_manager_t *vm)
Definition view.c:131
void dt_view_image_surface_fetcher_cleanup(dt_view_image_surface_fetcher_t *fetcher)
Definition view.c:842
static int dt_view_load_module(void *v, const char *libname, const char *module_name)
Definition view.c:182
void dt_view_manager_mouse_moved(dt_view_manager_t *vm, double x, double y, double pressure, int which)
Definition view.c:520
const char * dt_view_manager_name(dt_view_manager_t *vm)
Definition view.c:436
void dt_view_manager_mouse_enter(dt_view_manager_t *vm)
Definition view.c:514
void dt_view_manager_configure(dt_view_manager_t *vm, int width, int height)
Definition view.c:628
void dt_view_manager_reset(dt_view_manager_t *vm)
Definition view.c:486
static void _enqueue_surface_fetch(dt_view_image_surface_fetcher_t *fetcher)
Definition view.c:688
void dt_view_image_surface_fetcher_init(dt_view_image_surface_fetcher_t *fetcher)
Definition view.c:831
cairo_surface_t * dt_cairo_rescale_surface(cairo_surface_t *src, int dst_w, int dst_h)
Definition view.c:922
void dt_cairo_sharpen_surface_rgb24(cairo_surface_t *surface)
Definition view.c:971
static void _destroy_surface(cairo_surface_t **surface)
Definition view.c:674
static void dt_view_unload_module(dt_view_t *view)
Definition view.c:202
gboolean dt_view_active_images_has_imgid(int32_t imgid)
Definition view.c:1296
int dt_view_manager_button_released(dt_view_manager_t *vm, double x, double y, int which, uint32_t state)
Definition view.c:569
void dt_view_manager_expose(dt_view_manager_t *vm, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
Definition view.c:445
int dt_view_manager_key_pressed(dt_view_manager_t *vm, GdkEventKey *event)
Definition view.c:542
dt_view_surface_value_t dt_view_image_get_surface_async(dt_view_image_surface_fetcher_t *fetcher, int32_t imgid, int width, int height, cairo_surface_t **target, GtkWidget *widget, int zoom)
Definition view.c:881
void dt_view_audio_start(dt_view_manager_t *vm, int32_t imgid)
Definition view.c:1414
void dt_view_manager_module_toolbox_add(dt_view_manager_t *vm, GtkWidget *tool, dt_view_type_flags_t views)
Definition view.c:1326
void dt_view_active_images_add(int32_t imgid, gboolean raise)
Definition view.c:1276
void dt_view_manager_init(dt_view_manager_t *vm)
Definition view.c:114
void dt_view_active_images_reset(gboolean raise)
Definition view.c:1267
void dt_view_active_images_remove(int32_t imgid, gboolean raise)
Definition view.c:1284
int dt_view_manager_button_pressed(dt_view_manager_t *vm, double x, double y, double pressure, int which, int type, uint32_t state)
Definition view.c:596
GList * dt_view_active_images_get_all()
Definition view.c:1301
static gboolean _view_surface_commit_main(gpointer user_data)
Definition view.c:712
void dt_view_manager_gui_init(dt_view_manager_t *vm)
Definition view.c:122
void dt_vm_remove_child(GtkWidget *widget, gpointer data)
Definition view.c:209
static void _remove_child(GtkWidget *child, GtkContainer *container)
Definition view.c:219
void dt_view_audio_stop(dt_view_manager_t *vm)
Definition view.c:1446
int dt_view_manager_switch(dt_view_manager_t *vm, const char *view_name)
Definition view.c:235
static dt_view_surface_value_t _view_image_get_surface_internal(int32_t imgid, int width, int height, cairo_surface_t **surface, int zoom, dt_atomic_int *shutdown)
Definition view.c:1034
dt_view_surface_value_t dt_view_image_get_surface(int32_t imgid, int width, int height, cairo_surface_t **surface, int zoom)
Definition view.c:1215
int dt_view_manager_switch_by_view(dt_view_manager_t *vm, const dt_view_t *nv)
Definition view.c:257
static void dt_view_manager_load_modules(dt_view_manager_t *vm)
Definition view.c:170
static uint32_t default_flags()
Definition view.c:176
static gboolean _view_surface_matches(const dt_view_image_surface_fetcher_t *fetcher, cairo_surface_t **target, const int32_t imgid, const int width, const int height, const int zoom)
Definition view.c:681
static int32_t _view_surface_fetch_job_run(dt_job_t *job)
Definition view.c:778
int dt_view_manager_scrolled(dt_view_manager_t *vm, double x, double y, int up, int state, int delta_y)
Definition view.c:640
void dt_view_image_info_update(int32_t imgid)
Definition view.c:1470
void dt_view_manager_mouse_leave(dt_view_manager_t *vm)
Definition view.c:492
static gint sort_views(gconstpointer a, gconstpointer b)
Definition view.c:147
void dt_view_active_images_set(GList *images, gboolean raise)
Definition view.c:1312
int32_t dt_view_active_images_get_first()
Definition view.c:1306
dt_darkroom_layout_t dt_view_darkroom_get_layout(dt_view_manager_t *vm)
Definition view.c:1332
static void _audio_child_watch(GPid pid, gint status, gpointer data)
Definition view.c:1407
const dt_view_t * dt_view_manager_get_current_view(dt_view_manager_t *vm)
Definition view.c:140
char * dt_view_extend_modes_str(const char *name, const gboolean is_hdr, const gboolean is_bw, const gboolean is_bw_flow)
Definition view.c:1221
dt_view_type_flags_t
Definition view.h:76
@ DT_VIEW_LIGHTTABLE
Definition view.h:77
@ DT_VIEW_DARKROOM
Definition view.h:78
dt_darkroom_layout_t
Definition view.h:92
@ DT_DARKROOM_LAYOUT_EDITING
Definition view.h:94
dt_view_surface_value_t
Definition view.h:102
@ DT_VIEW_SURFACE_OK
Definition view.h:103
@ DT_VIEW_SURFACE_KO
Definition view.h:104
void dt_ui_cleanup_main_table(dt_ui_t *ui)
void dt_ui_restore_panels(dt_ui_t *ui)
void dt_ui_set_image_info_label(dt_ui_t *ui, const char *label)
void dt_ui_container_add_widget(dt_ui_t *ui, const dt_ui_container_t c, GtkWidget *w)
@ DT_UI_CONTAINER_SIZE