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 // Standalone dialog (no transient parent) to avoid GTK parent warnings from popup menus.
259 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("Active modules"),
260 NULL, GTK_DIALOG_DESTROY_WITH_PARENT,
261 _("_Close"), GTK_RESPONSE_CLOSE,
262 NULL);
263 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
264
265 GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
266 GtkWidget *scrolled = gtk_scrolled_window_new(NULL, NULL);
267 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
268 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
269 gtk_widget_set_size_request(scrolled, 420, 260);
270 gtk_container_set_border_width(GTK_CONTAINER(scrolled), 4);
271
272 GtkWidget *view = gtk_text_view_new();
273 dt_gui_textview_set_padding(GTK_TEXT_VIEW(view));
274 gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE);
275 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), FALSE);
276 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD_CHAR);
277 gtk_text_view_set_monospace(GTK_TEXT_VIEW(view), TRUE);
278 gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(view)), text->str, -1);
279 gtk_container_add(GTK_CONTAINER(scrolled), view);
280
281 gtk_box_pack_start(GTK_BOX(content), scrolled, TRUE, TRUE, 0);
282 gtk_widget_show_all(dialog);
283
284 g_signal_connect(dialog, "response", G_CALLBACK(gtk_widget_destroy), NULL);
285
286 g_string_free(text, TRUE);
287}
288
290{
291 // Always re-create the menu when we show it because we don't bother updating info during the lifetime of the thumbnail
292 GtkWidget *menu = gtk_menu_new();
293
294 // Filename: insensitive header to mean that the context menu is for this picture only
295 GtkWidget *menu_item = _gtk_menu_item_new_with_markup(thumb->info.filename, menu, NULL, thumb);
296 gtk_widget_set_sensitive(menu_item, FALSE);
297
298 GtkWidget *sep = gtk_separator_menu_item_new();
299 gtk_menu_shell_append(GTK_MENU_SHELL(menu), sep);
300
302 menu_item = _gtk_menu_item_new_with_markup(_("Image info"), menu, NULL, thumb);
303 GtkWidget *sub_menu = gtk_menu_new();
304 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), sub_menu);
305
306 _menuitem_from_text(_("Folder : "), thumb->info.folder, sub_menu, NULL, thumb);
307 _menuitem_from_text(_("Date : "), thumb->info.datetime, sub_menu, NULL, thumb);
308 _menuitem_from_text(_("Camera : "), thumb->info.camera_makermodel, sub_menu, NULL, thumb);
309 _menuitem_from_text(_("Lens : "), thumb->info.exif_lens, sub_menu, NULL, thumb);
310
311 sep = gtk_separator_menu_item_new();
312 gtk_menu_shell_append(GTK_MENU_SHELL(menu), sep);
313
315 menu_item = _gtk_menu_item_new_with_markup(_("Assign color labels"), menu, NULL, thumb);
316 sub_menu = gtk_menu_new();
317 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), sub_menu);
318
319 menu_item = _gtk_menu_item_new_with_markup("<span foreground='#BB2222'>\342\254\244</span> Red", sub_menu, _color_label_callback, thumb);
320 g_object_set_data(G_OBJECT(menu_item), "custom-data", GINT_TO_POINTER(0));
321
322 menu_item = _gtk_menu_item_new_with_markup("<span foreground='#BBBB22'>\342\254\244</span> Yellow", sub_menu, _color_label_callback, thumb);
323 g_object_set_data(G_OBJECT(menu_item), "custom-data", GINT_TO_POINTER(1));
324
325 menu_item = _gtk_menu_item_new_with_markup("<span foreground='#22BB22'>\342\254\244</span> Green", sub_menu, _color_label_callback, thumb);
326 g_object_set_data(G_OBJECT(menu_item), "custom-data", GINT_TO_POINTER(2));
327
328 menu_item = _gtk_menu_item_new_with_markup("<span foreground='#2222BB'>\342\254\244</span> Blue", sub_menu, _color_label_callback, thumb);
329 g_object_set_data(G_OBJECT(menu_item), "custom-data", GINT_TO_POINTER(3));
330
331 menu_item = _gtk_menu_item_new_with_markup("<span foreground='#BB22BB'>\342\254\244</span> Purple", sub_menu, _color_label_callback, thumb);
332 g_object_set_data(G_OBJECT(menu_item), "custom-data", GINT_TO_POINTER(4));
333
334 menu_item = _gtk_menu_item_new_with_markup(_("Open in preview window…"), menu, _preview_window_open, thumb);
335 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
336
337 sep = gtk_separator_menu_item_new();
338 gtk_menu_shell_append(GTK_MENU_SHELL(menu), sep);
339
340 menu_item = _gtk_menu_item_new_with_markup(_("Show active modules…"), menu, _active_modules_popup, thumb);
341 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
342
343 gtk_widget_show_all(menu);
344
345 return menu;
346}
347
348static gboolean _event_cursor_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
349{
350 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
352
353 GtkStateFlags state = gtk_widget_get_state_flags(thumb->w_cursor);
354 GtkStyleContext *context = gtk_widget_get_style_context(thumb->w_cursor);
355 GdkRGBA col;
356 gtk_style_context_get_color(context, state, &col);
357
358 cairo_set_source_rgba(cr, col.red, col.green, col.blue, col.alpha);
359 cairo_line_to(cr, gtk_widget_get_allocated_width(widget), 0);
360 cairo_line_to(cr, gtk_widget_get_allocated_width(widget) / 2, gtk_widget_get_allocated_height(widget));
361 cairo_line_to(cr, 0, 0);
362 cairo_close_path(cr);
363 cairo_fill(cr);
364
365 return TRUE;
366}
367
368
370{
371 if(thumb->img_surf && cairo_surface_get_reference_count(thumb->img_surf) > 0)
372 cairo_surface_destroy(thumb->img_surf);
373
374 thumb->img_surf = NULL;
375}
376
378{
379 if(IS_NULL_PTR(thumb)) return;
380
381 _free_image_surface(thumb);
383 dt_free(thumb);
384}
385
386static void _thumbnail_release(void *data)
387{
388 dt_thumbnail_t *thumb = (dt_thumbnail_t *)data;
389 if(IS_NULL_PTR(thumb)) return;
390
391 if(dt_atomic_sub_int(&thumb->ref_count, 1) == 1)
392 _thumbnail_free(thumb);
393}
394
395static gboolean _main_context_queue_draw(GtkWidget *widget)
396{
397 if(GTK_IS_WIDGET(widget))
398 {
399 gtk_widget_queue_draw(widget);
400 }
401
402 return G_SOURCE_REMOVE;
403}
404
405static int _finish_buffer_thread(dt_thumbnail_t *thumb, gboolean success)
406{
407 thumb_return_if_fails(thumb, 1);
408
410 thumb->image_inited = success;
411 thumb->job = NULL;
413
414 // Redraw events need to be sent from the main GUI thread
415 // though we may not have a target widget anymore...
416 if(!dt_atomic_get_int(&thumb->destroying) && thumb->w_image)
417 {
418 GMainContext *context = g_main_context_default();
419 g_main_context_invoke_full(context, G_PRIORITY_DEFAULT,
420 (GSourceFunc)_main_context_queue_draw,
421 g_object_ref(thumb->w_image),
422 (GDestroyNotify)g_object_unref);
423 g_main_context_wakeup(context);
424 }
425
426 return 0;
427}
428
429
431{
432 // WARNING: the target thumbnail GUI widget can be destroyed at any time during this
433 // control flow in the GUI mainthread.
435 thumb_return_if_fails(thumb, 1);
436 if(dt_atomic_get_int(&thumb->destroying)) return 1;
437
438 // The job was cancelled on the queue. Good chances of having thumb destroyed anytime soon.
439 if(IS_NULL_PTR(thumb->job) || thumb->job != job || dt_control_job_get_state(job) == DT_JOB_STATE_CANCELLED) return 1;
440
441 // Read and cache the thumb data now, while we have it. And lock it.
443
444 // These are the sizes of the widget bounding box
445 int img_w = thumb->img_w;
446 int img_h = thumb->img_h;
447
448 const int zoom = (thumb->table) ? thumb->table->zoom : DT_THUMBTABLE_ZOOM_FIT;
449 const gboolean show_focus_peaking = (thumb->table && thumb->table->focus_peaking);
450 const gboolean show_focus_clusters = (thumb->table && thumb->table->focus_regions);
451 const gboolean zoom_in = (thumb->table && thumb->table->zoom > DT_THUMBTABLE_ZOOM_FIT);
452 const int32_t imgid = thumb->info.id;
453
455
456 // These are the sizes of the actual image. Can be larger than the widget bounding box.
457 int img_width = 0;
458 int img_height = 0;
459
460 double zoomx = 0.;
461 double zoomy = 0.;
462 float x_center = 0.f;
463 float y_center = 0.f;
464
465 // From there, never read thumb->... directly since it might get destroyed in mainthread anytime.
466 dt_print(DT_DEBUG_LIGHTTABLE, "[lighttable] fetching or computing thumbnail %i\n", thumb->info.id);
467
468 // Get the actual image content. This typically triggers a rendering pipeline,
469 // and can possibly take a long time.
470 cairo_surface_t *surface = NULL;
471 dt_view_surface_value_t res = dt_view_image_get_surface(imgid, img_w, img_h, &surface, zoom);
472 if(surface && res == DT_VIEW_SURFACE_OK)
473 {
474 // The image is immediately available
475 img_width = cairo_image_surface_get_width(surface);
476 img_height = cairo_image_surface_get_height(surface);
477 }
478 else
479 {
481 return 0;
482 }
483
484 if(zoom > DT_THUMBTABLE_ZOOM_FIT || show_focus_peaking)
485 {
486 // Note: we compute the "sharpness density" unconditionnaly if the image is zoomed-in
487 // in order to get the details barycenter to init centering.
488 // Actual density are drawn only if the focus peaking mode is enabled.
489 cairo_t *cri = cairo_create(surface);
490 unsigned char *rgbbuf = cairo_image_surface_get_data(surface);
491 if(rgbbuf)
492 {
493 if(dt_focuspeaking(cri, rgbbuf, img_width, img_height, show_focus_peaking, &x_center, &y_center) != 0)
494 {
495 cairo_destroy(cri);
496 return 1;
497 }
498 }
499 cairo_destroy(cri);
500
501 // Init the zoom offset using the barycenter of details, to center
502 // the zoomed-in image on content that matters: details.
503 // Offset is expressed from the center of the image
504 if(zoom_in && x_center > 0.f && y_center > 0.f && thumb)
505 {
506 zoomx = (double)img_width / 2. - x_center;
507 zoomy = (double)img_height / 2. - y_center;
508 }
509 }
510
511 // if needed we compute and draw here the big rectangle to show focused areas
512 if(show_focus_clusters)
513 {
514 uint8_t *full_res_thumb = NULL;
515 int32_t full_res_thumb_wd, full_res_thumb_ht;
517 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))
518 {
519 // we look for focus areas
520 dt_focus_cluster_t full_res_focus[49];
521 const int frows = 5, fcols = 5;
522 dt_focus_create_clusters(full_res_focus, frows, fcols, full_res_thumb, full_res_thumb_wd,
523 full_res_thumb_ht);
524 // and we draw them on the image
525 cairo_t *cri = cairo_create(surface);
526 dt_focus_draw_clusters(cri, img_width, img_height, imgid, full_res_thumb_wd,
527 full_res_thumb_ht, full_res_focus, frows, fcols, 1.0, 0, 0);
528 cairo_destroy(cri);
529 }
530 dt_pixelpipe_cache_free_align(full_res_thumb);
531 }
532
533 // Commit the rendered surface only if the thumb is still live and expects this specific render.
534 // All validity checks and the surface write happen inside a single locked section to eliminate
535 // TOCTOU races with dt_thumbnail_destroy (which nulls widget pointers under the same lock)
536 // and dt_thumbnail_image_refresh_real (which clears thumb->job under the same lock to cancel
537 // stale renders from before a size change or data invalidation).
538 double sx = 1.0, sy = 1.0;
539 cairo_surface_get_device_scale(surface, &sx, &sy);
540
542 const gboolean still_valid = (thumb->job == job // not cancelled by refresh or destroy
543 && !dt_atomic_get_int(&thumb->destroying)
544 && thumb->w_image != NULL);
545 if(still_valid)
546 {
547 _free_image_surface(thumb);
548 thumb->img_width = roundf(img_width / sx);
549 thumb->img_height = roundf(img_height / sy);
550 thumb->zoomx = zoomx / sx;
551 thumb->zoomy = zoomy / sy;
552 thumb->img_surf = surface;
553 surface = NULL; // ownership transferred to thumb
554 }
556
557 if(surface)
558 {
559 cairo_surface_destroy(surface);
560 return 1;
561 }
562
564 return 0;
565}
566
568{
569 thumb_return_if_fails(thumb, 1);
570
571 // Avoid spawning multiple background jobs for the same thumbnail.
572 // Re-scheduling here is counter-productive because each new job replaces thumb->job and makes
573 // previously queued/running jobs exit early (thumb->job != job), which can lead to endless
574 // "busy" redraws without ever painting an image.
576 const gboolean job_running = (!IS_NULL_PTR(thumb->job));
578 if(job_running) return 0;
579
580 // - image inited: the cached buffer has a valid size. Invalid this flag when size changes.
581 // - img_surf: we have a cached buffer (cairo surface), regardless of its validity.
582 // - a rendering job has already been started
583 if((thumb->image_inited && thumb->img_surf && cairo_surface_get_reference_count(thumb->img_surf) > 0))
584 return 0;
585
586 // Get thumbnail GUI allocated size now (in GUI mainthread).
587 // Size requests may be unset (-1) during initial layout while allocations are already valid.
588 int img_w = gtk_widget_get_allocated_width(thumb->w_image);
589 int img_h = gtk_widget_get_allocated_height(thumb->w_image);
590 if(img_w < 2 || img_h < 2)
591 gtk_widget_get_size_request(thumb->w_image, &img_w, &img_h);
592
593 // Not allocated yet: wait for the next draw once the widget has a real size.
594 if(img_w < 2 || img_h < 2) return 0;
595
596 img_w = MAX(img_w, 32);
597 img_h = MAX(img_h, 32);
598
600 thumb->img_w = img_w;
601 thumb->img_h = img_h;
603
604 // Drawing the focus peaking and doing the color conversions
605 // can be expensive on large thumbnails. Do it in a background job,
606 // so the thumbtable stays responsive.
607 dt_job_t *job = dt_control_job_create(&_get_image_buffer, "get image %i", thumb->info.id);
608 if(IS_NULL_PTR(job)) return 1;
609
611 // Re-check now that we are about to publish the job pointer.
612 if(thumb->job || dt_atomic_get_int(&thumb->destroying))
613 {
616 return 0;
617 }
618 thumb->job = job;
619 dt_atomic_add_int(&thumb->ref_count, 1);
621
624 {
626 if(thumb->job == job) thumb->job = NULL;
628 _thumbnail_release(thumb);
629 return 1;
630 }
631
632 return 0;
633}
634
635
636
637static gboolean
638_thumb_draw_image(GtkWidget *widget, cairo_t *cr, gpointer user_data)
639{
640 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
642
643 int w = gtk_widget_get_allocated_width(widget);
644 int h = gtk_widget_get_allocated_height(widget);
645 if(w < 2 || h < 2) return TRUE;
646
655
656 dt_print(DT_DEBUG_LIGHTTABLE, "[lighttable] redrawing thumbnail %i\n", thumb->info.id);
657
659 if(thumb->img_surf && cairo_surface_get_reference_count(thumb->img_surf) > 0)
660 {
661 // we draw the image
662 cairo_save(cr);
663 double x_offset = (w - thumb->img_width) / 2.;
664 double y_offset = (h - thumb->img_height) / 2.;
665
666 // Sanitize zoom offsets
667 if(thumb->table && thumb->table->zoom > DT_THUMBTABLE_ZOOM_FIT)
668 {
669 thumb->zoomx = CLAMP(thumb->zoomx, -fabs(x_offset), fabs(x_offset));
670 thumb->zoomy = CLAMP(thumb->zoomy, -fabs(y_offset), fabs(y_offset));
671 }
672 else
673 {
674 thumb->zoomx = 0.;
675 thumb->zoomy = 0.;
676 }
677
678 cairo_set_source_surface(cr, thumb->img_surf, thumb->zoomx + x_offset, thumb->zoomy + y_offset);
679
680 // Paint background with CSS transparency
681 GdkRGBA im_color;
682 GtkStyleContext *context = gtk_widget_get_style_context(thumb->w_image);
683 gtk_style_context_get_color(context, gtk_widget_get_state_flags(thumb->w_image), &im_color);
684 cairo_paint_with_alpha(cr, im_color.alpha);
685
686 // Paint CSS borders
687 gtk_render_frame(context, cr, 0, 0, w, h);
688 cairo_restore(cr);
689 }
690
691 if(!thumb->image_inited || IS_NULL_PTR(thumb->img_surf))
692 {
693 dt_control_draw_busy_msg(cr, w, h);
694 }
696
697 return TRUE;
698}
699
700#define DEBUG 0
701
703{
705 if(IS_NULL_PTR(thumb->widget)) return;
706
707 gboolean show = (thumb->over > DT_THUMBNAIL_OVERLAYS_NONE);
708
709 if(GTK_IS_WIDGET(thumb->w_local_copy))
710 gtk_widget_set_visible(thumb->w_local_copy, (thumb->info.has_localcopy && show) || DEBUG);
711 if(GTK_IS_WIDGET(thumb->w_altered))
712 gtk_widget_set_visible(thumb->w_altered, (dt_thumbtable_info_is_altered(thumb->info) && show) || DEBUG);
713 if(GTK_IS_WIDGET(thumb->w_group))
714 gtk_widget_set_visible(thumb->w_group, (dt_thumbtable_info_is_grouped(thumb->info) && show) || DEBUG);
715 if(GTK_IS_WIDGET(thumb->w_audio))
716 gtk_widget_set_visible(thumb->w_audio, (thumb->info.has_audio && show) || DEBUG);
717 if(GTK_IS_WIDGET(thumb->w_color))
718 gtk_widget_set_visible(thumb->w_color, show || DEBUG);
719 if(GTK_IS_WIDGET(thumb->w_bottom_eb))
720 gtk_widget_set_visible(thumb->w_bottom_eb, show || DEBUG);
721 if(GTK_IS_WIDGET(thumb->w_reject))
722 gtk_widget_set_visible(thumb->w_reject, show || DEBUG);
723 if(GTK_IS_WIDGET(thumb->w_ext))
724 gtk_widget_set_visible(thumb->w_ext, show || DEBUG);
725 if(GTK_IS_WIDGET(thumb->w_cursor))
726 gtk_widget_show(thumb->w_cursor);
727
728 _set_flag(thumb->w_main, GTK_STATE_FLAG_PRELIGHT, thumb->mouse_over);
729 _set_flag(thumb->widget, GTK_STATE_FLAG_PRELIGHT, thumb->mouse_over);
730
731 _set_flag(thumb->w_reject, GTK_STATE_FLAG_ACTIVE, (thumb->info.rating == DT_VIEW_REJECT));
732
733 for(int i = 0; i < MAX_STARS; i++)
734 {
735 if(GTK_IS_WIDGET(thumb->w_stars[i]))
736 gtk_widget_set_visible(thumb->w_stars[i], show || DEBUG);
737 _set_flag(thumb->w_stars[i], GTK_STATE_FLAG_ACTIVE, (thumb->info.rating > i && thumb->info.rating < DT_VIEW_REJECT));
738 }
739
740 _set_flag(thumb->w_group, GTK_STATE_FLAG_ACTIVE, (thumb->info.id == thumb->info.group_id));
741 _set_flag(thumb->w_main, GTK_STATE_FLAG_SELECTED, thumb->selected);
742 _set_flag(thumb->widget, GTK_STATE_FLAG_SELECTED, thumb->selected);
743}
744
745static gboolean _event_main_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
746{
747 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
749 if(!gtk_widget_is_visible(thumb->widget)) return TRUE;
750
751 // Ensure mouse_over_id is set because that's what darkroom uses to open a picture.
752 // NOTE: Duplicate module uses that fucking thumbnail without a table...
753 if(thumb->table)
754 dt_thumbtable_dispatch_over(thumb->table, event->type, thumb->info.id);
755 else
757
758 // raise signal on double click
759 if(event->button == 1 && event->type == GDK_2BUTTON_PRESS)
760 {
761 thumb->dragging = FALSE;
763 return TRUE;
764 }
765 else if(event->button == GDK_BUTTON_SECONDARY && event->type == GDK_BUTTON_PRESS)
766 {
767 GtkWidget *menu = _create_menu(thumb);
768 gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL);
769 return TRUE;
770 }
771
772 return FALSE;
773}
774
775static gboolean _event_main_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
776{
777 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
779 thumb->dragging = FALSE;
780
781 // select on single click only in filemanager mode. Filmstrip mode only raises ACTIVATE signals.
782 if(event->button == 1
783 && thumb->table && thumb->table->mode == DT_THUMBTABLE_MODE_FILEMANAGER)
784 {
785 if(dt_modifier_is(event->state, 0))
787 else if(dt_modifier_is(event->state, GDK_CONTROL_MASK))
789 else if(dt_modifier_is(event->state, GDK_SHIFT_MASK) && thumb->table)
790 dt_thumbtable_select_range(thumb->table, thumb->rowid);
791 // Because selection might include several images, we handle styling globally
792 // in the thumbtable scope, catching the SELECTION_CHANGED signal.
793 return TRUE;
794 }
795 else if(event->button == 1
796 && thumb->table && thumb->table->mode == DT_THUMBTABLE_MODE_FILMSTRIP)
797 {
799 return TRUE;
800 }
801 return FALSE;
802}
803
804static gboolean _event_rating_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
805{
806 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
808 if(thumb->disable_actions) return FALSE;
809 if(dtgtk_thumbnail_btn_is_hidden(widget)) return FALSE;
810
811 if(event->button == 1)
812 {
814 if(widget == thumb->w_reject)
815 rating = DT_VIEW_REJECT;
816 else if(widget == thumb->w_stars[0])
817 rating = DT_VIEW_STAR_1;
818 else if(widget == thumb->w_stars[1])
819 rating = DT_VIEW_STAR_2;
820 else if(widget == thumb->w_stars[2])
821 rating = DT_VIEW_STAR_3;
822 else if(widget == thumb->w_stars[3])
823 rating = DT_VIEW_STAR_4;
824 else if(widget == thumb->w_stars[4])
825 rating = DT_VIEW_STAR_5;
826
827 if(rating != DT_VIEW_DESERT)
828 dt_ratings_apply_on_image(thumb->info.id, rating, TRUE, TRUE, TRUE);
829 }
830 return TRUE;
831}
832
833static gboolean _event_grouping_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
834{
835 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
837 if(thumb->disable_actions) return FALSE;
838 if(dtgtk_thumbnail_btn_is_hidden(widget)) return FALSE;
840 return FALSE;
841}
842
843static gboolean _event_audio_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
844{
845 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
847 if(thumb->disable_actions) return FALSE;
848 if(dtgtk_thumbnail_btn_is_hidden(widget)) return FALSE;
849
850 if(event->button == 1)
851 {
852 gboolean start_audio = TRUE;
854 {
855 // don't start the audio for the image we just killed it for
856 if(darktable.view_manager->audio.audio_player_id == thumb->info.id) start_audio = FALSE;
858 }
859
860 if(start_audio)
861 {
863 }
864 }
865 return FALSE;
866}
867
868
869
870void dt_thumbnail_update_selection(dt_thumbnail_t *thumb, gboolean selected)
871{
873 if(selected != thumb->selected)
874 {
875 thumb->selected = selected;
876 _thumb_update_icons(thumb);
877 }
878}
879
880
881// All the text info that we don't have room to display around the image
883{
885 gtk_label_set_text(GTK_LABEL(thumb->w_filename), thumb->info.filename);
886 gtk_label_set_text(GTK_LABEL(thumb->w_datetime), thumb->info.datetime);
887 gtk_label_set_text(GTK_LABEL(thumb->w_folder), thumb->info.folder);
888
889 char *exposure_time = dt_util_format_exposure(thumb->info.exif_exposure);
890 char *exposure_field = g_strdup_printf("%.0f ISO - f/%.1f - %s",
891 thumb->info.exif_iso,
892 thumb->info.exif_aperture,
893 exposure_time);
894 char *exposure_bias = g_strdup_printf("%+.1f EV", thumb->info.exif_exposure_bias);
895 char *focal = g_strdup_printf("%0.f mm @ %.2f m", thumb->info.exif_focal_length,
897
898 gtk_label_set_text(GTK_LABEL(thumb->w_exposure_bias), exposure_bias);
899 gtk_label_set_text(GTK_LABEL(thumb->w_exposure), exposure_field);
900 gtk_label_set_text(GTK_LABEL(thumb->w_camera), thumb->info.camera_makermodel);
901 gtk_label_set_text(GTK_LABEL(thumb->w_lens), thumb->info.exif_lens);
902 gtk_label_set_text(GTK_LABEL(thumb->w_focal), focal);
903
904 dt_free(focal);
905 dt_free(exposure_bias);
906 dt_free(exposure_field);
907 dt_free(exposure_time);
908}
909
910
912{
914 if(thumb->alternative_mode == enable) return;
915 thumb->alternative_mode = enable;
916 if(enable)
917 {
918 gtk_widget_set_no_show_all(thumb->w_alternative, FALSE);
919 gtk_widget_show_all(thumb->w_alternative);
920 }
921 else
922 {
923 gtk_widget_set_no_show_all(thumb->w_alternative, TRUE);
924 gtk_widget_hide(thumb->w_alternative);
925 }
926 gtk_widget_queue_draw(thumb->widget);
927}
928
929
930static gboolean _event_star_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
931{
932 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
934 if(thumb->disable_actions) return TRUE;
935 _set_flag(thumb->w_bottom_eb, GTK_STATE_FLAG_PRELIGHT, TRUE);
936
937 // we prelight all stars before the current one
938 gboolean pre = TRUE;
939 for(int i = 0; i < MAX_STARS; i++)
940 {
941 _set_flag(thumb->w_stars[i], GTK_STATE_FLAG_PRELIGHT, pre);
942
943 // We don't want the active state to overlap the prelight one because
944 // it makes the feature hard to read/understand.
945 _set_flag(thumb->w_stars[i], GTK_STATE_FLAG_ACTIVE, FALSE);
946
947 if(thumb->w_stars[i] == widget) pre = FALSE;
948 }
949 return TRUE;
950}
951
952
953static gboolean _event_star_leave(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
954{
955 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
957 if(thumb->disable_actions) return TRUE;
958
959 for(int i = 0; i < MAX_STARS; i++)
960 {
961 _set_flag(thumb->w_stars[i], GTK_STATE_FLAG_PRELIGHT, FALSE);
962
963 // restore active state
964 _set_flag(thumb->w_stars[i], GTK_STATE_FLAG_ACTIVE, i < thumb->info.rating && thumb->info.rating < DT_VIEW_REJECT);
965 }
966 return TRUE;
967}
968
969
970gboolean _event_expose(GtkWidget *self, cairo_t *cr, gpointer user_data)
971{
972 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
974 return FALSE;
975}
976
977static gboolean _event_main_motion(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
978{
979 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
981 if(!gtk_widget_is_visible(thumb->widget)) return TRUE;
982 if(!thumb->mouse_over)
983 {
984 // Thumbnails send leave-notify when in the thumbnail frame but over the image.
985 // If we lost the mouse-over in this case, grab it again from mouse motion.
986 // Be conservative with sending mouse_over_id events/signal because many
987 // places in the soft listen to them and refresh stuff from DB, so it's expensive.
988 if(thumb->table)
989 dt_thumbtable_dispatch_over(thumb->table, event->type, thumb->info.id);
990 else
992
994 }
995 return FALSE;
996}
997
998static gboolean _event_main_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
999{
1000 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1002 if(!gtk_widget_is_visible(thumb->widget)) return TRUE;
1003
1004 if(thumb->table)
1005 dt_thumbtable_dispatch_over(thumb->table, event->type, thumb->info.id);
1006 else
1008
1010 return FALSE;
1011}
1012
1013static gboolean _event_main_leave(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
1014{
1015 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1017 if(!gtk_widget_is_visible(thumb->widget)) return TRUE;
1018
1019 if(thumb->table)
1020 dt_thumbtable_dispatch_over(thumb->table, event->type, -1);
1021 else
1023
1025 return FALSE;
1026}
1027
1028// lazy-load the history tooltip only when mouse enters the button
1029static gboolean _altered_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
1030{
1031 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1034 {
1036 if(tooltip)
1037 {
1038 gtk_widget_set_tooltip_text(thumb->w_altered, tooltip);
1040 }
1041 }
1042 return FALSE;
1043}
1044
1045
1046static gboolean _group_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
1047{
1048 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1051 return FALSE;
1052}
1053
1054
1055static gboolean _event_image_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1056{
1057 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1059
1060 if(event->button == 1 && thumb->table && thumb->table->zoom > DT_THUMBTABLE_ZOOM_FIT)
1061 {
1062 thumb->dragging = TRUE;
1063 thumb->drag_x_start = event->x;
1064 thumb->drag_y_start = event->y;
1065 }
1066
1067 return FALSE;
1068}
1069
1070static gboolean _event_image_motion(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
1071{
1072 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1074 if(thumb->dragging)
1075 {
1076 const double delta_x = event->x - thumb->drag_x_start;
1077 const double delta_y = event->y - thumb->drag_y_start;
1078 const gboolean global_shift = dt_modifier_is(event->state, GDK_SHIFT_MASK) && thumb->table;
1079
1080 if(global_shift)
1081 {
1082 // Offset all thumbnails by this amount
1083 dt_thumbtable_offset_zoom(thumb->table, delta_x, delta_y);
1084 }
1085 else
1086 {
1087 // Offset only the current thumbnail
1088 thumb->zoomx += delta_x;
1089 thumb->zoomy += delta_y;
1090 }
1091
1092 // Reset drag origin
1093 thumb->drag_x_start = event->x;
1094 thumb->drag_y_start = event->y;
1095
1096 if(!global_shift)
1097 gtk_widget_queue_draw(thumb->w_image);
1098
1099 return TRUE;
1100 }
1101 return FALSE;
1102}
1103
1104static gboolean _event_image_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1105{
1106 dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
1108 thumb->dragging = FALSE;
1109 return FALSE;
1110}
1111
1113{
1114 // Let the background event box capture all user events from its children first,
1115 // so we don't have to wire leave/enter events to all of them individually.
1116 // Children buttons will mostly only use button pressed/released events
1117 thumb->widget = gtk_event_box_new();
1118 dt_gui_add_class(thumb->widget, "thumb-cell");
1119 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);
1120
1121 // this is only here to ensure that mouse-over value is updated correctly
1122 // all dragging actions take place inside thumbatble.c
1123 gtk_drag_dest_set(thumb->widget, GTK_DEST_DEFAULT_MOTION, target_list_all, n_targets_all, GDK_ACTION_MOVE);
1124 g_object_set_data(G_OBJECT(thumb->widget), "thumb", thumb);
1125 gtk_widget_show(thumb->widget);
1126
1127 g_signal_connect(G_OBJECT(thumb->widget), "button-press-event", G_CALLBACK(_event_main_press), thumb);
1128 g_signal_connect(G_OBJECT(thumb->widget), "button-release-event", G_CALLBACK(_event_main_release), thumb);
1129 g_signal_connect(G_OBJECT(thumb->widget), "enter-notify-event", G_CALLBACK(_event_main_enter), thumb);
1130 g_signal_connect(G_OBJECT(thumb->widget), "leave-notify-event", G_CALLBACK(_event_main_leave), thumb);
1131 g_signal_connect(G_OBJECT(thumb->widget), "motion-notify-event", G_CALLBACK(_event_main_motion), thumb);
1132 g_signal_connect(G_OBJECT(thumb->widget), "draw", G_CALLBACK(_event_expose), thumb);
1133
1134 // Main widget
1135 thumb->w_main = gtk_overlay_new();
1136 dt_gui_add_class(thumb->w_main, "thumb-main");
1137 gtk_widget_set_valign(thumb->w_main, GTK_ALIGN_CENTER);
1138 gtk_widget_set_halign(thumb->w_main, GTK_ALIGN_CENTER);
1139 gtk_container_add(GTK_CONTAINER(thumb->widget), thumb->w_main);
1140 gtk_widget_show(thumb->w_main);
1141
1142 thumb->w_background = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1143 dt_gui_add_class(thumb->w_background, "thumb-background");
1144 gtk_widget_set_valign(thumb->w_background, GTK_ALIGN_FILL);
1145 gtk_widget_set_halign(thumb->w_background, GTK_ALIGN_FILL);
1146 gtk_overlay_add_overlay(GTK_OVERLAY(thumb->w_main), thumb->w_background);
1147 gtk_widget_show(thumb->w_background);
1148 gtk_overlay_set_overlay_pass_through(GTK_OVERLAY(thumb->w_main), thumb->w_background, TRUE);
1149
1150 // triangle to indicate current image(s) in filmstrip
1151 thumb->w_cursor = gtk_drawing_area_new();
1152 dt_gui_add_class(thumb->w_cursor, "thumb-cursor");
1153 gtk_widget_set_valign(thumb->w_cursor, GTK_ALIGN_START);
1154 gtk_widget_set_halign(thumb->w_cursor, GTK_ALIGN_CENTER);
1155 g_signal_connect(G_OBJECT(thumb->w_cursor), "draw", G_CALLBACK(_event_cursor_draw), thumb);
1156 gtk_overlay_add_overlay(GTK_OVERLAY(thumb->w_main), thumb->w_cursor);
1157
1158 // the image drawing area
1159 thumb->w_image = gtk_drawing_area_new();
1160 dt_gui_add_class(thumb->w_image, "thumb-image");
1161 gtk_widget_set_valign(thumb->w_image, GTK_ALIGN_CENTER);
1162 gtk_widget_set_halign(thumb->w_image, GTK_ALIGN_CENTER);
1163 gtk_widget_set_events(thumb->w_image, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK);
1164 g_signal_connect(G_OBJECT(thumb->w_image), "draw", G_CALLBACK(_thumb_draw_image), thumb);
1165 g_signal_connect(G_OBJECT(thumb->w_image), "button-press-event", G_CALLBACK(_event_image_press), thumb);
1166 g_signal_connect(G_OBJECT(thumb->w_image), "button-release-event", G_CALLBACK(_event_image_release), thumb);
1167 g_signal_connect(G_OBJECT(thumb->w_image), "motion-notify-event", G_CALLBACK(_event_image_motion), thumb);
1168 gtk_widget_show(thumb->w_image);
1169 gtk_overlay_add_overlay(GTK_OVERLAY(thumb->w_main), thumb->w_image);
1170 gtk_overlay_set_overlay_pass_through(GTK_OVERLAY(thumb->w_main), thumb->w_image, TRUE);
1171
1172 thumb->w_bottom_eb = gtk_event_box_new();
1173 gtk_widget_set_valign(thumb->w_bottom_eb, GTK_ALIGN_END);
1174 gtk_widget_set_halign(thumb->w_bottom_eb, GTK_ALIGN_FILL);
1175 gtk_widget_show(thumb->w_bottom_eb);
1176 gtk_overlay_add_overlay(GTK_OVERLAY(thumb->w_main), thumb->w_bottom_eb);
1177
1178 GtkWidget *bottom_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1179 dt_gui_add_class(bottom_box, "thumb-bottom");
1180 gtk_container_add(GTK_CONTAINER(thumb->w_bottom_eb), bottom_box);
1181 gtk_widget_show(bottom_box);
1182
1183 // the reject icon
1185 dt_gui_add_class(thumb->w_reject, "thumb-reject");
1186 gtk_widget_set_valign(thumb->w_reject, GTK_ALIGN_CENTER);
1187 gtk_widget_set_halign(thumb->w_reject, GTK_ALIGN_START);
1188 gtk_widget_show(thumb->w_reject);
1189 g_signal_connect(G_OBJECT(thumb->w_reject), "button-release-event", G_CALLBACK(_event_rating_release), thumb);
1190 gtk_box_pack_start(GTK_BOX(bottom_box), thumb->w_reject, FALSE, FALSE, 0);
1191
1192 GtkWidget *stars_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1193 gtk_box_pack_start(GTK_BOX(bottom_box), stars_box, TRUE, TRUE, 0);
1194 gtk_widget_set_valign(stars_box, GTK_ALIGN_CENTER);
1195 gtk_widget_set_halign(stars_box, GTK_ALIGN_CENTER);
1196 gtk_widget_set_hexpand(stars_box, TRUE);
1197 gtk_widget_show(stars_box);
1198
1199 // the stars
1200 for(int i = 0; i < MAX_STARS; i++)
1201 {
1203 g_signal_connect(G_OBJECT(thumb->w_stars[i]), "enter-notify-event", G_CALLBACK(_event_star_enter), thumb);
1204 g_signal_connect(G_OBJECT(thumb->w_stars[i]), "leave-notify-event", G_CALLBACK(_event_star_leave), thumb);
1205 g_signal_connect(G_OBJECT(thumb->w_stars[i]), "button-release-event", G_CALLBACK(_event_rating_release),
1206 thumb);
1207 dt_gui_add_class(thumb->w_stars[i], "thumb-star");
1208 gtk_widget_set_valign(thumb->w_stars[i], GTK_ALIGN_CENTER);
1209 gtk_widget_set_halign(thumb->w_stars[i], GTK_ALIGN_CENTER);
1210 gtk_widget_show(thumb->w_stars[i]);
1211 gtk_box_pack_start(GTK_BOX(stars_box), thumb->w_stars[i], FALSE, FALSE, 0);
1212 }
1213
1214 // the color labels
1216 dt_gui_add_class(thumb->w_color, "thumb-colorlabels");
1217 gtk_widget_set_valign(thumb->w_color, GTK_ALIGN_CENTER);
1218 gtk_widget_set_halign(thumb->w_color, GTK_ALIGN_END);
1219 gtk_widget_set_no_show_all(thumb->w_color, TRUE);
1220 gtk_box_pack_start(GTK_BOX(bottom_box), thumb->w_color, FALSE, FALSE, 0);
1221
1222 thumb->w_top_eb = gtk_event_box_new();
1223 gtk_widget_set_valign(thumb->w_top_eb, GTK_ALIGN_START);
1224 gtk_widget_set_halign(thumb->w_top_eb, GTK_ALIGN_FILL);
1225 gtk_widget_show(thumb->w_top_eb);
1226 gtk_overlay_add_overlay(GTK_OVERLAY(thumb->w_main), thumb->w_top_eb);
1227
1228 GtkWidget *top_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1229 dt_gui_add_class(top_box, "thumb-top");
1230 gtk_container_add(GTK_CONTAINER(thumb->w_top_eb), top_box);
1231 gtk_widget_show(top_box);
1232
1233 // the file extension label
1234 thumb->w_ext = gtk_label_new("");
1235 dt_gui_add_class(thumb->w_ext, "thumb-ext");
1236 gtk_widget_set_valign(thumb->w_ext, GTK_ALIGN_CENTER);
1237 gtk_widget_show(thumb->w_ext);
1238 gtk_box_pack_start(GTK_BOX(top_box), thumb->w_ext, FALSE, FALSE, 0);
1239
1240 // the local copy indicator
1242 dt_gui_add_class(thumb->w_local_copy, "thumb-localcopy");
1243 gtk_widget_set_tooltip_text(thumb->w_local_copy, _("This picture is locally copied on your disk cache"));
1244 gtk_widget_set_valign(thumb->w_local_copy, GTK_ALIGN_CENTER);
1245 gtk_widget_set_no_show_all(thumb->w_local_copy, TRUE);
1246 gtk_box_pack_start(GTK_BOX(top_box), thumb->w_local_copy, FALSE, FALSE, 0);
1247
1248 // the altered icon
1250 g_signal_connect(G_OBJECT(thumb->w_altered), "enter-notify-event", G_CALLBACK(_altered_enter), thumb);
1251 dt_gui_add_class(thumb->w_altered, "thumb-altered");
1252 gtk_widget_set_valign(thumb->w_altered, GTK_ALIGN_CENTER);
1253 gtk_widget_set_no_show_all(thumb->w_altered, TRUE);
1254 gtk_box_pack_end(GTK_BOX(top_box), thumb->w_altered, FALSE, FALSE, 0);
1255
1256 // the group bouton
1258 dt_gui_add_class(thumb->w_group, "thumb-group");
1259 g_signal_connect(G_OBJECT(thumb->w_group), "button-release-event", G_CALLBACK(_event_grouping_release), thumb);
1260 g_signal_connect(G_OBJECT(thumb->w_group), "enter-notify-event", G_CALLBACK(_group_enter), thumb);
1261 gtk_widget_set_valign(thumb->w_group, GTK_ALIGN_CENTER);
1262 gtk_widget_set_no_show_all(thumb->w_group, TRUE);
1263 gtk_box_pack_end(GTK_BOX(top_box), thumb->w_group, FALSE, FALSE, 0);
1264
1265 // the sound icon
1267 dt_gui_add_class(thumb->w_audio, "thumb-audio");
1268 g_signal_connect(G_OBJECT(thumb->w_audio), "button-release-event", G_CALLBACK(_event_audio_release), thumb);
1269 gtk_widget_set_valign(thumb->w_audio, GTK_ALIGN_CENTER);
1270 gtk_widget_set_no_show_all(thumb->w_audio, TRUE);
1271 gtk_box_pack_end(GTK_BOX(top_box), thumb->w_audio, FALSE, FALSE, 0);
1272
1273 thumb->w_alternative = gtk_overlay_new();
1274 gtk_overlay_add_overlay(GTK_OVERLAY(thumb->w_main), thumb->w_alternative);
1275 gtk_widget_set_halign(thumb->w_alternative, GTK_ALIGN_FILL);
1276 gtk_widget_set_valign(thumb->w_alternative, GTK_ALIGN_FILL);
1277 gtk_widget_hide(thumb->w_alternative);
1278
1279 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1280 gtk_container_add(GTK_CONTAINER(thumb->w_alternative), box);
1281 dt_gui_add_class(box, "thumb-alternative");
1282
1283 GtkWidget *bbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1284 gtk_widget_set_valign(bbox, GTK_ALIGN_START);
1285 gtk_box_pack_start(GTK_BOX(box), bbox, TRUE, TRUE, 0);
1286 thumb->w_filename = gtk_label_new("");
1287 gtk_label_set_ellipsize(GTK_LABEL(thumb->w_filename), PANGO_ELLIPSIZE_MIDDLE);
1288 gtk_box_pack_start(GTK_BOX(bbox), thumb->w_filename, FALSE, FALSE, 0);
1289 thumb->w_datetime = gtk_label_new("");
1290 gtk_box_pack_start(GTK_BOX(bbox), thumb->w_datetime, FALSE, FALSE, 0);
1291 thumb->w_folder = gtk_label_new("");
1292 gtk_label_set_ellipsize(GTK_LABEL(thumb->w_folder), PANGO_ELLIPSIZE_MIDDLE);
1293 gtk_box_pack_start(GTK_BOX(bbox), thumb->w_folder, FALSE, FALSE, 0);
1294
1295 bbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1296 gtk_widget_set_valign(bbox, GTK_ALIGN_CENTER);
1297 gtk_box_pack_start(GTK_BOX(box), bbox, TRUE, TRUE, 0);
1298 thumb->w_exposure = gtk_label_new("");
1299 gtk_box_pack_start(GTK_BOX(bbox), thumb->w_exposure, FALSE, FALSE, 0);
1300 thumb->w_exposure_bias = gtk_label_new("");
1301 gtk_box_pack_start(GTK_BOX(bbox), thumb->w_exposure_bias, FALSE, FALSE, 0);
1302
1303 bbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1304 gtk_widget_set_valign(bbox, GTK_ALIGN_END);
1305 gtk_box_pack_start(GTK_BOX(box), bbox, TRUE, TRUE, 0);
1306 thumb->w_camera = gtk_label_new("");
1307 gtk_box_pack_start(GTK_BOX(bbox), thumb->w_camera, FALSE, FALSE, 0);
1308 thumb->w_lens = gtk_label_new("");
1309 gtk_label_set_ellipsize(GTK_LABEL(thumb->w_lens), PANGO_ELLIPSIZE_MIDDLE);
1310 gtk_box_pack_start(GTK_BOX(bbox), thumb->w_lens, FALSE, FALSE, 0);
1311 thumb->w_focal = gtk_label_new("");
1312 gtk_box_pack_start(GTK_BOX(bbox), thumb->w_focal, FALSE, FALSE, 0);
1313 gtk_widget_set_no_show_all(thumb->w_alternative, TRUE);
1314
1315 return thumb->widget;
1316}
1317
1319{
1320 if(IS_NULL_PTR(thumb) || IS_NULL_PTR(info)) return;
1321
1322 dt_thumbtable_copy_image(&thumb->info, info);
1323
1324 if(!thumb->widget || IS_NULL_PTR(thumb->w_main)) return;
1325
1327 _thumb_update_icons(thumb);
1330
1331 if(thumb->w_color)
1332 {
1334 btn->icon_flags = thumb->info.color_labels;
1335 }
1336}
1337
1339{
1340 dt_thumbnail_t *thumb = calloc(1, sizeof(dt_thumbnail_t));
1341
1342 thumb->rowid = rowid;
1343 thumb->over = over;
1344 thumb->table = table;
1345 thumb->zoomx = 0.;
1346 thumb->zoomy = 0.;
1347 thumb->job = NULL;
1348 thumb->img_h = 0;
1349 thumb->img_w = 0;
1351 dt_atomic_set_int(&thumb->ref_count, 1);
1352
1353 dt_pthread_mutex_init(&thumb->lock, NULL);
1354
1356
1357 dt_thumbnail_resync_info(thumb, info);
1359
1360 // This will then only run on "selection_changed" event
1362
1363 return thumb;
1364}
1365
1367{
1368 if(IS_NULL_PTR(thumb)) return 0;
1369
1371
1372 // Unregister the thumbnail from the parent table before any callback can
1373 // pick it again through the LUT or imgid hash during view/selection updates.
1374 if(thumb->table)
1375 {
1377 if(g_hash_table_lookup(thumb->table->list, GINT_TO_POINTER(thumb->info.id)) == thumb)
1378 g_hash_table_remove(thumb->table->list, GINT_TO_POINTER(thumb->info.id));
1379
1380 if(thumb->table->lut)
1381 for(int rowid = 0; rowid < thumb->table->collection_count; rowid++)
1382 if(thumb->table->lut[rowid].thumb == thumb)
1383 thumb->table->lut[rowid].thumb = NULL;
1384
1386 }
1387
1388 // Detach the thumbnail from Gtk immediately, but keep it alive until any queued
1389 // background rendering job gets cancelled and disposed by the job queue.
1390 dt_pthread_mutex_lock(&thumb->lock);
1391
1392 thumb->job = NULL;
1393
1394 // remove multiple delayed gtk_widget_queue_draw triggers
1395 while(g_idle_remove_by_data(thumb->widget))
1396 ;
1397 while(g_idle_remove_by_data(thumb->w_image))
1398 ;
1399
1400 if(thumb->img_surf && cairo_surface_get_reference_count(thumb->img_surf) > 0)
1401 cairo_surface_destroy(thumb->img_surf);
1402 thumb->img_surf = NULL;
1403
1404 if(thumb->widget)
1405 {
1406 GtkWidget *parent = gtk_widget_get_parent(thumb->widget);
1407 if(parent && GTK_IS_CONTAINER(parent))
1408 gtk_container_remove(GTK_CONTAINER(parent), thumb->widget);
1409 }
1410 thumb->widget = NULL;
1411 thumb->w_main = NULL;
1412 thumb->w_image = NULL;
1413 thumb->w_cursor = NULL;
1414 thumb->w_bottom_eb = NULL;
1415 thumb->w_reject = NULL;
1416 thumb->w_color = NULL;
1417 thumb->w_ext = NULL;
1418 thumb->w_local_copy = NULL;
1419 thumb->w_altered = NULL;
1420 thumb->w_group = NULL;
1421 thumb->w_audio = NULL;
1422 thumb->w_top_eb = NULL;
1423 thumb->w_alternative = NULL;
1424 thumb->w_filename = NULL;
1425 thumb->w_datetime = NULL;
1426 thumb->w_folder = NULL;
1427 thumb->w_exposure = NULL;
1428 thumb->w_exposure_bias = NULL;
1429 thumb->w_camera = NULL;
1430 thumb->w_lens = NULL;
1431 thumb->w_focal = NULL;
1432 for(int i = 0; i < MAX_STARS; i++) thumb->w_stars[i] = NULL;
1433
1435
1436 _thumbnail_release(thumb);
1437
1438 return 0;
1439}
1440
1442{
1443 thumb_return_if_fails(thumb);
1445 if(GTK_IS_WIDGET(thumb->w_color))
1446 {
1448 btn->icon_flags = thumb->info.color_labels;
1449 }
1451 _thumb_update_icons(thumb);
1453}
1454
1456{
1457 thumb_return_if_fails(thumb);
1458 thumb->over = mode;
1459}
1460
1461// if update, the internal width and height, minus margins and borders, are written back in input
1462void _widget_set_size(GtkWidget *w, int *parent_width, int *parent_height, const gboolean update)
1463{
1464 GtkStateFlags state = gtk_widget_get_state_flags(w);
1465 GtkStyleContext *context = gtk_widget_get_style_context(w);
1466
1467 GtkBorder margins;
1468 gtk_style_context_get_margin(context, state, &margins);
1469
1470 int width = *parent_width - margins.left - margins.right;
1471 int height = *parent_height - margins.top - margins.bottom;
1472
1473 if(width > 0 && height > 0)
1474 {
1475 gtk_widget_set_size_request(w, width, height);
1476
1477 // unvisible widgets need to be allocated to be able to measure the size of flexible boxes.
1478 GtkAllocation alloc = { .x = 0, .y = 0, .width = width, .height = height };
1479 gtk_widget_size_allocate(w, &alloc);
1480 }
1481
1482 if(update)
1483 {
1484 *parent_width = width;
1485 *parent_height = height;
1486 }
1487}
1488
1489
1491{
1492 thumb_return_if_fails(thumb, 0);
1493
1494 // we need to squeeze reject + space + stars + space + colorlabels icons on a thumbnail width
1495 // that means a width of 4 + MAX_STARS icons size
1496 // all icons and spaces having a width of 2 * r1
1497 // inner margins are defined in css (margin_* values)
1498
1499 // retrieves the size of the main icons in the top panel, thumbtable overlays shall not exceed that
1500 const int n_widgets = 4 + MAX_STARS;
1501 const float r1 = fminf(1.25 * DT_GUI_EM_SIZE / 2.,
1502 (float)(width - (n_widgets - 1) * DT_GUI_BOX_SPACING) / (2. * n_widgets));
1503 int icon_size = roundf(2 * r1);
1504
1505 // reject icon
1506 gtk_widget_set_size_request(thumb->w_reject, icon_size, icon_size);
1507
1508 // stars
1509 for(int i = 0; i < MAX_STARS; i++)
1510 gtk_widget_set_size_request(thumb->w_stars[i], icon_size, icon_size);
1511
1512 // the color labels
1513 gtk_widget_set_size_request(thumb->w_color, icon_size, icon_size);
1514
1515 // the local copy indicator
1516 _set_flag(thumb->w_local_copy, GTK_STATE_FLAG_ACTIVE, FALSE);
1517 gtk_widget_set_size_request(thumb->w_local_copy, icon_size, icon_size);
1518
1519 // the altered icon
1520 gtk_widget_set_size_request(thumb->w_altered, icon_size, icon_size);
1521
1522 // the group bouton
1523 gtk_widget_set_size_request(thumb->w_group, icon_size, icon_size);
1524
1525 // the sound icon
1526 gtk_widget_set_size_request(thumb->w_audio, icon_size, icon_size);
1527
1528 // the filmstrip cursor
1529 gtk_widget_set_size_request(thumb->w_cursor, 6.0 * r1, 1.5 * r1);
1530
1531 // extension text
1532 PangoAttrList *attrlist = pango_attr_list_new();
1533 PangoAttribute *attr = pango_attr_size_new_absolute(icon_size * PANGO_SCALE * 0.9);
1534 pango_attr_list_insert(attrlist, attr);
1535 gtk_label_set_attributes(GTK_LABEL(thumb->w_ext), attrlist);
1536 pango_attr_list_unref(attrlist);
1537
1538 return icon_size;
1539}
1540
1541// This function is called only from the thumbtable, when the grid size changed.
1542// NOTE: thumb->widget is a grid cell. It should not get styled, especially not with margins/padding.
1543// Styling starts at thumb->w_main, aka .thumb-main in CSS, which gets centered in the grid cell.
1544// Overlays need to be set prior to calling this function because they can change internal sizings.
1545// It is expected that this function is called only when needed, that is if the size requirements actually
1546// changed, meaning this check needs to be done upstream because we internally nuke the image surface on every call.
1548{
1549 thumb_return_if_fails(thumb);
1550 //fprintf(stdout, "calling resize on %i with overlay %i\n", thumb->info.id, thumb->over);
1551
1552 if(width < 1 || height < 1) return;
1553
1554 // widget resizing
1555 thumb->width = width;
1556 thumb->height = height;
1558
1559 // Apply margins & borders on the main widget
1561
1562 // Update show/hide status for overlays now, because we pack them in boxes
1563 // so the children need to be sized before their parents for the boxes to have proper size.
1564 gtk_widget_show_all(thumb->widget);
1565 _thumb_update_icons(thumb);
1566
1567 // Proceed with overlays resizing
1568 int icon_size = _thumb_resize_overlays(thumb, width, height);
1569
1570 // Finish with updating the image size
1572 {
1573 // Persistent overlays shouldn't overlap with image, so resize it.
1574 // NOTE: this is why we need to allocate above
1575 int margin_bottom = gtk_widget_get_allocated_height(thumb->w_bottom_eb);
1576 int margin_top = gtk_widget_get_allocated_height(thumb->w_top_eb);
1577 height -= 2 * MAX(MAX(margin_top, margin_bottom), icon_size);
1578 // In case top and bottom bars of overlays have different sizes,
1579 // we resize symetrically to the largest.
1580 }
1582
1584}
1585
1587{
1588 thumb_return_if_fails(thumb);
1589
1590 if(border == DT_THUMBNAIL_BORDER_NONE)
1591 {
1592 dt_gui_remove_class(thumb->widget, "dt_group_left");
1593 dt_gui_remove_class(thumb->widget, "dt_group_top");
1594 dt_gui_remove_class(thumb->widget, "dt_group_right");
1595 dt_gui_remove_class(thumb->widget, "dt_group_bottom");
1597 return;
1598 }
1599 if(border & DT_THUMBNAIL_BORDER_LEFT)
1600 dt_gui_add_class(thumb->widget, "dt_group_left");
1601 if(border & DT_THUMBNAIL_BORDER_TOP)
1602 dt_gui_add_class(thumb->widget, "dt_group_top");
1603 if(border & DT_THUMBNAIL_BORDER_RIGHT)
1604 dt_gui_add_class(thumb->widget, "dt_group_right");
1605 if(border & DT_THUMBNAIL_BORDER_BOTTOM)
1606 dt_gui_add_class(thumb->widget, "dt_group_bottom");
1607
1608 thumb->group_borders |= border;
1609}
1610
1612{
1613 thumb_return_if_fails(thumb);
1614
1615 if(thumb->mouse_over == over) return;
1616 thumb->mouse_over = over;
1617 if(thumb->table) thumb->table->rowid = thumb->rowid;
1618
1619 _set_flag(thumb->widget, GTK_STATE_FLAG_PRELIGHT, thumb->mouse_over);
1620 _set_flag(thumb->w_bottom_eb, GTK_STATE_FLAG_PRELIGHT, thumb->mouse_over);
1621 _set_flag(thumb->w_main, GTK_STATE_FLAG_PRELIGHT, thumb->mouse_over);
1622
1623 _thumb_update_icons(thumb);
1624}
1625
1626// set if the thumbnail should react (mouse_over) to drag and drop
1627// note that it's just cosmetic as dropping occurs in thumbtable in any case
1628void dt_thumbnail_set_drop(dt_thumbnail_t *thumb, gboolean accept_drop)
1629{
1630 thumb_return_if_fails(thumb);
1631
1632 if(accept_drop)
1633 gtk_drag_dest_set(thumb->w_main, GTK_DEST_DEFAULT_MOTION, target_list_all, n_targets_all, GDK_ACTION_MOVE);
1634 else
1635 gtk_drag_dest_unset(thumb->w_main);
1636}
1637
1638// Apply new mipmap on thumbnail
1640{
1641 thumb_return_if_fails(thumb, G_SOURCE_REMOVE);
1642
1643 dt_pthread_mutex_lock(&thumb->lock);
1644 // Cancel any in-flight render job. It was started for the old state (stale size or data).
1645 // The job checks thumb->job == job under the same lock before committing; clearing it here
1646 // makes that check fail so the old result is discarded and a fresh job can be queued.
1647 thumb->job = NULL;
1648 thumb->image_inited = FALSE;
1650
1651 // Queue redraw on the drawing area itself: it's the widget that requests/regenerates the cairo surface.
1652 // Queueing only the parent overlay may not invalidate the drawing area's window, leaving stale (too small)
1653 // cached surfaces until some pointer event happens.
1654 if(thumb->w_image) gtk_widget_queue_draw(thumb->w_image);
1655 if(thumb->w_main) gtk_widget_queue_draw(thumb->w_main);
1656 return G_SOURCE_REMOVE;
1657}
1658
1659// clang-format off
1660// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1661// vim: shiftwidth=2 expandtab tabstop=2 cindent
1662// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1663// clang-format on
int scrolled(struct dt_iop_module_t *self, double x, double y, int up, uint32_t state)
Definition ashift.c:4895
#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:505
void dt_control_set_mouse_over_id(int32_t value)
Definition control.c:931
uint32_t view(const dt_view_t *self)
Definition darkroom.c:227
darktable_t darktable
Definition darktable.c:181
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
@ DT_DEBUG_LIGHTTABLE
Definition darktable.h:725
#define dt_free(ptr)
Definition darktable.h:456
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:453
static const dt_aligned_pixel_simd_t value
Definition darktable.h:577
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:893
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
sqlite3 * dt_database_get(const dt_database_t *db)
Definition database.c:3646
#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:374
static int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Definition dtpthread.h:359
static int dt_pthread_mutex_destroy(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:379
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:364
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:118
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:2687
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:133
#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)
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:208
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:347
@ 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_selection_t * selection
Definition darktable.h:782
const struct dt_database_t * db
Definition darktable.h:779
struct dt_control_signal_t * signals
Definition darktable.h:774
struct dt_view_manager_t * view_manager
Definition darktable.h:772
struct dt_control_t * control
Definition darktable.h:773
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:108
gboolean focus_peaking
Definition thumbtable.h:179
dt_thumbtable_mode_t mode
Definition thumbtable.h:97
dt_pthread_mutex_t lock
Definition thumbtable.h:158
dt_thumbtable_zoom_t zoom
Definition thumbtable.h:175
gboolean focus_regions
Definition thumbtable.h:178
dt_thumbtable_cache_t * lut
Definition thumbtable.h:142
int32_t audio_player_id
Definition view.h:215
struct dt_view_manager_t::@66 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:843
static void _free_image_surface(dt_thumbnail_t *thumb)
Definition thumbnail.c:369
static gboolean _event_image_motion(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
Definition thumbnail.c:1070
void dt_thumbnail_set_drop(dt_thumbnail_t *thumb, gboolean accept_drop)
Definition thumbnail.c:1628
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:970
#define thumb_return_if_fails(thumb,...)
Definition thumbnail.c:78
static void _thumbnail_free(dt_thumbnail_t *thumb)
Definition thumbnail.c:377
dt_thumbnail_t * dt_thumbnail_new(int rowid, dt_thumbnail_overlay_t over, dt_thumbtable_t *table, dt_image_t *info)
Definition thumbnail.c:1338
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:930
static gboolean _main_context_queue_draw(GtkWidget *widget)
Definition thumbnail.c:395
void dt_thumbnail_set_mouseover(dt_thumbnail_t *thumb, gboolean over)
Definition thumbnail.c:1611
static gboolean _event_main_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition thumbnail.c:745
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:702
static gboolean _thumb_draw_image(GtkWidget *widget, cairo_t *cr, gpointer user_data)
Definition thumbnail.c:638
GtkWidget * dt_thumbnail_create_widget(dt_thumbnail_t *thumb)
Definition thumbnail.c:1112
static gboolean _event_cursor_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
Definition thumbnail.c:348
static void _thumbnail_release(void *data)
Definition thumbnail.c:386
static GtkWidget * _create_menu(dt_thumbnail_t *thumb)
Definition thumbnail.c:289
static gboolean _event_main_leave(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition thumbnail.c:1013
static gboolean _event_star_leave(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition thumbnail.c:953
static gboolean _event_main_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition thumbnail.c:998
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:833
void _create_alternative_view(dt_thumbnail_t *thumb)
Definition thumbnail.c:882
void dt_thumbnail_resync_info(dt_thumbnail_t *thumb, const dt_image_t *const info)
Definition thumbnail.c:1318
static gboolean _event_rating_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition thumbnail.c:804
void dt_thumbnail_set_overlay(dt_thumbnail_t *thumb, dt_thumbnail_overlay_t mode)
Definition thumbnail.c:1455
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:1586
static gboolean _event_image_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition thumbnail.c:1104
int dt_thumbnail_destroy(dt_thumbnail_t *thumb)
Definition thumbnail.c:1366
int dt_thumbnail_image_refresh_real(dt_thumbnail_t *thumb)
Definition thumbnail.c:1639
int32_t _get_image_buffer(dt_job_t *job)
Definition thumbnail.c:430
void dt_thumbnail_update_gui(dt_thumbnail_t *thumb)
Definition thumbnail.c:1441
void dt_thumbnail_update_selection(dt_thumbnail_t *thumb, gboolean selected)
Definition thumbnail.c:870
static gboolean _altered_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition thumbnail.c:1029
#define DEBUG
Definition thumbnail.c:700
static gboolean _event_main_motion(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
Definition thumbnail.c:977
static gboolean _event_image_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition thumbnail.c:1055
static int _finish_buffer_thread(dt_thumbnail_t *thumb, gboolean success)
Definition thumbnail.c:405
static gboolean _group_enter(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition thumbnail.c:1046
void dt_thumbnail_alternative_mode(dt_thumbnail_t *thumb, gboolean enable)
Definition thumbnail.c:911
static gboolean _event_main_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition thumbnail.c:775
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:1490
void _widget_set_size(GtkWidget *w, int *parent_width, int *parent_height, const gboolean update)
Definition thumbnail.c:1462
void dt_thumbnail_resize(dt_thumbnail_t *thumb, int width, int height)
Definition thumbnail.c:1547
int dt_thumbnail_get_image_buffer(dt_thumbnail_t *thumb)
Definition thumbnail.c:567
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)
void dt_thumbtable_offset_zoom(dt_thumbtable_t *table, const double delta_x, const double delta_y)
gboolean dt_thumbtable_get_thumbnail_info(dt_thumbtable_t *table, int32_t imgid, dt_image_t *out)
Definition thumbtable.c:874
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:1414
void dt_view_audio_stop(dt_view_manager_t *vm)
Definition view.c:1446
dt_view_surface_value_t dt_view_image_get_surface(int32_t imgid, int width, int height, cairo_surface_t **surface, int zoom)
Definition view.c:1215
char * dt_view_extend_modes_str(const char *name, const gboolean is_hdr, const gboolean is_bw, const gboolean is_bw_flow)
Definition view.c:1221
dt_view_surface_value_t
Definition view.h:102
@ DT_VIEW_SURFACE_OK
Definition view.h:103
dt_view_image_over_t
Definition view.h:166
@ DT_VIEW_STAR_4
Definition view.h:172
@ DT_VIEW_REJECT
Definition view.h:174
@ DT_VIEW_STAR_5
Definition view.h:173
@ DT_VIEW_STAR_3
Definition view.h:171
@ DT_VIEW_STAR_1
Definition view.h:169
@ DT_VIEW_STAR_2
Definition view.h:170
@ DT_VIEW_DESERT
Definition view.h:168