Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
thumbnail.c
Go to the documentation of this file.
1/*
2 This file is part of the Ansel project.
3 Copyright (C) 2020-2022 Aldric Renaudin.
4 Copyright (C) 2020, 2022-2023, 2025-2026 Aurélien PIERRE.
5 Copyright (C) 2020 Hanno Schwalm.
6 Copyright (C) 2020-2021 Hubert Kowalski.
7 Copyright (C) 2020 Mark-64.
8 Copyright (C) 2020, 2022 Nicolas Auffray.
9 Copyright (C) 2020-2022 Pascal Obry.
10 Copyright (C) 2020 Philippe Weyland.
11 Copyright (C) 2020 Roman Lebedev.
12 Copyright (C) 2020 Tianhao Chai.
13 Copyright (C) 2020 Tobias Ellinghaus.
14 Copyright (C) 2020 U-DESKTOP-HQME86J\marco.
15 Copyright (C) 2021 Dan Torop.
16 Copyright (C) 2021-2022 Diederik Ter Rahe.
17 Copyright (C) 2021 Fabio Heer.
18 Copyright (C) 2021 luzpaz.
19 Copyright (C) 2021 Ralf Brown.
20 Copyright (C) 2022 Martin Bařinka.
21 Copyright (C) 2022 Miloš Komarčević.
22 Copyright (C) 2022 Sakari Kapanen.
23 Copyright (C) 2023 Ricky Moon.
24 Copyright (C) 2026 Guillaume Stutin.
25
26 Ansel is free software: you can redistribute it and/or modify
27 it under the terms of the GNU General Public License as published by
28 the Free Software Foundation, either version 3 of the License, or
29 (at your option) any later version.
30
31 Ansel is distributed in the hope that it will be useful,
32 but WITHOUT ANY WARRANTY; without even the implied warranty of
33 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34 GNU General Public License for more details.
35
36 You should have received a copy of the GNU General Public License
37 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
38*/
41#include "gui/dtgtk/thumbnail.h"
44
45#include "widgets/bauhaus.h"
50#include "gui/dtgtk/focus.h"
52#include "common/grouping.h"
53#include "metadata/ratings.h"
54#include "common/selection.h"
55#include "common/utility.h"
56#include "common/variables.h"
57#include "control/control.h"
60#include "gui/drag_and_drop.h"
61
62#include "views/view.h"
63
64#include <glib-object.h>
65#include "gui/application.h"
66#include "widgets/label.h"
68#include "control/signal.h"
69
78#define thumb_return_if_fails(thumb, ...) { if(!thumb || !thumb->widget || !thumb->w_main) return __VA_ARGS__; }
79
80
81static void _set_flag(GtkWidget *w, GtkStateFlags flag, gboolean activate)
82{
83 if(!GTK_IS_WIDGET(w)) return;
84
85 if(activate)
86 gtk_widget_set_state_flags(w, flag, FALSE);
87 else
88 gtk_widget_unset_state_flags(w, flag);
89}
90
92{
95 {
96 gtk_widget_set_has_tooltip(thumb->w_group, FALSE);
97 return;
98 }
99
100 gchar *tt = NULL;
101 int nb = 0;
102
103 // the group leader
104 if(thumb->info.id == thumb->info.group_id)
105 tt = g_strdup_printf("\n\u2022 <b>%s (%s)</b>", _("current"), _("leader"));
106 else
107 {
108 dt_image_t leader = { 0 };
109 if(dt_thumbtable_get_thumbnail_info(thumb->table, thumb->info.group_id, &leader))
110 tt = g_strdup_printf("%s\n\u2022 <b>%s (%s)</b>", _("\nclick here to set this image as group leader\n"), leader.filename, _("leader"));
111 }
112
113 // and the other images
115 for(GList *m = members; m; m = g_list_next(m))
116 {
117 const dt_image_group_member_t *member = (const dt_image_group_member_t *)m->data;
118 nb++;
119
120 if(member->imgid != thumb->info.group_id)
121 {
122 if(member->imgid == thumb->info.id)
123 tt = dt_util_dstrcat(tt, "\n\u2022 %s", _("current"));
124 else
125 {
126 tt = dt_util_dstrcat(tt, "\n\u2022 %s", member->filename);
127 if(member->version > 0) tt = dt_util_dstrcat(tt, " v%d", member->version);
128 }
129 }
130 }
131 g_list_free_full(members, dt_image_group_member_free);
132
133 // and the number of grouped images
134 gchar *ttf = g_strdup_printf("%d %s\n%s", nb, _("grouped images"), tt);
135 dt_free(tt);
136
137 // let's apply the tooltip
138 gtk_widget_set_tooltip_markup(thumb->w_group, ttf);
139 dt_free(ttf);
140}
141
143{
145 for(int i = DT_VIEW_DESERT; i <= DT_VIEW_REJECT; i++)
146 {
147 gchar *cn = g_strdup_printf("dt_thumbnail_rating_%d", i);
148 if(thumb->info.rating == i)
149 dt_gui_add_class(thumb->w_main, cn);
150 else
151 dt_gui_remove_class(thumb->w_main, cn);
152 dt_free(cn);
153 }
154}
155
157{
158 // fill the file extension label
160 if(!thumb->info.filename[0]) return;
161 const char *ext = thumb->info.filename + strlen(thumb->info.filename);
162 while(ext > thumb->info.filename && *ext != '.') ext--;
163 ext++;
164 gchar *uext = dt_view_extend_modes_str(ext, thumb->info.is_hdr, thumb->info.is_bw, thumb->info.is_bw_flow);
165 gchar *label = g_strdup_printf("%s #%i", uext, thumb->rowid + 1);
166 gtk_label_set_text(GTK_LABEL(thumb->w_ext), label);
167 dt_free(uext);
168 dt_free(label);
169}
170
171static GtkWidget *_gtk_menu_item_new_with_markup(const char *label, GtkWidget *menu,
172 void (*activate_callback)(GtkWidget *widget,
173 dt_thumbnail_t *thumb),
174 dt_thumbnail_t *thumb)
175{
176 GtkWidget *menu_item = gtk_menu_item_new_with_label("");
177 GtkWidget *child = gtk_bin_get_child(GTK_BIN(menu_item));
178 gtk_label_set_markup(GTK_LABEL(child), label);
179 gtk_menu_item_set_reserve_indicator(GTK_MENU_ITEM(menu_item), FALSE);
180 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
181
182 if(activate_callback) g_signal_connect(G_OBJECT(menu_item), "activate", G_CALLBACK(activate_callback), thumb);
183
184 return menu_item;
185}
186
187static GtkWidget *_menuitem_from_text(const char *label, const char *value, GtkWidget *menu,
188 void (*activate_callback)(GtkWidget *widget, dt_thumbnail_t *thumb),
189 dt_thumbnail_t *thumb)
190{
191 gchar *text = g_strdup_printf("%s%s", label, value);
192 GtkWidget *menu_item = _gtk_menu_item_new_with_markup(text, menu, activate_callback, thumb);
193 dt_free(text);
194 return menu_item;
195}
196
198{
199 int color = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "custom-data"));
200 GList *imgs = g_list_append(NULL, GINT_TO_POINTER(thumb->info.id));
202 g_list_free(imgs);
203}
204
206{
208}
209
210static void _active_module_row(void *user_data, const int num, const char *op, const char *multi)
211{
212 GString *text = (GString *)user_data;
213 const gboolean has_multi = (multi && multi[0] && strcmp(multi, " "));
214
215 if(has_multi)
216 g_string_append_printf(text, "%d. %s (%s)\n", num, op ? op : "?", multi);
217 else
218 g_string_append_printf(text, "%d. %s\n", num, op ? op : "?");
219}
220
222{
223 (void)widget;
224 if(IS_NULL_PTR(thumb)) return;
225
226 GString *text = g_string_new(NULL);
227 g_string_append_printf(text, "image id: %d\nfile: %s\n\n", thumb->info.id,
228 (thumb->info.fullpath[0]) ? thumb->info.fullpath : thumb->info.filename);
230
231 if(text->len == 0) g_string_assign(text, _("No active modules"));
232
233 // Use the real application window as transient parent, not the popup menu toplevel
234 // (setting the menu's toplevel as parent triggers GTK parent warnings, since it is
235 // unmapped by the time this dialog shows). Without a transient parent, GTK/the window
236 // manager has nothing to return focus to when the dialog closes.
237 GtkWidget *main_window = dt_gui_main_window();
238 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("Active modules"),
239 GTK_IS_WINDOW(main_window) ? GTK_WINDOW(main_window) : NULL,
240 GTK_DIALOG_DESTROY_WITH_PARENT,
241 _("_Close"), GTK_RESPONSE_CLOSE,
242 NULL);
243 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
244
245 GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
246 GtkWidget *scrolled = gtk_scrolled_window_new(NULL, NULL);
247 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
248 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
249 gtk_widget_set_size_request(scrolled, 420, 260);
250 gtk_container_set_border_width(GTK_CONTAINER(scrolled), 4);
251
252 GtkWidget *view = gtk_text_view_new();
253 dt_gui_textview_set_padding(GTK_TEXT_VIEW(view));
254 gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE);
255 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), FALSE);
256 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD_CHAR);
257 gtk_text_view_set_monospace(GTK_TEXT_VIEW(view), TRUE);
258 gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(view)), text->str, -1);
259 gtk_container_add(GTK_CONTAINER(scrolled), view);
260
261 gtk_box_pack_start(GTK_BOX(content), scrolled, TRUE, TRUE, 0);
262 gtk_widget_show_all(dialog);
263
264 g_signal_connect(dialog, "response", G_CALLBACK(gtk_widget_destroy), NULL);
265
266 g_string_free(text, TRUE);
267}
268
270{
271 // Always re-create the menu when we show it because we don't bother updating info during the lifetime of the thumbnail
272 GtkWidget *menu = gtk_menu_new();
273
274 // Filename: insensitive header to mean that the context menu is for this picture only
275 GtkWidget *menu_item = _gtk_menu_item_new_with_markup(thumb->info.filename, menu, NULL, thumb);
276 gtk_widget_set_sensitive(menu_item, FALSE);
277
278 GtkWidget *sep = gtk_separator_menu_item_new();
279 gtk_menu_shell_append(GTK_MENU_SHELL(menu), sep);
280
282 menu_item = _gtk_menu_item_new_with_markup(_("Image info"), menu, NULL, thumb);
283 GtkWidget *sub_menu = gtk_menu_new();
284 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), sub_menu);
285
286 _menuitem_from_text(_("Folder : "), thumb->info.folder, sub_menu, NULL, thumb);
287 _menuitem_from_text(_("Date : "), thumb->info.datetime, sub_menu, NULL, thumb);
288 _menuitem_from_text(_("Camera : "), thumb->info.camera_makermodel, sub_menu, NULL, thumb);
289 _menuitem_from_text(_("Lens : "), thumb->info.exif_lens, sub_menu, NULL, thumb);
290
291 sep = gtk_separator_menu_item_new();
292 gtk_menu_shell_append(GTK_MENU_SHELL(menu), sep);
293
295 menu_item = _gtk_menu_item_new_with_markup(_("Assign color labels"), menu, NULL, thumb);
296 sub_menu = gtk_menu_new();
297 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), sub_menu);
298
299 menu_item = _gtk_menu_item_new_with_markup("<span foreground='#BB2222'>\342\254\244</span> Red", sub_menu, _color_label_callback, thumb);
300 g_object_set_data(G_OBJECT(menu_item), "custom-data", GINT_TO_POINTER(0));
301
302 menu_item = _gtk_menu_item_new_with_markup("<span foreground='#BBBB22'>\342\254\244</span> Yellow", sub_menu, _color_label_callback, thumb);
303 g_object_set_data(G_OBJECT(menu_item), "custom-data", GINT_TO_POINTER(1));
304
305 menu_item = _gtk_menu_item_new_with_markup("<span foreground='#22BB22'>\342\254\244</span> Green", sub_menu, _color_label_callback, thumb);
306 g_object_set_data(G_OBJECT(menu_item), "custom-data", GINT_TO_POINTER(2));
307
308 menu_item = _gtk_menu_item_new_with_markup("<span foreground='#2222BB'>\342\254\244</span> Blue", sub_menu, _color_label_callback, thumb);
309 g_object_set_data(G_OBJECT(menu_item), "custom-data", GINT_TO_POINTER(3));
310
311 menu_item = _gtk_menu_item_new_with_markup("<span foreground='#BB22BB'>\342\254\244</span> Purple", sub_menu, _color_label_callback, thumb);
312 g_object_set_data(G_OBJECT(menu_item), "custom-data", GINT_TO_POINTER(4));
313
314 menu_item = _gtk_menu_item_new_with_markup(_("Open in preview window…"), menu, _preview_window_open, thumb);
315 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
316
317 sep = gtk_separator_menu_item_new();
318 gtk_menu_shell_append(GTK_MENU_SHELL(menu), sep);
319
320 menu_item = _gtk_menu_item_new_with_markup(_("Show active modules…"), menu, _active_modules_popup, thumb);
321 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
322
323 gtk_widget_show_all(menu);
324
325 return menu;
326}
327
328static gboolean _event_cursor_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
329{
330 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
332
333 GtkStateFlags state = gtk_widget_get_state_flags(thumb->w_cursor);
334 GtkStyleContext *context = gtk_widget_get_style_context(thumb->w_cursor);
335 GdkRGBA col;
336 gtk_style_context_get_color(context, state, &col);
337
338 cairo_set_source_rgba(cr, col.red, col.green, col.blue, col.alpha);
339 cairo_line_to(cr, gtk_widget_get_allocated_width(widget), 0);
340 cairo_line_to(cr, gtk_widget_get_allocated_width(widget) / 2, gtk_widget_get_allocated_height(widget));
341 cairo_line_to(cr, 0, 0);
342 cairo_close_path(cr);
343 cairo_fill(cr);
344
345 return TRUE;
346}
347
348
350{
351 if(thumb->img_surf && cairo_surface_get_reference_count(thumb->img_surf) > 0)
352 cairo_surface_destroy(thumb->img_surf);
353
354 thumb->img_surf = NULL;
355}
356
358{
359 if(IS_NULL_PTR(thumb)) return;
360
361 _free_image_surface(thumb);
363 dt_free(thumb);
364}
365
366static void _thumbnail_release(void *data)
367{
368 dt_thumbnail_t *thumb = (dt_thumbnail_t *)data;
369 if(IS_NULL_PTR(thumb)) return;
370
371 if(dt_atomic_sub_int(&thumb->ref_count, 1) == 1)
372 _thumbnail_free(thumb);
373}
374
375static gboolean _main_context_queue_draw(GtkWidget *widget);
376
391{
393 return;
394
396 if(IS_NULL_PTR(thumb)) return;
397
399 const gboolean was_gating = (thumb->job == job);
400 GtkWidget *redraw_widget = NULL;
401 if(was_gating)
402 {
403 thumb->job = NULL;
404 // Capture and ref the redraw target under thumb->lock: dt_thumbnail_destroy() nulls
405 // thumb->w_image (after detaching it) under this same lock, so checking it outside would
406 // be a TOCTOU race — the pointer could be nulled (or the widget finalized) between the
407 // check and the g_object_ref().
408 if(!dt_atomic_get_int(&thumb->destroying) && thumb->w_image)
409 redraw_widget = g_object_ref(thumb->w_image);
410 }
412
413 if(redraw_widget)
414 {
415 GMainContext *context = g_main_context_default();
416 g_main_context_invoke_full(context, G_PRIORITY_DEFAULT,
417 (GSourceFunc)_main_context_queue_draw,
418 redraw_widget,
419 (GDestroyNotify)g_object_unref);
420 g_main_context_wakeup(context);
421 }
422}
423
424static gboolean _main_context_queue_draw(GtkWidget *widget)
425{
426 if(GTK_IS_WIDGET(widget))
427 {
428 gtk_widget_queue_draw(widget);
429 }
430
431 return G_SOURCE_REMOVE;
432}
433
434static int _finish_buffer_thread(dt_thumbnail_t *thumb, dt_job_t *job, gboolean success)
435{
436 thumb_return_if_fails(thumb, 1);
437
438 // Only the job currently gating the widget may re-arm it: a stale job finishing after a
439 // refresh already published a newer thumb->job must not clear that newer job nor touch
440 // image_inited, or the newer render gets orphaned/duplicated.
442 const gboolean was_gating = IS_NULL_PTR(job) || thumb->job == job;
443 GtkWidget *redraw_widget = NULL;
444 if(was_gating)
445 {
446 thumb->image_inited = success;
447 thumb->job = NULL;
448 // Redraw events need to be sent from the main GUI thread, though we may not have a target
449 // widget anymore. Capture and ref it under thumb->lock: dt_thumbnail_destroy() nulls
450 // thumb->w_image under this same lock, so a check-then-ref outside it races the teardown.
451 if(!dt_atomic_get_int(&thumb->destroying) && thumb->w_image)
452 redraw_widget = g_object_ref(thumb->w_image);
453 }
455
456 if(redraw_widget)
457 {
458 GMainContext *context = g_main_context_default();
459 g_main_context_invoke_full(context, G_PRIORITY_DEFAULT,
460 (GSourceFunc)_main_context_queue_draw,
461 redraw_widget,
462 (GDestroyNotify)g_object_unref);
463 g_main_context_wakeup(context);
464 }
465
466 return 0;
467}
468
469
471{
472 // WARNING: the target thumbnail GUI widget can be destroyed at any time during this
473 // control flow in the GUI mainthread.
475 thumb_return_if_fails(thumb, 1);
476 if(dt_atomic_get_int(&thumb->destroying)) return 1;
477
478 // The job was cancelled on the queue. Good chances of having thumb destroyed anytime soon.
479 if(IS_NULL_PTR(thumb->job) || thumb->job != job || dt_control_job_get_state(job) == DT_JOB_STATE_CANCELLED) return 1;
480
481 // Read and cache the thumb data now, while we have it. And lock it.
483
484 // These are the sizes of the widget bounding box
485 int img_w = thumb->img_w;
486 int img_h = thumb->img_h;
487
488 const int zoom = (thumb->table) ? thumb->table->zoom : DT_THUMBTABLE_ZOOM_FIT;
489 const gboolean show_focus_peaking = (thumb->table && thumb->table->focus_peaking);
490 const gboolean show_focus_clusters = (thumb->table && thumb->table->focus_regions);
491 const gboolean zoom_in = (thumb->table && thumb->table->zoom > DT_THUMBTABLE_ZOOM_FIT);
492 const int32_t imgid = thumb->info.id;
493
495
496 // These are the sizes of the actual image. Can be larger than the widget bounding box.
497 int img_width = 0;
498 int img_height = 0;
499
500 double zoomx = 0.;
501 double zoomy = 0.;
502 float x_center = 0.f;
503 float y_center = 0.f;
504
505 // From there, never read thumb->... directly since it might get destroyed in mainthread anytime.
506 dt_print(DT_DEBUG_LIGHTTABLE, "[lighttable] fetching or computing thumbnail %i\n", thumb->info.id);
507
508 // Get the actual image content. This typically triggers a rendering pipeline,
509 // and can possibly take a long time.
510 cairo_surface_t *surface = NULL;
511
512 // Thumbnail generation (mipmap fetch + pipeline render) is instrumented inside
513 // dt_view_image_get_surface(), where the real mip level is known.
514 dt_view_surface_value_t res = dt_view_image_get_surface(imgid, img_w, img_h, &surface, zoom);
515
516 if(surface && res == DT_VIEW_SURFACE_OK)
517 {
518 // The image is immediately available
519 img_width = cairo_image_surface_get_width(surface);
520 img_height = cairo_image_surface_get_height(surface);
521 }
522 else
523 {
524 if(surface) cairo_surface_destroy(surface);
525 _finish_buffer_thread(thumb, job, FALSE);
526 return 0;
527 }
528
529 if(zoom > DT_THUMBTABLE_ZOOM_FIT || show_focus_peaking)
530 {
531 // Note: we compute the "sharpness density" unconditionnaly if the image is zoomed-in
532 // in order to get the details barycenter to init centering.
533 // Actual density are drawn only if the focus peaking mode is enabled.
534 cairo_t *cri = cairo_create(surface);
535 unsigned char *rgbbuf = cairo_image_surface_get_data(surface);
536 if(rgbbuf)
537 {
538 if(dt_focuspeaking(cri, rgbbuf, img_width, img_height, show_focus_peaking, &x_center, &y_center) != 0)
539 {
540 cairo_destroy(cri);
541 cairo_surface_destroy(surface);
542 _finish_buffer_thread(thumb, job, FALSE);
543 return 1;
544 }
545 }
546 cairo_destroy(cri);
547
548 // Init the zoom offset using the barycenter of details, to center
549 // the zoomed-in image on content that matters: details.
550 // Offset is expressed from the center of the image
551 if(zoom_in && x_center > 0.f && y_center > 0.f && thumb)
552 {
553 zoomx = (double)img_width / 2. - x_center;
554 zoomy = (double)img_height / 2. - y_center;
555 }
556 }
557
558 // if needed we compute and draw here the big rectangle to show focused areas
559 if(show_focus_clusters)
560 {
561 uint8_t *full_res_thumb = NULL;
562 int32_t full_res_thumb_wd, full_res_thumb_ht;
564 if(!dt_imageio_large_thumbnail(thumb->info.fullpath, &full_res_thumb, &full_res_thumb_wd, &full_res_thumb_ht, &color_space, img_width, img_height))
565 {
566 // we look for focus areas
567 dt_focus_cluster_t full_res_focus[49];
568 const int frows = 5, fcols = 5;
569 dt_focus_create_clusters(full_res_focus, frows, fcols, full_res_thumb, full_res_thumb_wd,
570 full_res_thumb_ht);
571 // and we draw them on the image
572 cairo_t *cri = cairo_create(surface);
573 dt_focus_draw_clusters(cri, img_width, img_height, imgid, full_res_thumb_wd,
574 full_res_thumb_ht, full_res_focus, frows, fcols, 1.0, 0, 0);
575 cairo_destroy(cri);
576 }
577 dt_pixelpipe_cache_free_align(full_res_thumb);
578 }
579
580 // Commit the rendered surface only if the thumb is still live and expects this specific render.
581 // All validity checks and the surface write happen inside a single locked section to eliminate
582 // TOCTOU races with dt_thumbnail_destroy (which nulls widget pointers under the same lock)
583 // and dt_thumbnail_image_refresh_real (which clears thumb->job under the same lock to cancel
584 // stale renders from before a size change or data invalidation).
585 double sx = 1.0, sy = 1.0;
586 cairo_surface_get_device_scale(surface, &sx, &sy);
587
589 const gboolean still_valid = (thumb->job == job // not cancelled by refresh or destroy
590 && !dt_atomic_get_int(&thumb->destroying)
591 && thumb->w_image != NULL);
592 if(still_valid)
593 {
594 _free_image_surface(thumb);
595 thumb->img_width = roundf(img_width / sx);
596 thumb->img_height = roundf(img_height / sy);
597 thumb->zoomx = zoomx / sx;
598 thumb->zoomy = zoomy / sy;
599 thumb->img_surf = surface;
600 surface = NULL; // ownership transferred to thumb
601 }
603
604 if(surface)
605 {
606 // The commit was refused (widget destroyed/refreshed mid-render). Still run the gated
607 // cleanup: if this job somehow remains the gating one (e.g. w_image already nulled while
608 // thumb->job was not), the widget must be re-armed rather than left waiting forever.
609 cairo_surface_destroy(surface);
610 _finish_buffer_thread(thumb, job, FALSE);
611 return 1;
612 }
613
614 _finish_buffer_thread(thumb, job, TRUE);
615 return 0;
616}
617
619{
620 thumb_return_if_fails(thumb, 1);
621
622 // Avoid spawning multiple background jobs for the same thumbnail.
623 // Re-scheduling here is counter-productive because each new job replaces thumb->job and makes
624 // previously queued/running jobs exit early (thumb->job != job), which can lead to endless
625 // "busy" redraws without ever painting an image.
627 const gboolean job_running = (!IS_NULL_PTR(thumb->job));
629 if(job_running) return 0;
630
631 // - image inited: the cached buffer has a valid size. Invalid this flag when size changes.
632 // - img_surf: we have a cached buffer (cairo surface), regardless of its validity.
633 // - a rendering job has already been started
634 if((thumb->image_inited && thumb->img_surf && cairo_surface_get_reference_count(thumb->img_surf) > 0))
635 return 0;
636
637 // Get thumbnail GUI allocated size now (in GUI mainthread).
638 // Size requests may be unset (-1) during initial layout while allocations are already valid.
639 int img_w = gtk_widget_get_allocated_width(thumb->w_image);
640 int img_h = gtk_widget_get_allocated_height(thumb->w_image);
641 if(img_w < 2 || img_h < 2)
642 gtk_widget_get_size_request(thumb->w_image, &img_w, &img_h);
643
644 // Not allocated yet: wait for the next draw once the widget has a real size.
645 if(img_w < 2 || img_h < 2) return 0;
646
647 img_w = MAX(img_w, 32);
648 img_h = MAX(img_h, 32);
649
651 thumb->img_w = img_w;
652 thumb->img_h = img_h;
654
655 // Drawing the focus peaking and doing the color conversions
656 // can be expensive on large thumbnails. Do it in a background job,
657 // so the thumbtable stays responsive.
658 dt_job_t *job = dt_control_job_create(&_get_image_buffer, "get image %i", thumb->info.id);
659 if(IS_NULL_PTR(job)) return 1;
660 // Re-arm thumb->job when the job system kills this job without running it (see the hook).
662
664 // Re-check now that we are about to publish the job pointer.
665 if(thumb->job || dt_atomic_get_int(&thumb->destroying))
666 {
669 return 0;
670 }
671 thumb->job = job;
672 dt_atomic_add_int(&thumb->ref_count, 1);
674
677 {
679 if(thumb->job == job) thumb->job = NULL;
681 _thumbnail_release(thumb);
682 return 1;
683 }
684
685 return 0;
686}
687
688
689
690static gboolean
691_thumb_draw_image(GtkWidget *widget, cairo_t *cr, gpointer user_data)
692{
693 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
695
696 int w = gtk_widget_get_allocated_width(widget);
697 int h = gtk_widget_get_allocated_height(widget);
698 if(w < 2 || h < 2) return TRUE;
699
708
709 dt_print(DT_DEBUG_LIGHTTABLE, "[lighttable] redrawing thumbnail %i\n", thumb->info.id);
710
712 if(thumb->img_surf && cairo_surface_get_reference_count(thumb->img_surf) > 0)
713 {
714 // we draw the image
715 cairo_save(cr);
716 double x_offset = (w - thumb->img_width) / 2.;
717 double y_offset = (h - thumb->img_height) / 2.;
718
719 // Sanitize zoom offsets
720 if(thumb->table && thumb->table->zoom > DT_THUMBTABLE_ZOOM_FIT)
721 {
722 thumb->zoomx = CLAMP(thumb->zoomx, -fabs(x_offset), fabs(x_offset));
723 thumb->zoomy = CLAMP(thumb->zoomy, -fabs(y_offset), fabs(y_offset));
724 }
725 else
726 {
727 thumb->zoomx = 0.;
728 thumb->zoomy = 0.;
729 }
730
731 cairo_set_source_surface(cr, thumb->img_surf, thumb->zoomx + x_offset, thumb->zoomy + y_offset);
732
733 // Paint background with CSS transparency
734 GdkRGBA im_color;
735 GtkStyleContext *context = gtk_widget_get_style_context(thumb->w_image);
736 gtk_style_context_get_color(context, gtk_widget_get_state_flags(thumb->w_image), &im_color);
737 cairo_paint_with_alpha(cr, im_color.alpha);
738
739 // Paint CSS borders
740 gtk_render_frame(context, cr, 0, 0, w, h);
741 cairo_restore(cr);
742 }
743
744 if(!thumb->image_inited || IS_NULL_PTR(thumb->img_surf))
745 {
746 dt_control_draw_busy_msg(cr, w, h);
747 }
749
750 return TRUE;
751}
752
753#define DEBUG 0
754
756{
758 if(IS_NULL_PTR(thumb->widget)) return;
759
760 gboolean show = (thumb->over > DT_THUMBNAIL_OVERLAYS_NONE);
761
762 if(GTK_IS_WIDGET(thumb->w_local_copy))
763 gtk_widget_set_visible(thumb->w_local_copy, (thumb->info.has_localcopy && show) || DEBUG);
764 if(GTK_IS_WIDGET(thumb->w_altered))
765 gtk_widget_set_visible(thumb->w_altered, (dt_thumbtable_info_is_altered(thumb->info) && show) || DEBUG);
766 if(GTK_IS_WIDGET(thumb->w_group))
767 gtk_widget_set_visible(thumb->w_group, (dt_thumbtable_info_is_grouped(thumb->info) && show) || DEBUG);
768 if(GTK_IS_WIDGET(thumb->w_audio))
769 gtk_widget_set_visible(thumb->w_audio, (thumb->info.has_audio && show) || DEBUG);
770 if(GTK_IS_WIDGET(thumb->w_color))
771 gtk_widget_set_visible(thumb->w_color, show || DEBUG);
772 if(GTK_IS_WIDGET(thumb->w_bottom_eb))
773 gtk_widget_set_visible(thumb->w_bottom_eb, show || DEBUG);
774 if(GTK_IS_WIDGET(thumb->w_reject))
775 gtk_widget_set_visible(thumb->w_reject, show || DEBUG);
776 if(GTK_IS_WIDGET(thumb->w_ext))
777 gtk_widget_set_visible(thumb->w_ext, show || DEBUG);
778 if(GTK_IS_WIDGET(thumb->w_cursor))
779 gtk_widget_show(thumb->w_cursor);
780
781 _set_flag(thumb->w_main, GTK_STATE_FLAG_PRELIGHT, thumb->mouse_over);
782 _set_flag(thumb->widget, GTK_STATE_FLAG_PRELIGHT, thumb->mouse_over);
783
784 _set_flag(thumb->w_reject, GTK_STATE_FLAG_ACTIVE, (thumb->info.rating == DT_VIEW_REJECT));
785
786 for(int i = 0; i < MAX_STARS; i++)
787 {
788 if(GTK_IS_WIDGET(thumb->w_stars[i]))
789 gtk_widget_set_visible(thumb->w_stars[i], show || DEBUG);
790 _set_flag(thumb->w_stars[i], GTK_STATE_FLAG_ACTIVE, (thumb->info.rating > i && thumb->info.rating < DT_VIEW_REJECT));
791 }
792
793 _set_flag(thumb->w_group, GTK_STATE_FLAG_ACTIVE, (thumb->info.id == thumb->info.group_id));
794 _set_flag(thumb->w_main, GTK_STATE_FLAG_SELECTED, thumb->selected);
795 _set_flag(thumb->widget, GTK_STATE_FLAG_SELECTED, thumb->selected);
796}
797
798static gboolean _event_main_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
799{
800 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
802 if(!gtk_widget_is_visible(thumb->widget)) return TRUE;
803
804 // Ensure mouse_over_id is set because that's what darkroom uses to open a picture.
805 // NOTE: Duplicate module uses that fucking thumbnail without a table...
806 if(thumb->table)
807 dt_thumbtable_dispatch_over(thumb->table, event->type, thumb->info.id);
808 else
810
811 // raise signal on double click
812 if(event->button == 1 && event->type == GDK_2BUTTON_PRESS)
813 {
814 thumb->dragging = FALSE;
816 return TRUE;
817 }
818 else if(event->button == GDK_BUTTON_SECONDARY && event->type == GDK_BUTTON_PRESS)
819 {
820 GtkWidget *menu = _create_menu(thumb);
821 gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL);
822 return TRUE;
823 }
824
825 return FALSE;
826}
827
828static gboolean _event_main_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
829{
830 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
832 thumb->dragging = FALSE;
833
834 // select on single click only in filemanager mode. Filmstrip mode only raises ACTIVATE signals.
835 if(event->button == 1
836 && thumb->table && thumb->table->mode == DT_THUMBTABLE_MODE_FILEMANAGER)
837 {
838 if(dt_modifier_is(event->state, 0))
840 else if(dt_modifier_is(event->state, DT_PRIMARY_MASK))
842 else if(dt_modifier_is(event->state, GDK_SHIFT_MASK) && thumb->table)
843 dt_thumbtable_select_range(thumb->table, thumb->rowid);
844 // Because selection might include several images, we handle styling globally
845 // in the thumbtable scope, catching the SELECTION_CHANGED signal.
846 return TRUE;
847 }
848 else if(event->button == 1
849 && thumb->table && thumb->table->mode == DT_THUMBTABLE_MODE_FILMSTRIP)
850 {
852 return TRUE;
853 }
854 return FALSE;
855}
856
857static gboolean _event_rating_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
858{
859 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
861 if(thumb->disable_actions) return FALSE;
862 if(dtgtk_thumbnail_btn_is_hidden(widget)) return FALSE;
863
864 if(event->button == 1)
865 {
867 if(widget == thumb->w_reject)
868 rating = DT_VIEW_REJECT;
869 else if(widget == thumb->w_stars[0])
870 rating = DT_VIEW_STAR_1;
871 else if(widget == thumb->w_stars[1])
872 rating = DT_VIEW_STAR_2;
873 else if(widget == thumb->w_stars[2])
874 rating = DT_VIEW_STAR_3;
875 else if(widget == thumb->w_stars[3])
876 rating = DT_VIEW_STAR_4;
877 else if(widget == thumb->w_stars[4])
878 rating = DT_VIEW_STAR_5;
879
880 if(rating != DT_VIEW_DESERT)
881 dt_ratings_apply_on_image(thumb->info.id, rating, TRUE, TRUE, TRUE);
882 }
883 return TRUE;
884}
885
886static gboolean _event_grouping_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
887{
888 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
890 if(thumb->disable_actions) return FALSE;
891 if(dtgtk_thumbnail_btn_is_hidden(widget)) return FALSE;
893 return FALSE;
894}
895
896static gboolean _event_audio_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
897{
898 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
900 if(thumb->disable_actions) return FALSE;
901 if(dtgtk_thumbnail_btn_is_hidden(widget)) return FALSE;
902
903 if(event->button == 1)
904 {
905 gboolean start_audio = TRUE;
907 {
908 // don't start the audio for the image we just killed it for
909 if(dt_view_manager_get_global()->audio.audio_player_id == thumb->info.id) start_audio = FALSE;
911 }
912
913 if(start_audio)
914 {
916 }
917 }
918 return FALSE;
919}
920
921
922
924{
926 if(selected != thumb->selected)
927 {
928 thumb->selected = selected;
929 _thumb_update_icons(thumb);
930 }
931}
932
933
934// All the text info that we don't have room to display around the image
936{
938 gtk_label_set_text(GTK_LABEL(thumb->w_filename), thumb->info.filename);
939 gtk_label_set_text(GTK_LABEL(thumb->w_datetime), thumb->info.datetime);
940 gtk_label_set_text(GTK_LABEL(thumb->w_folder), thumb->info.folder);
941
942 char *exposure_time = dt_util_format_exposure(thumb->info.exif_exposure);
943 char *exposure_field = g_strdup_printf("%.0f ISO - f/%.1f - %s",
944 thumb->info.exif_iso,
945 thumb->info.exif_aperture,
946 exposure_time);
947 char *exposure_bias = g_strdup_printf("%+.1f EV", thumb->info.exif_exposure_bias);
948 char *focal = g_strdup_printf("%0.f mm @ %.2f m", thumb->info.exif_focal_length,
950
951 gtk_label_set_text(GTK_LABEL(thumb->w_exposure_bias), exposure_bias);
952 gtk_label_set_text(GTK_LABEL(thumb->w_exposure), exposure_field);
953 gtk_label_set_text(GTK_LABEL(thumb->w_camera), thumb->info.camera_makermodel);
954 gtk_label_set_text(GTK_LABEL(thumb->w_lens), thumb->info.exif_lens);
955 gtk_label_set_text(GTK_LABEL(thumb->w_focal), focal);
956
957 dt_free(focal);
958 dt_free(exposure_bias);
959 dt_free(exposure_field);
960 dt_free(exposure_time);
961}
962
963
965{
967 if(thumb->alternative_mode == enable) return;
968 thumb->alternative_mode = enable;
969 if(enable)
970 {
971 gtk_widget_set_no_show_all(thumb->w_alternative, FALSE);
972 gtk_widget_show_all(thumb->w_alternative);
973 }
974 else
975 {
976 gtk_widget_set_no_show_all(thumb->w_alternative, TRUE);
977 gtk_widget_hide(thumb->w_alternative);
978 }
979 gtk_widget_queue_draw(thumb->widget);
980}
981
982
983static gboolean _event_star_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
984{
985 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
987 if(thumb->disable_actions) return TRUE;
988 _set_flag(thumb->w_bottom_eb, GTK_STATE_FLAG_PRELIGHT, TRUE);
989
990 // we prelight all stars before the current one
991 gboolean pre = TRUE;
992 for(int i = 0; i < MAX_STARS; i++)
993 {
994 _set_flag(thumb->w_stars[i], GTK_STATE_FLAG_PRELIGHT, pre);
995
996 // We don't want the active state to overlap the prelight one because
997 // it makes the feature hard to read/understand.
998 _set_flag(thumb->w_stars[i], GTK_STATE_FLAG_ACTIVE, FALSE);
999
1000 if(thumb->w_stars[i] == widget) pre = FALSE;
1001 }
1002 return TRUE;
1003}
1004
1005
1006static gboolean _event_star_leave(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
1007{
1008 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1010 if(thumb->disable_actions) return TRUE;
1011
1012 for(int i = 0; i < MAX_STARS; i++)
1013 {
1014 _set_flag(thumb->w_stars[i], GTK_STATE_FLAG_PRELIGHT, FALSE);
1015
1016 // restore active state
1017 _set_flag(thumb->w_stars[i], GTK_STATE_FLAG_ACTIVE, i < thumb->info.rating && thumb->info.rating < DT_VIEW_REJECT);
1018 }
1019 return TRUE;
1020}
1021
1022
1023gboolean _event_expose(GtkWidget *self, cairo_t *cr, gpointer user_data)
1024{
1025 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1027 return FALSE;
1028}
1029
1030static gboolean _event_main_motion(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
1031{
1032 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1034 if(!gtk_widget_is_visible(thumb->widget)) return TRUE;
1035 if(!thumb->mouse_over)
1036 {
1037 // Thumbnails send leave-notify when in the thumbnail frame but over the image.
1038 // If we lost the mouse-over in this case, grab it again from mouse motion.
1039 // Be conservative with sending mouse_over_id events/signal because many
1040 // places in the soft listen to them and refresh stuff from DB, so it's expensive.
1041 if(thumb->table)
1042 dt_thumbtable_dispatch_over(thumb->table, event->type, thumb->info.id);
1043 else
1045
1047 }
1048 return FALSE;
1049}
1050
1051static gboolean _event_main_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
1052{
1053 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1055 if(!gtk_widget_is_visible(thumb->widget)) return TRUE;
1056
1057 if(thumb->table)
1058 dt_thumbtable_dispatch_over(thumb->table, event->type, thumb->info.id);
1059 else
1061
1063 return FALSE;
1064}
1065
1066static gboolean _event_main_leave(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
1067{
1068 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1070 if(!gtk_widget_is_visible(thumb->widget)) return TRUE;
1071
1072 if(thumb->table)
1073 dt_thumbtable_dispatch_over(thumb->table, event->type, -1);
1074 else
1076
1078 return FALSE;
1079}
1080
1081// lazy-load the history tooltip only when mouse enters the button
1082static gboolean _altered_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
1083{
1084 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1087 {
1089 if(tooltip)
1090 {
1091 gtk_widget_set_tooltip_text(thumb->w_altered, tooltip);
1093 }
1094 }
1095 return FALSE;
1096}
1097
1098
1099static gboolean _group_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
1100{
1101 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1104 return FALSE;
1105}
1106
1107
1108static gboolean _event_image_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1109{
1110 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1112
1113 if(event->button == 1 && thumb->table && thumb->table->zoom > DT_THUMBTABLE_ZOOM_FIT)
1114 {
1115 thumb->dragging = TRUE;
1116 thumb->drag_x_start = event->x;
1117 thumb->drag_y_start = event->y;
1118 }
1119
1120 return FALSE;
1121}
1122
1123static gboolean _event_image_motion(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
1124{
1125 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1127 if(thumb->dragging)
1128 {
1129 const double delta_x = event->x - thumb->drag_x_start;
1130 const double delta_y = event->y - thumb->drag_y_start;
1131 const gboolean global_shift = dt_modifier_is(event->state, GDK_SHIFT_MASK) && thumb->table;
1132
1133 if(global_shift)
1134 {
1135 // Offset all thumbnails by this amount
1136 dt_thumbtable_offset_zoom(thumb->table, delta_x, delta_y);
1137 }
1138 else
1139 {
1140 // Offset only the current thumbnail
1141 thumb->zoomx += delta_x;
1142 thumb->zoomy += delta_y;
1143 }
1144
1145 // Reset drag origin
1146 thumb->drag_x_start = event->x;
1147 thumb->drag_y_start = event->y;
1148
1149 if(!global_shift)
1150 gtk_widget_queue_draw(thumb->w_image);
1151
1152 return TRUE;
1153 }
1154 return FALSE;
1155}
1156
1157static gboolean _event_image_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1158{
1159 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1161 thumb->dragging = FALSE;
1162 return FALSE;
1163}
1164
1166{
1167 // Let the background event box capture all user events from its children first,
1168 // so we don't have to wire leave/enter events to all of them individually.
1169 // Children buttons will mostly only use button pressed/released events
1170 thumb->widget = gtk_event_box_new();
1171 dt_gui_add_class(thumb->widget, "thumb-cell");
1172 gtk_widget_set_events(thumb->widget, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK | GDK_POINTER_MOTION_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
1173
1174 // this is only here to ensure that mouse-over value is updated correctly
1175 // all dragging actions take place inside thumbatble.c
1176 gtk_drag_dest_set(thumb->widget, GTK_DEST_DEFAULT_MOTION, target_list_all, n_targets_all, GDK_ACTION_MOVE);
1177 g_object_set_data(G_OBJECT(thumb->widget), "thumb", thumb);
1178 gtk_widget_show(thumb->widget);
1179
1180 g_signal_connect(G_OBJECT(thumb->widget), "button-press-event", G_CALLBACK(_event_main_press), thumb);
1181 g_signal_connect(G_OBJECT(thumb->widget), "button-release-event", G_CALLBACK(_event_main_release), thumb);
1182 g_signal_connect(G_OBJECT(thumb->widget), "enter-notify-event", G_CALLBACK(_event_main_enter), thumb);
1183 g_signal_connect(G_OBJECT(thumb->widget), "leave-notify-event", G_CALLBACK(_event_main_leave), thumb);
1184 g_signal_connect(G_OBJECT(thumb->widget), "motion-notify-event", G_CALLBACK(_event_main_motion), thumb);
1185 g_signal_connect(G_OBJECT(thumb->widget), "draw", G_CALLBACK(_event_expose), thumb);
1186
1187 // Main widget
1188 thumb->w_main = gtk_overlay_new();
1189 dt_gui_add_class(thumb->w_main, "thumb-main");
1190 gtk_widget_set_valign(thumb->w_main, GTK_ALIGN_CENTER);
1191 gtk_widget_set_halign(thumb->w_main, GTK_ALIGN_CENTER);
1192 gtk_container_add(GTK_CONTAINER(thumb->widget), thumb->w_main);
1193 gtk_widget_show(thumb->w_main);
1194
1195 thumb->w_background = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1196 dt_gui_add_class(thumb->w_background, "thumb-background");
1197 gtk_widget_set_valign(thumb->w_background, GTK_ALIGN_FILL);
1198 gtk_widget_set_halign(thumb->w_background, GTK_ALIGN_FILL);
1199 gtk_overlay_add_overlay(GTK_OVERLAY(thumb->w_main), thumb->w_background);
1200 gtk_widget_show(thumb->w_background);
1201 gtk_overlay_set_overlay_pass_through(GTK_OVERLAY(thumb->w_main), thumb->w_background, TRUE);
1202
1203 // triangle to indicate current image(s) in filmstrip
1204 thumb->w_cursor = gtk_drawing_area_new();
1205 dt_gui_add_class(thumb->w_cursor, "thumb-cursor");
1206 gtk_widget_set_valign(thumb->w_cursor, GTK_ALIGN_START);
1207 gtk_widget_set_halign(thumb->w_cursor, GTK_ALIGN_CENTER);
1208 g_signal_connect(G_OBJECT(thumb->w_cursor), "draw", G_CALLBACK(_event_cursor_draw), thumb);
1209 gtk_overlay_add_overlay(GTK_OVERLAY(thumb->w_main), thumb->w_cursor);
1210
1211 // the image drawing area
1212 thumb->w_image = gtk_drawing_area_new();
1213 dt_gui_add_class(thumb->w_image, "thumb-image");
1214 gtk_widget_set_valign(thumb->w_image, GTK_ALIGN_CENTER);
1215 gtk_widget_set_halign(thumb->w_image, GTK_ALIGN_CENTER);
1216 gtk_widget_set_events(thumb->w_image, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK);
1217 g_signal_connect(G_OBJECT(thumb->w_image), "draw", G_CALLBACK(_thumb_draw_image), thumb);
1218 g_signal_connect(G_OBJECT(thumb->w_image), "button-press-event", G_CALLBACK(_event_image_press), thumb);
1219 g_signal_connect(G_OBJECT(thumb->w_image), "button-release-event", G_CALLBACK(_event_image_release), thumb);
1220 g_signal_connect(G_OBJECT(thumb->w_image), "motion-notify-event", G_CALLBACK(_event_image_motion), thumb);
1221 gtk_widget_show(thumb->w_image);
1222 gtk_overlay_add_overlay(GTK_OVERLAY(thumb->w_main), thumb->w_image);
1223 gtk_overlay_set_overlay_pass_through(GTK_OVERLAY(thumb->w_main), thumb->w_image, TRUE);
1224
1225 thumb->w_bottom_eb = gtk_event_box_new();
1226 gtk_widget_set_valign(thumb->w_bottom_eb, GTK_ALIGN_END);
1227 gtk_widget_set_halign(thumb->w_bottom_eb, GTK_ALIGN_FILL);
1228 gtk_widget_show(thumb->w_bottom_eb);
1229 gtk_overlay_add_overlay(GTK_OVERLAY(thumb->w_main), thumb->w_bottom_eb);
1230
1231 GtkWidget *bottom_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1232 dt_gui_add_class(bottom_box, "thumb-bottom");
1233 gtk_container_add(GTK_CONTAINER(thumb->w_bottom_eb), bottom_box);
1234 gtk_widget_show(bottom_box);
1235
1236 // the reject icon
1238 dt_gui_add_class(thumb->w_reject, "thumb-reject");
1239 gtk_widget_set_valign(thumb->w_reject, GTK_ALIGN_CENTER);
1240 gtk_widget_set_halign(thumb->w_reject, GTK_ALIGN_START);
1241 gtk_widget_show(thumb->w_reject);
1242 g_signal_connect(G_OBJECT(thumb->w_reject), "button-release-event", G_CALLBACK(_event_rating_release), thumb);
1243 gtk_box_pack_start(GTK_BOX(bottom_box), thumb->w_reject, FALSE, FALSE, 0);
1244
1245 GtkWidget *stars_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1246 gtk_box_pack_start(GTK_BOX(bottom_box), stars_box, TRUE, TRUE, 0);
1247 gtk_widget_set_valign(stars_box, GTK_ALIGN_CENTER);
1248 gtk_widget_set_halign(stars_box, GTK_ALIGN_CENTER);
1249 gtk_widget_set_hexpand(stars_box, TRUE);
1250 gtk_widget_show(stars_box);
1251
1252 // the stars
1253 for(int i = 0; i < MAX_STARS; i++)
1254 {
1256 g_signal_connect(G_OBJECT(thumb->w_stars[i]), "enter-notify-event", G_CALLBACK(_event_star_enter), thumb);
1257 g_signal_connect(G_OBJECT(thumb->w_stars[i]), "leave-notify-event", G_CALLBACK(_event_star_leave), thumb);
1258 g_signal_connect(G_OBJECT(thumb->w_stars[i]), "button-release-event", G_CALLBACK(_event_rating_release),
1259 thumb);
1260 dt_gui_add_class(thumb->w_stars[i], "thumb-star");
1261 gtk_widget_set_valign(thumb->w_stars[i], GTK_ALIGN_CENTER);
1262 gtk_widget_set_halign(thumb->w_stars[i], GTK_ALIGN_CENTER);
1263 gtk_widget_show(thumb->w_stars[i]);
1264 gtk_box_pack_start(GTK_BOX(stars_box), thumb->w_stars[i], FALSE, FALSE, 0);
1265 }
1266
1267 // the color labels
1269 dt_gui_add_class(thumb->w_color, "thumb-colorlabels");
1270 gtk_widget_set_valign(thumb->w_color, GTK_ALIGN_CENTER);
1271 gtk_widget_set_halign(thumb->w_color, GTK_ALIGN_END);
1272 gtk_widget_set_no_show_all(thumb->w_color, TRUE);
1273 gtk_box_pack_start(GTK_BOX(bottom_box), thumb->w_color, FALSE, FALSE, 0);
1274
1275 thumb->w_top_eb = gtk_event_box_new();
1276 gtk_widget_set_valign(thumb->w_top_eb, GTK_ALIGN_START);
1277 gtk_widget_set_halign(thumb->w_top_eb, GTK_ALIGN_FILL);
1278 gtk_widget_show(thumb->w_top_eb);
1279 gtk_overlay_add_overlay(GTK_OVERLAY(thumb->w_main), thumb->w_top_eb);
1280
1281 GtkWidget *top_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1282 dt_gui_add_class(top_box, "thumb-top");
1283 gtk_container_add(GTK_CONTAINER(thumb->w_top_eb), top_box);
1284 gtk_widget_show(top_box);
1285
1286 // the file extension label
1287 thumb->w_ext = gtk_label_new("");
1288 dt_gui_add_class(thumb->w_ext, "thumb-ext");
1289 gtk_widget_set_valign(thumb->w_ext, GTK_ALIGN_CENTER);
1290 gtk_widget_show(thumb->w_ext);
1291 gtk_box_pack_start(GTK_BOX(top_box), thumb->w_ext, FALSE, FALSE, 0);
1292
1293 // the local copy indicator
1295 dt_gui_add_class(thumb->w_local_copy, "thumb-localcopy");
1296 gtk_widget_set_tooltip_text(thumb->w_local_copy, _("This picture is locally copied on your disk cache"));
1297 gtk_widget_set_valign(thumb->w_local_copy, GTK_ALIGN_CENTER);
1298 gtk_widget_set_no_show_all(thumb->w_local_copy, TRUE);
1299 gtk_box_pack_start(GTK_BOX(top_box), thumb->w_local_copy, FALSE, FALSE, 0);
1300
1301 // the altered icon
1303 g_signal_connect(G_OBJECT(thumb->w_altered), "enter-notify-event", G_CALLBACK(_altered_enter), thumb);
1304 dt_gui_add_class(thumb->w_altered, "thumb-altered");
1305 gtk_widget_set_valign(thumb->w_altered, GTK_ALIGN_CENTER);
1306 gtk_widget_set_no_show_all(thumb->w_altered, TRUE);
1307 gtk_box_pack_end(GTK_BOX(top_box), thumb->w_altered, FALSE, FALSE, 0);
1308
1309 // the group bouton
1311 dt_gui_add_class(thumb->w_group, "thumb-group");
1312 g_signal_connect(G_OBJECT(thumb->w_group), "button-release-event", G_CALLBACK(_event_grouping_release), thumb);
1313 g_signal_connect(G_OBJECT(thumb->w_group), "enter-notify-event", G_CALLBACK(_group_enter), thumb);
1314 gtk_widget_set_valign(thumb->w_group, GTK_ALIGN_CENTER);
1315 gtk_widget_set_no_show_all(thumb->w_group, TRUE);
1316 gtk_box_pack_end(GTK_BOX(top_box), thumb->w_group, FALSE, FALSE, 0);
1317
1318 // the sound icon
1320 dt_gui_add_class(thumb->w_audio, "thumb-audio");
1321 g_signal_connect(G_OBJECT(thumb->w_audio), "button-release-event", G_CALLBACK(_event_audio_release), thumb);
1322 gtk_widget_set_valign(thumb->w_audio, GTK_ALIGN_CENTER);
1323 gtk_widget_set_no_show_all(thumb->w_audio, TRUE);
1324 gtk_box_pack_end(GTK_BOX(top_box), thumb->w_audio, FALSE, FALSE, 0);
1325
1326 thumb->w_alternative = gtk_overlay_new();
1327 gtk_overlay_add_overlay(GTK_OVERLAY(thumb->w_main), thumb->w_alternative);
1328 gtk_widget_set_halign(thumb->w_alternative, GTK_ALIGN_FILL);
1329 gtk_widget_set_valign(thumb->w_alternative, GTK_ALIGN_FILL);
1330 gtk_widget_hide(thumb->w_alternative);
1331
1332 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1333 gtk_container_add(GTK_CONTAINER(thumb->w_alternative), box);
1334 dt_gui_add_class(box, "thumb-alternative");
1335
1336 GtkWidget *bbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1337 gtk_widget_set_valign(bbox, GTK_ALIGN_START);
1338 gtk_box_pack_start(GTK_BOX(box), bbox, TRUE, TRUE, 0);
1339 thumb->w_filename = gtk_label_new("");
1340 gtk_label_set_ellipsize(GTK_LABEL(thumb->w_filename), PANGO_ELLIPSIZE_MIDDLE);
1341 gtk_box_pack_start(GTK_BOX(bbox), thumb->w_filename, FALSE, FALSE, 0);
1342 thumb->w_datetime = gtk_label_new("");
1343 gtk_box_pack_start(GTK_BOX(bbox), thumb->w_datetime, FALSE, FALSE, 0);
1344 thumb->w_folder = gtk_label_new("");
1345 gtk_label_set_ellipsize(GTK_LABEL(thumb->w_folder), PANGO_ELLIPSIZE_MIDDLE);
1346 gtk_box_pack_start(GTK_BOX(bbox), thumb->w_folder, FALSE, FALSE, 0);
1347
1348 bbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1349 gtk_widget_set_valign(bbox, GTK_ALIGN_CENTER);
1350 gtk_box_pack_start(GTK_BOX(box), bbox, TRUE, TRUE, 0);
1351 thumb->w_exposure = gtk_label_new("");
1352 gtk_box_pack_start(GTK_BOX(bbox), thumb->w_exposure, FALSE, FALSE, 0);
1353 thumb->w_exposure_bias = gtk_label_new("");
1354 gtk_box_pack_start(GTK_BOX(bbox), thumb->w_exposure_bias, FALSE, FALSE, 0);
1355
1356 bbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1357 gtk_widget_set_valign(bbox, GTK_ALIGN_END);
1358 gtk_box_pack_start(GTK_BOX(box), bbox, TRUE, TRUE, 0);
1359 thumb->w_camera = gtk_label_new("");
1360 gtk_box_pack_start(GTK_BOX(bbox), thumb->w_camera, FALSE, FALSE, 0);
1361 thumb->w_lens = gtk_label_new("");
1362 gtk_label_set_ellipsize(GTK_LABEL(thumb->w_lens), PANGO_ELLIPSIZE_MIDDLE);
1363 gtk_box_pack_start(GTK_BOX(bbox), thumb->w_lens, FALSE, FALSE, 0);
1364 thumb->w_focal = gtk_label_new("");
1365 gtk_box_pack_start(GTK_BOX(bbox), thumb->w_focal, FALSE, FALSE, 0);
1366 gtk_widget_set_no_show_all(thumb->w_alternative, TRUE);
1367
1368 return thumb->widget;
1369}
1370
1372{
1373 if(IS_NULL_PTR(thumb) || IS_NULL_PTR(info)) return;
1374
1375 dt_thumbtable_copy_image(&thumb->info, info);
1376
1377 if(!thumb->widget || IS_NULL_PTR(thumb->w_main)) return;
1378
1380 _thumb_update_icons(thumb);
1383
1384 if(thumb->w_color)
1385 {
1387 btn->icon_flags = thumb->info.color_labels;
1388 }
1389}
1390
1392{
1393 dt_thumbnail_t *thumb = calloc(1, sizeof(dt_thumbnail_t));
1394
1395 thumb->rowid = rowid;
1396 thumb->over = over;
1397 thumb->table = table;
1398 thumb->zoomx = 0.;
1399 thumb->zoomy = 0.;
1400 thumb->job = NULL;
1401 thumb->img_h = 0;
1402 thumb->img_w = 0;
1404 dt_atomic_set_int(&thumb->ref_count, 1);
1405
1406 dt_pthread_mutex_init(&thumb->lock, NULL);
1407
1409
1410 dt_thumbnail_resync_info(thumb, info);
1412
1413 // This will then only run on "selection_changed" event
1415
1416 return thumb;
1417}
1418
1420{
1421 if(IS_NULL_PTR(thumb)) return 0;
1422
1424
1425 // Unregister the thumbnail from the parent table before any callback can
1426 // pick it again through the LUT or imgid hash during view/selection updates.
1427 if(thumb->table)
1428 {
1430 if(g_hash_table_lookup(thumb->table->list, GINT_TO_POINTER(thumb->info.id)) == thumb)
1431 g_hash_table_remove(thumb->table->list, GINT_TO_POINTER(thumb->info.id));
1432
1433 if(thumb->table->lut)
1434 for(int rowid = 0; rowid < thumb->table->collection_count; rowid++)
1435 if(thumb->table->lut[rowid].thumb == thumb)
1436 thumb->table->lut[rowid].thumb = NULL;
1437
1439 }
1440
1441 // Detach the thumbnail from Gtk immediately, but keep it alive until any queued
1442 // background rendering job gets cancelled and disposed by the job queue.
1443 dt_pthread_mutex_lock(&thumb->lock);
1444
1445 thumb->job = NULL;
1446
1447 // remove multiple delayed gtk_widget_queue_draw triggers
1448 while(g_idle_remove_by_data(thumb->widget))
1449 ;
1450 while(g_idle_remove_by_data(thumb->w_image))
1451 ;
1452
1453 if(thumb->img_surf && cairo_surface_get_reference_count(thumb->img_surf) > 0)
1454 cairo_surface_destroy(thumb->img_surf);
1455 thumb->img_surf = NULL;
1456
1457 if(thumb->widget)
1458 {
1459 GtkWidget *parent = gtk_widget_get_parent(thumb->widget);
1460 if(parent && GTK_IS_CONTAINER(parent))
1461 gtk_container_remove(GTK_CONTAINER(parent), thumb->widget);
1462 }
1463 thumb->widget = NULL;
1464 thumb->w_main = NULL;
1465 thumb->w_image = NULL;
1466 thumb->w_cursor = NULL;
1467 thumb->w_bottom_eb = NULL;
1468 thumb->w_reject = NULL;
1469 thumb->w_color = NULL;
1470 thumb->w_ext = NULL;
1471 thumb->w_local_copy = NULL;
1472 thumb->w_altered = NULL;
1473 thumb->w_group = NULL;
1474 thumb->w_audio = NULL;
1475 thumb->w_top_eb = NULL;
1476 thumb->w_alternative = NULL;
1477 thumb->w_filename = NULL;
1478 thumb->w_datetime = NULL;
1479 thumb->w_folder = NULL;
1480 thumb->w_exposure = NULL;
1481 thumb->w_exposure_bias = NULL;
1482 thumb->w_camera = NULL;
1483 thumb->w_lens = NULL;
1484 thumb->w_focal = NULL;
1485 for(int i = 0; i < MAX_STARS; i++) thumb->w_stars[i] = NULL;
1486
1488
1489 _thumbnail_release(thumb);
1490
1491 return 0;
1492}
1493
1495{
1496 thumb_return_if_fails(thumb);
1498 if(GTK_IS_WIDGET(thumb->w_color))
1499 {
1501 btn->icon_flags = thumb->info.color_labels;
1502 }
1504 _thumb_update_icons(thumb);
1506}
1507
1509{
1510 thumb_return_if_fails(thumb);
1511 thumb->over = mode;
1512}
1513
1514// if update, the internal width and height, minus margins and borders, are written back in input
1515void _widget_set_size(GtkWidget *w, int *parent_width, int *parent_height, const gboolean update)
1516{
1517 GtkStateFlags state = gtk_widget_get_state_flags(w);
1518 GtkStyleContext *context = gtk_widget_get_style_context(w);
1519
1520 GtkBorder margins;
1521 gtk_style_context_get_margin(context, state, &margins);
1522
1523 int width = *parent_width - margins.left - margins.right;
1524 int height = *parent_height - margins.top - margins.bottom;
1525
1526 if(width > 0 && height > 0)
1527 {
1528 gtk_widget_set_size_request(w, width, height);
1529
1530 // unvisible widgets need to be allocated to be able to measure the size of flexible boxes.
1531 GtkAllocation alloc = { .x = 0, .y = 0, .width = width, .height = height };
1532 gtk_widget_size_allocate(w, &alloc);
1533 }
1534
1535 if(update)
1536 {
1537 *parent_width = width;
1538 *parent_height = height;
1539 }
1540}
1541
1542
1544{
1545 thumb_return_if_fails(thumb, 0);
1546
1547 // we need to squeeze reject + space + stars + space + colorlabels icons on a thumbnail width
1548 // that means a width of 4 + MAX_STARS icons size
1549 // all icons and spaces having a width of 2 * r1
1550 // inner margins are defined in css (margin_* values)
1551
1552 // retrieves the size of the main icons in the top panel, thumbtable overlays shall not exceed that
1553 const int n_widgets = 4 + MAX_STARS;
1554 // The width-based term goes NEGATIVE once the thumbnail is narrower than the icon row can fit
1555 // ((n_widgets - 1) * spacing, ~80px at default EM). A negative icon_size feeds -1 into the
1556 // widgets' size_request, which GTK reads as "use natural size" -> overlays balloon (huge
1557 // extension text) and the bar-height subtraction below collapses the image to a clipped sliver.
1558 // Clamp to 0 so the overlays instead degrade gracefully (shrink to nothing) on tiny filmstrip
1559 // thumbnails while the image keeps scaling. Above the threshold nothing changes.
1560 const float r1 = fmaxf(0.f, fminf(1.25 * DT_GUI_EM_SIZE / 2.,
1561 (float)(width - (n_widgets - 1) * DT_GUI_BOX_SPACING) / (2. * n_widgets)));
1562 int icon_size = roundf(2 * r1);
1563
1564 // reject icon
1565 gtk_widget_set_size_request(thumb->w_reject, icon_size, icon_size);
1566
1567 // stars
1568 for(int i = 0; i < MAX_STARS; i++)
1569 gtk_widget_set_size_request(thumb->w_stars[i], icon_size, icon_size);
1570
1571 // the color labels
1572 gtk_widget_set_size_request(thumb->w_color, icon_size, icon_size);
1573
1574 // the local copy indicator
1575 _set_flag(thumb->w_local_copy, GTK_STATE_FLAG_ACTIVE, FALSE);
1576 gtk_widget_set_size_request(thumb->w_local_copy, icon_size, icon_size);
1577
1578 // the altered icon
1579 gtk_widget_set_size_request(thumb->w_altered, icon_size, icon_size);
1580
1581 // the group bouton
1582 gtk_widget_set_size_request(thumb->w_group, icon_size, icon_size);
1583
1584 // the sound icon
1585 gtk_widget_set_size_request(thumb->w_audio, icon_size, icon_size);
1586
1587 // the filmstrip cursor
1588 gtk_widget_set_size_request(thumb->w_cursor, 6.0 * r1, 1.5 * r1);
1589
1590 // extension text
1591 PangoAttrList *attrlist = pango_attr_list_new();
1592 PangoAttribute *attr = pango_attr_size_new_absolute(icon_size * PANGO_SCALE * 0.9);
1593 pango_attr_list_insert(attrlist, attr);
1594 gtk_label_set_attributes(GTK_LABEL(thumb->w_ext), attrlist);
1595 pango_attr_list_unref(attrlist);
1596
1597 return icon_size;
1598}
1599
1600// This function is called only from the thumbtable, when the grid size changed.
1601// NOTE: thumb->widget is a grid cell. It should not get styled, especially not with margins/padding.
1602// Styling starts at thumb->w_main, aka .thumb-main in CSS, which gets centered in the grid cell.
1603// Overlays need to be set prior to calling this function because they can change internal sizings.
1604// It is expected that this function is called only when needed, that is if the size requirements actually
1605// changed, meaning this check needs to be done upstream because we internally nuke the image surface on every call.
1607{
1608 thumb_return_if_fails(thumb);
1609 //fprintf(stdout, "calling resize on %i with overlay %i\n", thumb->info.id, thumb->over);
1610
1611 if(width < 1 || height < 1) return;
1612
1613 // widget resizing
1614 thumb->width = width;
1615 thumb->height = height;
1617
1618 // Apply margins & borders on the main widget
1620
1621 // Update show/hide status for overlays now, because we pack them in boxes
1622 // so the children need to be sized before their parents for the boxes to have proper size.
1623 gtk_widget_show_all(thumb->widget);
1624 _thumb_update_icons(thumb);
1625
1626 // Proceed with overlays resizing
1627 int icon_size = _thumb_resize_overlays(thumb, width, height);
1628
1629 // Finish with updating the image size
1631 {
1632 // Persistent overlays shouldn't overlap with image, so resize it.
1633 // Use the bars' preferred (natural) height, not their allocated height: the bars were
1634 // allocated above (via w_main) with the PREVIOUS icon sizes, and _thumb_resize_overlays()
1635 // only just applied the new icon size_requests, so the allocated heights still lag by one
1636 // resize step (and are 0 before the first allocation at startup). The natural height reflects
1637 // the icon size_requests we just set, with no settled allocation required - this is what made
1638 // the filmstrip overlays compute on outdated sizes during a panel drag resize.
1639 int margin_bottom = 0, margin_top = 0;
1640 gtk_widget_get_preferred_height(thumb->w_bottom_eb, NULL, &margin_bottom);
1641 gtk_widget_get_preferred_height(thumb->w_top_eb, NULL, &margin_top);
1642 height -= 2 * MAX(MAX(margin_top, margin_bottom), icon_size);
1643 // In case top and bottom bars of overlays have different sizes,
1644 // we resize symetrically to the largest.
1645 }
1647
1649}
1650
1652{
1653 thumb_return_if_fails(thumb);
1654
1655 if(border == DT_THUMBNAIL_BORDER_NONE)
1656 {
1657 dt_gui_remove_class(thumb->widget, "dt_group_left");
1658 dt_gui_remove_class(thumb->widget, "dt_group_top");
1659 dt_gui_remove_class(thumb->widget, "dt_group_right");
1660 dt_gui_remove_class(thumb->widget, "dt_group_bottom");
1662 return;
1663 }
1664 if(border & DT_THUMBNAIL_BORDER_LEFT)
1665 dt_gui_add_class(thumb->widget, "dt_group_left");
1666 if(border & DT_THUMBNAIL_BORDER_TOP)
1667 dt_gui_add_class(thumb->widget, "dt_group_top");
1668 if(border & DT_THUMBNAIL_BORDER_RIGHT)
1669 dt_gui_add_class(thumb->widget, "dt_group_right");
1670 if(border & DT_THUMBNAIL_BORDER_BOTTOM)
1671 dt_gui_add_class(thumb->widget, "dt_group_bottom");
1672
1673 thumb->group_borders |= border;
1674}
1675
1677{
1678 thumb_return_if_fails(thumb);
1679
1680 if(thumb->mouse_over == over) return;
1681 thumb->mouse_over = over;
1682 if(thumb->table) thumb->table->rowid = thumb->rowid;
1683
1684 _set_flag(thumb->widget, GTK_STATE_FLAG_PRELIGHT, thumb->mouse_over);
1685 _set_flag(thumb->w_bottom_eb, GTK_STATE_FLAG_PRELIGHT, thumb->mouse_over);
1686 _set_flag(thumb->w_main, GTK_STATE_FLAG_PRELIGHT, thumb->mouse_over);
1687
1688 _thumb_update_icons(thumb);
1689}
1690
1691// set if the thumbnail should react (mouse_over) to drag and drop
1692// note that it's just cosmetic as dropping occurs in thumbtable in any case
1693void dt_thumbnail_set_drop(dt_thumbnail_t *thumb, gboolean accept_drop)
1694{
1695 thumb_return_if_fails(thumb);
1696
1697 if(accept_drop)
1698 gtk_drag_dest_set(thumb->w_main, GTK_DEST_DEFAULT_MOTION, target_list_all, n_targets_all, GDK_ACTION_MOVE);
1699 else
1700 gtk_drag_dest_unset(thumb->w_main);
1701}
1702
1703// Apply new mipmap on thumbnail
1705{
1706 thumb_return_if_fails(thumb, G_SOURCE_REMOVE);
1707
1708 dt_pthread_mutex_lock(&thumb->lock);
1709 // Cancel any in-flight render job. It was started for the old state (stale size or data).
1710 // The job checks thumb->job == job under the same lock before committing; clearing it here
1711 // makes that check fail so the old result is discarded and a fresh job can be queued.
1712 // NOTE: the orphaned job is deliberately NOT dt_control_job_cancel()ed here: jobs are not
1713 // refcounted, so a captured pointer can belong to a job being disposed concurrently (the
1714 // state hook clears thumb->job during dispose) and cancelling it would race the free. The
1715 // orphan drains on its own: it early-returns on the thumb->job != job gate, and if the
1716 // replacement request collides with it in dt_control_add_job()'s duplicate check, the state
1717 // hook re-arms the widget so the request is simply retried on the next expose.
1718 thumb->job = NULL;
1719 thumb->image_inited = FALSE;
1721
1722 // Queue redraw on the drawing area itself: it's the widget that requests/regenerates the cairo surface.
1723 // Queueing only the parent overlay may not invalidate the drawing area's window, leaving stale (too small)
1724 // cached surfaces until some pointer event happens.
1725 if(thumb->w_image) gtk_widget_queue_draw(thumb->w_image);
1726 if(thumb->w_main) gtk_widget_queue_draw(thumb->w_main);
1727 return G_SOURCE_REMOVE;
1728}
1729
1730// clang-format off
1731// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1732// vim: shiftwidth=2 expandtab tabstop=2 cindent
1733// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1734// clang-format on
Handle default and user-set shortcuts (accelerators)
#define DT_PRIMARY_MASK
GtkWidget * dt_gui_main_window(void)
int scrolled(struct dt_iop_module_t *self, double x, double y, int up, uint32_t state)
Definition ashift.c:4949
#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)
int dt_atomic_get_int(dt_atomic_int *var)
int dt_atomic_sub_int(dt_atomic_int *var, int decr)
int dt_atomic_add_int(dt_atomic_int *var, int incr)
#define m
Definition basecurve.c:283
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
void dt_colorlabels_toggle_label_on_list(GList *list, const int color, const gboolean undo_on)
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
void dt_control_draw_busy_msg(cairo_t *cr, int width, int height)
Definition control.c:530
void dt_control_set_mouse_over_id(int32_t value)
Definition control.c:994
struct dt_control_t * dt_control_get_global(void)
Definition darktable.c:651
struct dt_view_manager_t * dt_view_manager_get_global(void)
Definition darktable.c:599
GHashTable * selected
set of checked row labels, mirrored to conf on every change
GtkWidget * view
the tree view
static const GtkTargetEntry target_list_all[]
static const guint n_targets_all
const int res
Definition dtpthread.h:351
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_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:117
void dt_thumbtable_offset_zoom(dt_thumbtable_t *table, const double delta_x, const double delta_y)
static void dt_focus_draw_clusters(cairo_t *cr, int width, int height, int32_t imgid, int buffer_width, int buffer_height, dt_focus_cluster_t *focus, int frows, int fcols, float full_zoom, float full_x, float full_y)
Definition focus.h:228
static void dt_focus_create_clusters(dt_focus_cluster_t *focus, int frows, int fcols, uint8_t *buffer, int buffer_width, int buffer_height)
Definition focus.h:138
int dt_focuspeaking(cairo_t *cr, uint8_t *const restrict image, const int buf_width, const int buf_height, gboolean draw, float *x, float *y)
GdkRGBA color[]
Definition geotagging.c:541
int dt_grouping_change_representative(const int32_t image_id)
Definition grouping.c:108
static gboolean enable(const dt_image_t *image)
Definition highlights.c:879
char * dt_history_get_items_as_string(const int32_t imgid)
void dt_history_repository_foreach_active_module(const int32_t imgid, dt_history_repository_active_module_cb cb, void *user_data)
Every module an image has enabled, once each, in the order they run.
const char flag
Definition image.h:311
const char * tooltip
Definition image.h:310
dt_view_image_over_t
What a thumbnail shows over an image: its rating, or one of the badge states.
Definition image.h:347
@ DT_VIEW_STAR_4
Definition image.h:353
@ DT_VIEW_REJECT
Definition image.h:355
@ DT_VIEW_STAR_5
Definition image.h:354
@ DT_VIEW_STAR_3
Definition image.h:352
@ DT_VIEW_STAR_1
Definition image.h:350
@ DT_VIEW_STAR_2
Definition image.h:351
@ DT_VIEW_DESERT
Definition image.h:349
void dt_image_group_member_free(gpointer data)
Free one dt_image_group_member_t. Suits g_list_free_full().
GList * dt_image_repository_get_group_member_rows(const int32_t group_id)
Every member of group_id, in row order, named. Free with g_list_free_full(list, dt_image_group_member...
Reading and writing one dt_image_t to and from the library database. The SQL half of what used to be ...
int dt_imageio_large_thumbnail(const char *filename, uint8_t **buffer, int32_t *th_width, int32_t *th_height, dt_colorspaces_color_profile_type_t *color_space, const int width, const int height)
Load the thumbnail embedded into a RAW file having at least the size MAX(width, height) x MAX(width,...
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_state_callback(_dt_job_t *job, dt_job_state_change_callback cb)
Definition jobs.c:167
void dt_control_job_set_params(_dt_job_t *job, void *params, dt_job_destroy_callback callback)
Definition jobs.c:114
void dt_control_job_dispose(_dt_job_t *job)
Definition jobs.c:155
@ DT_JOB_QUEUE_SYSTEM_FG
Definition jobs.h:55
dt_job_state_t
Definition jobs.h:42
@ DT_JOB_STATE_DISCARDED
Definition jobs.h:48
@ DT_JOB_STATE_DISPOSED
Definition jobs.h:49
@ DT_JOB_STATE_CANCELLED
Definition jobs.h:47
void dt_gui_textview_set_padding(GtkTextView *textview)
Apply the standard recessed-input text padding to a GtkTextView.
Definition label.c:149
dt_map_box_t bbox
Definition location.c:4
@ DT_DEBUG_LIGHTTABLE
Definition logging.h:60
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(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_colorspaces_color_profile_type_t color_space
Definition mipmap_cache.c:5
#define dt_pixelpipe_cache_free_align(mem)
void dt_preview_window_spawn(const int32_t imgid)
dt_colorspaces_color_profile_type_t
void dt_ratings_apply_on_image(const int32_t imgid, const int rating, const gboolean single_star_toggle, const gboolean undo_on, const gboolean group_on)
Definition ratings.c:216
struct dt_selection_t * dt_selection_get_global(void)
Definition selection.c:80
gboolean dt_selection_is_id_selected(struct dt_selection_t *selection, int32_t imgid)
Definition selection.c:404
void dt_selection_select_single(dt_selection_t *selection, int32_t imgid)
Definition selection.c:297
void dt_selection_toggle(dt_selection_t *selection, int32_t imgid)
Definition selection.c:305
#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_VIEWMANAGER_THUMBTABLE_ACTIVATE
Definition signal.h:98
@ DT_SIGNAL_VIEWMANAGER_FILMSTRIP_ACTIVATE
This signal is raised when a thumb is single-clicked in the filmstrip. Views that want filmstrip clic...
Definition signal.h:106
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
const float uint32_t state[4]
One member of a group, with what a tooltip needs to name it.
gboolean has_audio
Definition image.h:476
gboolean is_hdr
Definition image.h:479
float exif_exposure
Definition image.h:367
float exif_focus_distance
Definition image.h:372
gboolean is_bw
Definition image.h:477
int32_t group_id
Definition image.h:401
char camera_makermodel[128]
Definition image.h:382
float exif_exposure_bias
Definition image.h:368
float exif_iso
Definition image.h:370
float exif_aperture
Definition image.h:369
float exif_focal_length
Definition image.h:371
int rating
Definition image.h:473
gboolean has_localcopy
Definition image.h:475
char exif_lens[128]
Definition image.h:376
char folder[DT_PATH_MAX]
Definition image.h:390
int color_labels
Definition image.h:472
char datetime[200]
Definition image.h:392
char filename[DT_MAX_FILENAME_LEN]
Definition image.h:386
int32_t id
Definition image.h:401
char fullpath[DT_PATH_MAX]
Definition image.h:387
gboolean is_bw_flow
Definition image.h:478
gboolean mouse_over
Definition thumbnail.h:74
GtkWidget * w_stars[5]
Definition thumbnail.h:91
GtkWidget * w_color
Definition thumbnail.h:92
dt_pthread_mutex_t lock
Definition thumbnail.h:131
GtkWidget * w_alternative
Definition thumbnail.h:100
GtkWidget * w_local_copy
Definition thumbnail.h:95
GtkWidget * w_folder
Definition thumbnail.h:129
gboolean dragging
Definition thumbnail.h:114
gboolean disable_actions
Definition thumbnail.h:105
GtkWidget * w_exposure_bias
Definition thumbnail.h:123
dt_image_t info
Definition thumbnail.h:77
dt_atomic_int ref_count
Definition thumbnail.h:134
GtkWidget * w_ext
Definition thumbnail.h:83
GtkWidget * w_datetime
Definition thumbnail.h:126
dt_atomic_int destroying
Definition thumbnail.h:133
GtkWidget * w_camera
Definition thumbnail.h:124
GtkWidget * w_main
Definition thumbnail.h:81
gboolean selected
Definition thumbnail.h:75
dt_thumbnail_border_t group_borders
Definition thumbnail.h:102
struct _dt_job_t * job
Definition thumbnail.h:132
int32_t rowid
Definition thumbnail.h:69
GtkWidget * w_group
Definition thumbnail.h:97
gboolean image_inited
Definition thumbnail.h:119
GtkWidget * w_exposure
Definition thumbnail.h:122
gboolean alternative_mode
Definition thumbnail.h:121
GtkWidget * w_audio
Definition thumbnail.h:98
GtkWidget * w_focal
Definition thumbnail.h:128
GtkWidget * w_background
Definition thumbnail.h:82
GtkWidget * w_top_eb
Definition thumbnail.h:94
GtkWidget * w_reject
Definition thumbnail.h:90
GtkWidget * w_lens
Definition thumbnail.h:127
double drag_x_start
Definition thumbnail.h:112
struct dt_thumbtable_t * table
Definition thumbnail.h:116
double drag_y_start
Definition thumbnail.h:113
GtkWidget * w_image
Definition thumbnail.h:85
dt_thumbnail_overlay_t over
Definition thumbnail.h:107
cairo_surface_t * img_surf
Definition thumbnail.h:86
GtkWidget * widget
Definition thumbnail.h:80
GtkWidget * w_altered
Definition thumbnail.h:96
GtkWidget * w_bottom_eb
Definition thumbnail.h:89
GtkWidget * w_cursor
Definition thumbnail.h:88
GtkWidget * w_filename
Definition thumbnail.h:125
dt_thumbnail_t * thumb
Definition thumbtable.h:90
GHashTable * list
Definition thumbtable.h:118
gboolean focus_peaking
Definition thumbtable.h:189
dt_thumbtable_mode_t mode
Definition thumbtable.h:103
dt_pthread_mutex_t lock
Definition thumbtable.h:168
dt_thumbtable_zoom_t zoom
Definition thumbtable.h:185
gboolean focus_regions
Definition thumbtable.h:188
dt_thumbtable_cache_t * lut
Definition thumbtable.h:152
int32_t audio_player_id
Definition view.h:208
typedef double((*spd)(unsigned long int wavelength, double TempK))
#define MAX(a, b)
Definition thinplate.c:29
static gboolean _event_audio_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition thumbnail.c:896
static void _free_image_surface(dt_thumbnail_t *thumb)
Definition thumbnail.c:349
static gboolean _event_image_motion(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
Definition thumbnail.c:1123
void dt_thumbnail_set_drop(dt_thumbnail_t *thumb, gboolean accept_drop)
Definition thumbnail.c:1693
static void _color_label_callback(GtkWidget *widget, dt_thumbnail_t *thumb)
Definition thumbnail.c:197
gboolean _event_expose(GtkWidget *self, cairo_t *cr, gpointer user_data)
Definition thumbnail.c:1023
#define thumb_return_if_fails(thumb,...)
Definition thumbnail.c:78
static void _thumbnail_free(dt_thumbnail_t *thumb)
Definition thumbnail.c:357
dt_thumbnail_t * dt_thumbnail_new(int rowid, dt_thumbnail_overlay_t over, dt_thumbtable_t *table, dt_image_t *info)
Definition thumbnail.c:1391
static GtkWidget * _menuitem_from_text(const char *label, const char *value, GtkWidget *menu, void(*activate_callback)(GtkWidget *widget, dt_thumbnail_t *thumb), dt_thumbnail_t *thumb)
Definition thumbnail.c:187
static gboolean _event_star_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition thumbnail.c:983
static gboolean _main_context_queue_draw(GtkWidget *widget)
Definition thumbnail.c:424
void dt_thumbnail_set_mouseover(dt_thumbnail_t *thumb, gboolean over)
Definition thumbnail.c:1676
static gboolean _event_main_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition thumbnail.c:798
static void _image_update_group_tooltip(dt_thumbnail_t *thumb)
Definition thumbnail.c:91
static GtkWidget * _gtk_menu_item_new_with_markup(const char *label, GtkWidget *menu, void(*activate_callback)(GtkWidget *widget, dt_thumbnail_t *thumb), dt_thumbnail_t *thumb)
Definition thumbnail.c:171
static void _thumb_update_icons(dt_thumbnail_t *thumb)
Definition thumbnail.c:755
static gboolean _thumb_draw_image(GtkWidget *widget, cairo_t *cr, gpointer user_data)
Definition thumbnail.c:691
GtkWidget * dt_thumbnail_create_widget(dt_thumbnail_t *thumb)
Definition thumbnail.c:1165
static gboolean _event_cursor_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
Definition thumbnail.c:328
static void _thumbnail_release(void *data)
Definition thumbnail.c:366
static GtkWidget * _create_menu(dt_thumbnail_t *thumb)
Definition thumbnail.c:269
static gboolean _event_main_leave(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition thumbnail.c:1066
static gboolean _event_star_leave(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition thumbnail.c:1006
static gboolean _event_main_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition thumbnail.c:1051
static void _thumb_update_rating_class(dt_thumbnail_t *thumb)
Definition thumbnail.c:142
static gboolean _event_grouping_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition thumbnail.c:886
void _create_alternative_view(dt_thumbnail_t *thumb)
Definition thumbnail.c:935
void dt_thumbnail_resync_info(dt_thumbnail_t *thumb, const dt_image_t *const info)
Definition thumbnail.c:1371
static gboolean _event_rating_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition thumbnail.c:857
void dt_thumbnail_set_overlay(dt_thumbnail_t *thumb, dt_thumbnail_overlay_t mode)
Definition thumbnail.c:1508
static void _active_module_row(void *user_data, const int num, const char *op, const char *multi)
Definition thumbnail.c:210
static void _set_flag(GtkWidget *w, GtkStateFlags flag, gboolean activate)
Definition thumbnail.c:81
void dt_thumbnail_set_group_border(dt_thumbnail_t *thumb, dt_thumbnail_border_t border)
Definition thumbnail.c:1651
static gboolean _event_image_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition thumbnail.c:1157
int dt_thumbnail_destroy(dt_thumbnail_t *thumb)
Definition thumbnail.c:1419
int dt_thumbnail_image_refresh_real(dt_thumbnail_t *thumb)
Definition thumbnail.c:1704
int32_t _get_image_buffer(dt_job_t *job)
Definition thumbnail.c:470
void dt_thumbnail_update_gui(dt_thumbnail_t *thumb)
Definition thumbnail.c:1494
void dt_thumbnail_update_selection(dt_thumbnail_t *thumb, gboolean selected)
Definition thumbnail.c:923
static gboolean _altered_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition thumbnail.c:1082
#define DEBUG
Definition thumbnail.c:753
static void _thumb_job_state_changed(dt_job_t *job, dt_job_state_t state)
Definition thumbnail.c:390
static gboolean _event_main_motion(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
Definition thumbnail.c:1030
static gboolean _event_image_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition thumbnail.c:1108
static gboolean _group_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition thumbnail.c:1099
void dt_thumbnail_alternative_mode(dt_thumbnail_t *thumb, gboolean enable)
Definition thumbnail.c:964
static gboolean _event_main_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition thumbnail.c:828
static void _thumb_write_extension(dt_thumbnail_t *thumb)
Definition thumbnail.c:156
static int _finish_buffer_thread(dt_thumbnail_t *thumb, dt_job_t *job, gboolean success)
Definition thumbnail.c:434
static void _preview_window_open(GtkWidget *widget, dt_thumbnail_t *thumb)
Definition thumbnail.c:205
static int _thumb_resize_overlays(dt_thumbnail_t *thumb, int width, int height)
Definition thumbnail.c:1543
void _widget_set_size(GtkWidget *w, int *parent_width, int *parent_height, const gboolean update)
Definition thumbnail.c:1515
void dt_thumbnail_resize(dt_thumbnail_t *thumb, int width, int height)
Definition thumbnail.c:1606
int dt_thumbnail_get_image_buffer(dt_thumbnail_t *thumb)
Definition thumbnail.c:618
static void _active_modules_popup(GtkWidget *widget, dt_thumbnail_t *thumb)
Definition thumbnail.c:221
#define MAX_STARS
Definition thumbnail.h:45
dt_thumbnail_border_t
Definition thumbnail.h:50
@ DT_THUMBNAIL_BORDER_BOTTOM
Definition thumbnail.h:55
@ DT_THUMBNAIL_BORDER_NONE
Definition thumbnail.h:51
@ DT_THUMBNAIL_BORDER_TOP
Definition thumbnail.h:53
@ DT_THUMBNAIL_BORDER_RIGHT
Definition thumbnail.h:54
@ DT_THUMBNAIL_BORDER_LEFT
Definition thumbnail.h:52
dt_thumbnail_overlay_t
Definition thumbnail.h:59
@ DT_THUMBNAIL_OVERLAYS_ALWAYS_NORMAL
Definition thumbnail.h:62
@ DT_THUMBNAIL_OVERLAYS_NONE
Definition thumbnail.h:60
GtkWidget * dtgtk_thumbnail_btn_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
gboolean dtgtk_thumbnail_btn_is_hidden(GtkWidget *widget)
gboolean dt_thumbtable_get_thumbnail_info(dt_thumbtable_t *table, int32_t imgid, dt_image_t *out)
Definition thumbtable.c:606
void dt_thumbtable_dispatch_over(dt_thumbtable_t *table, GdkEventType type, int32_t imgid)
Update the mouse-over image ID with conflict resolution.
void dt_thumbtable_select_range(dt_thumbtable_t *table, const int rowid)
Select a range of images in the collection.
A widget to manage and display image thumbnails in Ansel's lighttable and filmstrip views.
@ DT_THUMBTABLE_MODE_FILMSTRIP
Definition thumbtable.h:66
@ DT_THUMBTABLE_MODE_FILEMANAGER
Definition thumbtable.h:65
@ DT_THUMBTABLE_ZOOM_FIT
Definition thumbtable.h:77
void dt_thumbtable_copy_image(dt_image_t *info, const dt_image_t *const img)
static gboolean dt_thumbtable_info_is_grouped(const dt_image_t info)
static gboolean dt_thumbtable_info_is_altered(const dt_image_t info)
char * dt_util_format_exposure(const float exposuretime)
Definition utility.c:868
gchar * dt_util_dstrcat(gchar *str, const gchar *format,...)
Definition utility.c:99
void dt_view_audio_start(dt_view_manager_t *vm, int32_t imgid)
Definition view.c:1417
void dt_view_audio_stop(dt_view_manager_t *vm)
Definition view.c:1449
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
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_surface_value_t
Definition view.h:104
@ DT_VIEW_SURFACE_OK
Definition view.h:105
static gboolean dt_modifier_is(GdkModifierType state, const GdkModifierType desired_modifier_mask)
#define DT_GUI_BOX_SPACING
#define DT_GUI_EM_SIZE
void dt_gui_remove_class(GtkWidget *widget, const gchar *class_name)
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
void dtgtk_cairo_paint_local_copy(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_audio(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_label_flower(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_reject(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_star(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_altered(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_grouping(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)