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