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"
75#include "common/variables.h"
76#include "widgets/bauhaus.h"
77#include "common/sentry.h"
78#include "common/telemetry.h"
79#include "caches/image_cache.h"
80#include "caches/mipmap_cache.h"
81#include "common/module.h"
83#include "common/times.h"
84#include "common/undo.h"
86#include "common/conf.h"
87#include "control/control.h"
88#include "develop/develop.h"
89#include "develop/supervisor.h"
90#include "widgets/expander.h"
92
93#include "gui/application.h"
94#include "libs/lib.h"
95#ifdef GDK_WINDOWING_QUARTZ
96#endif
97
98#include <glib.h>
99#include <math.h>
100#include <stdio.h>
101#include <stdlib.h>
102#include <string.h>
103#include <strings.h>
104#include "control/signal.h"
105
106#define DECORATION_SIZE_LIMIT 40
107
109static int dt_view_load_module(void *v, const char *libname, const char *module_name);
111static int32_t _view_surface_fetch_job_run(dt_job_t *job);
112
114{
116 vm->current_view = NULL;
117 vm->audio.audio_player_id = -1;
118 vm->active_images = NULL;
119}
120
122{
123 for(GList *iter = vm->views; iter; iter = g_list_next(iter))
124 {
125 dt_view_t *view = (dt_view_t *)iter->data;
126 if(view->gui_init) view->gui_init(view);
127 }
128}
129
131{
132 g_list_free(vm->active_images);
133 vm->active_images = NULL;
134 for(GList *iter = vm->views; iter; iter = g_list_next(iter)) dt_view_unload_module((dt_view_t *)iter->data);
135 g_list_free_full(vm->views, dt_free_gpointer);
136 vm->views = NULL;
137}
138
143
144// we want a stable order of views, for example for viewswitcher.
145// anything not hardcoded will be put alphabetically wrt. localised names.
146static gint sort_views(gconstpointer a, gconstpointer b)
147{
148 static const char *view_order[] = {"lighttable", "darkroom"};
149 static const int n_view_order = G_N_ELEMENTS(view_order);
150
151 dt_view_t *av = (dt_view_t *)a;
152 dt_view_t *bv = (dt_view_t *)b;
153 const char *aname = av->name(av);
154 const char *bname = bv->name(bv);
155 int apos = n_view_order;
156 int bpos = n_view_order;
157
158 for(int i = 0; i < n_view_order; i++)
159 {
160 if(!strcmp(av->module_name, view_order[i])) apos = i;
161 if(!strcmp(bv->module_name, view_order[i])) bpos = i;
162 }
163
164 // order will be zero iff apos == bpos which can only happen when both views are not in view_order
165 const int order = apos - bpos;
166 return order ? order : strcmp(aname, bname);
167}
168
173
174/* default flags for view which does not implement the flags() function */
175static uint32_t default_flags()
176{
177 return 0;
178}
179
181static int dt_view_load_module(void *v, const char *libname, const char *module_name)
182{
183 dt_view_t *module = (dt_view_t *)v;
184 g_strlcpy(module->module_name, module_name, sizeof(module->module_name));
185
186#define INCLUDE_API_FROM_MODULE_LOAD "view_load_module"
187#include "views/view_api.h"
188
189 module->data = NULL;
190 module->vscroll_size = module->vscroll_viewport_size = 1.0;
191 module->hscroll_size = module->hscroll_viewport_size = 1.0;
192 module->vscroll_pos = module->hscroll_pos = 0.0;
193 module->height = module->width = 100; // set to non-insane defaults before first expose/configure.
194
195 if(module->init) module->init(module);
196
197 return 0;
198}
199
202{
203 if(view->cleanup) view->cleanup(view);
204
205 if(view->module) g_module_close(view->module);
206}
207
208void dt_vm_remove_child(GtkWidget *widget, gpointer data)
209{
210 if(GTK_IS_CONTAINER(data))
211 gtk_container_remove(GTK_CONTAINER(data), widget);
212}
213
214/*
215 When expanders get destroyed, they destroy the child
216 so remove the child before that
217 */
218static void _remove_child(GtkWidget *child,GtkContainer *container)
219{
220 // some libs module can be used inside popups and not attached to panels, they have no container.
221 if(DTGTK_IS_EXPANDER(child))
222 {
224 if(GTK_IS_CONTAINER(evb))
225 gtk_container_remove(GTK_CONTAINER(evb), dtgtk_expander_get_body(DTGTK_EXPANDER(child)));
226 gtk_widget_destroy(child);
227 }
228 else if(GTK_IS_CONTAINER(container))
229 {
230 gtk_container_remove(container, child);
231 }
232}
233
234int dt_view_manager_switch(dt_view_manager_t *vm, const char *view_name)
235{
236 gboolean switching_to_none = *view_name == '\0';
237 dt_view_t *new_view = NULL;
238
239 if(!switching_to_none)
240 {
241 for(GList *iter = vm->views; iter; iter = g_list_next(iter))
242 {
243 dt_view_t *v = (dt_view_t *)iter->data;
244 if(!strcmp(v->module_name, view_name))
245 {
246 new_view = v;
247 break;
248 }
249 }
250 if(IS_NULL_PTR(new_view)) return 1; // the requested view doesn't exist
251 }
252
253 return dt_view_manager_switch_by_view(vm, new_view);
254}
255
257{
258 dt_view_t *old_view = vm->current_view;
259 dt_view_t *new_view = (dt_view_t *)nv; // views belong to us, we can de-const them :-)
260
261 // reset the cursor to the default one
262 dt_control_change_cursor(GDK_LEFT_PTR);
263
264 /* Reset Gtk focus */
265 gtk_window_set_focus(GTK_WINDOW(dt_gui_main_window()), NULL);
267
268 // also ignore what scrolling there was previously happening
269 memset(dt_gui_get_global()->scroll_to, 0, sizeof(dt_gui_get_global()->scroll_to));
270
271 // destroy old module list
272
273 /* clear the undo list, for now we do this unconditionally. At some point we will probably want to clear
274 only part
275 of the undo list. This should probably done with a view proxy routine returning the type of undo to
276 remove. */
278
279 /* Special case when entering nothing (just before leaving dt) */
280 if(IS_NULL_PTR(new_view))
281 {
282 if(old_view)
283 {
284 /* leave the current view*/
285 if(old_view->leave) old_view->leave(old_view);
286
287 /* iterator plugins and cleanup plugins in current view */
288 for(GList *iter = dt_lib_get_global()->plugins; iter; iter = g_list_next(iter))
289 {
290 dt_lib_module_t *plugin = (dt_lib_module_t *)(iter->data);
291
292 /* does this module belong to current view ?*/
293 if(dt_lib_is_visible_in_view(plugin, old_view))
294 {
295 if(plugin->view_leave) plugin->view_leave(plugin, old_view, NULL);
296 plugin->gui_cleanup(plugin);
297 plugin->data = NULL;
298 plugin->widget = NULL;
299 }
300 }
301 }
302
303 /* remove all widgets in all containers */
305 for(int l = 0; l < DT_UI_CONTAINER_SIZE; l++)
307 vm->current_view = NULL;
308
309 return 0;
310 }
311
312 // invariant: !IS_NULL_PTR(new_view) after this point
313 assert(!IS_NULL_PTR(new_view));
314
315 if(new_view->try_enter)
316 {
317 const int error = new_view->try_enter(new_view);
318 if(error)
319 {
321 return error;
322 }
323 }
324
325 /* cleanup current view before initialization of new */
326 if(old_view)
327 {
328 /* leave current view */
329 if(old_view->leave) old_view->leave(old_view);
330
331 /* iterator plugins and cleanup plugins in current view */
332 for(GList *iter = dt_lib_get_global()->plugins; iter; iter = g_list_next(iter))
333 {
334 dt_lib_module_t *plugin = (dt_lib_module_t *)(iter->data);
335
336 /* does this module belong to current view ?*/
337 if(dt_lib_is_visible_in_view(plugin, old_view))
338 {
339 if(plugin->view_leave) plugin->view_leave(plugin, old_view, new_view);
340 }
341 }
342
343 /* remove all widets in all containers */
344 for(int l = 0; l < DT_UI_CONTAINER_SIZE; l++)
346 }
347
348 /* change current view to the new view */
349 vm->current_view = new_view;
350
351 /* restore visible stat of panels for the new view */
353
354 /* lets add plugins related to new view into panels.
355 * this has to be done in reverse order to have the lowest position at the bottom! */
356 for(GList *iter = g_list_last(dt_lib_get_global()->plugins); iter; iter = g_list_previous(iter))
357 {
358 dt_lib_module_t *plugin = (dt_lib_module_t *)(iter->data);
359 if(dt_lib_is_visible_in_view(plugin, new_view))
360 {
361
362 /* try get the module expander */
364
365 /* if we didn't get an expander let's add the widget */
366 if(IS_NULL_PTR(w)) w = plugin->widget;
367
369 // some plugins help links depend on the view
370 if(!strcmp(plugin->plugin_name,"module_toolbox")
371 || !strcmp(plugin->plugin_name,"view_toolbox"))
372 {
373 dt_view_type_flags_t view_type = new_view->view(new_view);
374 if(view_type == DT_VIEW_LIGHTTABLE)
375 dt_gui_add_help_link(w, dt_get_help_url("lighttable_mode"));
376 if(view_type == DT_VIEW_DARKROOM)
377 dt_gui_add_help_link(w, dt_get_help_url("darkroom_bottom_panel"));
378 }
379
380
381 /* add module to its container */
382 dt_ui_container_add_widget(dt_gui_get_ui(), plugin->container(plugin), w);
383 }
384 }
385
386 /* hide/show modules as last config */
387 for(GList *iter = dt_lib_get_global()->plugins; iter; iter = g_list_next(iter))
388 {
389 dt_lib_module_t *plugin = (dt_lib_module_t *)(iter->data);
390 if(dt_lib_is_visible_in_view(plugin, new_view))
391 {
392 /* set expanded if last mode was that */
393 char var[1024];
394 gboolean expanded = FALSE;
395 gboolean visible = dt_lib_is_visible(plugin);
396 if(plugin->expandable(plugin))
397 {
398 snprintf(var, sizeof(var), "plugins/%s/%s/expanded", new_view->module_name, plugin->plugin_name);
399 expanded = dt_conf_get_bool(var);
400 dt_lib_gui_set_expanded(plugin, expanded);
401 dt_lib_set_visible(plugin, visible);
402 }
403 else
404 {
405 /* show/hide plugin widget depending on expanded flag or if plugin
406 not is expandeable() */
407 if(visible)
408 gtk_widget_show_all(plugin->widget);
409 else
410 gtk_widget_hide(plugin->widget);
411 }
412 if(plugin->view_enter) plugin->view_enter(plugin, old_view, new_view);
413 }
414 }
415
416 /* enter view. crucially, do this before initing the plugins below,
417 as e.g. modulegroups requires the dr stuff to be inited. */
418 if(new_view->enter) new_view->enter(new_view);
419
420 /* record view usage for crash reports and usage analytics */
423
424 /* raise view changed signal */
426
427 // update log visibility
429
430 // update toast visibility
432 return 0;
433}
434
436{
437 if(IS_NULL_PTR(vm->current_view)) return "";
438 if(vm->current_view->name)
439 return vm->current_view->name(vm->current_view);
440 else
441 return vm->current_view->module_name;
442}
443
445{
446 if(IS_NULL_PTR(vm) || IS_NULL_PTR(vm->current_view) || IS_NULL_PTR(vm->current_view->flags)) return 0;
447 return vm->current_view->flags();
448}
449
450void dt_view_manager_expose(dt_view_manager_t *vm, cairo_t *cr, int32_t width, int32_t height,
451 int32_t pointerx, int32_t pointery)
452{
454 {
456 cairo_paint(cr);
457 return;
458 }
459 vm->current_view->width = width;
461
462 if(vm->current_view->expose)
463 {
464 /* expose the view */
465 cairo_rectangle(cr, 0, 0, vm->current_view->width, vm->current_view->height);
466 cairo_clip(cr);
467 cairo_new_path(cr);
468 cairo_save(cr);
469 float px = pointerx, py = pointery;
470 if(pointery > vm->current_view->height)
471 {
472 px = 10000.0;
473 py = -1.0;
474 }
475 vm->current_view->expose(vm->current_view, cr, vm->current_view->width, vm->current_view->height, px, py);
476
477 cairo_restore(cr);
478 /* expose plugins */
479 for(const GList *plugins = g_list_last(dt_lib_get_global()->plugins); plugins; plugins = g_list_previous(plugins))
480 {
481 dt_lib_module_t *plugin = (dt_lib_module_t *)(plugins->data);
482
483 /* does this module belong to current view ?*/
484 if(plugin->gui_post_expose
486 plugin->gui_post_expose(plugin, cr, vm->current_view->width, vm->current_view->height, px, py);
487 }
488 }
489}
490
492{
493 if(IS_NULL_PTR(vm->current_view)) return;
494 if(vm->current_view->reset) vm->current_view->reset(vm->current_view);
495}
496
498{
499 if(IS_NULL_PTR(vm->current_view)) return;
500 dt_view_t *v = vm->current_view;
501
502 /* lets check if any plugins want to handle mouse move */
503 gboolean handled = FALSE;
504 for(const GList *plugins = g_list_last(dt_lib_get_global()->plugins);
505 plugins;
506 plugins = g_list_previous(plugins))
507 {
508 dt_lib_module_t *plugin = (dt_lib_module_t *)(plugins->data);
509
510 /* does this module belong to current view ?*/
511 if(plugin->mouse_leave && dt_lib_is_visible_in_view(plugin, v))
512 if(plugin->mouse_leave(plugin)) handled = TRUE;
513 }
514
515 /* if not handled by any plugin let pass to view handler*/
516 if(!handled && v->mouse_leave) v->mouse_leave(v);
517}
518
520{
521 if(IS_NULL_PTR(vm->current_view)) return;
522 if(vm->current_view->mouse_enter) vm->current_view->mouse_enter(vm->current_view);
523}
524
525void dt_view_manager_mouse_moved(dt_view_manager_t *vm, double x, double y, double pressure, int which)
526{
527 if(IS_NULL_PTR(vm->current_view)) return;
528 dt_view_t *v = vm->current_view;
529
530 /* lets check if any plugins want to handle mouse move */
531 gboolean handled = FALSE;
532 for(const GList *plugins = g_list_last(dt_lib_get_global()->plugins);
533 plugins;
534 plugins = g_list_previous(plugins))
535 {
536 dt_lib_module_t *plugin = (dt_lib_module_t *)(plugins->data);
537
538 /* does this module belong to current view ?*/
539 if(plugin->mouse_moved && dt_lib_is_visible_in_view(plugin, v))
540 if(plugin->mouse_moved(plugin, x, y, pressure, which)) handled = TRUE;
541 }
542
543 /* if not handled by any plugin let pass to view handler*/
544 if(!handled && v->mouse_moved) v->mouse_moved(v, x, y, pressure, which);
545}
546
548{
549 if(IS_NULL_PTR(vm->current_view)) return 0;
550 dt_view_t *v = vm->current_view;
551
552 /* lets check if any plugins want to handle button press */
553 gboolean handled = FALSE;
554 for(const GList *plugins = g_list_last(dt_lib_get_global()->plugins);
555 plugins;
556 plugins = g_list_previous(plugins))
557 {
558 dt_lib_module_t *plugin = (dt_lib_module_t *)(plugins->data);
559
560 /* does this module belong to current view ?*/
561 if(plugin->key_pressed && dt_lib_is_visible_in_view(plugin, v))
562 if(plugin->key_pressed(plugin, event)) handled = TRUE;
563 }
564
565 if(handled)
566 return 1;
567 /* if not handled by any plugin let pass to view handler*/
568 else if(v->key_pressed)
569 handled = v->key_pressed(v, event);
570
571 return handled;
572}
573
574int dt_view_manager_button_released(dt_view_manager_t *vm, double x, double y, int which, uint32_t state)
575{
576 if(IS_NULL_PTR(vm->current_view)) return 0;
577 dt_view_t *v = vm->current_view;
578
579 /* lets check if any plugins want to handle button press */
580 gboolean handled = FALSE;
581 for(const GList *plugins = g_list_last(dt_lib_get_global()->plugins);
582 plugins;
583 plugins = g_list_previous(plugins))
584 {
585 dt_lib_module_t *plugin = (dt_lib_module_t *)(plugins->data);
586
587 /* does this module belong to current view ?*/
588 if(plugin->button_released && dt_lib_is_visible_in_view(plugin, v))
589 if(plugin->button_released(plugin, x, y, which, state)) handled = TRUE;
590 }
591
592 if(handled)
593 return 1;
594 /* if not handled by any plugin let pass to view handler*/
595 else if(v->button_released)
596 v->button_released(v, x, y, which, state);
597
598 return 0;
599}
600
601int dt_view_manager_button_pressed(dt_view_manager_t *vm, double x, double y, double pressure, int which,
602 int type, uint32_t state)
603{
604 if(IS_NULL_PTR(vm->current_view)) return 0;
605 dt_view_t *v = vm->current_view;
606
607 /* Reset Gtk focus */
608 gtk_window_set_focus(GTK_WINDOW(dt_gui_main_window()), NULL);
610
611 /* lets check if any plugins want to handle button press */
612 gboolean handled = FALSE;
613
614 for(const GList *plugins = g_list_last(dt_lib_get_global()->plugins);
615 plugins && !handled;
616 plugins = g_list_previous(plugins))
617 {
618 dt_lib_module_t *plugin = (dt_lib_module_t *)(plugins->data);
619
620 /* does this module belong to current view ?*/
621 if(plugin->button_pressed && dt_lib_is_visible_in_view(plugin, v))
622 if(plugin->button_pressed(plugin, x, y, pressure, which, type, state)) handled = TRUE;
623 }
624
625 if(handled) return 1;
626 /* if not handled by any plugin let pass to view handler*/
627 else if(v->button_pressed)
628 return v->button_pressed(v, x, y, pressure, which, type, state);
629
630 return 0;
631}
632
634{
635 for(GList *iter = vm->views; iter; iter = g_list_next(iter))
636 {
637 // this is necessary for all
638 dt_view_t *v = (dt_view_t *)iter->data;
639 v->width = width;
640 v->height = height;
641 if(v->configure) v->configure(v, width, height);
642 }
643}
644
645int dt_view_manager_scrolled(dt_view_manager_t *vm, double x, double y, int up, int state, int delta_y)
646{
647 if(IS_NULL_PTR(vm->current_view)) return FALSE;
648 if(vm->current_view->scrolled)
649 return vm->current_view->scrolled(vm->current_view, x, y, up, state, delta_y);
650 return 0;
651}
652
662
674
676 cairo_surface_t **surface, int zoom,
677 dt_atomic_int *shutdown);
678
679static void _destroy_surface(cairo_surface_t **surface)
680{
681 if(surface && *surface && cairo_surface_get_reference_count(*surface) > 0)
682 cairo_surface_destroy(*surface);
683 if(surface) *surface = NULL;
684}
685
686static gboolean _view_surface_matches(const dt_view_image_surface_fetcher_t *fetcher, cairo_surface_t **target,
687 const int32_t imgid, const int width, const int height, const int zoom)
688{
689 return target && *target && fetcher->cached_imgid == imgid && fetcher->cached_width == width
690 && fetcher->cached_height == height && fetcher->cached_zoom == zoom;
691}
692
694{
695 dt_view_surface_fetch_job_t *params = g_malloc0(sizeof(dt_view_surface_fetch_job_t));
696 params->fetcher = fetcher;
697 params->request_id = fetcher->request_id;
698 params->imgid = fetcher->imgid;
699 params->width = fetcher->width;
700 params->height = fetcher->height;
701 params->zoom = fetcher->zoom;
702
703 dt_job_t *job = dt_control_job_create(&_view_surface_fetch_job_run, "fetch image surface %i", params->imgid);
704 if(IS_NULL_PTR(job))
705 {
706 g_free(params);
707 return;
708 }
709
710 dt_atomic_set_int(&fetcher->shutdown, FALSE);
712 fetcher->job_queued = TRUE;
713 fetcher->queued_request_id = fetcher->request_id;
715}
716
717static gboolean _view_surface_commit_main(gpointer user_data)
718{
720 dt_view_image_surface_fetcher_t *fetcher = commit->fetcher;
721
722 gboolean queue_redraw = FALSE;
723 gboolean enqueue_next = FALSE;
724
725 dt_pthread_mutex_lock(&fetcher->lock);
726 fetcher->commit_pending = FALSE;
727 pthread_cond_broadcast(&fetcher->cond);
728
729 if(!fetcher->destroying && commit->request_id == fetcher->request_id && commit->result == DT_VIEW_SURFACE_OK)
730 {
731 _destroy_surface(fetcher->target);
732 if(fetcher->target) *fetcher->target = commit->surface;
733 commit->surface = NULL;
734 fetcher->cached_imgid = commit->imgid;
735 fetcher->cached_width = commit->width;
736 fetcher->cached_height = commit->height;
737 fetcher->cached_zoom = commit->zoom;
738 queue_redraw = TRUE;
739 }
740
741 if(!fetcher->destroying && !fetcher->job_queued && fetcher->request_id != commit->request_id)
742 enqueue_next = TRUE;
743 dt_pthread_mutex_unlock(&fetcher->lock);
744
745 if(commit->surface) cairo_surface_destroy(commit->surface);
746
747 if(queue_redraw)
748 {
749 GtkWidget *widget = g_weak_ref_get(&fetcher->widget_ref);
750 if(widget)
751 {
752 if(widget == dt_gui_center_widget())
753 {
755 }
756 else
757 {
758 gtk_widget_queue_draw(widget);
759 GdkWindow *window = gtk_widget_get_window(widget);
760 if(window)
761 {
762 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
763 gdk_window_process_updates(window, TRUE);
764 G_GNUC_END_IGNORE_DEPRECATIONS
765 }
766 }
767 g_object_unref(widget);
768 }
769 }
770
771 if(enqueue_next)
772 {
773 dt_pthread_mutex_lock(&fetcher->lock);
774 if(!fetcher->destroying && !fetcher->job_queued && !fetcher->commit_pending)
775 _enqueue_surface_fetch(fetcher);
776 dt_pthread_mutex_unlock(&fetcher->lock);
777 }
778
779 g_free(commit);
780 return G_SOURCE_REMOVE;
781}
782
784{
786 dt_view_image_surface_fetcher_t *fetcher = params->fetcher;
787
788 dt_pthread_mutex_lock(&fetcher->lock);
789 const gboolean stale = fetcher->destroying || params->request_id != fetcher->request_id;
790 const gboolean cancelled = dt_control_job_get_state(job) == DT_JOB_STATE_CANCELLED;
791 if(stale || cancelled)
792 {
793 if(fetcher->job_queued && fetcher->queued_request_id == params->request_id) fetcher->job_queued = FALSE;
794
795 // A stale request means the widget parameters changed while the current
796 // job was queued or running. If no newer request is already tracked
797 // locally, start the latest one immediately from the current fetcher
798 // state instead of trying to reason about queue-owned job objects.
799 if(!fetcher->destroying && params->request_id != fetcher->request_id && !fetcher->job_queued
800 && !fetcher->commit_pending)
801 _enqueue_surface_fetch(fetcher);
802
803 pthread_cond_broadcast(&fetcher->cond);
804 dt_pthread_mutex_unlock(&fetcher->lock);
805 return 0;
806 }
807 dt_pthread_mutex_unlock(&fetcher->lock);
808
809 cairo_surface_t *surface = NULL;
810 const dt_view_surface_value_t result =
811 _view_image_get_surface_internal(params->imgid, params->width, params->height, &surface, params->zoom,
812 &fetcher->shutdown);
813
815 commit->fetcher = fetcher;
816 commit->request_id = params->request_id;
817 commit->imgid = params->imgid;
818 commit->width = params->width;
819 commit->height = params->height;
820 commit->zoom = params->zoom;
821 commit->surface = surface;
822 commit->result = result;
823
824 dt_pthread_mutex_lock(&fetcher->lock);
825 if(fetcher->job_queued && fetcher->queued_request_id == params->request_id) fetcher->job_queued = FALSE;
826 fetcher->commit_pending = TRUE;
827 pthread_cond_broadcast(&fetcher->cond);
828 dt_pthread_mutex_unlock(&fetcher->lock);
829
830 g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT, _view_surface_commit_main,
831 commit, NULL);
832 g_main_context_wakeup(g_main_context_default());
833 return 0;
834}
835
837{
838 memset(fetcher, 0, sizeof(dt_view_image_surface_fetcher_t));
839 dt_pthread_mutex_init(&fetcher->lock, NULL);
840 pthread_cond_init(&fetcher->cond, NULL);
841 g_weak_ref_init(&fetcher->widget_ref, NULL);
842 fetcher->cached_imgid = UNKNOWN_IMAGE;
843 fetcher->imgid = UNKNOWN_IMAGE;
844 dt_atomic_set_int(&fetcher->shutdown, FALSE);
845}
846
848{
849 dt_pthread_mutex_lock(&fetcher->lock);
850 fetcher->destroying = TRUE;
851 dt_atomic_set_int(&fetcher->shutdown, TRUE);
852 while(fetcher->job_queued)
853 dt_pthread_cond_wait(&fetcher->cond, &fetcher->lock);
854 dt_pthread_mutex_unlock(&fetcher->lock);
855
856 for(;;)
857 {
858 dt_pthread_mutex_lock(&fetcher->lock);
859 const gboolean pending = fetcher->commit_pending;
860 dt_pthread_mutex_unlock(&fetcher->lock);
861 if(!pending) break;
862 g_main_context_iteration(g_main_context_default(), TRUE);
863 }
864
865 _destroy_surface(fetcher->target);
866 g_weak_ref_clear(&fetcher->widget_ref);
867 pthread_cond_destroy(&fetcher->cond);
869}
870
872{
873 dt_pthread_mutex_lock(&fetcher->lock);
874 fetcher->target = target;
875 fetcher->request_id++;
876 dt_atomic_set_int(&fetcher->shutdown, TRUE);
877 fetcher->cached_imgid = UNKNOWN_IMAGE;
878 fetcher->cached_width = 0;
879 fetcher->cached_height = 0;
880 fetcher->cached_zoom = 0;
881 // Also clear the *requested* imgid, not just the cached one: dt_view_image_get_surface_async()
882 // only enqueues a fetch when imgid/width/height/zoom differ from the previous call. A caller
883 // invalidating because the underlying image changed (e.g. a style applied) but requesting the
884 // exact same imgid/size/zoom afterward would otherwise see "nothing changed" and never
885 // re-enqueue a fetch, leaving the display stuck on the busy message forever.
886 fetcher->imgid = UNKNOWN_IMAGE;
887 dt_pthread_mutex_unlock(&fetcher->lock);
888
889 _destroy_surface(target);
890}
891
893 int width, int height, cairo_surface_t **target,
894 GtkWidget *widget, int zoom)
895{
896 if(IS_NULL_PTR(fetcher) || !target || !widget || width < 2 || height < 2 || imgid <= UNKNOWN_IMAGE)
897 return DT_VIEW_SURFACE_KO;
898
900
901 dt_pthread_mutex_lock(&fetcher->lock);
902 const gboolean changed = fetcher->target != target || fetcher->imgid != imgid || fetcher->width != width
903 || fetcher->height != height || fetcher->zoom != zoom;
904 const gboolean exact_match = _view_surface_matches(fetcher, target, imgid, width, height, zoom);
905 const gboolean fallback_match = target && *target && fetcher->cached_imgid == imgid
906 && fetcher->cached_zoom == zoom;
907 fetcher->target = target;
908 fetcher->imgid = imgid;
909 fetcher->width = width;
910 fetcher->height = height;
911 fetcher->zoom = zoom;
912 g_weak_ref_set(&fetcher->widget_ref, widget);
913
914 if(exact_match || fallback_match)
915 ret = DT_VIEW_SURFACE_OK;
916
917 if(changed && !exact_match && !fetcher->destroying && !fetcher->job_queued
918 && !fetcher->commit_pending)
919 {
920 fetcher->request_id++;
921 _enqueue_surface_fetch(fetcher);
922 }
923 else if(changed && !exact_match && !fetcher->destroying)
924 {
925 fetcher->request_id++;
926 dt_atomic_set_int(&fetcher->shutdown, TRUE);
927 }
928 dt_pthread_mutex_unlock(&fetcher->lock);
929
930 return ret;
931}
932
933cairo_surface_t *dt_cairo_rescale_surface(cairo_surface_t *src, int dst_w, int dst_h)
934{
935 if(!src || dst_w <= 0 || dst_h <= 0)
936 return NULL;
937
938 const int src_w = cairo_image_surface_get_width(src);
939 const int src_h = cairo_image_surface_get_height(src);
940
941 cairo_surface_t *dst =
942 cairo_image_surface_create(CAIRO_FORMAT_RGB24, dst_w, dst_h);
943
944 cairo_t *cr = cairo_create(dst);
945
946 // clear (important if aspect ratio leaves borders)
947 cairo_set_source_rgba(cr, 0, 0, 0, 0);
948 cairo_paint(cr);
949
950 double scale_x = (double)dst_w / src_w;
951 double scale_y = (double)dst_h / src_h;
952
953 double sx = scale_x;
954 double sy = scale_y;
955 double tx = 0.0;
956 double ty = 0.0;
957
958
959 double s = MIN(scale_x, scale_y);
960 sx = sy = s;
961
962 tx = (dst_w - src_w * s) * 0.5;
963 ty = (dst_h - src_h * s) * 0.5;
964
965
966 cairo_translate(cr, tx, ty);
967 cairo_scale(cr, sx, sy);
968
969 cairo_set_source_surface(cr, src, 0, 0);
970
971 cairo_pattern_t *pat = cairo_get_source(cr);
972 cairo_pattern_set_filter(pat, CAIRO_FILTER_BEST);
973 cairo_pattern_set_extend(pat, CAIRO_EXTEND_PAD);
974
975 cairo_paint(cr);
976
977 cairo_destroy(cr);
978
979 return dst;
980}
981
982void dt_cairo_sharpen_surface_rgb24(cairo_surface_t *surface)
983{
984 if(!surface) return;
985
986 cairo_surface_flush(surface);
987
988 unsigned char *data = cairo_image_surface_get_data(surface);
989 int width = cairo_image_surface_get_width(surface);
990 int height = cairo_image_surface_get_height(surface);
991 int stride = cairo_image_surface_get_stride(surface);
992
993 // Copy original buffer
994 unsigned char *copy = dt_alloc_align(stride * height);
995 memcpy(copy, data, stride * height);
996
997 // Kernel weights
998 const float k_center = 4.0f;
999 const float k_edge = -0.5f;
1000 const float k_corner = -0.25f;
1001
1002 // Unsharp mask coeffs
1003 const float amount = 0.05f;
1004 const float amount_inv = 1.f - amount;
1005
1006 __OMP_PARALLEL_FOR__(collapse(2))
1007 for(int y = 1; y < height - 1; y++)
1008 for(int x = 1; x < width - 1; x++)
1009 {
1010 int idx = y * stride + x * 4;
1011
1012 for(int c = 0; c < 3; c++) // B, G, R
1013 {
1014 int i = idx + c;
1015
1016 float v =
1017 k_center * copy[i]
1018
1019 + k_edge * (
1020 copy[i - 4] + // left
1021 copy[i + 4] + // right
1022 copy[i - stride] + // top
1023 copy[i + stride] // bottom
1024 )
1025
1026 + k_corner * (
1027 copy[i - stride - 4] + // top-left
1028 copy[i - stride + 4] + // top-right
1029 copy[i + stride - 4] + // bottom-left
1030 copy[i + stride + 4] // bottom-right
1031 );
1032
1033 // Unsharp-style recombination
1034 const float out = amount_inv * copy[i] + amount * v;
1035 data[i] = (unsigned char)(CLAMP(roundf(out), 0.f, 255.f));
1036 }
1037
1038 data[idx + 3] = copy[idx + 3];
1039 }
1040
1041 dt_free_align(copy); // paired with the dt_alloc_align above
1042 cairo_surface_mark_dirty(surface);
1043}
1044
1046 cairo_surface_t **surface, int zoom,
1047 dt_atomic_int *shutdown)
1048{
1049 double tt = 0;
1051 tt = dt_get_wtime();
1052
1054
1055 // if surface not null, clean it up
1056 if(*surface && cairo_surface_get_reference_count(*surface) > 0)
1057 cairo_surface_destroy(*surface);
1058 *surface = NULL;
1059
1060 // get mipmap cache image
1062
1063 if(zoom == DT_THUMBTABLE_ZOOM_FIT)
1064 {
1065 mip = dt_mipmap_cache_get_matching_size(ceilf(width * dt_gui_get_global()->ppd), ceilf(height * dt_gui_get_global()->ppd), imgid);
1066 }
1067 else
1068 {
1069 const dt_image_t *image = dt_image_cache_get(imgid, 'r');
1070 const int full_width = image->width;
1071 const int full_height = image->height;
1073
1074 if(zoom == DT_THUMBTABLE_ZOOM_HALF)
1075 mip = dt_mipmap_cache_get_matching_size(ceilf(full_width / 2.f ), ceilf(full_height / 2.f), imgid);
1076 else if(zoom >= DT_THUMBTABLE_ZOOM_FULL)
1077 mip = dt_mipmap_cache_get_matching_size(full_width, full_height, imgid);
1078 }
1079
1080 // Thumbnail generation: mipmap fetch (and, on a miss, the pipeline render
1081 // behind it, which surfaces through the generic thumbnail-pipe events). mip is
1082 // now resolved, so the request and its result share the real (imgid, mip) key.
1085
1086 // Can't have float32 types here
1087 if(mip >= DT_MIPMAP_F)
1088 {
1091 return DT_VIEW_SURFACE_KO;
1092 }
1093
1094 // if needed, we load the mimap buffer
1096 dt_mipmap_cache_get_with_shutdown(&buf, imgid, mip, DT_MIPMAP_BLOCKING, 'r', shutdown);
1097 const int buf_wd = buf.width;
1098 const int buf_ht = buf.height;
1099
1100 // if we don't get buffer, no image is available at the moment
1101 if(IS_NULL_PTR(buf.buf))
1102 {
1106 return DT_VIEW_SURFACE_KO;
1107 }
1108
1109 // so we create a new image surface to return
1110 float scale = 1.f;
1111 int img_width = buf_wd;
1112 int img_height = buf_ht;
1113
1114 if(zoom == DT_THUMBTABLE_ZOOM_FIT)
1115 {
1116 scale = fminf((float)width / (float)buf_wd, (float)height / (float)buf_ht) * dt_gui_get_global()->ppd;
1117 img_width = roundf(buf_wd * scale);
1118 img_height = roundf(buf_ht * scale);
1119
1120 // due to the forced rounding above, we need to recompute scaling
1121 scale = fmaxf((float)img_width / (float)buf_wd, (float)img_height / (float)buf_ht);
1122 }
1123 else if(zoom == DT_THUMBTABLE_ZOOM_TWICE)
1124 {
1125 // NOTE: we upscale the image surface, which means we will oversample
1126 // the full-res input buffer
1127 scale = 2.f;
1128 img_width = roundf(buf_wd * scale);
1129 img_height = roundf(buf_ht * scale);
1130 }
1131
1132 *surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, img_width, img_height);
1133
1134 // we transfer cached image on a cairo_surface (with colorspace transform if needed)
1135 uint8_t *rgbbuf = (uint8_t *)calloc((size_t)buf_wd * buf_ht * 4, sizeof(uint8_t));
1136 if(IS_NULL_PTR(rgbbuf))
1137 {
1139 return ret;
1140 }
1141
1142 /* Colour-manage into display space. sRGB and AdobeRGB use the module's prepared
1143 * transforms, DT_COLORSPACE_DISPLAY passes through with an R <-> B swap, anything else
1144 * is resolved and built inside the module. A thumbnail cached with a tag that has no
1145 * DISPLAY-direction profile falls back to the plain swap rather than being dropped. */
1148
1149 const int32_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, buf_wd);
1150 cairo_surface_t *tmp_surface = cairo_image_surface_create_for_data(rgbbuf, CAIRO_FORMAT_RGB24, buf_wd, buf_ht, stride);
1151 if(IS_NULL_PTR(tmp_surface))
1152 {
1153 dt_free(rgbbuf);
1154 return ret;
1155 }
1156
1157 // draw the image scaled:
1158 cairo_t *cr = cairo_create(*surface);
1159 cairo_scale(cr, scale, scale);
1160 cairo_set_source_surface(cr, tmp_surface, 0, 0);
1161
1162 // set filter no nearest:
1163 // in skull mode, we want to see big pixels.
1164 // in 1 iir mode for the right mip, we want to see exactly what the pipe gave us, 1:1 pixel for pixel.
1165 // in between, filtering just makes stuff go unsharp.
1166 if((buf_wd <= 8 && buf_ht <= 8)
1167 || fabsf(scale - 1.0f) < 0.01f
1168 || zoom == DT_THUMBTABLE_ZOOM_TWICE)
1169 cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
1170 else
1171 cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BEST);
1172
1173 cairo_paint(cr);
1174 cairo_surface_destroy(tmp_surface);
1175 cairo_destroy(cr);
1176
1177 /* The async/shared surface path returns pixel-sized Cairo image surfaces.
1178 * Publish the widget PPD on the finished surface so GUI callers can place it
1179 * in logical coordinates without re-deriving HiDPI scaling on every draw. */
1180 cairo_surface_set_device_scale(*surface, dt_gui_get_global()->ppd, dt_gui_get_global()->ppd);
1181
1182 // we consider skull as ok as the image hasn't to be reloaded
1183 if(buf_wd <= 8 && buf_ht <= 8)
1184 {
1185 ret = DT_VIEW_SURFACE_OK;
1186 }
1187 else
1188 {
1189 ret = DT_VIEW_SURFACE_OK;
1190 //dt_cairo_sharpen_surface_rgb24(*surface);
1191 }
1192
1193 dt_free(rgbbuf);
1194
1195 // logs
1197 {
1199 "[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",
1200 imgid, mip, width, height, buf_wd, buf_ht, img_width, img_height, dt_get_wtime() - tt);
1201 }
1203 {
1204 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,
1205 width, height, buf_wd, buf_ht, img_width, img_height);
1206 }
1207
1208 // we consider skull as ok as the image hasn't to be reload
1211 return ret;
1212}
1213
1214dt_view_surface_value_t dt_view_image_get_surface(int32_t imgid, int width, int height, cairo_surface_t **surface,
1215 int zoom)
1216{
1217 return _view_image_get_surface_internal(imgid, width, height, surface, zoom, NULL);
1218}
1219
1220char* dt_view_extend_modes_str(const char * name, const gboolean is_hdr, const gboolean is_bw, const gboolean is_bw_flow)
1221{
1222 char* upcase = g_ascii_strup(name, -1); // extension in capital letters to avoid character descenders
1223 // convert to canonical format extension
1224 if(0 == g_ascii_strcasecmp(upcase, "JPG"))
1225 {
1226 gchar* canonical = g_strdup("JPEG");
1227 dt_free(upcase);
1228 upcase = canonical;
1229 }
1230 else if(0 == g_ascii_strcasecmp(upcase, "HDR"))
1231 {
1232 gchar* canonical = g_strdup("RGBE");
1233 dt_free(upcase);
1234 upcase = canonical;
1235 }
1236 else if(0 == g_ascii_strcasecmp(upcase, "TIF"))
1237 {
1238 gchar* canonical = g_strdup("TIFF");
1239 dt_free(upcase);
1240 upcase = canonical;
1241 }
1242
1243 if(is_hdr)
1244 {
1245 gchar* fullname = g_strdup_printf("%s HDR", upcase);
1246 dt_free(upcase);
1247 upcase = fullname;
1248 }
1249 if(is_bw)
1250 {
1251 gchar* fullname = g_strdup_printf("%s B&W", upcase);
1252 dt_free(upcase);
1253 upcase = fullname;
1254 if(!is_bw_flow)
1255 {
1256 fullname = g_strdup_printf("%s-", upcase);
1257 dt_free(upcase);
1258 upcase = fullname;
1259 }
1260 }
1261
1262 return upcase;
1263}
1264
1265
1267{
1269 if(IS_NULL_PTR(vm->active_images)) return;
1270 g_list_free(vm->active_images);
1271 vm->active_images = NULL;
1272
1274}
1275
1276void dt_view_active_images_add(int32_t imgid, gboolean raise)
1277{
1279 vm->active_images = g_list_append(vm->active_images, GINT_TO_POINTER(imgid));
1280 if(raise)
1282}
1283
1284void dt_view_active_images_remove(int32_t imgid, gboolean raise)
1285{
1287 GList *link = g_list_find(vm->active_images, GINT_TO_POINTER(imgid));
1288 if(link)
1289 {
1290 vm->active_images = g_list_delete_link(vm->active_images, link);
1291
1292 if(raise)
1294 }
1295}
1296
1297gboolean dt_view_active_images_has_imgid(int32_t imgid)
1298{
1299 return g_list_find(dt_view_active_images_get_all(), GINT_TO_POINTER(imgid)) != NULL;
1300}
1301
1306
1308{
1310 if(IS_NULL_PTR(vm->active_images)) return -1;
1311 return GPOINTER_TO_INT(vm->active_images->data);
1312}
1313
1314void dt_view_active_images_set(GList *images, gboolean raise)
1315{
1317 if(!IS_NULL_PTR(vm->active_images)
1318 && vm->active_images != images)
1319 {
1320 g_list_free(vm->active_images);
1321 }
1322
1323 vm->active_images = images;
1324
1325 if(raise)
1327}
1328
1334
1342
1343#ifdef HAVE_MAP
1344void dt_view_map_center_on_location(const dt_view_manager_t *vm, gdouble lon, gdouble lat, gdouble zoom)
1345{
1346 if(vm->proxy.map.view)
1347 vm->proxy.map.center_on_location(vm->proxy.map.view, lon, lat, zoom);
1348}
1349
1350void dt_view_map_center_on_bbox(const dt_view_manager_t *vm, gdouble lon1, gdouble lat1, gdouble lon2, gdouble lat2)
1351{
1352 if(vm->proxy.map.view)
1353 vm->proxy.map.center_on_bbox(vm->proxy.map.view, lon1, lat1, lon2, lat2);
1354}
1355
1357{
1358 if(vm->proxy.map.view)
1359 vm->proxy.map.show_osd(vm->proxy.map.view);
1360}
1361
1362void dt_view_map_set_map_source(const dt_view_manager_t *vm, OsmGpsMapSource_t map_source)
1363{
1364 if(vm->proxy.map.view)
1365 vm->proxy.map.set_map_source(vm->proxy.map.view, map_source);
1366}
1367
1369{
1370 if(vm->proxy.map.view)
1371 return vm->proxy.map.add_marker(vm->proxy.map.view, type, points);
1372 return NULL;
1373}
1374
1376{
1377 if(vm->proxy.map.view)
1378 return vm->proxy.map.remove_marker(vm->proxy.map.view, type, marker);
1379 return FALSE;
1380}
1382{
1383 if(vm->proxy.map.view)
1384 vm->proxy.map.add_location(vm->proxy.map.view, p, posid);
1385}
1386
1387void dt_view_map_location_action(const dt_view_manager_t *vm, const int action)
1388{
1389 if(vm->proxy.map.view)
1390 vm->proxy.map.location_action(vm->proxy.map.view, action);
1391}
1392
1394{
1395 if(vm->proxy.map.view)
1396 vm->proxy.map.redraw(vm->proxy.map.view);
1397}
1398
1399#endif
1400
1401#ifdef HAVE_PRINT
1403{
1404 if (vm->proxy.print.view)
1405 vm->proxy.print.print_settings(vm->proxy.print.view, pinfo, imgs);
1406}
1407#endif
1408
1409
1410static void _audio_child_watch(GPid pid, gint status, gpointer data)
1411{
1413 vm->audio.audio_player_id = -1;
1414 g_spawn_close_pid(pid);
1415}
1416
1418{
1419 char *player = dt_conf_get_string("plugins/lighttable/audio_player");
1420 if(player && *player)
1421 {
1422 char *filename = dt_image_get_audio_path(imgid);
1423 if(filename)
1424 {
1425 char *argv[] = { player, filename, NULL };
1426 gboolean ret = g_spawn_async(NULL, argv, NULL,
1427 G_SPAWN_DO_NOT_REAP_CHILD
1428 | G_SPAWN_SEARCH_PATH
1429 | G_SPAWN_STDOUT_TO_DEV_NULL
1430 | G_SPAWN_STDERR_TO_DEV_NULL,
1431 NULL, NULL,
1432 &vm->audio.audio_player_pid, NULL);
1433
1434 if(ret)
1435 {
1436 vm->audio.audio_player_id = imgid;
1438 = g_child_watch_add(vm->audio.audio_player_pid, (GChildWatchFunc)_audio_child_watch, vm);
1439 }
1440 else
1441 vm->audio.audio_player_id = -1;
1442
1443 dt_free(filename);
1444 }
1445 }
1446 dt_free(player);
1447}
1448
1450{
1451 // make sure that the process didn't finish yet and that _audio_child_watch() hasn't run
1452 if(vm->audio.audio_player_id == -1)
1453 return;
1454
1455 // we don't want to trigger the callback due to a possible race condition
1456 g_source_remove(vm->audio.audio_player_event_source);
1457#ifdef _WIN32
1458// TODO: add Windows code to actually kill the process
1459#else // _WIN32
1460 if(vm->audio.audio_player_id != -1)
1461 {
1462 if(getpgid(0) != getpgid(vm->audio.audio_player_pid))
1463 kill(-vm->audio.audio_player_pid, SIGKILL);
1464 else
1465 kill(vm->audio.audio_player_pid, SIGKILL);
1466 }
1467#endif // _WIN32
1468 g_spawn_close_pid(vm->audio.audio_player_pid);
1469 vm->audio.audio_player_id = -1;
1470}
1471
1472
1473void dt_view_image_info_update(int32_t imgid)
1474{
1476
1477 if(imgid == UNKNOWN_IMAGE)
1478 {
1480 return;
1481 }
1482
1483 char input_dir[512] = { 0 };
1484 gboolean from_cache = TRUE;
1485 dt_image_full_path(imgid, input_dir, sizeof(input_dir), &from_cache, __FUNCTION__);
1486
1489
1490 vp->filename = input_dir;
1491 vp->jobcode = "infos";
1492 vp->imgid = imgid;
1493 vp->sequence = 0;
1494 vp->escape_markup = TRUE;
1495
1496 gchar *pattern = dt_conf_get_string("plugins/darkroom/image_infos_pattern");
1497 gchar *msg = dt_variables_expand(vp, pattern, TRUE);
1498 dt_free(pattern);
1500
1502
1503 dt_free(msg);
1504}
1505
1506
1507// clang-format off
1508// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1509// vim: shiftwidth=2 expandtab tabstop=2 cindent
1510// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1511// clang-format on
GtkWidget * dt_gui_main_window(void)
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
void dt_ui_container_destroy_children(dt_ui_t *ui, const dt_ui_container_t c)
destroy all child widgets from container
struct dt_ui_t * dt_gui_get_ui(void)
GtkWidget * dt_gui_center_widget(void)
void dt_gui_add_help_link(GtkWidget *widget, char *link)
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:68
uint32_t container(dt_lib_module_t *self)
const char ** views(dt_lib_module_t *self)
static const float x
const float v
gboolean dt_colorprofiles_rgba8_to_display_bgra8(const uint8_t *const in, uint8_t *const out, const int width, const int height, const dt_colorspaces_color_profile_type_t src_space)
Convert a whole 8-bit plane from src_space to the display profile: packed RGBA8 in,...
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
const dt_colormatrix_t dt_aligned_pixel_t out
int dt_conf_get_bool(const char *name)
gchar * dt_conf_get_string(const char *name)
Read the stored string for name as a private copy.
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.
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:924
#define dt_control_change_cursor(cursor)
Definition control.h:121
struct dt_control_t * dt_control_get_global(void)
Definition darktable.c:651
struct dt_lib_t * dt_lib_get_global(void)
Definition darktable.c:621
struct dt_view_manager_t * dt_view_manager_get_global(void)
Definition darktable.c:599
void * dt_alloc_align(size_t size)
Allocate cacheline-aligned memory.
Definition darktable.c:508
struct dt_gui_gtk_t * dt_gui_get_global(void)
Definition darktable.c:523
GtkWidget * window
GtkWidget * status
result of the last capture
GtkWidget * view
the tree view
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:127
static int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Initialise a mutex. With mutexattr NULL – which is how 54 of the 56 call sites in this tree spell it ...
Definition dtpthread.h:104
static int dt_pthread_mutex_destroy(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:132
static int dt_pthread_cond_wait(pthread_cond_t *cond, dt_pthread_mutex_t *mutex)
Wait on cond, releasing mutex for the duration.
Definition dtpthread.h:149
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:117
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:31
#define DTGTK_IS_EXPANDER(obj)
Definition expander.h:32
dt_geo_map_display_t
Definition geo.h:26
#define UNKNOWN_IMAGE
Definition image.h:78
dt_image_t * dt_image_cache_get(const int32_t imgid, char mode)
void dt_image_cache_read_release(const dt_image_t *img)
dt_job_state_t dt_control_job_get_state(_dt_job_t *job)
Definition jobs.c:105
dt_job_t * dt_control_job_create(dt_job_execute_callback execute, const char *msg,...)
Definition jobs.c:137
int dt_control_add_job(dt_control_t *control, dt_job_queue_t queue_id, _dt_job_t *job)
Definition jobs.c:407
void * dt_control_job_get_params(const _dt_job_t *job)
Definition jobs.c:131
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:122
@ DT_JOB_QUEUE_SYSTEM_FG
Definition jobs.h:55
@ DT_JOB_STATE_CANCELLED
Definition jobs.h:47
gboolean dt_lib_is_visible_in_view(dt_lib_module_t *module, const dt_view_t *view)
Definition lib.c:114
gboolean dt_lib_is_visible(dt_lib_module_t *module)
Definition lib.c:1178
void dt_lib_gui_set_expanded(dt_lib_module_t *module, gboolean expanded)
Definition lib.c:854
void dt_lib_set_visible(dt_lib_module_t *module, gboolean visible)
Definition lib.c:1188
GtkWidget * dt_lib_gui_get_expander(dt_lib_module_t *module)
Definition lib.c:1039
_lib_location_type_t type
Definition location.c:1
float lat
Definition location.c:3
float lon
Definition location.c:2
@ DT_DEBUG_LIGHTTABLE
Definition logging.h:60
@ DT_DEBUG_PERF
Definition logging.h:55
@ DT_DEBUG_IMAGEIO
Definition logging.h:68
int32_t dt_get_debug_flags(void)
Definition darktable.c:2085
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
#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 macros.h:96
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static void dt_free_gpointer(gpointer ptr)
g_free() one pointer, with the signature GDestroyNotify wants.
Definition mem_alloc.h:184
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
dt_mipmap_size_t dt_mipmap_cache_get_matching_size(const int32_t width, const int32_t height, const uint32_t imgid)
#define dt_mipmap_cache_get_with_shutdown(B, C, D, E, F, G)
@ DT_MIPMAP_BLOCKING
dt_mipmap_size_t
@ DT_MIPMAP_F
@ DT_MIPMAP_NONE
#define dt_mipmap_cache_release(B)
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))
Load every module the given directory's manifest lists.
Definition module.c:106
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
const char * name
Definition pdf.h:90
void copy(TYPE *dest, TYPE *source, size_t num_el)
Copy a flat buffer used to initialize test matrices.
void dt_sentry_record_module_usage(const char *category, const char *name)
Definition sentry.c:692
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:386
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ 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:67
@ DT_SIGNAL_CONTROL_TOAST_REDRAW
This signal is raised when dt_control_toast_redraw() is called. no param, no returned value.
Definition signal.h:291
@ 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:91
@ DT_SIGNAL_CONTROL_LOG_REDRAW
This signal is raised when dt_control_log_redraw() is called. no param, no returned value.
Definition signal.h:286
@ 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:84
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]
int32_t height
Definition image.h:397
int32_t width
Definition image.h:397
char plugin_name[128]
Definition lib.h:81
GModule *void * data
Definition lib.h:79
GtkWidget * widget
Definition lib.h:83
dt_colorspaces_color_profile_type_t color_space
const gchar * filename
Definition variables.h:48
const gchar * jobcode
Definition variables.h:51
Track one asynchronous Cairo surface fetch request for a GUI widget.
Definition view.h:119
dt_pthread_mutex_t lock
Definition view.h:120
cairo_surface_t ** target
Definition view.h:123
GPid audio_player_pid
Definition view.h:207
guint audio_player_event_source
Definition view.h:209
dt_view_t * current_view
Definition view.h:193
int32_t audio_player_id
Definition view.h:208
GObject *(* add_marker)(const dt_view_t *view, dt_geo_map_display_t type, GList *points)
Definition view.h:251
gboolean(* redraw)(gpointer user_data)
Definition view.h:255
void(* center_on_location)(const dt_view_t *view, gdouble lon, gdouble lat, double zoom)
Definition view.h:247
struct dt_view_manager_t::@60::@65 print
struct dt_view_manager_t::@59 audio
gboolean(* remove_marker)(const dt_view_t *view, dt_geo_map_display_t type, GObject *marker)
Definition view.h:252
int32_t image_info_id
Definition view.h:203
dt_darkroom_layout_t(* get_layout)(struct dt_view_t *view)
Definition view.h:238
void(* print_settings)(const dt_view_t *view, dt_print_info_t *pinfo, dt_images_box *imgs)
Definition view.h:264
GList * views
Definition view.h:192
void(* add_location)(const dt_view_t *view, dt_map_location_data_t *p, const guint posid)
Definition view.h:253
void(* location_action)(const dt_view_t *view, const int action)
Definition view.h:254
void(* set_map_source)(const dt_view_t *view, OsmGpsMapSource_t map_source)
Definition view.h:250
struct dt_view_t * view
Definition view.h:237
struct dt_view_manager_t::@60 proxy
struct dt_view_manager_t::@60::@64 map
struct dt_view_manager_t::@60::@63 darkroom
struct dt_view_manager_t::@60::@61 module_toolbox
struct dt_lib_module_t *void(* add)(struct dt_lib_module_t *, GtkWidget *, dt_view_type_flags_t)
Definition view.h:224
void(* show_osd)(const dt_view_t *view)
Definition view.h:249
GList * active_images
Definition view.h:196
void(* center_on_bbox)(const dt_view_t *view, gdouble lon1, gdouble lat1, gdouble lon2, gdouble lat2)
Definition view.h:248
dt_view_image_surface_fetcher_t * fetcher
Definition view.c:665
cairo_surface_t * surface
Definition view.c:671
dt_view_surface_value_t result
Definition view.c:672
dt_view_image_surface_fetcher_t * fetcher
Definition view.c:655
uint32_t height
Definition view.h:161
GModule *void * data
Definition view.h:159
uint32_t width
Definition view.h:161
char module_name[64]
Definition view.h:155
void dt_supervisor_thumbnail(const dt_sv_op_t op, const int32_t imgid, const int width, const int height, const int mip, const gboolean success)
@ DT_SV_READ
Definition supervisor.h:80
@ DT_SV_UPDATE
Definition supervisor.h:79
static gboolean dt_supervisor_active(void)
Definition supervisor.h:95
void dt_telemetry_record_module_usage(const char *category, const char *name)
Definition telemetry.c:460
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:80
@ DT_THUMBTABLE_ZOOM_FULL
Definition thumbtable.h:79
@ DT_THUMBTABLE_ZOOM_HALF
Definition thumbtable.h:78
@ DT_THUMBTABLE_ZOOM_FIT
Definition thumbtable.h:77
static double dt_get_wtime(void)
Definition times.h:43
void dt_undo_clear(dt_undo_t *self, uint32_t filter)
Definition undo.c:367
@ DT_UNDO_ALL
Definition undo.h:61
dt_undo_t * dt_undo_get_global(void)
Definition darktable.c:611
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:871
void dt_view_manager_cleanup(dt_view_manager_t *vm)
Definition view.c:130
void dt_view_image_surface_fetcher_cleanup(dt_view_image_surface_fetcher_t *fetcher)
Definition view.c:847
static int dt_view_load_module(void *v, const char *libname, const char *module_name)
Definition view.c:181
void dt_view_manager_mouse_moved(dt_view_manager_t *vm, double x, double y, double pressure, int which)
Definition view.c:525
const char * dt_view_manager_name(dt_view_manager_t *vm)
Definition view.c:435
void dt_view_map_center_on_location(const dt_view_manager_t *vm, gdouble lon, gdouble lat, gdouble zoom)
Definition view.c:1344
void dt_view_manager_mouse_enter(dt_view_manager_t *vm)
Definition view.c:519
void dt_view_manager_configure(dt_view_manager_t *vm, int width, int height)
Definition view.c:633
void dt_view_manager_reset(dt_view_manager_t *vm)
Definition view.c:491
static void _enqueue_surface_fetch(dt_view_image_surface_fetcher_t *fetcher)
Definition view.c:693
void dt_view_image_surface_fetcher_init(dt_view_image_surface_fetcher_t *fetcher)
Definition view.c:836
void dt_view_map_location_action(const dt_view_manager_t *vm, const int action)
Definition view.c:1387
cairo_surface_t * dt_cairo_rescale_surface(cairo_surface_t *src, int dst_w, int dst_h)
Definition view.c:933
void dt_view_map_redraw(const dt_view_manager_t *vm)
Definition view.c:1393
void dt_cairo_sharpen_surface_rgb24(cairo_surface_t *surface)
Definition view.c:982
static void _destroy_surface(cairo_surface_t **surface)
Definition view.c:679
static void dt_view_unload_module(dt_view_t *view)
Definition view.c:201
gboolean dt_view_active_images_has_imgid(int32_t imgid)
Definition view.c:1297
GObject * dt_view_map_add_marker(const dt_view_manager_t *vm, dt_geo_map_display_t type, GList *points)
Definition view.c:1368
int dt_view_manager_button_released(dt_view_manager_t *vm, double x, double y, int which, uint32_t state)
Definition view.c:574
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:450
void dt_view_map_show_osd(const dt_view_manager_t *vm)
Definition view.c:1356
int dt_view_manager_key_pressed(dt_view_manager_t *vm, GdkEventKey *event)
Definition view.c:547
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:892
void dt_view_audio_start(dt_view_manager_t *vm, int32_t imgid)
Definition view.c:1417
void dt_view_manager_module_toolbox_add(dt_view_manager_t *vm, GtkWidget *tool, dt_view_type_flags_t views)
Definition view.c:1329
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:113
void dt_view_active_images_reset(gboolean raise)
Definition view.c:1266
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:601
uint32_t dt_view_manager_current_flags(const dt_view_manager_t *vm)
Definition view.c:444
GList * dt_view_active_images_get_all()
Definition view.c:1302
static gboolean _view_surface_commit_main(gpointer user_data)
Definition view.c:717
void dt_view_manager_gui_init(dt_view_manager_t *vm)
Definition view.c:121
void dt_vm_remove_child(GtkWidget *widget, gpointer data)
Definition view.c:208
static void _remove_child(GtkWidget *child, GtkContainer *container)
Definition view.c:218
void dt_view_map_add_location(const dt_view_manager_t *vm, dt_map_location_data_t *p, const guint posid)
Definition view.c:1381
void dt_view_print_settings(const dt_view_manager_t *vm, dt_print_info_t *pinfo, dt_images_box *imgs)
Definition view.c:1402
void dt_view_audio_stop(dt_view_manager_t *vm)
Definition view.c:1449
int dt_view_manager_switch(dt_view_manager_t *vm, const char *view_name)
Definition view.c:234
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:1045
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:1214
int dt_view_manager_switch_by_view(dt_view_manager_t *vm, const dt_view_t *nv)
Definition view.c:256
static void dt_view_manager_load_modules(dt_view_manager_t *vm)
Definition view.c:169
static uint32_t default_flags()
Definition view.c:175
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:686
void dt_view_map_center_on_bbox(const dt_view_manager_t *vm, gdouble lon1, gdouble lat1, gdouble lon2, gdouble lat2)
Definition view.c:1350
static int32_t _view_surface_fetch_job_run(dt_job_t *job)
Definition view.c:783
int dt_view_manager_scrolled(dt_view_manager_t *vm, double x, double y, int up, int state, int delta_y)
Definition view.c:645
void dt_view_map_set_map_source(const dt_view_manager_t *vm, OsmGpsMapSource_t map_source)
Definition view.c:1362
void dt_view_image_info_update(int32_t imgid)
Definition view.c:1473
void dt_view_manager_mouse_leave(dt_view_manager_t *vm)
Definition view.c:497
static gint sort_views(gconstpointer a, gconstpointer b)
Definition view.c:146
void dt_view_active_images_set(GList *images, gboolean raise)
Definition view.c:1314
int32_t dt_view_active_images_get_first()
Definition view.c:1307
gboolean dt_view_map_remove_marker(const dt_view_manager_t *vm, dt_geo_map_display_t type, GObject *marker)
Definition view.c:1375
dt_darkroom_layout_t dt_view_darkroom_get_layout(dt_view_manager_t *vm)
Definition view.c:1335
static void _audio_child_watch(GPid pid, gint status, gpointer data)
Definition view.c:1410
const dt_view_t * dt_view_manager_get_current_view(dt_view_manager_t *vm)
Definition view.c:139
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:1220
dt_view_type_flags_t
Definition view.h:76
@ DT_VIEW_LIGHTTABLE
Definition view.h:77
@ DT_VIEW_DARKROOM
Definition view.h:79
dt_darkroom_layout_t
Definition view.h:94
@ DT_DARKROOM_LAYOUT_EDITING
Definition view.h:96
dt_view_surface_value_t
Definition view.h:104
@ DT_VIEW_SURFACE_OK
Definition view.h:105
@ DT_VIEW_SURFACE_KO
Definition view.h:106
void dt_widget_set_source_rgb(cairo_t *cr, dt_gui_color_t color)
void dt_widget_set_scroll_focus(GtkWidget *widget)
@ DT_GUI_COLOR_BG
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