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 "dtgtk/thumbnail.h"
42#include "dtgtk/thumbtable.h"
44
45#include "bauhaus/bauhaus.h"
46#include "common/collection.h"
47#include "common/datetime.h"
48#include "common/darktable.h"
49#include "common/debug.h"
50#include "common/focus.h"
52#include "common/grouping.h"
53#include "common/image_cache.h"
54#include "common/database.h"
55#include "common/ratings.h"
56#include "common/selection.h"
57#include "common/variables.h"
58#include "control/control.h"
59#include "dtgtk/button.h"
60#include "dtgtk/icon.h"
62#include "dtgtk/thumbnail_btn.h"
63#include "gui/drag_and_drop.h"
64
65#include "views/view.h"
66
67#include <glib-object.h>
68#include <sqlite3.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
114 sqlite3_stmt *stmt;
115 // clang-format off
117 "SELECT id, version, filename"
118 " FROM main.images"
119 " WHERE group_id = ?1", -1, &stmt,
120 NULL);
121 // clang-format on
122 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, thumb->info.group_id);
123 while(sqlite3_step(stmt) == SQLITE_ROW)
124 {
125 nb++;
126 const int id = sqlite3_column_int(stmt, 0);
127 const int v = sqlite3_column_int(stmt, 1);
128
129 if(id != thumb->info.group_id)
130 {
131 if(id == thumb->info.id)
132 tt = dt_util_dstrcat(tt, "\n\u2022 %s", _("current"));
133 else
134 {
135 tt = dt_util_dstrcat(tt, "\n\u2022 %s", sqlite3_column_text(stmt, 2));
136 if(v > 0) tt = dt_util_dstrcat(tt, " v%d", v);
137 }
138 }
139 }
140 sqlite3_finalize(stmt);
141
142 // and the number of grouped images
143 gchar *ttf = g_strdup_printf("%d %s\n%s", nb, _("grouped images"), tt);
144 dt_free(tt);
145
146 // let's apply the tooltip
147 gtk_widget_set_tooltip_markup(thumb->w_group, ttf);
148 dt_free(ttf);
149}
150
152{
154 for(int i = DT_VIEW_DESERT; i <= DT_VIEW_REJECT; i++)
155 {
156 gchar *cn = g_strdup_printf("dt_thumbnail_rating_%d", i);
157 if(thumb->info.rating == i)
158 dt_gui_add_class(thumb->w_main, cn);
159 else
160 dt_gui_remove_class(thumb->w_main, cn);
161 dt_free(cn);
162 }
163}
164
166{
167 // fill the file extension label
169 if(!thumb->info.filename[0]) return;
170 const char *ext = thumb->info.filename + strlen(thumb->info.filename);
171 while(ext > thumb->info.filename && *ext != '.') ext--;
172 ext++;
173 gchar *uext = dt_view_extend_modes_str(ext, thumb->info.is_hdr, thumb->info.is_bw, thumb->info.is_bw_flow);
174 gchar *label = g_strdup_printf("%s #%i", uext, thumb->rowid + 1);
175 gtk_label_set_text(GTK_LABEL(thumb->w_ext), label);
176 dt_free(uext);
177 dt_free(label);
178}
179
180static GtkWidget *_gtk_menu_item_new_with_markup(const char *label, GtkWidget *menu,
181 void (*activate_callback)(GtkWidget *widget,
182 dt_thumbnail_t *thumb),
183 dt_thumbnail_t *thumb)
184{
185 GtkWidget *menu_item = gtk_menu_item_new_with_label("");
186 GtkWidget *child = gtk_bin_get_child(GTK_BIN(menu_item));
187 gtk_label_set_markup(GTK_LABEL(child), label);
188 gtk_menu_item_set_reserve_indicator(GTK_MENU_ITEM(menu_item), FALSE);
189 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
190
191 if(activate_callback) g_signal_connect(G_OBJECT(menu_item), "activate", G_CALLBACK(activate_callback), thumb);
192
193 return menu_item;
194}
195
196static GtkWidget *_menuitem_from_text(const char *label, const char *value, GtkWidget *menu,
197 void (*activate_callback)(GtkWidget *widget, dt_thumbnail_t *thumb),
198 dt_thumbnail_t *thumb)
199{
200 gchar *text = g_strdup_printf("%s%s", label, value);
201 GtkWidget *menu_item = _gtk_menu_item_new_with_markup(text, menu, activate_callback, thumb);
202 dt_free(text);
203 return menu_item;
204}
205
207{
208 int color = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "custom-data"));
209 GList *imgs = g_list_append(NULL, GINT_TO_POINTER(thumb->info.id));
211 g_list_free(imgs);
212}
213
215{
217}
218
220{
221 (void)widget;
222 if(IS_NULL_PTR(thumb)) return;
223
224 sqlite3 *handle = dt_database_get(darktable.db);
225 if(IS_NULL_PTR(handle)) return;
226
227 static const char *sql =
228 "SELECT MIN(num) AS num, operation, multi_name "
229 "FROM main.history "
230 "WHERE imgid = ?1 AND enabled = 1 "
231 "GROUP BY operation, multi_name "
232 "ORDER BY MIN(num) ASC";
233
234 sqlite3_stmt *stmt = NULL;
235 DT_DEBUG_SQLITE3_PREPARE_V2(handle, sql, -1, &stmt, NULL);
236 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, thumb->info.id);
237
238 GString *text = g_string_new(NULL);
239 g_string_append_printf(text, "image id: %d\nfile: %s\n\n", thumb->info.id,
240 (thumb->info.fullpath[0]) ? thumb->info.fullpath : thumb->info.filename);
241 while(sqlite3_step(stmt) == SQLITE_ROW)
242 {
243 const int num = sqlite3_column_int(stmt, 0);
244 const char *op = (const char *)sqlite3_column_text(stmt, 1);
245 const char *multi = (const char *)sqlite3_column_text(stmt, 2);
246 const gboolean has_multi = (multi && multi[0] && strcmp(multi, " "));
247
248 if(has_multi)
249 g_string_append_printf(text, "%d. %s (%s)\n", num, op ? op : "?", multi);
250 else
251 g_string_append_printf(text, "%d. %s\n", num, op ? op : "?");
252 }
253 sqlite3_finalize(stmt);
254
255 if(text->len == 0) g_string_assign(text, _("No active modules"));
256
257 // Use the real application window as transient parent, not the popup menu toplevel
258 // (setting the menu's toplevel as parent triggers GTK parent warnings, since it is
259 // unmapped by the time this dialog shows). Without a transient parent, GTK/the window
260 // manager has nothing to return focus to when the dialog closes.
261 GtkWidget *main_window = dt_ui_main_window(darktable.gui->ui);
262 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("Active modules"),
263 GTK_IS_WINDOW(main_window) ? GTK_WINDOW(main_window) : NULL,
264 GTK_DIALOG_DESTROY_WITH_PARENT,
265 _("_Close"), GTK_RESPONSE_CLOSE,
266 NULL);
267 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
268
269 GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
270 GtkWidget *scrolled = gtk_scrolled_window_new(NULL, NULL);
271 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
272 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
273 gtk_widget_set_size_request(scrolled, 420, 260);
274 gtk_container_set_border_width(GTK_CONTAINER(scrolled), 4);
275
276 GtkWidget *view = gtk_text_view_new();
277 dt_gui_textview_set_padding(GTK_TEXT_VIEW(view));
278 gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE);
279 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), FALSE);
280 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD_CHAR);
281 gtk_text_view_set_monospace(GTK_TEXT_VIEW(view), TRUE);
282 gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(view)), text->str, -1);
283 gtk_container_add(GTK_CONTAINER(scrolled), view);
284
285 gtk_box_pack_start(GTK_BOX(content), scrolled, TRUE, TRUE, 0);
286 gtk_widget_show_all(dialog);
287
288 g_signal_connect(dialog, "response", G_CALLBACK(gtk_widget_destroy), NULL);
289
290 g_string_free(text, TRUE);
291}
292
294{
295 // Always re-create the menu when we show it because we don't bother updating info during the lifetime of the thumbnail
296 GtkWidget *menu = gtk_menu_new();
297
298 // Filename: insensitive header to mean that the context menu is for this picture only
299 GtkWidget *menu_item = _gtk_menu_item_new_with_markup(thumb->info.filename, menu, NULL, thumb);
300 gtk_widget_set_sensitive(menu_item, FALSE);
301
302 GtkWidget *sep = gtk_separator_menu_item_new();
303 gtk_menu_shell_append(GTK_MENU_SHELL(menu), sep);
304
306 menu_item = _gtk_menu_item_new_with_markup(_("Image info"), menu, NULL, thumb);
307 GtkWidget *sub_menu = gtk_menu_new();
308 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), sub_menu);
309
310 _menuitem_from_text(_("Folder : "), thumb->info.folder, sub_menu, NULL, thumb);
311 _menuitem_from_text(_("Date : "), thumb->info.datetime, sub_menu, NULL, thumb);
312 _menuitem_from_text(_("Camera : "), thumb->info.camera_makermodel, sub_menu, NULL, thumb);
313 _menuitem_from_text(_("Lens : "), thumb->info.exif_lens, sub_menu, NULL, thumb);
314
315 sep = gtk_separator_menu_item_new();
316 gtk_menu_shell_append(GTK_MENU_SHELL(menu), sep);
317
319 menu_item = _gtk_menu_item_new_with_markup(_("Assign color labels"), menu, NULL, thumb);
320 sub_menu = gtk_menu_new();
321 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), sub_menu);
322
323 menu_item = _gtk_menu_item_new_with_markup("<span foreground='#BB2222'>\342\254\244</span> Red", sub_menu, _color_label_callback, thumb);
324 g_object_set_data(G_OBJECT(menu_item), "custom-data", GINT_TO_POINTER(0));
325
326 menu_item = _gtk_menu_item_new_with_markup("<span foreground='#BBBB22'>\342\254\244</span> Yellow", sub_menu, _color_label_callback, thumb);
327 g_object_set_data(G_OBJECT(menu_item), "custom-data", GINT_TO_POINTER(1));
328
329 menu_item = _gtk_menu_item_new_with_markup("<span foreground='#22BB22'>\342\254\244</span> Green", sub_menu, _color_label_callback, thumb);
330 g_object_set_data(G_OBJECT(menu_item), "custom-data", GINT_TO_POINTER(2));
331
332 menu_item = _gtk_menu_item_new_with_markup("<span foreground='#2222BB'>\342\254\244</span> Blue", sub_menu, _color_label_callback, thumb);
333 g_object_set_data(G_OBJECT(menu_item), "custom-data", GINT_TO_POINTER(3));
334
335 menu_item = _gtk_menu_item_new_with_markup("<span foreground='#BB22BB'>\342\254\244</span> Purple", sub_menu, _color_label_callback, thumb);
336 g_object_set_data(G_OBJECT(menu_item), "custom-data", GINT_TO_POINTER(4));
337
338 menu_item = _gtk_menu_item_new_with_markup(_("Open in preview window…"), menu, _preview_window_open, thumb);
339 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
340
341 sep = gtk_separator_menu_item_new();
342 gtk_menu_shell_append(GTK_MENU_SHELL(menu), sep);
343
344 menu_item = _gtk_menu_item_new_with_markup(_("Show active modules…"), menu, _active_modules_popup, thumb);
345 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
346
347 gtk_widget_show_all(menu);
348
349 return menu;
350}
351
352static gboolean _event_cursor_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
353{
354 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
356
357 GtkStateFlags state = gtk_widget_get_state_flags(thumb->w_cursor);
358 GtkStyleContext *context = gtk_widget_get_style_context(thumb->w_cursor);
359 GdkRGBA col;
360 gtk_style_context_get_color(context, state, &col);
361
362 cairo_set_source_rgba(cr, col.red, col.green, col.blue, col.alpha);
363 cairo_line_to(cr, gtk_widget_get_allocated_width(widget), 0);
364 cairo_line_to(cr, gtk_widget_get_allocated_width(widget) / 2, gtk_widget_get_allocated_height(widget));
365 cairo_line_to(cr, 0, 0);
366 cairo_close_path(cr);
367 cairo_fill(cr);
368
369 return TRUE;
370}
371
372
374{
375 if(thumb->img_surf && cairo_surface_get_reference_count(thumb->img_surf) > 0)
376 cairo_surface_destroy(thumb->img_surf);
377
378 thumb->img_surf = NULL;
379}
380
382{
383 if(IS_NULL_PTR(thumb)) return;
384
385 _free_image_surface(thumb);
387 dt_free(thumb);
388}
389
390static void _thumbnail_release(void *data)
391{
392 dt_thumbnail_t *thumb = (dt_thumbnail_t *)data;
393 if(IS_NULL_PTR(thumb)) return;
394
395 if(dt_atomic_sub_int(&thumb->ref_count, 1) == 1)
396 _thumbnail_free(thumb);
397}
398
399static gboolean _main_context_queue_draw(GtkWidget *widget)
400{
401 if(GTK_IS_WIDGET(widget))
402 {
403 gtk_widget_queue_draw(widget);
404 }
405
406 return G_SOURCE_REMOVE;
407}
408
409static int _finish_buffer_thread(dt_thumbnail_t *thumb, gboolean success)
410{
411 thumb_return_if_fails(thumb, 1);
412
414 thumb->image_inited = success;
415 thumb->job = NULL;
417
418 // Redraw events need to be sent from the main GUI thread
419 // though we may not have a target widget anymore...
420 if(!dt_atomic_get_int(&thumb->destroying) && thumb->w_image)
421 {
422 GMainContext *context = g_main_context_default();
423 g_main_context_invoke_full(context, G_PRIORITY_DEFAULT,
424 (GSourceFunc)_main_context_queue_draw,
425 g_object_ref(thumb->w_image),
426 (GDestroyNotify)g_object_unref);
427 g_main_context_wakeup(context);
428 }
429
430 return 0;
431}
432
433
435{
436 // WARNING: the target thumbnail GUI widget can be destroyed at any time during this
437 // control flow in the GUI mainthread.
439 thumb_return_if_fails(thumb, 1);
440 if(dt_atomic_get_int(&thumb->destroying)) return 1;
441
442 // The job was cancelled on the queue. Good chances of having thumb destroyed anytime soon.
443 if(IS_NULL_PTR(thumb->job) || thumb->job != job || dt_control_job_get_state(job) == DT_JOB_STATE_CANCELLED) return 1;
444
445 // Read and cache the thumb data now, while we have it. And lock it.
447
448 // These are the sizes of the widget bounding box
449 int img_w = thumb->img_w;
450 int img_h = thumb->img_h;
451
452 const int zoom = (thumb->table) ? thumb->table->zoom : DT_THUMBTABLE_ZOOM_FIT;
453 const gboolean show_focus_peaking = (thumb->table && thumb->table->focus_peaking);
454 const gboolean show_focus_clusters = (thumb->table && thumb->table->focus_regions);
455 const gboolean zoom_in = (thumb->table && thumb->table->zoom > DT_THUMBTABLE_ZOOM_FIT);
456 const int32_t imgid = thumb->info.id;
457
459
460 // These are the sizes of the actual image. Can be larger than the widget bounding box.
461 int img_width = 0;
462 int img_height = 0;
463
464 double zoomx = 0.;
465 double zoomy = 0.;
466 float x_center = 0.f;
467 float y_center = 0.f;
468
469 // From there, never read thumb->... directly since it might get destroyed in mainthread anytime.
470 dt_print(DT_DEBUG_LIGHTTABLE, "[lighttable] fetching or computing thumbnail %i\n", thumb->info.id);
471
472 // Get the actual image content. This typically triggers a rendering pipeline,
473 // and can possibly take a long time.
474 cairo_surface_t *surface = NULL;
475
476 // Thumbnail generation (mipmap fetch + pipeline render) is instrumented inside
477 // dt_view_image_get_surface(), where the real mip level is known.
478 dt_view_surface_value_t res = dt_view_image_get_surface(imgid, img_w, img_h, &surface, zoom);
479
480 if(surface && res == DT_VIEW_SURFACE_OK)
481 {
482 // The image is immediately available
483 img_width = cairo_image_surface_get_width(surface);
484 img_height = cairo_image_surface_get_height(surface);
485 }
486 else
487 {
489 return 0;
490 }
491
492 if(zoom > DT_THUMBTABLE_ZOOM_FIT || show_focus_peaking)
493 {
494 // Note: we compute the "sharpness density" unconditionnaly if the image is zoomed-in
495 // in order to get the details barycenter to init centering.
496 // Actual density are drawn only if the focus peaking mode is enabled.
497 cairo_t *cri = cairo_create(surface);
498 unsigned char *rgbbuf = cairo_image_surface_get_data(surface);
499 if(rgbbuf)
500 {
501 if(dt_focuspeaking(cri, rgbbuf, img_width, img_height, show_focus_peaking, &x_center, &y_center) != 0)
502 {
503 cairo_destroy(cri);
504 return 1;
505 }
506 }
507 cairo_destroy(cri);
508
509 // Init the zoom offset using the barycenter of details, to center
510 // the zoomed-in image on content that matters: details.
511 // Offset is expressed from the center of the image
512 if(zoom_in && x_center > 0.f && y_center > 0.f && thumb)
513 {
514 zoomx = (double)img_width / 2. - x_center;
515 zoomy = (double)img_height / 2. - y_center;
516 }
517 }
518
519 // if needed we compute and draw here the big rectangle to show focused areas
520 if(show_focus_clusters)
521 {
522 uint8_t *full_res_thumb = NULL;
523 int32_t full_res_thumb_wd, full_res_thumb_ht;
525 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))
526 {
527 // we look for focus areas
528 dt_focus_cluster_t full_res_focus[49];
529 const int frows = 5, fcols = 5;
530 dt_focus_create_clusters(full_res_focus, frows, fcols, full_res_thumb, full_res_thumb_wd,
531 full_res_thumb_ht);
532 // and we draw them on the image
533 cairo_t *cri = cairo_create(surface);
534 dt_focus_draw_clusters(cri, img_width, img_height, imgid, full_res_thumb_wd,
535 full_res_thumb_ht, full_res_focus, frows, fcols, 1.0, 0, 0);
536 cairo_destroy(cri);
537 }
538 dt_pixelpipe_cache_free_align(full_res_thumb);
539 }
540
541 // Commit the rendered surface only if the thumb is still live and expects this specific render.
542 // All validity checks and the surface write happen inside a single locked section to eliminate
543 // TOCTOU races with dt_thumbnail_destroy (which nulls widget pointers under the same lock)
544 // and dt_thumbnail_image_refresh_real (which clears thumb->job under the same lock to cancel
545 // stale renders from before a size change or data invalidation).
546 double sx = 1.0, sy = 1.0;
547 cairo_surface_get_device_scale(surface, &sx, &sy);
548
550 const gboolean still_valid = (thumb->job == job // not cancelled by refresh or destroy
551 && !dt_atomic_get_int(&thumb->destroying)
552 && thumb->w_image != NULL);
553 if(still_valid)
554 {
555 _free_image_surface(thumb);
556 thumb->img_width = roundf(img_width / sx);
557 thumb->img_height = roundf(img_height / sy);
558 thumb->zoomx = zoomx / sx;
559 thumb->zoomy = zoomy / sy;
560 thumb->img_surf = surface;
561 surface = NULL; // ownership transferred to thumb
562 }
564
565 if(surface)
566 {
567 cairo_surface_destroy(surface);
568 return 1;
569 }
570
572 return 0;
573}
574
576{
577 thumb_return_if_fails(thumb, 1);
578
579 // Avoid spawning multiple background jobs for the same thumbnail.
580 // Re-scheduling here is counter-productive because each new job replaces thumb->job and makes
581 // previously queued/running jobs exit early (thumb->job != job), which can lead to endless
582 // "busy" redraws without ever painting an image.
584 const gboolean job_running = (!IS_NULL_PTR(thumb->job));
586 if(job_running) return 0;
587
588 // - image inited: the cached buffer has a valid size. Invalid this flag when size changes.
589 // - img_surf: we have a cached buffer (cairo surface), regardless of its validity.
590 // - a rendering job has already been started
591 if((thumb->image_inited && thumb->img_surf && cairo_surface_get_reference_count(thumb->img_surf) > 0))
592 return 0;
593
594 // Get thumbnail GUI allocated size now (in GUI mainthread).
595 // Size requests may be unset (-1) during initial layout while allocations are already valid.
596 int img_w = gtk_widget_get_allocated_width(thumb->w_image);
597 int img_h = gtk_widget_get_allocated_height(thumb->w_image);
598 if(img_w < 2 || img_h < 2)
599 gtk_widget_get_size_request(thumb->w_image, &img_w, &img_h);
600
601 // Not allocated yet: wait for the next draw once the widget has a real size.
602 if(img_w < 2 || img_h < 2) return 0;
603
604 img_w = MAX(img_w, 32);
605 img_h = MAX(img_h, 32);
606
608 thumb->img_w = img_w;
609 thumb->img_h = img_h;
611
612 // Drawing the focus peaking and doing the color conversions
613 // can be expensive on large thumbnails. Do it in a background job,
614 // so the thumbtable stays responsive.
615 dt_job_t *job = dt_control_job_create(&_get_image_buffer, "get image %i", thumb->info.id);
616 if(IS_NULL_PTR(job)) return 1;
617
619 // Re-check now that we are about to publish the job pointer.
620 if(thumb->job || dt_atomic_get_int(&thumb->destroying))
621 {
624 return 0;
625 }
626 thumb->job = job;
627 dt_atomic_add_int(&thumb->ref_count, 1);
629
632 {
634 if(thumb->job == job) thumb->job = NULL;
636 _thumbnail_release(thumb);
637 return 1;
638 }
639
640 return 0;
641}
642
643
644
645static gboolean
646_thumb_draw_image(GtkWidget *widget, cairo_t *cr, gpointer user_data)
647{
648 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
650
651 int w = gtk_widget_get_allocated_width(widget);
652 int h = gtk_widget_get_allocated_height(widget);
653 if(w < 2 || h < 2) return TRUE;
654
663
664 dt_print(DT_DEBUG_LIGHTTABLE, "[lighttable] redrawing thumbnail %i\n", thumb->info.id);
665
667 if(thumb->img_surf && cairo_surface_get_reference_count(thumb->img_surf) > 0)
668 {
669 // we draw the image
670 cairo_save(cr);
671 double x_offset = (w - thumb->img_width) / 2.;
672 double y_offset = (h - thumb->img_height) / 2.;
673
674 // Sanitize zoom offsets
675 if(thumb->table && thumb->table->zoom > DT_THUMBTABLE_ZOOM_FIT)
676 {
677 thumb->zoomx = CLAMP(thumb->zoomx, -fabs(x_offset), fabs(x_offset));
678 thumb->zoomy = CLAMP(thumb->zoomy, -fabs(y_offset), fabs(y_offset));
679 }
680 else
681 {
682 thumb->zoomx = 0.;
683 thumb->zoomy = 0.;
684 }
685
686 cairo_set_source_surface(cr, thumb->img_surf, thumb->zoomx + x_offset, thumb->zoomy + y_offset);
687
688 // Paint background with CSS transparency
689 GdkRGBA im_color;
690 GtkStyleContext *context = gtk_widget_get_style_context(thumb->w_image);
691 gtk_style_context_get_color(context, gtk_widget_get_state_flags(thumb->w_image), &im_color);
692 cairo_paint_with_alpha(cr, im_color.alpha);
693
694 // Paint CSS borders
695 gtk_render_frame(context, cr, 0, 0, w, h);
696 cairo_restore(cr);
697 }
698
699 if(!thumb->image_inited || IS_NULL_PTR(thumb->img_surf))
700 {
701 dt_control_draw_busy_msg(cr, w, h);
702 }
704
705 return TRUE;
706}
707
708#define DEBUG 0
709
711{
713 if(IS_NULL_PTR(thumb->widget)) return;
714
715 gboolean show = (thumb->over > DT_THUMBNAIL_OVERLAYS_NONE);
716
717 if(GTK_IS_WIDGET(thumb->w_local_copy))
718 gtk_widget_set_visible(thumb->w_local_copy, (thumb->info.has_localcopy && show) || DEBUG);
719 if(GTK_IS_WIDGET(thumb->w_altered))
720 gtk_widget_set_visible(thumb->w_altered, (dt_thumbtable_info_is_altered(thumb->info) && show) || DEBUG);
721 if(GTK_IS_WIDGET(thumb->w_group))
722 gtk_widget_set_visible(thumb->w_group, (dt_thumbtable_info_is_grouped(thumb->info) && show) || DEBUG);
723 if(GTK_IS_WIDGET(thumb->w_audio))
724 gtk_widget_set_visible(thumb->w_audio, (thumb->info.has_audio && show) || DEBUG);
725 if(GTK_IS_WIDGET(thumb->w_color))
726 gtk_widget_set_visible(thumb->w_color, show || DEBUG);
727 if(GTK_IS_WIDGET(thumb->w_bottom_eb))
728 gtk_widget_set_visible(thumb->w_bottom_eb, show || DEBUG);
729 if(GTK_IS_WIDGET(thumb->w_reject))
730 gtk_widget_set_visible(thumb->w_reject, show || DEBUG);
731 if(GTK_IS_WIDGET(thumb->w_ext))
732 gtk_widget_set_visible(thumb->w_ext, show || DEBUG);
733 if(GTK_IS_WIDGET(thumb->w_cursor))
734 gtk_widget_show(thumb->w_cursor);
735
736 _set_flag(thumb->w_main, GTK_STATE_FLAG_PRELIGHT, thumb->mouse_over);
737 _set_flag(thumb->widget, GTK_STATE_FLAG_PRELIGHT, thumb->mouse_over);
738
739 _set_flag(thumb->w_reject, GTK_STATE_FLAG_ACTIVE, (thumb->info.rating == DT_VIEW_REJECT));
740
741 for(int i = 0; i < MAX_STARS; i++)
742 {
743 if(GTK_IS_WIDGET(thumb->w_stars[i]))
744 gtk_widget_set_visible(thumb->w_stars[i], show || DEBUG);
745 _set_flag(thumb->w_stars[i], GTK_STATE_FLAG_ACTIVE, (thumb->info.rating > i && thumb->info.rating < DT_VIEW_REJECT));
746 }
747
748 _set_flag(thumb->w_group, GTK_STATE_FLAG_ACTIVE, (thumb->info.id == thumb->info.group_id));
749 _set_flag(thumb->w_main, GTK_STATE_FLAG_SELECTED, thumb->selected);
750 _set_flag(thumb->widget, GTK_STATE_FLAG_SELECTED, thumb->selected);
751}
752
753static gboolean _event_main_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
754{
755 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
757 if(!gtk_widget_is_visible(thumb->widget)) return TRUE;
758
759 // Ensure mouse_over_id is set because that's what darkroom uses to open a picture.
760 // NOTE: Duplicate module uses that fucking thumbnail without a table...
761 if(thumb->table)
762 dt_thumbtable_dispatch_over(thumb->table, event->type, thumb->info.id);
763 else
765
766 // raise signal on double click
767 if(event->button == 1 && event->type == GDK_2BUTTON_PRESS)
768 {
769 thumb->dragging = FALSE;
771 return TRUE;
772 }
773 else if(event->button == GDK_BUTTON_SECONDARY && event->type == GDK_BUTTON_PRESS)
774 {
775 GtkWidget *menu = _create_menu(thumb);
776 gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL);
777 return TRUE;
778 }
779
780 return FALSE;
781}
782
783static gboolean _event_main_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
784{
785 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
787 thumb->dragging = FALSE;
788
789 // select on single click only in filemanager mode. Filmstrip mode only raises ACTIVATE signals.
790 if(event->button == 1
791 && thumb->table && thumb->table->mode == DT_THUMBTABLE_MODE_FILEMANAGER)
792 {
793 if(dt_modifier_is(event->state, 0))
795 else if(dt_modifier_is(event->state, GDK_CONTROL_MASK))
797 else if(dt_modifier_is(event->state, GDK_SHIFT_MASK) && thumb->table)
798 dt_thumbtable_select_range(thumb->table, thumb->rowid);
799 // Because selection might include several images, we handle styling globally
800 // in the thumbtable scope, catching the SELECTION_CHANGED signal.
801 return TRUE;
802 }
803 else if(event->button == 1
804 && thumb->table && thumb->table->mode == DT_THUMBTABLE_MODE_FILMSTRIP)
805 {
807 return TRUE;
808 }
809 return FALSE;
810}
811
812static gboolean _event_rating_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
813{
814 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
816 if(thumb->disable_actions) return FALSE;
817 if(dtgtk_thumbnail_btn_is_hidden(widget)) return FALSE;
818
819 if(event->button == 1)
820 {
822 if(widget == thumb->w_reject)
823 rating = DT_VIEW_REJECT;
824 else if(widget == thumb->w_stars[0])
825 rating = DT_VIEW_STAR_1;
826 else if(widget == thumb->w_stars[1])
827 rating = DT_VIEW_STAR_2;
828 else if(widget == thumb->w_stars[2])
829 rating = DT_VIEW_STAR_3;
830 else if(widget == thumb->w_stars[3])
831 rating = DT_VIEW_STAR_4;
832 else if(widget == thumb->w_stars[4])
833 rating = DT_VIEW_STAR_5;
834
835 if(rating != DT_VIEW_DESERT)
836 dt_ratings_apply_on_image(thumb->info.id, rating, TRUE, TRUE, TRUE);
837 }
838 return TRUE;
839}
840
841static gboolean _event_grouping_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
842{
843 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
845 if(thumb->disable_actions) return FALSE;
846 if(dtgtk_thumbnail_btn_is_hidden(widget)) return FALSE;
848 return FALSE;
849}
850
851static gboolean _event_audio_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
852{
853 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
855 if(thumb->disable_actions) return FALSE;
856 if(dtgtk_thumbnail_btn_is_hidden(widget)) return FALSE;
857
858 if(event->button == 1)
859 {
860 gboolean start_audio = TRUE;
862 {
863 // don't start the audio for the image we just killed it for
864 if(darktable.view_manager->audio.audio_player_id == thumb->info.id) start_audio = FALSE;
866 }
867
868 if(start_audio)
869 {
871 }
872 }
873 return FALSE;
874}
875
876
877
878void dt_thumbnail_update_selection(dt_thumbnail_t *thumb, gboolean selected)
879{
881 if(selected != thumb->selected)
882 {
883 thumb->selected = selected;
884 _thumb_update_icons(thumb);
885 }
886}
887
888
889// All the text info that we don't have room to display around the image
891{
893 gtk_label_set_text(GTK_LABEL(thumb->w_filename), thumb->info.filename);
894 gtk_label_set_text(GTK_LABEL(thumb->w_datetime), thumb->info.datetime);
895 gtk_label_set_text(GTK_LABEL(thumb->w_folder), thumb->info.folder);
896
897 char *exposure_time = dt_util_format_exposure(thumb->info.exif_exposure);
898 char *exposure_field = g_strdup_printf("%.0f ISO - f/%.1f - %s",
899 thumb->info.exif_iso,
900 thumb->info.exif_aperture,
901 exposure_time);
902 char *exposure_bias = g_strdup_printf("%+.1f EV", thumb->info.exif_exposure_bias);
903 char *focal = g_strdup_printf("%0.f mm @ %.2f m", thumb->info.exif_focal_length,
905
906 gtk_label_set_text(GTK_LABEL(thumb->w_exposure_bias), exposure_bias);
907 gtk_label_set_text(GTK_LABEL(thumb->w_exposure), exposure_field);
908 gtk_label_set_text(GTK_LABEL(thumb->w_camera), thumb->info.camera_makermodel);
909 gtk_label_set_text(GTK_LABEL(thumb->w_lens), thumb->info.exif_lens);
910 gtk_label_set_text(GTK_LABEL(thumb->w_focal), focal);
911
912 dt_free(focal);
913 dt_free(exposure_bias);
914 dt_free(exposure_field);
915 dt_free(exposure_time);
916}
917
918
920{
922 if(thumb->alternative_mode == enable) return;
923 thumb->alternative_mode = enable;
924 if(enable)
925 {
926 gtk_widget_set_no_show_all(thumb->w_alternative, FALSE);
927 gtk_widget_show_all(thumb->w_alternative);
928 }
929 else
930 {
931 gtk_widget_set_no_show_all(thumb->w_alternative, TRUE);
932 gtk_widget_hide(thumb->w_alternative);
933 }
934 gtk_widget_queue_draw(thumb->widget);
935}
936
937
938static gboolean _event_star_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
939{
940 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
942 if(thumb->disable_actions) return TRUE;
943 _set_flag(thumb->w_bottom_eb, GTK_STATE_FLAG_PRELIGHT, TRUE);
944
945 // we prelight all stars before the current one
946 gboolean pre = TRUE;
947 for(int i = 0; i < MAX_STARS; i++)
948 {
949 _set_flag(thumb->w_stars[i], GTK_STATE_FLAG_PRELIGHT, pre);
950
951 // We don't want the active state to overlap the prelight one because
952 // it makes the feature hard to read/understand.
953 _set_flag(thumb->w_stars[i], GTK_STATE_FLAG_ACTIVE, FALSE);
954
955 if(thumb->w_stars[i] == widget) pre = FALSE;
956 }
957 return TRUE;
958}
959
960
961static gboolean _event_star_leave(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
962{
963 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
965 if(thumb->disable_actions) return TRUE;
966
967 for(int i = 0; i < MAX_STARS; i++)
968 {
969 _set_flag(thumb->w_stars[i], GTK_STATE_FLAG_PRELIGHT, FALSE);
970
971 // restore active state
972 _set_flag(thumb->w_stars[i], GTK_STATE_FLAG_ACTIVE, i < thumb->info.rating && thumb->info.rating < DT_VIEW_REJECT);
973 }
974 return TRUE;
975}
976
977
978gboolean _event_expose(GtkWidget *self, cairo_t *cr, gpointer user_data)
979{
980 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
982 return FALSE;
983}
984
985static gboolean _event_main_motion(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
986{
987 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
989 if(!gtk_widget_is_visible(thumb->widget)) return TRUE;
990 if(!thumb->mouse_over)
991 {
992 // Thumbnails send leave-notify when in the thumbnail frame but over the image.
993 // If we lost the mouse-over in this case, grab it again from mouse motion.
994 // Be conservative with sending mouse_over_id events/signal because many
995 // places in the soft listen to them and refresh stuff from DB, so it's expensive.
996 if(thumb->table)
997 dt_thumbtable_dispatch_over(thumb->table, event->type, thumb->info.id);
998 else
1000
1002 }
1003 return FALSE;
1004}
1005
1006static gboolean _event_main_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
1007{
1008 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1010 if(!gtk_widget_is_visible(thumb->widget)) return TRUE;
1011
1012 if(thumb->table)
1013 dt_thumbtable_dispatch_over(thumb->table, event->type, thumb->info.id);
1014 else
1016
1018 return FALSE;
1019}
1020
1021static gboolean _event_main_leave(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
1022{
1023 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1025 if(!gtk_widget_is_visible(thumb->widget)) return TRUE;
1026
1027 if(thumb->table)
1028 dt_thumbtable_dispatch_over(thumb->table, event->type, -1);
1029 else
1031
1033 return FALSE;
1034}
1035
1036// lazy-load the history tooltip only when mouse enters the button
1037static gboolean _altered_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
1038{
1039 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1042 {
1044 if(tooltip)
1045 {
1046 gtk_widget_set_tooltip_text(thumb->w_altered, tooltip);
1048 }
1049 }
1050 return FALSE;
1051}
1052
1053
1054static gboolean _group_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
1055{
1056 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1059 return FALSE;
1060}
1061
1062
1063static gboolean _event_image_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1064{
1065 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1067
1068 if(event->button == 1 && thumb->table && thumb->table->zoom > DT_THUMBTABLE_ZOOM_FIT)
1069 {
1070 thumb->dragging = TRUE;
1071 thumb->drag_x_start = event->x;
1072 thumb->drag_y_start = event->y;
1073 }
1074
1075 return FALSE;
1076}
1077
1078static gboolean _event_image_motion(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
1079{
1080 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1082 if(thumb->dragging)
1083 {
1084 const double delta_x = event->x - thumb->drag_x_start;
1085 const double delta_y = event->y - thumb->drag_y_start;
1086 const gboolean global_shift = dt_modifier_is(event->state, GDK_SHIFT_MASK) && thumb->table;
1087
1088 if(global_shift)
1089 {
1090 // Offset all thumbnails by this amount
1091 dt_thumbtable_offset_zoom(thumb->table, delta_x, delta_y);
1092 }
1093 else
1094 {
1095 // Offset only the current thumbnail
1096 thumb->zoomx += delta_x;
1097 thumb->zoomy += delta_y;
1098 }
1099
1100 // Reset drag origin
1101 thumb->drag_x_start = event->x;
1102 thumb->drag_y_start = event->y;
1103
1104 if(!global_shift)
1105 gtk_widget_queue_draw(thumb->w_image);
1106
1107 return TRUE;
1108 }
1109 return FALSE;
1110}
1111
1112static gboolean _event_image_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1113{
1114 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1116 thumb->dragging = FALSE;
1117 return FALSE;
1118}
1119
1121{
1122 // Let the background event box capture all user events from its children first,
1123 // so we don't have to wire leave/enter events to all of them individually.
1124 // Children buttons will mostly only use button pressed/released events
1125 thumb->widget = gtk_event_box_new();
1126 dt_gui_add_class(thumb->widget, "thumb-cell");
1127 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);
1128
1129 // this is only here to ensure that mouse-over value is updated correctly
1130 // all dragging actions take place inside thumbatble.c
1131 gtk_drag_dest_set(thumb->widget, GTK_DEST_DEFAULT_MOTION, target_list_all, n_targets_all, GDK_ACTION_MOVE);
1132 g_object_set_data(G_OBJECT(thumb->widget), "thumb", thumb);
1133 gtk_widget_show(thumb->widget);
1134
1135 g_signal_connect(G_OBJECT(thumb->widget), "button-press-event", G_CALLBACK(_event_main_press), thumb);
1136 g_signal_connect(G_OBJECT(thumb->widget), "button-release-event", G_CALLBACK(_event_main_release), thumb);
1137 g_signal_connect(G_OBJECT(thumb->widget), "enter-notify-event", G_CALLBACK(_event_main_enter), thumb);
1138 g_signal_connect(G_OBJECT(thumb->widget), "leave-notify-event", G_CALLBACK(_event_main_leave), thumb);
1139 g_signal_connect(G_OBJECT(thumb->widget), "motion-notify-event", G_CALLBACK(_event_main_motion), thumb);
1140 g_signal_connect(G_OBJECT(thumb->widget), "draw", G_CALLBACK(_event_expose), thumb);
1141
1142 // Main widget
1143 thumb->w_main = gtk_overlay_new();
1144 dt_gui_add_class(thumb->w_main, "thumb-main");
1145 gtk_widget_set_valign(thumb->w_main, GTK_ALIGN_CENTER);
1146 gtk_widget_set_halign(thumb->w_main, GTK_ALIGN_CENTER);
1147 gtk_container_add(GTK_CONTAINER(thumb->widget), thumb->w_main);
1148 gtk_widget_show(thumb->w_main);
1149
1150 thumb->w_background = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1151 dt_gui_add_class(thumb->w_background, "thumb-background");
1152 gtk_widget_set_valign(thumb->w_background, GTK_ALIGN_FILL);
1153 gtk_widget_set_halign(thumb->w_background, GTK_ALIGN_FILL);
1154 gtk_overlay_add_overlay(GTK_OVERLAY(thumb->w_main), thumb->w_background);
1155 gtk_widget_show(thumb->w_background);
1156 gtk_overlay_set_overlay_pass_through(GTK_OVERLAY(thumb->w_main), thumb->w_background, TRUE);
1157
1158 // triangle to indicate current image(s) in filmstrip
1159 thumb->w_cursor = gtk_drawing_area_new();
1160 dt_gui_add_class(thumb->w_cursor, "thumb-cursor");
1161 gtk_widget_set_valign(thumb->w_cursor, GTK_ALIGN_START);
1162 gtk_widget_set_halign(thumb->w_cursor, GTK_ALIGN_CENTER);
1163 g_signal_connect(G_OBJECT(thumb->w_cursor), "draw", G_CALLBACK(_event_cursor_draw), thumb);
1164 gtk_overlay_add_overlay(GTK_OVERLAY(thumb->w_main), thumb->w_cursor);
1165
1166 // the image drawing area
1167 thumb->w_image = gtk_drawing_area_new();
1168 dt_gui_add_class(thumb->w_image, "thumb-image");
1169 gtk_widget_set_valign(thumb->w_image, GTK_ALIGN_CENTER);
1170 gtk_widget_set_halign(thumb->w_image, GTK_ALIGN_CENTER);
1171 gtk_widget_set_events(thumb->w_image, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK);
1172 g_signal_connect(G_OBJECT(thumb->w_image), "draw", G_CALLBACK(_thumb_draw_image), thumb);
1173 g_signal_connect(G_OBJECT(thumb->w_image), "button-press-event", G_CALLBACK(_event_image_press), thumb);
1174 g_signal_connect(G_OBJECT(thumb->w_image), "button-release-event", G_CALLBACK(_event_image_release), thumb);
1175 g_signal_connect(G_OBJECT(thumb->w_image), "motion-notify-event", G_CALLBACK(_event_image_motion), thumb);
1176 gtk_widget_show(thumb->w_image);
1177 gtk_overlay_add_overlay(GTK_OVERLAY(thumb->w_main), thumb->w_image);
1178 gtk_overlay_set_overlay_pass_through(GTK_OVERLAY(thumb->w_main), thumb->w_image, TRUE);
1179
1180 thumb->w_bottom_eb = gtk_event_box_new();
1181 gtk_widget_set_valign(thumb->w_bottom_eb, GTK_ALIGN_END);
1182 gtk_widget_set_halign(thumb->w_bottom_eb, GTK_ALIGN_FILL);
1183 gtk_widget_show(thumb->w_bottom_eb);
1184 gtk_overlay_add_overlay(GTK_OVERLAY(thumb->w_main), thumb->w_bottom_eb);
1185
1186 GtkWidget *bottom_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1187 dt_gui_add_class(bottom_box, "thumb-bottom");
1188 gtk_container_add(GTK_CONTAINER(thumb->w_bottom_eb), bottom_box);
1189 gtk_widget_show(bottom_box);
1190
1191 // the reject icon
1193 dt_gui_add_class(thumb->w_reject, "thumb-reject");
1194 gtk_widget_set_valign(thumb->w_reject, GTK_ALIGN_CENTER);
1195 gtk_widget_set_halign(thumb->w_reject, GTK_ALIGN_START);
1196 gtk_widget_show(thumb->w_reject);
1197 g_signal_connect(G_OBJECT(thumb->w_reject), "button-release-event", G_CALLBACK(_event_rating_release), thumb);
1198 gtk_box_pack_start(GTK_BOX(bottom_box), thumb->w_reject, FALSE, FALSE, 0);
1199
1200 GtkWidget *stars_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1201 gtk_box_pack_start(GTK_BOX(bottom_box), stars_box, TRUE, TRUE, 0);
1202 gtk_widget_set_valign(stars_box, GTK_ALIGN_CENTER);
1203 gtk_widget_set_halign(stars_box, GTK_ALIGN_CENTER);
1204 gtk_widget_set_hexpand(stars_box, TRUE);
1205 gtk_widget_show(stars_box);
1206
1207 // the stars
1208 for(int i = 0; i < MAX_STARS; i++)
1209 {
1211 g_signal_connect(G_OBJECT(thumb->w_stars[i]), "enter-notify-event", G_CALLBACK(_event_star_enter), thumb);
1212 g_signal_connect(G_OBJECT(thumb->w_stars[i]), "leave-notify-event", G_CALLBACK(_event_star_leave), thumb);
1213 g_signal_connect(G_OBJECT(thumb->w_stars[i]), "button-release-event", G_CALLBACK(_event_rating_release),
1214 thumb);
1215 dt_gui_add_class(thumb->w_stars[i], "thumb-star");
1216 gtk_widget_set_valign(thumb->w_stars[i], GTK_ALIGN_CENTER);
1217 gtk_widget_set_halign(thumb->w_stars[i], GTK_ALIGN_CENTER);
1218 gtk_widget_show(thumb->w_stars[i]);
1219 gtk_box_pack_start(GTK_BOX(stars_box), thumb->w_stars[i], FALSE, FALSE, 0);
1220 }
1221
1222 // the color labels
1224 dt_gui_add_class(thumb->w_color, "thumb-colorlabels");
1225 gtk_widget_set_valign(thumb->w_color, GTK_ALIGN_CENTER);
1226 gtk_widget_set_halign(thumb->w_color, GTK_ALIGN_END);
1227 gtk_widget_set_no_show_all(thumb->w_color, TRUE);
1228 gtk_box_pack_start(GTK_BOX(bottom_box), thumb->w_color, FALSE, FALSE, 0);
1229
1230 thumb->w_top_eb = gtk_event_box_new();
1231 gtk_widget_set_valign(thumb->w_top_eb, GTK_ALIGN_START);
1232 gtk_widget_set_halign(thumb->w_top_eb, GTK_ALIGN_FILL);
1233 gtk_widget_show(thumb->w_top_eb);
1234 gtk_overlay_add_overlay(GTK_OVERLAY(thumb->w_main), thumb->w_top_eb);
1235
1236 GtkWidget *top_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1237 dt_gui_add_class(top_box, "thumb-top");
1238 gtk_container_add(GTK_CONTAINER(thumb->w_top_eb), top_box);
1239 gtk_widget_show(top_box);
1240
1241 // the file extension label
1242 thumb->w_ext = gtk_label_new("");
1243 dt_gui_add_class(thumb->w_ext, "thumb-ext");
1244 gtk_widget_set_valign(thumb->w_ext, GTK_ALIGN_CENTER);
1245 gtk_widget_show(thumb->w_ext);
1246 gtk_box_pack_start(GTK_BOX(top_box), thumb->w_ext, FALSE, FALSE, 0);
1247
1248 // the local copy indicator
1250 dt_gui_add_class(thumb->w_local_copy, "thumb-localcopy");
1251 gtk_widget_set_tooltip_text(thumb->w_local_copy, _("This picture is locally copied on your disk cache"));
1252 gtk_widget_set_valign(thumb->w_local_copy, GTK_ALIGN_CENTER);
1253 gtk_widget_set_no_show_all(thumb->w_local_copy, TRUE);
1254 gtk_box_pack_start(GTK_BOX(top_box), thumb->w_local_copy, FALSE, FALSE, 0);
1255
1256 // the altered icon
1258 g_signal_connect(G_OBJECT(thumb->w_altered), "enter-notify-event", G_CALLBACK(_altered_enter), thumb);
1259 dt_gui_add_class(thumb->w_altered, "thumb-altered");
1260 gtk_widget_set_valign(thumb->w_altered, GTK_ALIGN_CENTER);
1261 gtk_widget_set_no_show_all(thumb->w_altered, TRUE);
1262 gtk_box_pack_end(GTK_BOX(top_box), thumb->w_altered, FALSE, FALSE, 0);
1263
1264 // the group bouton
1266 dt_gui_add_class(thumb->w_group, "thumb-group");
1267 g_signal_connect(G_OBJECT(thumb->w_group), "button-release-event", G_CALLBACK(_event_grouping_release), thumb);
1268 g_signal_connect(G_OBJECT(thumb->w_group), "enter-notify-event", G_CALLBACK(_group_enter), thumb);
1269 gtk_widget_set_valign(thumb->w_group, GTK_ALIGN_CENTER);
1270 gtk_widget_set_no_show_all(thumb->w_group, TRUE);
1271 gtk_box_pack_end(GTK_BOX(top_box), thumb->w_group, FALSE, FALSE, 0);
1272
1273 // the sound icon
1275 dt_gui_add_class(thumb->w_audio, "thumb-audio");
1276 g_signal_connect(G_OBJECT(thumb->w_audio), "button-release-event", G_CALLBACK(_event_audio_release), thumb);
1277 gtk_widget_set_valign(thumb->w_audio, GTK_ALIGN_CENTER);
1278 gtk_widget_set_no_show_all(thumb->w_audio, TRUE);
1279 gtk_box_pack_end(GTK_BOX(top_box), thumb->w_audio, FALSE, FALSE, 0);
1280
1281 thumb->w_alternative = gtk_overlay_new();
1282 gtk_overlay_add_overlay(GTK_OVERLAY(thumb->w_main), thumb->w_alternative);
1283 gtk_widget_set_halign(thumb->w_alternative, GTK_ALIGN_FILL);
1284 gtk_widget_set_valign(thumb->w_alternative, GTK_ALIGN_FILL);
1285 gtk_widget_hide(thumb->w_alternative);
1286
1287 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1288 gtk_container_add(GTK_CONTAINER(thumb->w_alternative), box);
1289 dt_gui_add_class(box, "thumb-alternative");
1290
1291 GtkWidget *bbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1292 gtk_widget_set_valign(bbox, GTK_ALIGN_START);
1293 gtk_box_pack_start(GTK_BOX(box), bbox, TRUE, TRUE, 0);
1294 thumb->w_filename = gtk_label_new("");
1295 gtk_label_set_ellipsize(GTK_LABEL(thumb->w_filename), PANGO_ELLIPSIZE_MIDDLE);
1296 gtk_box_pack_start(GTK_BOX(bbox), thumb->w_filename, FALSE, FALSE, 0);
1297 thumb->w_datetime = gtk_label_new("");
1298 gtk_box_pack_start(GTK_BOX(bbox), thumb->w_datetime, FALSE, FALSE, 0);
1299 thumb->w_folder = gtk_label_new("");
1300 gtk_label_set_ellipsize(GTK_LABEL(thumb->w_folder), PANGO_ELLIPSIZE_MIDDLE);
1301 gtk_box_pack_start(GTK_BOX(bbox), thumb->w_folder, FALSE, FALSE, 0);
1302
1303 bbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1304 gtk_widget_set_valign(bbox, GTK_ALIGN_CENTER);
1305 gtk_box_pack_start(GTK_BOX(box), bbox, TRUE, TRUE, 0);
1306 thumb->w_exposure = gtk_label_new("");
1307 gtk_box_pack_start(GTK_BOX(bbox), thumb->w_exposure, FALSE, FALSE, 0);
1308 thumb->w_exposure_bias = gtk_label_new("");
1309 gtk_box_pack_start(GTK_BOX(bbox), thumb->w_exposure_bias, FALSE, FALSE, 0);
1310
1311 bbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1312 gtk_widget_set_valign(bbox, GTK_ALIGN_END);
1313 gtk_box_pack_start(GTK_BOX(box), bbox, TRUE, TRUE, 0);
1314 thumb->w_camera = gtk_label_new("");
1315 gtk_box_pack_start(GTK_BOX(bbox), thumb->w_camera, FALSE, FALSE, 0);
1316 thumb->w_lens = gtk_label_new("");
1317 gtk_label_set_ellipsize(GTK_LABEL(thumb->w_lens), PANGO_ELLIPSIZE_MIDDLE);
1318 gtk_box_pack_start(GTK_BOX(bbox), thumb->w_lens, FALSE, FALSE, 0);
1319 thumb->w_focal = gtk_label_new("");
1320 gtk_box_pack_start(GTK_BOX(bbox), thumb->w_focal, FALSE, FALSE, 0);
1321 gtk_widget_set_no_show_all(thumb->w_alternative, TRUE);
1322
1323 return thumb->widget;
1324}
1325
1327{
1328 if(IS_NULL_PTR(thumb) || IS_NULL_PTR(info)) return;
1329
1330 dt_thumbtable_copy_image(&thumb->info, info);
1331
1332 if(!thumb->widget || IS_NULL_PTR(thumb->w_main)) return;
1333
1335 _thumb_update_icons(thumb);
1338
1339 if(thumb->w_color)
1340 {
1342 btn->icon_flags = thumb->info.color_labels;
1343 }
1344}
1345
1347{
1348 dt_thumbnail_t *thumb = calloc(1, sizeof(dt_thumbnail_t));
1349
1350 thumb->rowid = rowid;
1351 thumb->over = over;
1352 thumb->table = table;
1353 thumb->zoomx = 0.;
1354 thumb->zoomy = 0.;
1355 thumb->job = NULL;
1356 thumb->img_h = 0;
1357 thumb->img_w = 0;
1359 dt_atomic_set_int(&thumb->ref_count, 1);
1360
1361 dt_pthread_mutex_init(&thumb->lock, NULL);
1362
1364
1365 dt_thumbnail_resync_info(thumb, info);
1367
1368 // This will then only run on "selection_changed" event
1370
1371 return thumb;
1372}
1373
1375{
1376 if(IS_NULL_PTR(thumb)) return 0;
1377
1379
1380 // Unregister the thumbnail from the parent table before any callback can
1381 // pick it again through the LUT or imgid hash during view/selection updates.
1382 if(thumb->table)
1383 {
1385 if(g_hash_table_lookup(thumb->table->list, GINT_TO_POINTER(thumb->info.id)) == thumb)
1386 g_hash_table_remove(thumb->table->list, GINT_TO_POINTER(thumb->info.id));
1387
1388 if(thumb->table->lut)
1389 for(int rowid = 0; rowid < thumb->table->collection_count; rowid++)
1390 if(thumb->table->lut[rowid].thumb == thumb)
1391 thumb->table->lut[rowid].thumb = NULL;
1392
1394 }
1395
1396 // Detach the thumbnail from Gtk immediately, but keep it alive until any queued
1397 // background rendering job gets cancelled and disposed by the job queue.
1398 dt_pthread_mutex_lock(&thumb->lock);
1399
1400 thumb->job = NULL;
1401
1402 // remove multiple delayed gtk_widget_queue_draw triggers
1403 while(g_idle_remove_by_data(thumb->widget))
1404 ;
1405 while(g_idle_remove_by_data(thumb->w_image))
1406 ;
1407
1408 if(thumb->img_surf && cairo_surface_get_reference_count(thumb->img_surf) > 0)
1409 cairo_surface_destroy(thumb->img_surf);
1410 thumb->img_surf = NULL;
1411
1412 if(thumb->widget)
1413 {
1414 GtkWidget *parent = gtk_widget_get_parent(thumb->widget);
1415 if(parent && GTK_IS_CONTAINER(parent))
1416 gtk_container_remove(GTK_CONTAINER(parent), thumb->widget);
1417 }
1418 thumb->widget = NULL;
1419 thumb->w_main = NULL;
1420 thumb->w_image = NULL;
1421 thumb->w_cursor = NULL;
1422 thumb->w_bottom_eb = NULL;
1423 thumb->w_reject = NULL;
1424 thumb->w_color = NULL;
1425 thumb->w_ext = NULL;
1426 thumb->w_local_copy = NULL;
1427 thumb->w_altered = NULL;
1428 thumb->w_group = NULL;
1429 thumb->w_audio = NULL;
1430 thumb->w_top_eb = NULL;
1431 thumb->w_alternative = NULL;
1432 thumb->w_filename = NULL;
1433 thumb->w_datetime = NULL;
1434 thumb->w_folder = NULL;
1435 thumb->w_exposure = NULL;
1436 thumb->w_exposure_bias = NULL;
1437 thumb->w_camera = NULL;
1438 thumb->w_lens = NULL;
1439 thumb->w_focal = NULL;
1440 for(int i = 0; i < MAX_STARS; i++) thumb->w_stars[i] = NULL;
1441
1443
1444 _thumbnail_release(thumb);
1445
1446 return 0;
1447}
1448
1450{
1451 thumb_return_if_fails(thumb);
1453 if(GTK_IS_WIDGET(thumb->w_color))
1454 {
1456 btn->icon_flags = thumb->info.color_labels;
1457 }
1459 _thumb_update_icons(thumb);
1461}
1462
1464{
1465 thumb_return_if_fails(thumb);
1466 thumb->over = mode;
1467}
1468
1469// if update, the internal width and height, minus margins and borders, are written back in input
1470void _widget_set_size(GtkWidget *w, int *parent_width, int *parent_height, const gboolean update)
1471{
1472 GtkStateFlags state = gtk_widget_get_state_flags(w);
1473 GtkStyleContext *context = gtk_widget_get_style_context(w);
1474
1475 GtkBorder margins;
1476 gtk_style_context_get_margin(context, state, &margins);
1477
1478 int width = *parent_width - margins.left - margins.right;
1479 int height = *parent_height - margins.top - margins.bottom;
1480
1481 if(width > 0 && height > 0)
1482 {
1483 gtk_widget_set_size_request(w, width, height);
1484
1485 // unvisible widgets need to be allocated to be able to measure the size of flexible boxes.
1486 GtkAllocation alloc = { .x = 0, .y = 0, .width = width, .height = height };
1487 gtk_widget_size_allocate(w, &alloc);
1488 }
1489
1490 if(update)
1491 {
1492 *parent_width = width;
1493 *parent_height = height;
1494 }
1495}
1496
1497
1499{
1500 thumb_return_if_fails(thumb, 0);
1501
1502 // we need to squeeze reject + space + stars + space + colorlabels icons on a thumbnail width
1503 // that means a width of 4 + MAX_STARS icons size
1504 // all icons and spaces having a width of 2 * r1
1505 // inner margins are defined in css (margin_* values)
1506
1507 // retrieves the size of the main icons in the top panel, thumbtable overlays shall not exceed that
1508 const int n_widgets = 4 + MAX_STARS;
1509 // The width-based term goes NEGATIVE once the thumbnail is narrower than the icon row can fit
1510 // ((n_widgets - 1) * spacing, ~80px at default EM). A negative icon_size feeds -1 into the
1511 // widgets' size_request, which GTK reads as "use natural size" -> overlays balloon (huge
1512 // extension text) and the bar-height subtraction below collapses the image to a clipped sliver.
1513 // Clamp to 0 so the overlays instead degrade gracefully (shrink to nothing) on tiny filmstrip
1514 // thumbnails while the image keeps scaling. Above the threshold nothing changes.
1515 const float r1 = fmaxf(0.f, fminf(1.25 * DT_GUI_EM_SIZE / 2.,
1516 (float)(width - (n_widgets - 1) * DT_GUI_BOX_SPACING) / (2. * n_widgets)));
1517 int icon_size = roundf(2 * r1);
1518
1519 // reject icon
1520 gtk_widget_set_size_request(thumb->w_reject, icon_size, icon_size);
1521
1522 // stars
1523 for(int i = 0; i < MAX_STARS; i++)
1524 gtk_widget_set_size_request(thumb->w_stars[i], icon_size, icon_size);
1525
1526 // the color labels
1527 gtk_widget_set_size_request(thumb->w_color, icon_size, icon_size);
1528
1529 // the local copy indicator
1530 _set_flag(thumb->w_local_copy, GTK_STATE_FLAG_ACTIVE, FALSE);
1531 gtk_widget_set_size_request(thumb->w_local_copy, icon_size, icon_size);
1532
1533 // the altered icon
1534 gtk_widget_set_size_request(thumb->w_altered, icon_size, icon_size);
1535
1536 // the group bouton
1537 gtk_widget_set_size_request(thumb->w_group, icon_size, icon_size);
1538
1539 // the sound icon
1540 gtk_widget_set_size_request(thumb->w_audio, icon_size, icon_size);
1541
1542 // the filmstrip cursor
1543 gtk_widget_set_size_request(thumb->w_cursor, 6.0 * r1, 1.5 * r1);
1544
1545 // extension text
1546 PangoAttrList *attrlist = pango_attr_list_new();
1547 PangoAttribute *attr = pango_attr_size_new_absolute(icon_size * PANGO_SCALE * 0.9);
1548 pango_attr_list_insert(attrlist, attr);
1549 gtk_label_set_attributes(GTK_LABEL(thumb->w_ext), attrlist);
1550 pango_attr_list_unref(attrlist);
1551
1552 return icon_size;
1553}
1554
1555// This function is called only from the thumbtable, when the grid size changed.
1556// NOTE: thumb->widget is a grid cell. It should not get styled, especially not with margins/padding.
1557// Styling starts at thumb->w_main, aka .thumb-main in CSS, which gets centered in the grid cell.
1558// Overlays need to be set prior to calling this function because they can change internal sizings.
1559// It is expected that this function is called only when needed, that is if the size requirements actually
1560// changed, meaning this check needs to be done upstream because we internally nuke the image surface on every call.
1562{
1563 thumb_return_if_fails(thumb);
1564 //fprintf(stdout, "calling resize on %i with overlay %i\n", thumb->info.id, thumb->over);
1565
1566 if(width < 1 || height < 1) return;
1567
1568 // widget resizing
1569 thumb->width = width;
1570 thumb->height = height;
1572
1573 // Apply margins & borders on the main widget
1575
1576 // Update show/hide status for overlays now, because we pack them in boxes
1577 // so the children need to be sized before their parents for the boxes to have proper size.
1578 gtk_widget_show_all(thumb->widget);
1579 _thumb_update_icons(thumb);
1580
1581 // Proceed with overlays resizing
1582 int icon_size = _thumb_resize_overlays(thumb, width, height);
1583
1584 // Finish with updating the image size
1586 {
1587 // Persistent overlays shouldn't overlap with image, so resize it.
1588 // Use the bars' preferred (natural) height, not their allocated height: the bars were
1589 // allocated above (via w_main) with the PREVIOUS icon sizes, and _thumb_resize_overlays()
1590 // only just applied the new icon size_requests, so the allocated heights still lag by one
1591 // resize step (and are 0 before the first allocation at startup). The natural height reflects
1592 // the icon size_requests we just set, with no settled allocation required - this is what made
1593 // the filmstrip overlays compute on outdated sizes during a panel drag resize.
1594 int margin_bottom = 0, margin_top = 0;
1595 gtk_widget_get_preferred_height(thumb->w_bottom_eb, NULL, &margin_bottom);
1596 gtk_widget_get_preferred_height(thumb->w_top_eb, NULL, &margin_top);
1597 height -= 2 * MAX(MAX(margin_top, margin_bottom), icon_size);
1598 // In case top and bottom bars of overlays have different sizes,
1599 // we resize symetrically to the largest.
1600 }
1602
1604}
1605
1607{
1608 thumb_return_if_fails(thumb);
1609
1610 if(border == DT_THUMBNAIL_BORDER_NONE)
1611 {
1612 dt_gui_remove_class(thumb->widget, "dt_group_left");
1613 dt_gui_remove_class(thumb->widget, "dt_group_top");
1614 dt_gui_remove_class(thumb->widget, "dt_group_right");
1615 dt_gui_remove_class(thumb->widget, "dt_group_bottom");
1617 return;
1618 }
1619 if(border & DT_THUMBNAIL_BORDER_LEFT)
1620 dt_gui_add_class(thumb->widget, "dt_group_left");
1621 if(border & DT_THUMBNAIL_BORDER_TOP)
1622 dt_gui_add_class(thumb->widget, "dt_group_top");
1623 if(border & DT_THUMBNAIL_BORDER_RIGHT)
1624 dt_gui_add_class(thumb->widget, "dt_group_right");
1625 if(border & DT_THUMBNAIL_BORDER_BOTTOM)
1626 dt_gui_add_class(thumb->widget, "dt_group_bottom");
1627
1628 thumb->group_borders |= border;
1629}
1630
1632{
1633 thumb_return_if_fails(thumb);
1634
1635 if(thumb->mouse_over == over) return;
1636 thumb->mouse_over = over;
1637 if(thumb->table) thumb->table->rowid = thumb->rowid;
1638
1639 _set_flag(thumb->widget, GTK_STATE_FLAG_PRELIGHT, thumb->mouse_over);
1640 _set_flag(thumb->w_bottom_eb, GTK_STATE_FLAG_PRELIGHT, thumb->mouse_over);
1641 _set_flag(thumb->w_main, GTK_STATE_FLAG_PRELIGHT, thumb->mouse_over);
1642
1643 _thumb_update_icons(thumb);
1644}
1645
1646// set if the thumbnail should react (mouse_over) to drag and drop
1647// note that it's just cosmetic as dropping occurs in thumbtable in any case
1648void dt_thumbnail_set_drop(dt_thumbnail_t *thumb, gboolean accept_drop)
1649{
1650 thumb_return_if_fails(thumb);
1651
1652 if(accept_drop)
1653 gtk_drag_dest_set(thumb->w_main, GTK_DEST_DEFAULT_MOTION, target_list_all, n_targets_all, GDK_ACTION_MOVE);
1654 else
1655 gtk_drag_dest_unset(thumb->w_main);
1656}
1657
1658// Apply new mipmap on thumbnail
1660{
1661 thumb_return_if_fails(thumb, G_SOURCE_REMOVE);
1662
1663 dt_pthread_mutex_lock(&thumb->lock);
1664 // Cancel any in-flight render job. It was started for the old state (stale size or data).
1665 // The job checks thumb->job == job under the same lock before committing; clearing it here
1666 // makes that check fail so the old result is discarded and a fresh job can be queued.
1667 thumb->job = NULL;
1668 thumb->image_inited = FALSE;
1670
1671 // Queue redraw on the drawing area itself: it's the widget that requests/regenerates the cairo surface.
1672 // Queueing only the parent overlay may not invalidate the drawing area's window, leaving stale (too small)
1673 // cached surfaces until some pointer event happens.
1674 if(thumb->w_image) gtk_widget_queue_draw(thumb->w_image);
1675 if(thumb->w_main) gtk_widget_queue_draw(thumb->w_main);
1676 return G_SOURCE_REMOVE;
1677}
1678
1679// clang-format off
1680// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1681// vim: shiftwidth=2 expandtab tabstop=2 cindent
1682// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1683// clang-format on
int scrolled(struct dt_iop_module_t *self, double x, double y, int up, uint32_t state)
Definition ashift.c:4898
#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)
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
void dt_colorlabels_toggle_label_on_list(GList *list, const int color, const gboolean undo_on)
dt_colorspaces_color_profile_type_t
Definition colorspaces.h:81
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
char * dt_history_get_items_as_string(const int32_t imgid)
void dt_control_draw_busy_msg(cairo_t *cr, int width, int height)
Definition control.c:521
void dt_control_set_mouse_over_id(int32_t value)
Definition control.c:947
uint32_t view(const dt_view_t *self)
Definition darkroom.c:228
darktable_t darktable
Definition darktable.c:183
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1600
@ DT_DEBUG_LIGHTTABLE
Definition darktable.h:747
#define dt_free(ptr)
Definition darktable.h:478
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:475
static const dt_aligned_pixel_simd_t value
Definition darktable.h:599
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:955
#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
sqlite3 * dt_database_get(const dt_database_t *db)
Definition database.c:3653
#define DT_DEBUG_SQLITE3_PREPARE_V2(a, b, c, d, e)
Definition debug.h:107
#define DT_DEBUG_SQLITE3_BIND_INT(a, b, c)
Definition debug.h:115
static const GtkTargetEntry target_list_all[]
static const guint n_targets_all
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)
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_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:374
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:226
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:136
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)
int dt_grouping_change_representative(const int32_t image_id)
Definition grouping.c:119
void dt_gui_remove_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:143
void dt_gui_textview_set_padding(GtkTextView *textview)
Apply the standard recessed-input text padding to a GtkTextView.
Definition gtk.c:2919
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:133
GtkWidget * dt_ui_main_window(dt_ui_t *ui)
get the main window widget
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_GUI_EM_SIZE
Definition gtk.h:107
static gboolean enable(dt_image_t *image)
Definition highlights.c:653
const char flag
Definition image.h:252
const char * tooltip
Definition image.h:251
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,...
Definition imageio.c:209
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(_dt_job_t *job, void *params, dt_job_destroy_callback callback)
Definition jobs.c:112
void dt_control_job_dispose(_dt_job_t *job)
Definition jobs.c:153
@ DT_JOB_QUEUE_SYSTEM_FG
Definition jobs.h:54
@ DT_JOB_STATE_CANCELLED
Definition jobs.h:46
dt_map_box_t bbox
Definition location.c:4
dt_colorspaces_color_profile_type_t color_space
Definition mipmap_cache.c:5
void dt_preview_window_spawn(const int32_t imgid)
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:219
gboolean dt_selection_is_id_selected(struct dt_selection_t *selection, int32_t imgid)
Definition selection.c:393
void dt_selection_select_single(dt_selection_t *selection, int32_t imgid)
Definition selection.c:289
void dt_selection_toggle(dt_selection_t *selection, int32_t imgid)
Definition selection.c:296
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:366
@ DT_SIGNAL_VIEWMANAGER_THUMBTABLE_ACTIVATE
Definition signal.h:95
@ 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:103
struct _GtkWidget GtkWidget
Definition splash.h:29
const float uint32_t state[4]
struct dt_gui_gtk_t * gui
Definition darktable.h:803
struct dt_selection_t * selection
Definition darktable.h:810
const struct dt_database_t * db
Definition darktable.h:807
struct dt_control_signal_t * signals
Definition darktable.h:802
struct dt_view_manager_t * view_manager
Definition darktable.h:800
struct dt_control_t * control
Definition darktable.h:801
dt_ui_t * ui
Definition gtk.h:165
gboolean has_audio
Definition image.h:375
gboolean is_hdr
Definition image.h:378
float exif_exposure
Definition image.h:285
float exif_focus_distance
Definition image.h:290
gboolean is_bw
Definition image.h:376
int32_t group_id
Definition image.h:319
char camera_makermodel[128]
Definition image.h:300
float exif_exposure_bias
Definition image.h:286
float exif_iso
Definition image.h:288
float exif_aperture
Definition image.h:287
char fullpath[PATH_MAX]
Definition image.h:305
float exif_focal_length
Definition image.h:289
int rating
Definition image.h:372
gboolean has_localcopy
Definition image.h:374
char exif_lens[128]
Definition image.h:294
int color_labels
Definition image.h:371
char datetime[200]
Definition image.h:310
char filename[DT_MAX_FILENAME_LEN]
Definition image.h:304
char folder[PATH_MAX]
Definition image.h:308
int32_t id
Definition image.h:319
gboolean is_bw_flow
Definition image.h:377
gboolean mouse_over
Definition thumbnail.h:73
GtkWidget * w_stars[5]
Definition thumbnail.h:90
GtkWidget * w_color
Definition thumbnail.h:91
dt_pthread_mutex_t lock
Definition thumbnail.h:130
GtkWidget * w_alternative
Definition thumbnail.h:99
GtkWidget * w_local_copy
Definition thumbnail.h:94
GtkWidget * w_folder
Definition thumbnail.h:128
gboolean dragging
Definition thumbnail.h:113
gboolean disable_actions
Definition thumbnail.h:104
GtkWidget * w_exposure_bias
Definition thumbnail.h:122
dt_image_t info
Definition thumbnail.h:76
dt_atomic_int ref_count
Definition thumbnail.h:133
GtkWidget * w_ext
Definition thumbnail.h:82
GtkWidget * w_datetime
Definition thumbnail.h:125
dt_atomic_int destroying
Definition thumbnail.h:132
GtkWidget * w_camera
Definition thumbnail.h:123
GtkWidget * w_main
Definition thumbnail.h:80
gboolean selected
Definition thumbnail.h:74
dt_thumbnail_border_t group_borders
Definition thumbnail.h:101
struct _dt_job_t * job
Definition thumbnail.h:131
int32_t rowid
Definition thumbnail.h:68
GtkWidget * w_group
Definition thumbnail.h:96
gboolean image_inited
Definition thumbnail.h:118
GtkWidget * w_exposure
Definition thumbnail.h:121
gboolean alternative_mode
Definition thumbnail.h:120
GtkWidget * w_audio
Definition thumbnail.h:97
GtkWidget * w_focal
Definition thumbnail.h:127
GtkWidget * w_background
Definition thumbnail.h:81
GtkWidget * w_top_eb
Definition thumbnail.h:93
GtkWidget * w_reject
Definition thumbnail.h:89
GtkWidget * w_lens
Definition thumbnail.h:126
double drag_x_start
Definition thumbnail.h:111
struct dt_thumbtable_t * table
Definition thumbnail.h:115
double drag_y_start
Definition thumbnail.h:112
GtkWidget * w_image
Definition thumbnail.h:84
dt_thumbnail_overlay_t over
Definition thumbnail.h:106
cairo_surface_t * img_surf
Definition thumbnail.h:85
GtkWidget * widget
Definition thumbnail.h:79
GtkWidget * w_altered
Definition thumbnail.h:95
GtkWidget * w_bottom_eb
Definition thumbnail.h:88
GtkWidget * w_cursor
Definition thumbnail.h:87
GtkWidget * w_filename
Definition thumbnail.h:124
dt_thumbnail_t * thumb
Definition thumbtable.h:89
GHashTable * list
Definition thumbtable.h:117
gboolean focus_peaking
Definition thumbtable.h:188
dt_thumbtable_mode_t mode
Definition thumbtable.h:102
dt_pthread_mutex_t lock
Definition thumbtable.h:167
dt_thumbtable_zoom_t zoom
Definition thumbtable.h:184
gboolean focus_regions
Definition thumbtable.h:187
dt_thumbtable_cache_t * lut
Definition thumbtable.h:151
int32_t audio_player_id
Definition view.h:216
struct dt_view_manager_t::@68 audio
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:851
static void _free_image_surface(dt_thumbnail_t *thumb)
Definition thumbnail.c:373
static gboolean _event_image_motion(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
Definition thumbnail.c:1078
void dt_thumbnail_set_drop(dt_thumbnail_t *thumb, gboolean accept_drop)
Definition thumbnail.c:1648
static void _color_label_callback(GtkWidget *widget, dt_thumbnail_t *thumb)
Definition thumbnail.c:206
gboolean _event_expose(GtkWidget *self, cairo_t *cr, gpointer user_data)
Definition thumbnail.c:978
#define thumb_return_if_fails(thumb,...)
Definition thumbnail.c:78
static void _thumbnail_free(dt_thumbnail_t *thumb)
Definition thumbnail.c:381
dt_thumbnail_t * dt_thumbnail_new(int rowid, dt_thumbnail_overlay_t over, dt_thumbtable_t *table, dt_image_t *info)
Definition thumbnail.c:1346
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:196
static gboolean _event_star_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition thumbnail.c:938
static gboolean _main_context_queue_draw(GtkWidget *widget)
Definition thumbnail.c:399
void dt_thumbnail_set_mouseover(dt_thumbnail_t *thumb, gboolean over)
Definition thumbnail.c:1631
static gboolean _event_main_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition thumbnail.c:753
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:180
static void _thumb_update_icons(dt_thumbnail_t *thumb)
Definition thumbnail.c:710
static gboolean _thumb_draw_image(GtkWidget *widget, cairo_t *cr, gpointer user_data)
Definition thumbnail.c:646
GtkWidget * dt_thumbnail_create_widget(dt_thumbnail_t *thumb)
Definition thumbnail.c:1120
static gboolean _event_cursor_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
Definition thumbnail.c:352
static void _thumbnail_release(void *data)
Definition thumbnail.c:390
static GtkWidget * _create_menu(dt_thumbnail_t *thumb)
Definition thumbnail.c:293
static gboolean _event_main_leave(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition thumbnail.c:1021
static gboolean _event_star_leave(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition thumbnail.c:961
static gboolean _event_main_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition thumbnail.c:1006
static void _thumb_update_rating_class(dt_thumbnail_t *thumb)
Definition thumbnail.c:151
static gboolean _event_grouping_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition thumbnail.c:841
void _create_alternative_view(dt_thumbnail_t *thumb)
Definition thumbnail.c:890
void dt_thumbnail_resync_info(dt_thumbnail_t *thumb, const dt_image_t *const info)
Definition thumbnail.c:1326
static gboolean _event_rating_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition thumbnail.c:812
void dt_thumbnail_set_overlay(dt_thumbnail_t *thumb, dt_thumbnail_overlay_t mode)
Definition thumbnail.c:1463
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:1606
static gboolean _event_image_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition thumbnail.c:1112
int dt_thumbnail_destroy(dt_thumbnail_t *thumb)
Definition thumbnail.c:1374
int dt_thumbnail_image_refresh_real(dt_thumbnail_t *thumb)
Definition thumbnail.c:1659
int32_t _get_image_buffer(dt_job_t *job)
Definition thumbnail.c:434
void dt_thumbnail_update_gui(dt_thumbnail_t *thumb)
Definition thumbnail.c:1449
void dt_thumbnail_update_selection(dt_thumbnail_t *thumb, gboolean selected)
Definition thumbnail.c:878
static gboolean _altered_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition thumbnail.c:1037
#define DEBUG
Definition thumbnail.c:708
static gboolean _event_main_motion(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
Definition thumbnail.c:985
static gboolean _event_image_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition thumbnail.c:1063
static int _finish_buffer_thread(dt_thumbnail_t *thumb, gboolean success)
Definition thumbnail.c:409
static gboolean _group_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition thumbnail.c:1054
void dt_thumbnail_alternative_mode(dt_thumbnail_t *thumb, gboolean enable)
Definition thumbnail.c:919
static gboolean _event_main_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition thumbnail.c:783
static void _thumb_write_extension(dt_thumbnail_t *thumb)
Definition thumbnail.c:165
static void _preview_window_open(GtkWidget *widget, dt_thumbnail_t *thumb)
Definition thumbnail.c:214
static int _thumb_resize_overlays(dt_thumbnail_t *thumb, int width, int height)
Definition thumbnail.c:1498
void _widget_set_size(GtkWidget *w, int *parent_width, int *parent_height, const gboolean update)
Definition thumbnail.c:1470
void dt_thumbnail_resize(dt_thumbnail_t *thumb, int width, int height)
Definition thumbnail.c:1561
int dt_thumbnail_get_image_buffer(dt_thumbnail_t *thumb)
Definition thumbnail.c:575
static void _active_modules_popup(GtkWidget *widget, dt_thumbnail_t *thumb)
Definition thumbnail.c:219
#define MAX_STARS
Definition thumbnail.h:44
dt_thumbnail_border_t
Definition thumbnail.h:49
@ DT_THUMBNAIL_BORDER_BOTTOM
Definition thumbnail.h:54
@ DT_THUMBNAIL_BORDER_NONE
Definition thumbnail.h:50
@ DT_THUMBNAIL_BORDER_TOP
Definition thumbnail.h:52
@ DT_THUMBNAIL_BORDER_RIGHT
Definition thumbnail.h:53
@ DT_THUMBNAIL_BORDER_LEFT
Definition thumbnail.h:51
dt_thumbnail_overlay_t
Definition thumbnail.h:58
@ DT_THUMBNAIL_OVERLAYS_ALWAYS_NORMAL
Definition thumbnail.h:61
@ DT_THUMBNAIL_OVERLAYS_NONE
Definition thumbnail.h:59
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:593
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:65
@ DT_THUMBTABLE_MODE_FILEMANAGER
Definition thumbtable.h:64
@ DT_THUMBTABLE_ZOOM_FIT
Definition thumbtable.h:76
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:865
gchar * dt_util_dstrcat(gchar *str, const gchar *format,...)
Definition utility.c:95
void dt_view_audio_start(dt_view_manager_t *vm, int32_t imgid)
Definition view.c:1445
void dt_view_audio_stop(dt_view_manager_t *vm)
Definition view.c:1477
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
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_surface_value_t
Definition view.h:103
@ DT_VIEW_SURFACE_OK
Definition view.h:104
dt_view_image_over_t
Definition view.h:167
@ DT_VIEW_STAR_4
Definition view.h:173
@ DT_VIEW_REJECT
Definition view.h:175
@ DT_VIEW_STAR_5
Definition view.h:174
@ DT_VIEW_STAR_3
Definition view.h:172
@ DT_VIEW_STAR_1
Definition view.h:170
@ DT_VIEW_STAR_2
Definition view.h:171
@ DT_VIEW_DESERT
Definition view.h:169