Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
thumbtable.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2020-2022 Aldric Renaudin.
4 Copyright (C) 2020 Bill Ferguson.
5 Copyright (C) 2020 Chris Elston.
6 Copyright (C) 2020-2022 Diederik Ter Rahe.
7 Copyright (C) 2020, 2022 Hanno Schwalm.
8 Copyright (C) 2020 Heiko Bauke.
9 Copyright (C) 2020 Hubert Kowalski.
10 Copyright (C) 2020-2021 Pascal Obry.
11 Copyright (C) 2020, 2022 Philippe Weyland.
12 Copyright (C) 2020-2021 Ralf Brown.
13 Copyright (C) 2021 Dan Torop.
14 Copyright (C) 2021 domosbg.
15 Copyright (C) 2021 Erkan Ozgur Yilmaz.
16 Copyright (C) 2021 Fabio Heer.
17 Copyright (C) 2021 luzpaz.
18 Copyright (C) 2022-2023, 2025-2026 Aurélien PIERRE.
19 Copyright (C) 2022 Martin Bařinka.
20 Copyright (C) 2022 Miloš Komarčević.
21 Copyright (C) 2022 Nicolas Auffray.
22 Copyright (C) 2022 solarer.
23 Copyright (C) 2022 Victor Forsiuk.
24 Copyright (C) 2023 Luca Zulberti.
25 Copyright (C) 2023 Ricky Moon.
26 Copyright (C) 2025-2026 Guillaume Stutin.
27
28 darktable is free software: you can redistribute it and/or modify
29 it under the terms of the GNU General Public License as published by
30 the Free Software Foundation, either version 3 of the License, or
31 (at your option) any later version.
32
33 darktable is distributed in the hope that it will be useful,
34 but WITHOUT ANY WARRANTY; without even the implied warranty of
35 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36 GNU General Public License for more details.
37
38 You should have received a copy of the GNU General Public License
39 along with darktable. If not, see <http://www.gnu.org/licenses/>.
40*/
43#include "common/darktable.h"
44#include "gui/gdkkeys.h"
45#include "dtgtk/thumbtable.h"
47#include "dtgtk/thumbnail.h"
49#include "common/collection.h"
50#include "common/colorlabels.h"
51#include "common/history.h"
52#include "common/image_cache.h"
53#include "common/grouping.h"
54#include "common/ratings.h"
55#include "common/selection.h"
56#include "common/undo.h"
57#include "control/control.h"
59
60#include "gui/accelerators.h"
61#include "gui/drag_and_drop.h"
62#include "views/view.h"
63#include "bauhaus/bauhaus.h"
64
65#ifdef GDK_WINDOWING_QUARTZ
66#include "osx/osx.h"
67#endif
68
69#include <glib-object.h>
70#include <math.h>
71
72
74{
76
77 dt_thumbtable_t *src = NULL;
80 else if(darktable.gui->ui->thumbtable_filmstrip == dst)
82
83 if(IS_NULL_PTR(src) || src == dst) return FALSE;
84
86 const gboolean can_clone = (src->lut
87 && src->collection_inited
88 && src->collection_hash == dst->collection_hash
89 && src->collapse_groups == dst->collapse_groups
90 && src->collection_count > 0);
91 if(!can_clone)
92 {
94 return FALSE;
95 }
96
97 const uint32_t count = src->collection_count;
98 dt_thumbtable_cache_t *cloned_lut = malloc(count * sizeof(dt_thumbtable_cache_t));
99 if(IS_NULL_PTR(cloned_lut))
100 {
102 return FALSE;
103 }
104
105 memcpy(cloned_lut, src->lut, count * sizeof(dt_thumbtable_cache_t));
107
108 for(uint32_t i = 0; i < count; i++)
109 cloned_lut[i].thumb = NULL;
110
111 dt_thumbtable_cache_t *old_lut = NULL;
113 old_lut = dst->lut;
114 dst->lut = cloned_lut;
115 dst->collection_count = count;
116 dst->collection_inited = TRUE;
118
119 dt_free(old_lut);
120 return TRUE;
121}
122
163
164static gboolean _thumbtable_idle_update(gpointer user_data);
166static void _scrollbar_value_changed(GtkAdjustment *adjustment, gpointer user_data);
167static void _scrollbar_page_size_notify(GObject *object, GParamSpec *pspec, gpointer user_data);
168static void _parent_overlay_size_allocate(GtkWidget *widget, GtkAllocation *allocation, gpointer user_data);
169static void _scrollbar_widget_size_allocate(GtkWidget *widget, GtkAllocation *allocation, gpointer user_data);
170
171static gint _thumb_compare_rowid_desc(gconstpointer a, gconstpointer b)
172{
173 const dt_thumbnail_t *thumb_a = (const dt_thumbnail_t *)a;
174 const dt_thumbnail_t *thumb_b = (const dt_thumbnail_t *)b;
175
176 if(thumb_a->rowid < thumb_b->rowid) return 1;
177 if(thumb_a->rowid > thumb_b->rowid) return -1;
178 return 0;
179}
180
181static int _grab_focus(dt_thumbtable_t *table)
182{
183 if(IS_NULL_PTR(table)) return 0;
184 table->focus_idle_id = 0;
185
186 if(table->ops->grab_focus) table->ops->grab_focus(table);
188 return 0;
189}
190
191void dt_thumbtable_schedule_focus(dt_thumbtable_t *table, const gint priority)
192{
193 if(IS_NULL_PTR(table) || table->focus_idle_id) return;
194 table->focus_idle_id = g_idle_add_full(priority, (GSourceFunc)_grab_focus, table, NULL);
195}
196
197static gboolean _thumbtable_idle_update(gpointer user_data)
198{
199 dt_thumbtable_t *table = (dt_thumbtable_t *)user_data;
200 if(IS_NULL_PTR(table)) return G_SOURCE_REMOVE;
201
202 table->idle_update_id = 0;
205 if(table->thumb_nb == 0) gtk_widget_queue_draw(table->grid);
206 return G_SOURCE_REMOVE;
207}
208
210{
211 if(IS_NULL_PTR(table)) return;
212 if(table->scroll_window && !gtk_widget_is_visible(table->scroll_window)) return;
213 if(table->idle_update_id) return;
214 table->idle_update_id = g_idle_add_full(G_PRIORITY_LOW, (GSourceFunc)_thumbtable_idle_update, table, NULL);
215}
216
221
222static void _scrollbar_value_changed(GtkAdjustment *adjustment, gpointer user_data)
223{
224 dt_thumbtable_t *table = (dt_thumbtable_t *)user_data;
225 if(IS_NULL_PTR(table)) return;
226
227 // Only react to the adjustment that is meaningful in the current mode.
228 if(!table->ops->wants_scroll_value(table, adjustment)) return;
229
231}
232
233static void _scrollbar_page_size_notify(GObject *object, GParamSpec *pspec, gpointer user_data)
234{
235 dt_thumbtable_t *table = (dt_thumbtable_t *)user_data;
236 if(IS_NULL_PTR(table)) return;
237
238 // Page size is only used to size the filemanager/grid (filmstrip uses its parent allocation), and
239 // only the scroll-axis (vertical) adjustment matters. Reacting to the cross-axis adjustment would
240 // re-enter configure (whose grid-width write drives that page size) and can spin in a resize loop.
241 if(!table->ops->wants_page_size_notify(table, object)) return;
242
244}
245
246static void _parent_overlay_size_allocate(GtkWidget *widget, GtkAllocation *allocation, gpointer user_data)
247{
248 dt_thumbtable_t *table = (dt_thumbtable_t *)user_data;
249 if(IS_NULL_PTR(table) || IS_NULL_PTR(allocation)) return;
250
251 if(allocation->width == table->last_parent_width && allocation->height == table->last_parent_height)
252 return;
253
254 table->last_parent_width = allocation->width;
255 table->last_parent_height = allocation->height;
257}
258
259static void _scrollbar_widget_size_allocate(GtkWidget *widget, GtkAllocation *allocation, gpointer user_data)
260{
261 dt_thumbtable_t *table = (dt_thumbtable_t *)user_data;
262 if(IS_NULL_PTR(table) || IS_NULL_PTR(allocation) || IS_NULL_PTR(table->scroll_window)) return;
263
264 // The frontend decides which scrollbar's geometry drives its thumbnail sizing (filmstrip: the
265 // horizontal scrollbar height; filemanager: the vertical scrollbar width) and records it.
266 if(table->ops->relevant_scrollbar_changed(table, widget, allocation))
268}
269
270// We can't trust the mouse enter/leave events on thumnbails to properly
271// update active thumbnail styling, so we need to catch the signal here and update the whole list.
272void _mouse_over_image_callback(gpointer instance, gpointer user_data)
273{
274 if(IS_NULL_PTR(user_data)) return;
275 dt_thumbtable_t *table = (dt_thumbtable_t *)user_data;
276 if(IS_NULL_PTR(table->lut) || table->collection_count == 0) return;
277
278 const int32_t imgid = dt_control_get_mouse_over_id();
279
281
282 int32_t group_id = UNKNOWN_IMAGE;
283
284 // We iterate and update only over the visible range of thumbs + 2 rows as a safety margin
285 int32_t row_start = CLAMP(table->min_row_id - 2 * table->thumbs_per_row, 0, table->collection_count - 1);
286 int32_t row_end = CLAMP(table->max_row_id + 2 * table->thumbs_per_row, 0, table->collection_count - 1);
287 for(int rowid = row_start; rowid <= row_end; rowid++)
288 {
289 dt_thumbnail_t *thumb = table->lut[rowid].thumb;
290 if(IS_NULL_PTR(thumb)) continue; // thumb object not inited
291
292 const gboolean mouse_over = thumb->mouse_over;
293 dt_thumbnail_set_mouseover(thumb, thumb->info.id == imgid);
294
295 if(thumb->info.id == imgid && dt_thumbtable_info_is_grouped(table->lut[rowid].thumb->info))
296 group_id = thumb->info.group_id;
297
298 if(thumb->mouse_over != mouse_over)
299 gtk_widget_queue_draw(thumb->widget);
300 }
301
302 // Now, we update all the thumbs of the same image group
303 if(table->draw_group_borders)
304 {
305 for(int rowid = row_start; rowid <= row_end; rowid++)
306 {
307 dt_thumbnail_t *thumb = table->lut[rowid].thumb;
308 if(IS_NULL_PTR(thumb)) continue; // thumb object not inited
309
310 // In CSS:
311 // images borders from non-grouped images are transparent (default),
312 // images borders from non-hovered groups, when there is none, are dark orange (base border classes)
313 // images borders from the hovered group, when there is one, are bright orange (overwrite base border classes)
314 // images borders from non-hovered groups, when there is one, are transparent (overwrite base border classes width default)
315 // Here we dispatch the additional CSS classes alloying to overwrite
316 // the base border classes.
317 if(thumb->info.group_id == group_id)
318 {
319 dt_gui_add_class(thumb->widget, "hovered-group");
320 dt_gui_remove_class(thumb->widget, "non-hovered-group");
321 }
322 else if(group_id == UNKNOWN_IMAGE)
323 {
324 dt_gui_remove_class(thumb->widget, "hovered-group");
325 dt_gui_remove_class(thumb->widget, "non-hovered-group");
326 }
327 else
328 {
329 dt_gui_remove_class(thumb->widget, "hovered-group");
330 dt_gui_add_class(thumb->widget, "non-hovered-group");
331 }
332 }
333 }
334
336}
337
338
339static void _rowid_to_position(dt_thumbtable_t *table, int rowid, int *x, int *y)
340{
341 if(table->thumbs_per_row < 1) return;
342 table->ops->rowid_to_position(table, rowid, x, y);
343}
344
345// Needs updated table->x_position and table->y_position
346static int _position_to_rowid(dt_thumbtable_t *table, const double x, const double y)
347{
348 return table->ops->position_to_rowid(table, x, y);
349}
350
351// Find the x, y coordinates of any given thumbnail
352// Return TRUE if a position could be computed
353// thumb->rowid and table->thumbs_per_row need to have been inited before calling this
355{
356 if(table->thumbs_per_row < 1) return FALSE;
357 _rowid_to_position(table, thumb->rowid, &thumb->x, &thumb->y);
358 return TRUE;
359}
360
361// Updates table->x_position and table->y_position
363{
364 *y = gtk_adjustment_get_value(table->v_scrollbar);
365 *x = gtk_adjustment_get_value(table->h_scrollbar);
366}
367
369{
370 double x = 0.;
371 double y = 0.;
373 table->rowid = _position_to_rowid(table, x, y);
374}
375
376static int dt_thumbtable_scroll_to_position(dt_thumbtable_t *table, const double x, const double y)
377{
378 gtk_adjustment_set_value(table->v_scrollbar, y);
379 gtk_adjustment_set_value(table->h_scrollbar, x);
380 return 0;
381}
382
383static void dt_thumbtable_scroll_to_rowid(dt_thumbtable_t *table, int rowid)
384{
385 // Find (x, y) of the current thumbnail (north-west corner)
386 int x = 0, y = 0;
387 _rowid_to_position(table, rowid, &x, &y);
388
389 // Put the image always in the center of the view, if possible,
390 // aka move from north-west corner to center of the thumb
391 x += table->thumb_width / 2;
392 y += table->thumb_height / 2;
393
394 // Scroll viewport there
395 const double x_scroll = (double)x - (double)table->view_width / 2.;
396 const double y_scroll = (double)y - (double)table->view_height / 2.;
397 dt_thumbtable_scroll_to_position(table, x_scroll, y_scroll);
398}
399
401{
402 if(IS_NULL_PTR(table) || IS_NULL_PTR(table->lut) || table->collection_count <= 0) return UNKNOWN_IMAGE;
403
404 for(int i = 0; i < table->collection_count; i++)
405 if(table->lut[i].imgid == imgid)
406 return i;
407
408 return UNKNOWN_IMAGE;
409}
410
412{
413 if(IS_NULL_PTR(table) || !table->collection_inited || IS_NULL_PTR(table->lut) || table->collection_count <= 0)
414 return 1;
415
416 int rowid = UNKNOWN_IMAGE;
417 if(imgid > UNKNOWN_IMAGE)
418 {
420 rowid = dt_thumbtable_find_rowid_from_imgid(table, imgid);
422 }
423 else
424 rowid = table->rowid;
425
426 if(rowid == UNKNOWN_IMAGE) return 1;
427
428 dt_thumbtable_scroll_to_rowid(table, rowid);
429
430 return 0;
431}
432
433
435{
436 if(table->rowid > UNKNOWN_IMAGE)
438 else
440 return 0;
441}
442
443
445{
447 if(id < 0) id = dt_control_get_keyboard_over_id();
448 if(id < 0) id = dt_control_get_mouse_over_id();
449 //fprintf(stdout, "scrolling to %i\n", id);
451 return 0;
452}
453
454// Find the row ids (as in SQLite indices) of the images contained within viewport at current scrolling stage
455static gboolean _get_row_ids(dt_thumbtable_t *table, int *rowid_min, int *rowid_max)
456{
457 if(!table->configured || IS_NULL_PTR(table->v_scrollbar) || IS_NULL_PTR(table->h_scrollbar)) return FALSE;
458 table->ops->get_row_ids(table, rowid_min, rowid_max);
459 return TRUE;
460}
461
462// Find out if a given row id is visible at current scroll step
463gboolean _is_rowid_visible(dt_thumbtable_t *table, int rowid)
464{
465 if(!table->configured || IS_NULL_PTR(table->v_scrollbar) || IS_NULL_PTR(table->h_scrollbar)) return FALSE;
466 return table->ops->is_rowid_visible(table, rowid);
467}
468
469// Returns TRUE if visible row ids have changed since last check
471{
472 int rowid_min = 0;
473 int rowid_max = 0;
474 if(!_get_row_ids(table, &rowid_min, &rowid_max)) return FALSE;
475 if(rowid_min != table->min_row_id || rowid_max != table->max_row_id)
476 {
477 table->min_row_id = rowid_min;
478 table->max_row_id = rowid_max;
479 table->thumbs_inited = FALSE;
480 return TRUE;
481 }
482 return FALSE;
483}
484
486{
487 if(!table->configured || !table->collection_inited) return;
488 table->ops->update_content_size(table);
489}
490
491
492// Store the geometry computed by dt_thumbtable_configure. It must NOT re-derive thumb_width/height:
493// configure already applies the cell-decoration budget when computing them, so re-deriving here
494// (e.g. floor(width/cols), ignoring deco) would disagree by a pixel and make thumbs_changed forever
495// true - an infinite configure/redraw loop.
496void _grid_configure(dt_thumbtable_t *table, int width, int height, int per_row, int thumb_width, int thumb_height)
497{
498 if(width < 32 || height < 32) return;
499
500 table->thumbs_per_row = per_row;
501 table->view_width = width;
502 table->view_height = height;
503 table->thumb_width = thumb_width;
504 table->thumb_height = thumb_height;
505
506 table->configured = TRUE;
507
508 dt_print(DT_DEBUG_LIGHTTABLE, "Configuring thumbtable w=%i h=%i thumbs/row=%i thumb_width=%i\n",
509 table->view_width, table->view_height, table->thumbs_per_row, table->thumb_width);
510}
511
512// Extra extent one thumb cell adds beyond the layout stride (thumb_width/height).
513//
514// The cells (.thumb-cell) carry a transparent border plus a negative margin, used to overlap and merge
515// the borders of adjacent cells. As a result a cell's margin box is larger than the stride by
516// (border + margin); inner cells overlap their neighbours so it cancels out, but the last row/column
517// has no neighbour to overlap into, so the GtkFixed grid ends up exactly that much larger than
518// cols*thumb_width (GtkFixed sizes itself as max(child->x + child_preferred), and the child's
519// preferred size includes its margins). Budgeting for it - read from the theme rather than hardcoded -
520// keeps the grid within its viewport under the NEVER scroll policy instead of forcing the scrolled
521// window (and its scrollbar) past the parent overlay. The cell is square, so H and V surplus are equal.
523{
524 GtkWidgetPath *path = gtk_widget_path_new();
525 gtk_widget_path_append_type(path, GTK_TYPE_EVENT_BOX);
526 gtk_widget_path_iter_add_class(path, -1, "thumb-cell");
527
528 GtkStyleContext *ctx = gtk_style_context_new();
529 gtk_style_context_set_path(ctx, path);
530
531 GtkBorder margin, border;
532 gtk_style_context_get_margin(ctx, GTK_STATE_FLAG_NORMAL, &margin);
533 gtk_style_context_get_border(ctx, GTK_STATE_FLAG_NORMAL, &border);
534
535 g_object_unref(ctx);
536 gtk_widget_path_unref(path);
537
538 return MAX(0, (border.left + border.right) + (margin.left + margin.right));
539}
540
541// Track size changes of the container or number of thumbs per row
542// and recomputed the size of individual thumbnails accordingly
544{
545 if(!gtk_widget_is_visible(table->scroll_window)) return;
546
547 int new_width = 0;
548 int new_height = 0;
549 int new_thumbs_per_row = 0;
550 int new_thumb_width = 0;
551 int new_thumb_height = 0;
552
553 // The frontend reads the parent allocation and derives the viewport size, per-row count and
554 // individual thumbnail size (including the scrollbar gutter and per-cell decoration budget).
555 table->ops->configure_dims(table, &new_width, &new_height, &new_thumbs_per_row,
556 &new_thumb_width, &new_thumb_height);
557
558 // Parent is not allocated or something went wrong:
559 // ensure to reset everything so no further code will run.
560 if(new_width < 32 || new_height < 32)
561 {
562 table->thumbs_inited = FALSE;
563 table->configured = FALSE;
564 table->thumbs_per_row = 0;
565 table->thumb_height = 0;
566 table->thumb_width = 0;
567 return;
568 }
569
570 const gboolean thumbs_changed = (!table->configured
571 || new_thumbs_per_row != table->thumbs_per_row
572 || new_thumb_width != table->thumb_width
573 || new_thumb_height != table->thumb_height);
574
575 // Always keep view sizes in sync: they are used for navigation and scroll centering.
576 table->view_width = new_width;
577 table->view_height = new_height;
578
579 if(thumbs_changed)
580 {
581 table->thumbs_inited = FALSE;
582 _grid_configure(table, new_width, new_height, new_thumbs_per_row, new_thumb_width, new_thumb_height);
583 _update_grid_area(table);
584 }
585}
586
588{
589 if(IS_NULL_PTR(table) || imgid <= 0) return NULL;
590 return (dt_thumbnail_t *)g_hash_table_lookup(table->list, GINT_TO_POINTER(imgid));
591}
592
594{
595 if(IS_NULL_PTR(table) || IS_NULL_PTR(out) || imgid <= 0) return FALSE;
596
597 // Prefer LUT-backed metadata to avoid touching the image cache for read-only UI needs.
598 gboolean found = FALSE;
600 if(table->lut)
601 {
602 for(int rowid = 0; rowid < table->collection_count; rowid++)
603 {
604 if(table->lut[rowid].imgid == imgid && table->lut[rowid].thumb)
605 {
606 dt_thumbnail_t *thumb = table->lut[rowid].thumb;
607 if(g_hash_table_lookup(table->list, GINT_TO_POINTER(imgid)) == thumb)
608 {
609 *out = thumb->info;
610 found = TRUE;
611 break;
612 }
613 table->lut[rowid].thumb = NULL;
614 }
615 }
616 }
618
619 return found;
620}
621
622#define CLAMP_ROW(rowid) CLAMP(rowid, 0, table->collection_count - 1)
623#define IS_COLLECTION_EDGE(rowid) (rowid < 0 || rowid >= table->collection_count)
624
626{
627 // Reset all CSS classes
628 dt_thumbnail_border_t borders = 0;
629 dt_thumbnail_set_group_border(thumb, borders);
630
631 const int32_t rowid = thumb->rowid;
632
633 // Ungrouped image: abort
634 if(!dt_thumbtable_info_is_grouped(table->lut[rowid].thumb->info) || !table->draw_group_borders) return;
635
636 // The frontend adds the mode-specific border flags (grid closes on all four neighbours; the
637 // filmstrip always closes top+bottom and checks only its horizontal neighbours).
638 table->ops->group_borders(table, thumb, &borders);
639
640 dt_thumbnail_set_group_border(thumb, borders);
641}
642
643void _add_thumbnail_at_rowid(dt_thumbtable_t *table, const size_t rowid, const int32_t mouse_over)
644{
645 const int32_t imgid = table->lut[rowid].imgid;
646 dt_image_t info;
648 if(IS_NULL_PTR(img)) return;
649
650 // Take a private copy
651 info = *img;
653
654 dt_thumbnail_t *thumb = NULL;
655 gboolean new_item = TRUE;
656 gboolean new_position = TRUE;
657
658 // Do we already have a thumbnail at the correct postion for the correct imgid ?
659 if(table->lut[rowid].thumb && table->lut[rowid].imgid == imgid)
660 {
661 // Reuse the LUT entry only if it still matches the live thumbnail registry.
662 dt_thumbnail_t *mapped_thumb = _find_thumb_by_imgid(table, imgid);
663 if(mapped_thumb == table->lut[rowid].thumb)
664 {
665 thumb = mapped_thumb;
666 new_position = FALSE;
667 }
668 else
669 {
670 table->lut[rowid].thumb = NULL;
671 }
672 }
673 else
674 {
675 // NO : Try to find an existing thumbnail widget by imgid in table->list
676 // That will be faster if we only changed the sorting order but are still in the same collection.
677 // NOTE: the thumb widget position in grid will be wrong
678 thumb = _find_thumb_by_imgid(table, imgid);
679 }
680
681 if(thumb)
682 {
683 // Ensure everything is up-to-date
684 thumb->rowid = rowid;
685 dt_thumbnail_resync_info(thumb, &info);
686 new_item = FALSE;
687 }
688 else
689 {
690 thumb = dt_thumbnail_new(rowid, table->overlays, table, &info);
691 if(IS_NULL_PTR(thumb)) return;
692 g_hash_table_insert(table->list, GINT_TO_POINTER(thumb->info.id), thumb);
693 table->thumb_nb += 1;
694 }
695
696 table->lut[rowid].thumb = thumb;
697
698 // Resize
699 gboolean size_changed = (table->thumb_height != thumb->height || table->thumb_width != thumb->width);
700 if(new_item || size_changed || table->overlays != thumb->over)
701 {
702 dt_thumbnail_set_overlay(thumb, table->overlays);
703 dt_thumbnail_resize(thumb, table->thumb_width, table->thumb_height);
704 }
705
706 // Actually moving the widgets in the grid is more expensive, do it only if necessary
707 if(new_item)
708 {
709 _set_thumb_position(table, thumb);
710 table->ops->place_child(table, thumb);
711 //fprintf(stdout, "adding new thumb at #%lu: %i, %i\n", rowid, thumb->x, thumb->y);
712 }
713 else if(new_position || size_changed)
714 {
715 _set_thumb_position(table, thumb);
716 table->ops->move_child(table, thumb);
717 //fprintf(stdout, "moving new thumb at #%lu: %i, %i\n", rowid, thumb->x, thumb->y);
718 }
719
720 // Update visual states and flags. Mouse over is not connected to a signal and cheap to update
721 dt_thumbnail_set_mouseover(thumb, (mouse_over == thumb->info.id));
723
724 // Per-mode selection/action state (filmstrip tracks active images and disables actions).
725 table->ops->on_thumbnail_added(table, thumb);
726
727 _add_thumbnail_group_borders(table, thumb);
728 gtk_widget_show(thumb->widget);
729}
730
731
732// Add and/or resize thumbnails within visible viewort at current scroll level
734{
735 const int32_t mouse_over = dt_control_get_mouse_over_id();
736
737 // for(size_t rowid = 0; rowid < table->collection_count; rowid++)
738 for(size_t rowid = MAX(table->min_row_id, 0); rowid < MIN(table->max_row_id, table->collection_count); rowid++)
739 _add_thumbnail_at_rowid(table, rowid, mouse_over);
740}
741
742// Resize the thumbnails that are still existing but outside of visible viewport at current scroll level
744{
745 if(!table->configured) return;
746
747 GHashTableIter iter;
748 gpointer value = NULL;
749 g_hash_table_iter_init(&iter, table->list);
750 while(g_hash_table_iter_next(&iter, NULL, &value))
751 {
753 gboolean size_changed = (table->thumb_height != thumb->height || table->thumb_width != thumb->width);
754
755 if(size_changed || table->overlays != thumb->over)
756 {
757 // Overlay modes may change the height of the image
758 // to accommodate buttons. We need to resize on overlay changes.
759 dt_thumbnail_set_overlay(thumb, table->overlays);
760 dt_thumbnail_resize(thumb, table->thumb_width, table->thumb_height);
761 if(size_changed)
762 {
763 _set_thumb_position(table, thumb);
764 table->ops->move_child(table, thumb);
765 }
767 }
768
770 _add_thumbnail_group_borders(table, thumb);
771 gtk_widget_queue_draw(thumb->widget);
772 }
773}
774
775
777{
778 _update_row_ids(table);
779
780 if(!gtk_widget_is_visible(table->scroll_window) || IS_NULL_PTR(table->lut) || !table->configured || !table->collection_inited
781 || table->thumbs_inited || table->collection_count == 0)
782 return;
783
784 if(table->reset_collection)
785 {
787 table->reset_collection = FALSE;
788 }
789
790 const double start = dt_get_wtime();
791
793
794 gboolean empty_list = (table->thumb_nb == 0);
795
797
798 if(!empty_list)
799 _resize_thumbnails(table);
800
801 table->thumbs_inited = TRUE;
802
804
805 const char *const name = gtk_widget_get_name(table->grid);
806 dt_print(DT_DEBUG_LIGHTTABLE, "[%s] Populated %d thumbs between %i and %i in %0.04f sec \n",
807 name ? name : "thumbtable", table->thumb_nb, table->min_row_id, table->max_row_id,
808 dt_get_wtime() - start);
809}
810
811
812static void _dt_profile_change_callback(gpointer instance, int type, gpointer user_data)
813{
814 if(IS_NULL_PTR(user_data)) return;
815 dt_thumbtable_t *table = (dt_thumbtable_t *)user_data;
817}
818
819// Repaint every materialised thumbnail's "highlighted" state from the mode's source of truth
820// (grid: the selection; filmstrip: the active/developed image - see is_thumb_highlighted). Called
821// on both selection and active-image changes so the mode tracks whichever one it cares about and
822// is not clobbered by the other. Issue #954: the darkroom clears the selection AND sets a new
823// active image when the developed picture changes; the filmstrip must follow the active image.
825{
826 if(IS_NULL_PTR(table)) return;
827 // Hidden tables re-evaluate highlights on view re-entry (populate calls on_thumbnail_added), so
828 // there is no point (and, for huge collections, real cost) in scanning them here.
829 if(!gtk_widget_is_visible(table->scroll_window)) return;
830
831 gboolean first = TRUE;
832
834 if(IS_NULL_PTR(table->lut) || !table->collection_inited || table->collection_count == 0)
835 {
837 return;
838 }
839
840 for(int rowid = 0; rowid < table->collection_count; rowid++)
841 {
842 dt_thumbnail_t *thumb = table->lut[rowid].thumb;
843 if(IS_NULL_PTR(thumb)) continue;
844
845 if(g_hash_table_lookup(table->list, GINT_TO_POINTER(table->lut[rowid].imgid)) != thumb)
846 {
847 table->lut[rowid].thumb = NULL;
848 continue;
849 }
850
851 const gboolean was_highlighted = thumb->selected;
852 dt_thumbnail_update_selection(thumb, table->ops->is_thumb_highlighted(table, thumb->info.id));
853
854 if(thumb->selected && first)
855 {
857
858 // Sync the table active row id with the first highlighted thumb
859 table->rowid = thumb->rowid;
860 first = FALSE;
861 }
862
863 if(thumb->selected != was_highlighted)
864 gtk_widget_queue_draw(thumb->widget);
865 }
867}
868
869static void _dt_selection_changed_callback(gpointer instance, gpointer user_data)
870{
871 if(IS_NULL_PTR(user_data)) return;
873}
874
875// The filmstrip highlights the active/developed image; refresh when it changes (e.g. navigating to
876// another picture in darkroom). The grid's highlight source is the selection, so this is a no-op
877// there.
878static void _dt_active_images_changed_callback(gpointer instance, gpointer user_data)
879{
880 if(IS_NULL_PTR(user_data)) return;
882}
883
889
891{
892 return table->focus_regions;
893}
894
900
902{
903 return table->focus_peaking;
904}
905
913
915{
916 return table->draw_group_borders;
917}
918
919// can be called with imgid = -1, in that case we reload all mipmaps
920// reinit = FALSE should be called when the mipmap is ready to redraw,
921// reinit = TRUE should be called when a refreshed mipmap has been requested but we have nothing yet to draw
922void dt_thumbtable_refresh_thumbnail_real(dt_thumbtable_t *table, int32_t imgid, gboolean reinit)
923{
925 if(imgid != UNKNOWN_IMAGE)
926 {
927 dt_thumbnail_t *thumb = (dt_thumbnail_t *)g_hash_table_lookup(table->list, GINT_TO_POINTER(imgid));
928 if(thumb)
930 }
931 else
932 {
933 GHashTableIter iter;
934 gpointer value = NULL;
935 g_hash_table_iter_init(&iter, table->list);
936 while(g_hash_table_iter_next(&iter, NULL, &value))
937 {
940 }
941 }
943}
944
945
946// this is called each time the images info change
947// NOTE: remember we populate the table->lut with the current collection
948// but we init actual thumbnail objects and add their widgets to the grid
949// only when they appear in viewport.
950// So, we have to account for the fact that we may not have actual
951// thumbnails to refresh yet, and the table->list doesn't contain
952// uninited thumbs.
953// So we always use table->lut to find imgid, update the cached info
954// and try to update widgets only if we have some.
955// Otherwise, fresh info will be read when initing new thumbnails objects.
956static void _dt_image_info_changed_callback(gpointer instance, gpointer imgs, gpointer user_data)
957{
958 if(!user_data || IS_NULL_PTR(imgs)) return;
959 dt_thumbtable_t *table = (dt_thumbtable_t *)user_data;
960 if(IS_NULL_PTR(table->lut)) return;
961
963
964 for(GList *l = g_list_first(imgs); l; l = g_list_next(l))
965 {
966 const int32_t imgid = GPOINTER_TO_INT(l->data);
967 if(imgid <= 0) continue;
968
969 const int rowid = dt_thumbtable_find_rowid_from_imgid(table, imgid);
970 if(rowid == UNKNOWN_IMAGE) continue;
971
972 dt_thumbnail_t *thumb = table->lut[rowid].thumb;
973 if(thumb)
974 {
975 // Refresh the cached LUT info from the image cache for write-driven updates
976 // (ratings, color labels, etc.) while still keeping read paths cache-free.
977 const dt_image_t *img = dt_image_cache_testget(darktable.image_cache, imgid, 'r');
978 if(img)
979 {
980 dt_thumbnail_resync_info(thumb, img);
983 _add_thumbnail_group_borders(table, thumb);
984 gtk_widget_queue_draw(thumb->widget);
985 }
986 }
987
990 }
991
993}
994
996{
997 // In-memory collected images don't store group_id, so we need to fetch it again from DB
998 sqlite3_stmt *stmt = dt_thumbtable_info_get_collection_stmt();
999
1000 // NOTE: non-grouped images have group_id equal to their own id
1001 // grouped images have group_id equal to the id of the "group leader".
1002 // In old database versions, it's possible that group_id may have been set to -1 for non-grouped images.
1003 // That would actually make group detection much easier...
1004
1005 // Convert SQL imgids into C objects we can work with
1006 GArray *collection = g_array_new(FALSE, FALSE, sizeof(dt_thumbtable_cache_t));
1007 while(sqlite3_step(stmt) == SQLITE_ROW)
1008 {
1009 const int32_t imgid = sqlite3_column_int(stmt, 0);
1010 const int32_t groupid = sqlite3_column_int(stmt, 1);
1011
1012 if(table->collapse_groups && imgid != groupid)
1013 {
1014 // if user requested to collapse image groups in GUI,
1015 // only the group leader is shown. But we need to make sure
1016 // there is no dangling selection pointing to hidden group members
1017 // because it's unexpected that unvisible items might be selected,
1018 // and selection sanitization only deals with imgids outside of current collection,
1019 // but group members are always within the collection.
1021 continue;
1022 }
1023
1024 dt_thumbtable_cache_t entry = { .thumb = NULL, .imgid = imgid, .groupid = groupid };
1025 g_array_append_val(collection, entry);
1026
1027 // Populate the image cache. We don't keep a copy here because it wouldn't
1028 // be memory-managed
1029 dt_image_t info;
1030 dt_image_init(&info);
1031 dt_image_from_stmt(&info, stmt);
1032
1033#ifndef NDEBUG
1035#endif
1036
1037 // Seed the cache and be done
1039 }
1040 sqlite3_reset(stmt);
1041
1042 if(IS_NULL_PTR(collection) || collection->len == 0)
1043 {
1044 dt_thumbtable_cache_t *old_lut = NULL;
1045 dt_pthread_mutex_lock(&table->lock);
1046 old_lut = table->lut;
1047 table->lut = NULL;
1048 table->collection_count = 0;
1049 table->collection_inited = FALSE;
1051
1052 dt_free(old_lut);
1053 if(collection) g_array_free(collection, TRUE);
1054 return;
1055 }
1056
1057 // Build the collection LUT, aka a fixed-sized array of image objects
1058 // where the position of an image in the collection is directly the index in the LUT/array.
1059 // This makes for very efficient position -> imgid/thumbnail accesses directly in C,
1060 // especially from GUI code. The downside is we need to fully clear and recreate the LUT
1061 // everytime a collection changes (meaning filters OR sorting changed).
1062 dt_thumbtable_cache_t *new_lut = malloc(collection->len * sizeof(dt_thumbtable_cache_t));
1063
1064 if(IS_NULL_PTR(new_lut))
1065 {
1066 g_array_free(collection, TRUE);
1067 return;
1068 }
1069
1070 memcpy(new_lut, collection->data, collection->len * sizeof(dt_thumbtable_cache_t));
1071
1072 dt_thumbtable_cache_t *old_lut = NULL;
1073 dt_pthread_mutex_lock(&table->lock);
1074 old_lut = table->lut;
1075 table->lut = new_lut;
1076 table->collection_count = collection->len;
1077 table->collection_inited = TRUE;
1079
1080 dt_free(old_lut);
1081 g_array_free(collection, TRUE);
1082}
1083
1085{
1086 // Hash the collection query string
1087 const char *const query = dt_collection_get_query(darktable.collection);
1088 size_t len = strlen(query);
1089 uint64_t hash = dt_hash(5384, query, len);
1090
1091 // Factor in the number of images in the collection result
1092 uint32_t num_pics = dt_collection_get_count(darktable.collection);
1093 hash = dt_hash(hash, (char *)&num_pics, sizeof(uint32_t));
1094
1095 if(hash != table->collection_hash || table->reset_collection)
1096 {
1097 // Collection changed: reset everything
1098 table->collection_hash = hash;
1099 table->collection_inited = FALSE;
1100 return TRUE;
1101 }
1102 return FALSE;
1103}
1104
1105
1106// this is called each time collected images change
1107static void _dt_collection_changed_callback(gpointer instance, dt_collection_change_t query_change,
1108 dt_collection_properties_t changed_property, gpointer imgs,
1109 const int next, gpointer user_data)
1110{
1111 if(IS_NULL_PTR(user_data)) return;
1112 dt_thumbtable_t *table = (dt_thumbtable_t *)user_data;
1113
1114 gboolean collapse_groups = dt_conf_get_bool("ui_last/grouping");
1115 gboolean collapsing_changed = (table->collapse_groups != collapse_groups);
1116
1117 // Remember where the scrolling is at to possibly get the same visible images
1118 // despite collection changes (provided they are still there).
1120
1121 // See if the collection changed
1122 gboolean grouping_changed = changed_property == DT_COLLECTION_PROP_GROUPING;
1123 gboolean changed = _dt_collection_get_hash(table) || collapsing_changed || grouping_changed;
1124 if(changed)
1125 {
1126 // If groups are collapsed, we add only the group leader image to the collection
1127 // It needs to be set before running _dt_collection_lut()
1128 table->collapse_groups = collapse_groups;
1129 if(!_thumbtable_clone_lut(table))
1130 _dt_collection_lut(table);
1131
1132 table->thumbs_inited = FALSE;
1134
1135 if(table->collection_count == 0)
1136 {
1138 "The current filtered collection contains no image. Relax your filters or fetch a non-empty collection"));
1139 }
1140
1141 // Ensure we have something to scroll
1143
1144 // Number of images may have changed, size of grid too:
1145 _update_grid_area(table);
1146
1147 // Coalesce multiple layout/resize signals that can happen during collection loads.
1149
1150 dt_thumbtable_schedule_focus(table, G_PRIORITY_DEFAULT_IDLE);
1151 }
1152}
1153
1154// get the class name associated with the overlays mode
1156{
1157 switch(over)
1158 {
1160 return g_strdup("dt_overlays_none");
1162 return g_strdup("dt_overlays_always");
1164 default:
1165 return g_strdup("dt_overlays_hover");
1166 }
1167}
1168
1169// update thumbtable class and overlays mode, depending on size category
1171{
1172 // we change the overlay mode
1173 gchar *txt = g_strdup("plugins/lighttable/overlays/global");
1175 dt_free(txt);
1176
1178}
1179
1180// change the type of overlays that should be shown
1182{
1183 if(IS_NULL_PTR(table)) return;
1184 if(over == table->overlays) return;
1185
1186 // Cleanup old Darktable stupid modes
1187 dt_conf_set_int("plugins/lighttable/overlays/global", sanitize_overlays(over));
1188
1189 gchar *cl0 = _thumbs_get_overlays_class(table->overlays);
1190 gchar *cl1 = _thumbs_get_overlays_class(over);
1191 dt_gui_remove_class(table->grid, cl0);
1192 dt_gui_add_class(table->grid, cl1);
1193 dt_free(cl0);
1194 dt_free(cl1);
1195
1196 table->thumbs_inited = FALSE;
1197 table->overlays = over;
1198
1199 dt_pthread_mutex_lock(&table->lock);
1200 _resize_thumbnails(table);
1202}
1203
1204static void _event_dnd_get(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data,
1205 const guint target_type, const guint time, gpointer user_data)
1206{
1207 dt_thumbtable_t *table = (dt_thumbtable_t *)user_data;
1208 g_assert(!IS_NULL_PTR(selection_data));
1209
1210 switch(target_type)
1211 {
1212 case DND_TARGET_IMGID:
1213 {
1214 const int imgs_nb = g_list_length(table->drag_list);
1215 if(imgs_nb)
1216 {
1217 uint32_t *imgs = malloc(sizeof(uint32_t) * imgs_nb);
1218 GList *l = table->drag_list;
1219 for(int i = 0; i < imgs_nb; i++)
1220 {
1221 imgs[i] = GPOINTER_TO_INT(l->data);
1222 l = g_list_next(l);
1223 }
1224 gtk_selection_data_set(selection_data, gtk_selection_data_get_target(selection_data),
1225 _DWORD, (guchar *)imgs, imgs_nb * sizeof(uint32_t));
1226 dt_free(imgs);
1227 }
1228 break;
1229 }
1230 default: // return the location of the file as a last resort
1231 case DND_TARGET_URI:
1232 {
1233 GList *l = table->drag_list;
1234 if(g_list_is_singleton(l))
1235 {
1236 gchar pathname[PATH_MAX] = { 0 };
1237 gboolean from_cache = TRUE;
1238 const int id = GPOINTER_TO_INT(l->data);
1239 dt_image_full_path(id, pathname, sizeof(pathname), &from_cache, __FUNCTION__);
1240 gchar *uri = g_strdup_printf("file://%s", pathname); // TODO: should we add the host?
1241 gtk_selection_data_set(selection_data, gtk_selection_data_get_target(selection_data),
1242 _BYTE, (guchar *)uri, strlen(uri));
1243 dt_free(uri);
1244 }
1245 else
1246 {
1247 GList *images = NULL;
1248 for(; l; l = g_list_next(l))
1249 {
1250 const int id = GPOINTER_TO_INT(l->data);
1251 gchar pathname[PATH_MAX] = { 0 };
1252 gboolean from_cache = TRUE;
1253 dt_image_full_path(id, pathname, sizeof(pathname), &from_cache, __FUNCTION__);
1254 gchar *uri = g_strdup_printf("file://%s", pathname); // TODO: should we add the host?
1255 images = g_list_prepend(images, uri);
1256 }
1257 images = g_list_reverse(images); // list was built in reverse order, so un-reverse it
1258 gchar *uri_list = dt_util_glist_to_str("\r\n", images);
1259 g_list_free_full(images, dt_free_gpointer);
1260 images = NULL;
1261 gtk_selection_data_set(selection_data, gtk_selection_data_get_target(selection_data), _BYTE,
1262 (guchar *)uri_list, strlen(uri_list));
1263 dt_free(uri_list);
1264 }
1265 break;
1266 }
1267 }
1268}
1269
1270static void _thumbtable_drag_set_icon(dt_thumbtable_t *table, GdkDragContext *context)
1271{
1272 if(IS_NULL_PTR(table) || !table->drag_list) return;
1273
1274 const int32_t imgid = dt_control_get_mouse_over_id();
1275 dt_thumbnail_t *thumb = _find_thumb_by_imgid(table, imgid);
1276 if(IS_NULL_PTR(thumb)) return;
1277
1278 cairo_surface_t *surface = NULL;
1279 int hotspot_x = 0;
1280 int hotspot_y = 0;
1281 int width = 0;
1282 int height = 0;
1283
1284 dt_pthread_mutex_lock(&thumb->lock);
1285 if(thumb->img_surf && cairo_surface_get_reference_count(thumb->img_surf) > 0)
1286 {
1287 surface = cairo_surface_reference(thumb->img_surf);
1288 width = thumb->img_width;
1289 height = thumb->img_height;
1290 hotspot_x = width / 2;
1291 hotspot_y = height / 2;
1292 }
1294
1295 if(IS_NULL_PTR(surface)) return;
1296
1297 GtkWidget *image = gtk_image_new_from_surface(surface);
1298 cairo_surface_destroy(surface);
1299 // thumb->img_width/img_height are already in logical (CSS) pixels, but GtkImage sizes itself
1300 // from the surface's raw physical pixel count, ignoring its device scale: on HiDPI screens
1301 // (PPD > 1) that makes the drag icon balloon to PPD× the intended size. Pin the widget to the
1302 // correct logical size explicitly; the surface still paints crisply since cairo itself honors
1303 // the device scale when compositing.
1304 gtk_widget_set_size_request(image, width, height);
1305
1306 gtk_widget_show(image);
1307 gtk_drag_set_icon_widget(context, image, hotspot_x, hotspot_y);
1308}
1309
1310static void _event_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
1311{
1312 dt_thumbtable_t *table = (dt_thumbtable_t *)user_data;
1313 const int32_t imgid = dt_control_get_mouse_over_id();
1314
1315 // Commit the hovered image before dt_act_on_get_images() snapshots the payload: the filmstrip
1316 // raises a view signal, the grid extends the selection.
1317 table->ops->on_drag_begin(table, imgid);
1318
1320 _thumbtable_drag_set_icon(table, context);
1321}
1322
1323GList *_thumbtable_dnd_import_check(GList *files, const char *pathname, int *elements)
1324{
1325 if (IS_NULL_PTR(pathname) || IS_NULL_PTR(pathname))
1326 {
1327 fprintf(stdout,"DND check: no pathname.\n");
1328 return files;
1329 }
1330 fprintf(stdout,"DND check pathname: %s\n", pathname);
1331
1332 if(g_file_test(pathname, G_FILE_TEST_IS_REGULAR))
1333 {
1334 if (dt_supported_image(pathname))
1335 {
1336 files = g_list_prepend(files, g_strdup(pathname));
1337 (*elements)++;
1338 }
1339 else
1340 fprintf(stderr, "`%s`: Unkonwn format.", pathname);
1341 }
1342 else if(g_file_test(pathname, G_FILE_TEST_IS_DIR))
1343 {
1344 fprintf(stderr, "DND check: Folders are not allowed");
1345 dt_control_log(_("'%s': Please use 'File > Import' to import a folder."), pathname);
1346 }
1347 else
1348 {
1349 fprintf(stderr, "DND check: `%s` not a file or folder.\n", pathname);
1350 }
1351
1352 return files;
1353}
1354
1355static gboolean _thumbtable_dnd_import(GtkSelectionData *selection_data)
1356{
1357 gchar **uris = gtk_selection_data_get_uris(selection_data);
1358 int elements = 0;
1359 GList *files = NULL;
1360
1361 if(uris)
1362 {
1363 GVfs *vfs = g_vfs_get_default();
1364 for (int i = 0; !IS_NULL_PTR(uris[i]); i++)
1365 {
1366 GFile *filepath = g_vfs_get_file_for_uri(vfs, uris[i]);
1367 const gchar *pathname = g_strdup(g_file_get_path(filepath));
1368 files = _thumbtable_dnd_import_check(files, pathname, &elements);
1369 g_object_unref(filepath);
1370 }
1371
1372 if(elements > 0)
1373 {
1374 // WARNING: we copy a Glist of pathes as char*
1375 // The references to the char* still belong to the original.
1376 // We will free them in the import job.
1377 dt_control_import_t data = {.imgs = g_list_copy(files),
1378 .datetime = g_date_time_new_now_local(),
1379 .copy = FALSE, // we only import in place.
1380 .jobcode = dt_conf_get_string("ui_last/import_jobcode"),
1381 .base_folder = dt_conf_get_string("session/base_directory_pattern"),
1382 .target_subfolder_pattern = dt_conf_get_string("session/sub_directory_pattern"),
1383 .target_file_pattern = dt_conf_get_string("session/filename_pattern"),
1384 .target_dir = NULL,
1385 .elements = elements,
1386 .discarded = NULL
1387 };
1388
1389 if(dt_control_import(data))
1390 dt_control_log(_("Could not start the import job."));
1391 }
1392 else fprintf(stderr,"No files to import. Check your selection or use 'File > Import'.");
1393 }
1394
1395 g_strfreev(uris);
1396 g_list_free(files);
1397 files = NULL;
1398 return elements >= 0 ? TRUE : FALSE;
1399}
1400
1401void dt_thumbtable_event_dnd_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y,
1402 GtkSelectionData *selection_data, guint target_type, guint time,
1403 gpointer user_data)
1404{
1405 gboolean success = FALSE;
1406
1407 if((target_type == DND_TARGET_URI) && (!IS_NULL_PTR(selection_data))
1408 && (gtk_selection_data_get_length(selection_data) >= 0))
1409 {
1410 success = _thumbtable_dnd_import(selection_data);
1411 }
1412
1413 gtk_drag_finish(context, success, FALSE, time);
1414}
1415
1416static void _event_dnd_end(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
1417{
1418 dt_thumbtable_t *table = (dt_thumbtable_t *)user_data;
1419 if(table->drag_list)
1420 {
1421 g_list_free(table->drag_list);
1422 table->drag_list = NULL;
1423 }
1424 // in any case, with reset the reordering class if any
1425 dt_gui_remove_class(table->grid, "dt_thumbtable_reorder");
1426}
1427
1428int _imgid_to_rowid(dt_thumbtable_t *table, int32_t imgid)
1429{
1430 if(IS_NULL_PTR(table->lut)) return UNKNOWN_IMAGE;
1431
1432 int rowid = UNKNOWN_IMAGE;
1433
1434 dt_pthread_mutex_lock(&table->lock);
1435 for(int i = 0; i < table->collection_count; i++)
1436 {
1437 if(table->lut[i].imgid == imgid)
1438 {
1439 rowid = i;
1440 break;
1441 }
1442 }
1444
1445 return rowid;
1446}
1447
1448void dt_thumbtable_move_in_grid(dt_thumbtable_t *table, GdkEventKey *event, dt_thumbtable_direction_t direction, int origin_imgid)
1449{
1450 if(IS_NULL_PTR(table->lut)) return;
1451 if(!gtk_widget_is_visible(table->scroll_window)) return;
1452
1453 int current_rowid = _imgid_to_rowid(table, origin_imgid);
1454 int offset = 0;
1455
1456 switch(direction)
1457 {
1458 case DT_TT_MOVE_UP:
1459 offset = - table->thumbs_per_row;
1460 break;
1461 case DT_TT_MOVE_DOWN:
1462 offset = + table->thumbs_per_row;
1463 break;
1464 case DT_TT_MOVE_LEFT:
1465 offset = - 1;
1466 break;
1467 case DT_TT_MOVE_RIGHT:
1468 offset = + 1;
1469 break;
1471 offset = - table->view_height / table->thumb_height * table->thumbs_per_row;
1472 break;
1474 offset = + table->view_height / table->thumb_height * table->thumbs_per_row;
1475 break;
1476 case DT_TT_MOVE_START:
1477 offset = - origin_imgid;
1478 break;
1479 case DT_TT_MOVE_END:
1480 offset = +table->collection_count; // will be clamped below, don't care
1481 break;
1482 }
1483
1484 int new_rowid = CLAMP(current_rowid + offset, 0, table->collection_count - 1);
1485
1486 dt_pthread_mutex_lock(&table->lock);
1487 int new_imgid = table->lut[new_rowid].imgid;
1489
1490 dt_thumbtable_dispatch_over(table, event->type, new_imgid);
1491
1492 if(!_is_rowid_visible(table, new_rowid))
1493 {
1494 // GUI update will be handled through the value-changed event of the GtkAdjustment
1495 dt_thumbtable_scroll_to_imgid(table, new_imgid);
1496 }
1497 else
1498 {
1499 // We still need to update all visible thumbs to keep mouse_over states in sync
1500 table->thumbs_inited = FALSE;
1501 dt_thumbtable_update(table);
1502 }
1503}
1504
1506{
1507 if(table->alternate_mode == enable) return;
1508 table->alternate_mode = enable;
1509
1510 dt_pthread_mutex_lock(&table->lock);
1511 GHashTableIter iter;
1512 gpointer value = NULL;
1513 g_hash_table_iter_init(&iter, table->list);
1514 while(g_hash_table_iter_next(&iter, NULL, &value))
1515 {
1518 }
1520}
1521
1522
1523gboolean dt_thumbtable_key_pressed_grid(GtkWidget *self, GdkEventKey *event, gpointer user_data)
1524{
1525 if(!gtk_window_is_active(GTK_WINDOW(darktable.gui->ui->main_window))) return FALSE;
1526 if(IS_NULL_PTR(user_data)) return FALSE;
1527 dt_thumbtable_t *table = (dt_thumbtable_t *)user_data;
1528 if(IS_NULL_PTR(table->lut)) return FALSE;
1529
1530 // Find out the current image
1531 // NOTE: when moving into the grid from key arrow events,
1532 // if the cursor is still overlaying the grid when scrolling, it can collide
1533 // with key event and set the mouse_over focus elsewhere.
1534 // For this reason, we use our own private keyboard_over event,
1535 // and use the mouse_over as a fall-back only.
1536 // Key events are "knobby", therefore more reliale than "hover",
1537 // so they always take precedence.
1538 int32_t imgid = dt_control_get_keyboard_over_id();
1539 if(imgid < 0) imgid = dt_control_get_mouse_over_id();
1540 if(imgid < 0) imgid = dt_selection_get_first_id(darktable.selection);
1541 if(imgid < 0 && table->lut)
1542 {
1543 dt_pthread_mutex_lock(&table->lock);
1544 imgid = table->lut[0].imgid;
1546 }
1547
1548 //fprintf(stdout, "%s\n", gtk_accelerator_name(event->keyval, event->state));
1549
1550 // Exit alternative mode on any keystroke other than alt
1551 if(event->keyval != GDK_KEY_Alt_L && event->keyval != GDK_KEY_Alt_R)
1552 _alternative_mode(table, FALSE);
1553
1554 guint key = dt_keys_mainpad_alternatives(event->keyval);
1555
1556 // Mode-specific navigation/selection keys first: the grid consumes Up/Down (row navigation) and
1557 // space/nobreakspace (selection); the filmstrip has none and lets them fall through.
1558 if(table->ops->handle_key && table->ops->handle_key(table, event, key, imgid))
1559 return TRUE;
1560
1561 // Keys handled identically in both modes.
1562 switch(key)
1563 {
1564 case GDK_KEY_Left:
1565 {
1566 dt_thumbtable_move_in_grid(table, event, DT_TT_MOVE_LEFT, imgid);
1567 return TRUE;
1568 }
1569 case GDK_KEY_Right:
1570 {
1571 dt_thumbtable_move_in_grid(table, event, DT_TT_MOVE_RIGHT, imgid);
1572 return TRUE;
1573 }
1574 case GDK_KEY_Page_Up:
1575 {
1577 return TRUE;
1578 }
1579 case GDK_KEY_Page_Down:
1580 {
1582 return TRUE;
1583 }
1584 case GDK_KEY_Home:
1585 {
1586 dt_thumbtable_move_in_grid(table, event, DT_TT_MOVE_START, imgid);
1587 return TRUE;
1588 }
1589 case GDK_KEY_End:
1590 {
1591 dt_thumbtable_move_in_grid(table, event, DT_TT_MOVE_END, imgid);
1592 return TRUE;
1593 }
1594 case GDK_KEY_Return:
1595 {
1596 // This is only to be consistent with mouse events:
1597 // opening to darkroom happens with double click (aka ACTIVATE event),
1598 // but the first click always select the clicked thumbnail before.
1599 // So we do the same here, even though it's not required and actually slightly annoying.
1600 // (grid selects the thumbnail first; the filmstrip just activates.)
1601 if(table->ops->pre_activate) table->ops->pre_activate(table, imgid);
1602
1604 return TRUE;
1605 }
1606 case GDK_KEY_Alt_L:
1607 case GDK_KEY_Alt_R:
1608 {
1609 _alternative_mode(table, TRUE);
1610 return TRUE;
1611 }
1612 case GDK_KEY_Delete:
1613 {
1615 return TRUE;
1616 }
1617 }
1618 return FALSE;
1619}
1620
1621gboolean dt_thumbtable_key_released_grid(GtkWidget *self, GdkEventKey *event, gpointer user_data)
1622{
1623 if(!gtk_window_is_active(GTK_WINDOW(darktable.gui->ui->main_window))) return FALSE;
1624
1625 if(IS_NULL_PTR(user_data)) return FALSE;
1626 dt_thumbtable_t *table = (dt_thumbtable_t *)user_data;
1627
1628 //fprintf(stdout, "%s\n", gtk_accelerator_name(event->keyval, event->state));
1629 _alternative_mode(table, FALSE);
1630 return FALSE;
1631}
1632
1633
1634static gboolean _draw_callback(GtkWidget *widget, cairo_t *cr, gpointer user_data)
1635{
1636 if(IS_NULL_PTR(user_data)) return TRUE;
1637
1638 // Ensure the background color is painted
1639 GtkStyleContext *context = gtk_widget_get_style_context(widget);
1640 GtkAllocation allocation;
1641 gtk_widget_get_allocation(widget, &allocation);
1642 gtk_render_background(context, cr, 0, 0, allocation.width, allocation.height);
1643 gtk_render_frame(context, cr, 0, 0, allocation.width, allocation.height);
1644
1645 return FALSE;
1646}
1647
1652
1653gboolean _event_main_leave(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
1654{
1655 if(IS_NULL_PTR(user_data)) return TRUE;
1657 return TRUE;
1658}
1659
1660
1661// Single dispatch point mapping a mode to its layout strategy. Every former if(mode==...) branch
1662// now routes through the returned vtable (see thumbtable_internal.h and the two frontends).
1664{
1665 switch(mode)
1666 {
1670 default:
1671 return dt_thumbtable_grid_ops();
1672 }
1673}
1674
1676{
1677 dt_thumbtable_t *table = (dt_thumbtable_t *)calloc(1, sizeof(dt_thumbtable_t));
1678
1679 // Bind the layout strategy before building the content widget: the frontend decides whether the
1680 // content is a GtkFixed (grid) or a GtkLayout (filmstrip).
1681 table->mode = mode;
1682 table->ops = _ops_for_mode(mode);
1683
1684 table->scroll_window = gtk_scrolled_window_new(NULL, NULL);
1685 // Named so the theme can zero its "scrollbar-spacing" style property (see dt_thumbtable_configure).
1686 gtk_widget_set_name(table->scroll_window, "thumbtable-scroll");
1687 gtk_scrolled_window_set_overlay_scrolling(GTK_SCROLLED_WINDOW(table->scroll_window), FALSE);
1688 // No frame: the image grid is full-bleed content and needs no recessed border.
1689 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(table->scroll_window), GTK_SHADOW_NONE);
1690
1691 table->v_scrollbar = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(table->scroll_window));
1692 table->h_scrollbar = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(table->scroll_window));
1693 g_signal_connect(G_OBJECT(table->v_scrollbar), "value-changed", G_CALLBACK(_scrollbar_value_changed), table);
1694 g_signal_connect(G_OBJECT(table->h_scrollbar), "value-changed", G_CALLBACK(_scrollbar_value_changed), table);
1695 g_signal_connect(G_OBJECT(table->v_scrollbar), "notify::page-size", G_CALLBACK(_scrollbar_page_size_notify), table);
1696 g_signal_connect(G_OBJECT(table->h_scrollbar), "notify::page-size", G_CALLBACK(_scrollbar_page_size_notify), table);
1697
1698 // Track actual scrollbar widget allocations: filmstrip sizing depends on the horizontal scrollbar height,
1699 // and filemanager fallback sizing depends on the vertical scrollbar width.
1700 GtkWidget *h_scroll = gtk_scrolled_window_get_hscrollbar(GTK_SCROLLED_WINDOW(table->scroll_window));
1701 GtkWidget *v_scroll = gtk_scrolled_window_get_vscrollbar(GTK_SCROLLED_WINDOW(table->scroll_window));
1702 if(h_scroll)
1703 g_signal_connect(G_OBJECT(h_scroll), "size-allocate", G_CALLBACK(_scrollbar_widget_size_allocate), table);
1704 if(v_scroll)
1705 g_signal_connect(G_OBJECT(v_scroll), "size-allocate", G_CALLBACK(_scrollbar_widget_size_allocate), table);
1706
1707 // Content widget: GtkFixed for the grid, GtkLayout for the filmstrip (the latter is a
1708 // GtkScrollable, avoiding the implicit GtkViewport that collapsed the strip - issue #877).
1709 table->grid = table->ops->create_content_widget();
1710 dt_gui_add_class(table->grid, "dt_thumbtable");
1711 gtk_container_add(GTK_CONTAINER(table->scroll_window), table->grid);
1712
1713 // A non-scrollable content widget (GtkFixed) gets wrapped in an implicit GtkViewport whose
1714 // default shadow is GTK_SHADOW_IN (a .frame border). Drop it so it doesn't add to the scrolled
1715 // window's size. A GtkScrollable (GtkLayout) is added directly, with no viewport to adjust.
1716 GtkWidget *viewport = gtk_bin_get_child(GTK_BIN(table->scroll_window));
1717 if(GTK_IS_VIEWPORT(viewport))
1718 gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE);
1719
1720 g_object_set_data(G_OBJECT(table->grid), DT_ACCELS_WIDGET_TOOLTIP_DISABLED_KEY, GINT_TO_POINTER(1));
1721 gtk_widget_set_has_tooltip(table->grid, FALSE);
1722 // The filmstrip must never hold keyboard focus (see its ops' grab_focus comment): clicking one
1723 // of its thumbnails would otherwise steal focus away from the current view's own center widget,
1724 // and Return then gets swallowed here (raising a plain ACTIVATE/preview) instead of reaching the
1725 // view's own key_pressed (e.g. Studio Capture's "open in darkroom").
1726 const gboolean can_focus = (table->mode != DT_THUMBTABLE_MODE_FILMSTRIP);
1727 gtk_widget_set_can_focus(table->grid, can_focus);
1728 gtk_widget_set_focus_on_click(table->grid, can_focus);
1729 gtk_widget_add_events(table->grid, GDK_LEAVE_NOTIFY_MASK);
1730 gtk_widget_set_app_paintable(table->grid, TRUE);
1731 g_signal_connect(G_OBJECT(table->grid), "leave-notify-event", G_CALLBACK(_event_main_leave), table);
1732
1733 // Disable auto re-scrolling to beginning when a child of scrolled window gets the focus
1734 // Doesn't seem to work...
1735 // https://stackoverflow.com/questions/26693042/gtkscrolledwindow-disable-scroll-to-focused-child
1736 GtkAdjustment *dummy = gtk_adjustment_new(0., 0., 1., 1., 1., 1.);
1737 gtk_container_set_focus_hadjustment(GTK_CONTAINER(table->scroll_window), dummy);
1738 gtk_container_set_focus_vadjustment(GTK_CONTAINER(table->scroll_window), dummy);
1739 gtk_container_set_focus_hadjustment(GTK_CONTAINER(table->grid), dummy);
1740 gtk_container_set_focus_vadjustment(GTK_CONTAINER(table->grid), dummy);
1741 g_object_unref(dummy);
1742
1743 // drag and drop : used for reordering, interactions with maps, exporting uri to external apps, importing images
1744 // in filmroll...
1745 gtk_drag_source_set(table->grid, GDK_BUTTON1_MASK, target_list_all, n_targets_all, GDK_ACTION_MOVE);
1746 gtk_drag_dest_set(table->grid, GTK_DEST_DEFAULT_ALL, target_list_all, n_targets_all, GDK_ACTION_MOVE);
1747 g_signal_connect_after(table->grid, "drag-begin", G_CALLBACK(_event_dnd_begin), table);
1748 g_signal_connect_after(table->grid, "drag-end", G_CALLBACK(_event_dnd_end), table);
1749 g_signal_connect(table->grid, "drag-data-get", G_CALLBACK(_event_dnd_get), table);
1750 g_signal_connect(table->grid, "drag-data-received", G_CALLBACK(dt_thumbtable_event_dnd_received), table);
1751
1752 gtk_widget_add_events(table->grid, GDK_STRUCTURE_MASK | GDK_EXPOSURE_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK);
1753 g_signal_connect(table->grid, "draw", G_CALLBACK(_draw_callback), table);
1754 g_signal_connect(table->grid, "key-press-event", G_CALLBACK(dt_thumbtable_key_pressed_grid), table);
1755 g_signal_connect(table->grid, "key-release-event", G_CALLBACK(dt_thumbtable_key_released_grid), table);
1756 gtk_widget_show(table->grid);
1757
1758 table->thumb_nb = 0;
1759 table->grid_cols = 0;
1760 table->collection_inited = FALSE;
1761 table->configured = FALSE;
1762 table->thumbs_inited = FALSE;
1763 table->collection_hash = -1;
1764 table->list = g_hash_table_new(g_direct_hash, g_direct_equal);
1765 table->collection_count = 0;
1766 table->min_row_id = 0;
1767 table->max_row_id = 0;
1768 table->lut = NULL;
1769 table->reset_collection = FALSE;
1770 table->x_position = 0.;
1771 table->y_position = 0.;
1772 table->alternate_mode = FALSE;
1773 table->rowid = -1;
1774 table->collapse_groups = dt_conf_get_bool("ui_last/grouping");
1775 table->draw_group_borders = dt_conf_get_bool("plugins/lighttable/group_borders");
1776 table->idle_update_id = 0;
1777 table->focus_idle_id = 0;
1778 table->last_parent_width = 0;
1779 table->last_parent_height = 0;
1780 table->last_h_scrollbar_height = -1;
1781 table->last_v_scrollbar_width = -1;
1782
1783 dt_pthread_mutex_init(&table->lock, NULL);
1784
1785 dt_gui_add_help_link(table->grid, dt_get_help_url("lighttable_filemanager"));
1786
1787 // set css name and class
1788 gtk_widget_set_name(table->grid, "thumbtable-filemanager");
1789
1790 // overlays mode
1792
1793 // we register globals signals
1795 G_CALLBACK(_dt_collection_changed_callback), table);
1797 G_CALLBACK(_dt_selection_changed_callback), table);
1799 G_CALLBACK(_dt_active_images_changed_callback), table);
1801 G_CALLBACK(_dt_profile_change_callback), table);
1803 G_CALLBACK(_dt_image_info_changed_callback), table);
1805 G_CALLBACK(_mouse_over_image_callback), table);
1806
1807 dt_thumbtable_set_parent(table, mode);
1808
1809 // The file manager belongs only to lighttable, while the filmstrip widget is
1810 // shared by darkroom, print and map. Each owner view needs its own accel
1811 // paths so shortcut search, menus and dispatch stay in sync with the
1812 // currently active accel group.
1813 GtkAccelGroup *accel_groups[] = {
1818 };
1819 const char *path_bases[] = {
1820 _("Lighttable/Thumbtable"),
1821 _("Darkroom/Filmstrip"),
1822 _("Print/Filmstrip"),
1823 _("Map/Filmstrip")
1824 };
1825 // The filemanager owns only the lighttable accel group (index 0); the shared filmstrip owns
1826 // the darkroom/print/map groups (indices 1-3).
1827 int first_group = 1, last_group = 4;
1828 switch(mode)
1829 {
1831 first_group = 0;
1832 last_group = 1;
1833 break;
1834 default:
1835 break;
1836 }
1837
1838 gchar *path = NULL;
1839 for(int group_index = first_group; group_index < last_group; group_index++)
1840 {
1841 GtkAccelGroup *accel_group = accel_groups[group_index];
1842 const char *path_base = path_bases[group_index];
1843
1844 path = dt_accels_build_path(path_base, _("Move up"));
1846 path, table->grid, GDK_KEY_Up, 0);
1847 dt_free(path);
1848 path = dt_accels_build_path(path_base, _("Move down"));
1850 path, table->grid, GDK_KEY_Down, 0);
1851 dt_free(path);
1852 path = dt_accels_build_path(path_base, _("Move left"));
1854 path, table->grid, GDK_KEY_Left, 0);
1855 dt_free(path);
1856 path = dt_accels_build_path(path_base, _("Move right"));
1858 path, table->grid, GDK_KEY_Right, 0);
1859 dt_free(path);
1860 path = dt_accels_build_path(path_base, _("Go to previous page"));
1862 path, table->grid, GDK_KEY_Page_Up, 0);
1863 dt_free(path);
1864 path = dt_accels_build_path(path_base, _("Go to next page"));
1866 path, table->grid, GDK_KEY_Page_Down, 0);
1867 dt_free(path);
1868 path = dt_accels_build_path(path_base, _("Go to the start"));
1870 path, table->grid, GDK_KEY_Home, 0);
1871 dt_free(path);
1872 path = dt_accels_build_path(path_base, _("Go to the end"));
1874 path, table->grid, GDK_KEY_End, 0);
1875 dt_free(path);
1876 path = dt_accels_build_path(path_base, _("Select the current thumbnail"));
1878 path, table->grid, GDK_KEY_space, 0);
1879 dt_free(path);
1880 path = dt_accels_build_path(path_base, _("Toogle the current thumbnail from selection"));
1882 path, table->grid, GDK_KEY_space, GDK_CONTROL_MASK);
1883 dt_free(path);
1884 path = dt_accels_build_path(path_base, _("Expand the current selection up to the hovered thumbnail"));
1886 path, table->grid, GDK_KEY_space, GDK_SHIFT_MASK);
1887 dt_free(path);
1888 path = dt_accels_build_path(path_base, _("Open the current thumbnail in darkroom"));
1890 path, table->grid, GDK_KEY_Return, 0);
1891 dt_free(path);
1892 path = dt_accels_build_path(path_base, _("Enable thumbnail transient alternative view"));
1894 path, table->grid, GDK_KEY_Alt_L, 0);
1895 dt_free(path);
1896 }
1897
1898 path = dt_accels_build_path(_("Global/Menu/File"), _("Remove image from library"));
1900 path, table->grid, GDK_KEY_Delete, 0);
1901 dt_free(path);
1902 return table;
1903}
1904
1905
1907{
1908 // Cleanup existing stuff
1909 const double start = dt_get_wtime();
1910
1911 dt_pthread_mutex_lock(&table->lock);
1912 if(table->lut)
1913 for(int rowid = 0; rowid < table->collection_count; rowid++)
1914 table->lut[rowid].thumb = NULL;
1915
1916 // WARNING: we need to detach children from parent starting from the last
1917 // otherwise, Gtk updates the index of all the next children in sequence
1918 // and that takes forever when thumb_nb > 1000
1919 GList *thumbs = g_hash_table_get_values(table->list);
1920 thumbs = g_list_sort(thumbs, _thumb_compare_rowid_desc);
1921 g_hash_table_remove_all(table->list);
1923
1924 for(GList *l = thumbs; l; l = g_list_next(l))
1925 {
1926 dt_thumbnail_t *thumb = (dt_thumbnail_t *)l->data;
1927 gtk_widget_hide(thumb->widget);
1928 dt_thumbnail_destroy(thumb);
1929 }
1930 g_list_free(thumbs);
1931 thumbs = NULL;
1932
1933 dt_print(DT_DEBUG_LIGHTTABLE, "Cleaning the list of %i elements in %0.04f sec\n", table->thumb_nb,
1934 dt_get_wtime() - start);
1935
1936 table->thumb_nb = 0;
1937 table->thumbs_inited = FALSE;
1938}
1939
1940
1942{
1943 if(table->idle_update_id)
1944 {
1945 g_source_remove(table->idle_update_id);
1946 table->idle_update_id = 0;
1947 }
1948 if(table->focus_idle_id)
1949 {
1950 g_source_remove(table->focus_idle_id);
1951 table->focus_idle_id = 0;
1952 }
1953
1959
1961
1963
1965
1966 g_hash_table_destroy(table->list);
1967 if(table->lut)
1968 {
1969 dt_free(table->lut);
1970 }
1971
1972 dt_free(table);
1973}
1974
1976{
1977 if(IS_NULL_PTR(table)) return;
1978
1979 if(table->idle_update_id)
1980 {
1981 g_source_remove(table->idle_update_id);
1982 table->idle_update_id = 0;
1983 }
1984 if(table->focus_idle_id)
1985 {
1986 g_source_remove(table->focus_idle_id);
1987 table->focus_idle_id = 0;
1988 }
1989
1990 table->reset_collection = TRUE;
1991
1992 dt_pthread_mutex_lock(&table->lock);
1993 if(table->lut)
1994 for(int rowid = 0; rowid < table->collection_count; rowid++)
1995 table->lut[rowid].thumb = NULL;
1996
1997 GList *thumbs = g_hash_table_get_values(table->list);
1998 thumbs = g_list_sort(thumbs, _thumb_compare_rowid_desc);
1999 g_hash_table_remove_all(table->list);
2001
2002 for(GList *l = thumbs; l; l = g_list_next(l))
2003 {
2004 dt_thumbnail_t *thumb = (dt_thumbnail_t *)l->data;
2005 gtk_widget_hide(thumb->widget);
2006 dt_thumbnail_destroy(thumb);
2007 }
2008 g_list_free(thumbs);
2009
2010 table->thumb_nb = 0;
2011 table->thumbs_inited = FALSE;
2012}
2013
2015{
2016 dt_thumbtable_schedule_focus(table, G_PRIORITY_DEFAULT_IDLE);
2017}
2018
2020{
2021 table->mode = mode;
2022 table->ops = _ops_for_mode(mode);
2023
2024 table->parent_overlay = gtk_overlay_new();
2025 g_signal_connect(G_OBJECT(table->parent_overlay), "size-allocate", G_CALLBACK(_parent_overlay_size_allocate), table);
2026
2027 // The frontend builds its own scroll stack (overlay child vs. main child), widget name, help link
2028 // and scroll policy.
2029 table->ops->setup_parent(table);
2030}
2031
2033{
2034 if(!table->collection_inited || table->collection_count == 0) return;
2035
2036 if(table->collapse_groups)
2037 dt_control_log(_("Image groups are collapsed in view.\n"
2038 "Selecting all images will only target visible members of image groups.\n"
2039 "Uncollapse groups to select all their members"));
2040
2041 GList *img = NULL;
2042
2043 dt_pthread_mutex_lock(&table->lock);
2044 for(size_t i = 0; i < table->collection_count; i++)
2045 img = g_list_prepend(img, GINT_TO_POINTER(table->lut[i].imgid));
2047
2048 if(img)
2049 {
2051 g_list_free(img);
2052 img = NULL;
2053 }
2054}
2055
2056void dt_thumbtable_select_range(dt_thumbtable_t *table, const int rowid)
2057{
2058 if(!table->collection_inited || table->collection_count == 0) return;
2059 if(rowid < 0 || rowid > table->collection_count - 1) return;
2060
2061 if(table->collapse_groups)
2062 dt_control_log(_("Image groups are collapsed in view.\n"
2063 "Selecting a range of images will only target visible members of image groups.\n"
2064 "Uncollapse groups to select all their members"));
2065
2066 dt_pthread_mutex_lock(&table->lock);
2067
2068 // Find the bounds of the current selection
2069 size_t rowid_end = 0;
2070 size_t rowid_start = table->collection_count - 1;
2071 GList *selected = dt_selection_get_list(darktable.selection);
2072
2073 if(selected)
2074 {
2075 for(GList *s = g_list_first(selected); s; s = g_list_next(s))
2076 {
2077 int32_t imgid = GPOINTER_TO_INT(s->data);
2078 int row = dt_thumbtable_find_rowid_from_imgid(table, imgid);
2079 if(row < 0) continue; // not found - should not happen
2080 if(row < rowid_start) rowid_start = row;
2081 if(row > rowid_end) rowid_end = row;
2082 }
2083 g_list_free(selected);
2084 selected = NULL;
2085 }
2086 else
2087 {
2088 // range selection always has to start from a previous selection
2090 return;
2091 }
2092
2093 if(rowid_start > rowid_end)
2094 {
2095 // the start is strictly after the end, we have a deep problem
2097 return;
2098 }
2099
2100 // Find the extra imgids to select
2101 GList *img = NULL;
2102 if(rowid > rowid_end && rowid_end < table->collection_count - 1)
2103 {
2104 // select after
2105 for(int i = rowid_end + 1; i <= rowid; i++)
2106 img = g_list_prepend(img, GINT_TO_POINTER(table->lut[i].imgid));
2107 }
2108 else if(rowid < rowid_start && rowid_start > 0)
2109 {
2110 // select before
2111 for(int i = rowid_start - 1; i >= rowid; i--)
2112 img = g_list_prepend(img, GINT_TO_POINTER(table->lut[i].imgid));
2113 }
2114 // else: select within. What should that yield ??? Deselect ?
2115
2117
2118 if(img)
2119 {
2121 g_list_free(img);
2122 img = NULL;
2123 }
2124}
2125
2127{
2128 if(!table->collection_inited || table->collection_count == 0) return;
2129
2130 // Record initial selection, select all, then deselect initial selection
2131 GList *to_deselect = dt_selection_get_list(darktable.selection);
2132 if(to_deselect)
2133 {
2136 g_list_free(to_deselect);
2137 to_deselect = NULL;
2138 }
2139}
2140
2141static gint64 next_over_time = 0;
2142
2143void dt_thumbtable_dispatch_over(dt_thumbtable_t *table, GdkEventType type, int32_t imgid)
2144{
2145 if(!gtk_widget_is_visible(table->scroll_window)) return;
2146
2147 gint64 current_time = g_get_real_time(); // microseconds
2148 if(type == GDK_KEY_PRESS || type == GDK_KEY_RELEASE)
2149 {
2150 // allow the mouse to capture the next hover events
2151 // in more than 100 ms:
2152 next_over_time = current_time + 100000;
2153
2156 }
2157 else if(type == GDK_ENTER_NOTIFY || type == GDK_LEAVE_NOTIFY)
2158 {
2159 // When navigating the grid with arrow keys, the view will get scrolled.
2160 // If the mouse pointer is over the grid, it will enter a new thumbnail
2161 // which will trigger leave/enter events.
2162 // But we don't want that when interacting from the keyboard, so disallow
2163 // recording enter/leave events in the next 100 ms after keyboard interaction.
2164 if(current_time > next_over_time)
2166 else
2167 return;
2168 }
2169 else if(type == GDK_MOTION_NOTIFY || type == GDK_BUTTON_PRESS || type == GDK_2BUTTON_PRESS)
2170 {
2171 // Active mouse pointer interactions: accept unconditionnaly
2173 }
2174 else
2175 {
2176 fprintf(stderr, "[dt_thumbtable_dispatch_over] unsupported event type: %i\n", type);
2177 return;
2178 }
2179
2180 dt_pthread_mutex_lock(&table->lock);
2181 table->rowid = dt_thumbtable_find_rowid_from_imgid(table, imgid);
2183
2184 // Attempt to re-grab focus on every interaction to restore keyboard navigation,
2185 // for example after a combobox grabbed it on click.
2186 if(!gtk_widget_has_focus(table->grid))
2187 {
2188 // But giving focus to the grid scrolls it back to top, so we have to re-scroll it after
2189 double x = 0.;
2190 double y = 0.;
2192 gtk_widget_grab_focus(table->grid);
2194 }
2195}
2196
2197// clang-format off
2198// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2199// vim: shiftwidth=2 expandtab tabstop=2 cindent
2200// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2201// clang-format on
void dt_accels_new_virtual_shortcut(dt_accels_t *accels, GtkAccelGroup *accel_group, const gchar *accel_path, GtkWidget *widget, guint key_val, GdkModifierType accel_mods)
Add a new virtual shortcut. Virtual shortcuts are immutable, read-only and don't trigger any action....
gchar * dt_accels_build_path(const gchar *scope, const gchar *feature)
Handle default and user-set shortcuts (accelerators)
#define DT_ACCELS_WIDGET_TOOLTIP_DISABLED_KEY
GList * dt_act_on_get_images()
Definition act_on.c:39
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
const gchar * dt_collection_get_query(const dt_collection_t *collection)
Definition collection.c:552
uint32_t dt_collection_get_count(const dt_collection_t *collection)
Definition collection.c:836
dt_collection_properties_t
Definition collection.h:107
@ DT_COLLECTION_PROP_GROUPING
Definition collection.h:130
dt_collection_change_t
Definition collection.h:147
dt_iop_colorout_gui_data_t dummy
Definition colorout.c:828
const dt_colormatrix_t dt_aligned_pixel_t out
static const int row
void dt_image_init(dt_image_t *img)
void dt_image_full_path(const int32_t imgid, char *pathname, size_t pathname_len, gboolean *from_cache, const char *calling_func)
Get the full path of an image out of the database.
char * key
int type
char * name
int dt_conf_get_bool(const char *name)
gchar * dt_conf_get_string(const char *name)
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
int32_t dt_control_get_mouse_over_id()
Definition control.c:939
void dt_control_set_mouse_over_id(int32_t value)
Definition control.c:947
void dt_control_log(const char *msg,...)
Definition control.c:777
int32_t dt_control_get_keyboard_over_id()
Definition control.c:965
void dt_control_set_keyboard_over_id(int32_t value)
Definition control.c:973
gboolean dt_control_remove_images()
darktable_t darktable
Definition darktable.c:183
gboolean dt_supported_image(const gchar *filename)
check if file is a supported image
Definition darktable.c:350
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1600
#define UNKNOWN_IMAGE
Definition darktable.h:194
@ DT_DEBUG_LIGHTTABLE
Definition darktable.h:747
#define g_list_is_singleton(list)
Definition darktable.h:1000
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:485
#define dt_free(ptr)
Definition darktable.h:478
static uint64_t dt_hash(uint64_t hash, const char *str, size_t size)
Definition darktable.h:1123
static const dt_aligned_pixel_simd_t value
Definition darktable.h:599
static double dt_get_wtime(void)
Definition darktable.h:976
#define PATH_MAX
Definition darktable.h:1189
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:293
static const GtkTargetEntry target_list_all[]
#define _DWORD
#define _BYTE
@ DND_TARGET_URI
@ DND_TARGET_IMGID
static const guint n_targets_all
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
const dt_thumbtable_layout_ops_t * dt_thumbtable_grid_ops(void)
const dt_thumbtable_layout_ops_t * dt_thumbtable_filmstrip_ops(void)
Definition filmstrip.c:249
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
Definition gdkkeys.h:113
void dt_gui_remove_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:143
void dt_gui_add_help_link(GtkWidget *widget, char *link)
Definition gtk.c:2232
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:133
static gboolean enable(dt_image_t *image)
Definition highlights.c:653
void dt_image_cache_read_release(dt_image_cache_t *cache, const dt_image_t *img)
dt_image_t * dt_image_cache_get(dt_image_cache_t *cache, const int32_t imgid, char mode)
void dt_image_from_stmt(dt_image_t *img, sqlite3_stmt *stmt)
dt_image_t * dt_image_cache_testget(dt_image_cache_t *cache, const int32_t imgid, char mode)
int dt_control_import(dt_control_import_t data)
Process a list of images to import with or without copying the files on an arbitrary hard-drive.
static const float x
const float *const lut
int32_t dt_selection_get_first_id(struct dt_selection_t *selection)
Definition selection.c:69
void dt_selection_deselect_list(struct dt_selection_t *selection, const GList *const l)
Definition selection.c:343
GList * dt_selection_get_list(struct dt_selection_t *selection)
Definition selection.c:172
void dt_selection_select_list(struct dt_selection_t *selection, const GList *const l)
Definition selection.c:320
void dt_selection_deselect(dt_selection_t *selection, int32_t imgid)
Definition selection.c:281
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:387
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:366
@ DT_SIGNAL_VIEWMANAGER_THUMBTABLE_ACTIVATE
Definition signal.h:95
@ DT_SIGNAL_ACTIVE_IMAGES_CHANGE
This signal is raised when image shown in the main view change no param, no returned value.
Definition signal.h:64
@ DT_SIGNAL_CONTROL_PROFILE_USER_CHANGED
This signal is raised when a profile is changed by the user 1 uint32_t : the profile type that has ch...
Definition signal.h:242
@ DT_SIGNAL_MOUSE_OVER_IMAGE_CHANGE
This signal is raised when mouse hovers over image thumbs both on lighttable and in the filmstrip....
Definition signal.h:59
@ DT_SIGNAL_IMAGE_INFO_CHANGED
This signal is raised when any of image info has changed
Definition signal.h:144
@ DT_SIGNAL_SELECTION_CHANGED
This signal is raised when the selection is changed no param, no returned value.
Definition signal.h:127
@ DT_SIGNAL_COLLECTION_CHANGED
This signal is raised when collection changed. To avoid leaking the list, dt_collection_t is connecte...
Definition signal.h:122
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:376
struct _GtkWidget GtkWidget
Definition splash.h:29
unsigned __int64 uint64_t
Definition strptime.c:75
struct dt_gui_gtk_t * gui
Definition darktable.h:803
struct dt_collection_t * collection
Definition darktable.h:809
struct dt_selection_t * selection
Definition darktable.h:810
struct dt_control_signal_t * signals
Definition darktable.h:802
struct dt_image_cache_t * image_cache
Definition darktable.h:805
struct dt_view_manager_t * view_manager
Definition darktable.h:800
GtkAccelGroup * map_accels
GtkAccelGroup * global_accels
GtkAccelGroup * print_accels
GtkAccelGroup * lighttable_accels
GtkAccelGroup * darkroom_accels
dt_accels_t * accels
Definition gtk.h:199
dt_ui_t * ui
Definition gtk.h:165
int32_t group_id
Definition image.h:319
int32_t id
Definition image.h:319
gboolean mouse_over
Definition thumbnail.h:73
dt_pthread_mutex_t lock
Definition thumbnail.h:130
dt_image_t info
Definition thumbnail.h:76
gboolean selected
Definition thumbnail.h:74
int32_t rowid
Definition thumbnail.h:68
dt_thumbnail_overlay_t over
Definition thumbnail.h:106
cairo_surface_t * img_surf
Definition thumbnail.h:85
GtkWidget * widget
Definition thumbnail.h:79
Cache entry for a single thumbnail.
Definition thumbtable.h:88
dt_thumbnail_t * thumb
Definition thumbtable.h:89
Per-mode layout strategy. One instance per frontend, shared (const) by all tables of that mode and st...
void(* pre_activate)(dt_thumbtable_t *table, int32_t imgid)
void(* on_drag_begin)(dt_thumbtable_t *table, int32_t imgid)
int(* position_to_rowid)(dt_thumbtable_t *table, const double x, const double y)
void(* configure_dims)(dt_thumbtable_t *table, int *new_width, int *new_height, int *per_row, int *thumb_width, int *thumb_height)
void(* get_row_ids)(dt_thumbtable_t *table, int *rowid_min, int *rowid_max)
gboolean(* wants_page_size_notify)(dt_thumbtable_t *table, GObject *object)
void(* move_child)(dt_thumbtable_t *table, dt_thumbnail_t *thumb)
void(* group_borders)(dt_thumbtable_t *table, dt_thumbnail_t *thumb, dt_thumbnail_border_t *borders)
gboolean(* wants_scroll_value)(dt_thumbtable_t *table, GtkAdjustment *adjustment)
void(* update_content_size)(dt_thumbtable_t *table)
void(* grab_focus)(dt_thumbtable_t *table)
gboolean(* is_rowid_visible)(dt_thumbtable_t *table, int rowid)
void(* rowid_to_position)(dt_thumbtable_t *table, int rowid, int *x, int *y)
void(* on_thumbnail_added)(dt_thumbtable_t *table, dt_thumbnail_t *thumb)
gboolean(* relevant_scrollbar_changed)(dt_thumbtable_t *table, GtkWidget *widget, GtkAllocation *allocation)
gboolean(* is_thumb_highlighted)(dt_thumbtable_t *table, int32_t imgid)
GtkWidget *(* create_content_widget)(void)
gboolean(* handle_key)(dt_thumbtable_t *table, GdkEventKey *event, guint key, int32_t imgid)
void(* place_child)(dt_thumbtable_t *table, dt_thumbnail_t *thumb)
void(* setup_parent)(dt_thumbtable_t *table)
const dt_thumbtable_layout_ops_t * ops
Definition thumbtable.h:105
GHashTable * list
Definition thumbtable.h:117
gboolean focus_peaking
Definition thumbtable.h:188
gboolean collapse_groups
Definition thumbtable.h:181
GtkWidget * grid
Definition thumbtable.h:109
gboolean alternate_mode
Definition thumbtable.h:173
gboolean collection_inited
Definition thumbtable.h:139
dt_thumbtable_mode_t mode
Definition thumbtable.h:102
dt_pthread_mutex_t lock
Definition thumbtable.h:167
int last_h_scrollbar_height
Definition thumbtable.h:201
GtkAdjustment * v_scrollbar
Definition thumbtable.h:156
int last_v_scrollbar_width
Definition thumbtable.h:202
gboolean focus_regions
Definition thumbtable.h:187
GList * drag_list
Definition thumbtable.h:131
gboolean configured
Definition thumbtable.h:141
GtkWidget * scroll_window
Definition thumbtable.h:153
dt_thumbtable_cache_t * lut
Definition thumbtable.h:151
gboolean thumbs_inited
Definition thumbtable.h:140
GtkAdjustment * h_scrollbar
Definition thumbtable.h:157
uint32_t thumb_nb
Definition thumbtable.h:134
dt_thumbnail_overlay_t overlays
Definition thumbtable.h:107
gboolean reset_collection
Definition thumbtable.h:170
GtkWidget * parent_overlay
Definition thumbtable.h:162
uint64_t collection_hash
Definition thumbtable.h:144
gboolean draw_group_borders
Definition thumbtable.h:190
dt_thumbtable_t * thumbtable_lighttable
GtkWidget * main_window
dt_thumbtable_t * thumbtable_filmstrip
int32_t image_info_id
Definition view.h:211
typedef double((*spd)(unsigned long int wavelength, double TempK))
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
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
void dt_thumbnail_set_mouseover(dt_thumbnail_t *thumb, gboolean over)
Definition thumbnail.c:1631
void dt_thumbnail_resync_info(dt_thumbnail_t *thumb, const dt_image_t *const info)
Definition thumbnail.c:1326
void dt_thumbnail_set_overlay(dt_thumbnail_t *thumb, dt_thumbnail_overlay_t mode)
Definition thumbnail.c:1463
void dt_thumbnail_set_group_border(dt_thumbnail_t *thumb, dt_thumbnail_border_t border)
Definition thumbnail.c:1606
int dt_thumbnail_destroy(dt_thumbnail_t *thumb)
Definition thumbnail.c:1374
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
void dt_thumbnail_alternative_mode(dt_thumbnail_t *thumb, gboolean enable)
Definition thumbnail.c:919
void dt_thumbnail_resize(dt_thumbnail_t *thumb, int width, int height)
Definition thumbnail.c:1561
#define dt_thumbnail_image_refresh(thumb)
Definition thumbnail.h:165
static dt_thumbnail_overlay_t sanitize_overlays(dt_thumbnail_overlay_t overlays)
Definition thumbnail.h:173
dt_thumbnail_border_t
Definition thumbnail.h:49
dt_thumbnail_overlay_t
Definition thumbnail.h:58
@ DT_THUMBNAIL_OVERLAYS_HOVER_NORMAL
Definition thumbnail.h:60
@ DT_THUMBNAIL_OVERLAYS_ALWAYS_NORMAL
Definition thumbnail.h:61
@ DT_THUMBNAIL_OVERLAYS_NONE
Definition thumbnail.h:59
static int _position_to_rowid(dt_thumbtable_t *table, const double x, const double y)
Definition thumbtable.c:346
void _add_thumbnail_at_rowid(dt_thumbtable_t *table, const size_t rowid, const int32_t mouse_over)
Definition thumbtable.c:643
static void _scrollbar_page_size_notify(GObject *object, GParamSpec *pspec, gpointer user_data)
Definition thumbtable.c:233
int dt_thumbtable_scroll_to_selection(dt_thumbtable_t *table)
Scroll to show selected content.
Definition thumbtable.c:444
gboolean _event_main_leave(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
int dt_thumbtable_scroll_to_imgid(dt_thumbtable_t *table, int32_t imgid)
Scroll the view to show a specific image.
Definition thumbtable.c:411
static void _dt_collection_changed_callback(gpointer instance, dt_collection_change_t query_change, dt_collection_properties_t changed_property, gpointer imgs, const int next, gpointer user_data)
gboolean dt_thumbtable_get_focus_peaking(dt_thumbtable_t *table)
Definition thumbtable.c:901
static gboolean _thumbtable_idle_update(gpointer user_data)
Definition thumbtable.c:197
void dt_thumbtable_update(dt_thumbtable_t *table)
Definition thumbtable.c:776
static void _thumbtable_drag_set_icon(dt_thumbtable_t *table, GdkDragContext *context)
void dt_thumbtable_set_active_rowid(dt_thumbtable_t *table)
Update internal active row tracking.
Definition thumbtable.c:368
void dt_thumbtable_get_scroll_position(dt_thumbtable_t *table, double *x, double *y)
Definition thumbtable.c:362
static gint _thumb_compare_rowid_desc(gconstpointer a, gconstpointer b)
Definition thumbtable.c:171
static int _grab_focus(dt_thumbtable_t *table)
Definition thumbtable.c:181
static int dt_thumbtable_scroll_to_position(dt_thumbtable_t *table, const double x, const double y)
Definition thumbtable.c:376
static void _thumbs_update_overlays_mode(dt_thumbtable_t *table)
void dt_thumbtable_configure(dt_thumbtable_t *table)
Definition thumbtable.c:543
void dt_thumbtable_cleanup(dt_thumbtable_t *table)
static gint64 next_over_time
void _grid_configure(dt_thumbtable_t *table, int width, int height, int per_row, int thumb_width, int thumb_height)
Definition thumbtable.c:496
static void _dt_active_images_changed_callback(gpointer instance, gpointer user_data)
Definition thumbtable.c:878
gboolean _is_rowid_visible(dt_thumbtable_t *table, int rowid)
Definition thumbtable.c:463
int _imgid_to_rowid(dt_thumbtable_t *table, int32_t imgid)
gboolean dt_thumbtable_get_focus_regions(dt_thumbtable_t *table)
Definition thumbtable.c:890
void dt_thumbtable_set_focus_peaking(dt_thumbtable_t *table, gboolean enable)
Definition thumbtable.c:895
GList * _thumbtable_dnd_import_check(GList *files, const char *pathname, int *elements)
void dt_thumbtable_event_dnd_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *selection_data, guint target_type, guint time, gpointer user_data)
Handle drag-and-drop data received.
static gboolean _thumbtable_dnd_import(GtkSelectionData *selection_data)
static void _scrollbar_widget_size_allocate(GtkWidget *widget, GtkAllocation *allocation, gpointer user_data)
Definition thumbtable.c:259
static void _dt_collection_lut(dt_thumbtable_t *table)
Definition thumbtable.c:995
void _resize_thumbnails(dt_thumbtable_t *table)
Definition thumbtable.c:743
void _populate_thumbnails(dt_thumbtable_t *table)
Definition thumbtable.c:733
void dt_thumbtable_invert_selection(dt_thumbtable_t *table)
Invert the current selection.
void _dt_thumbtable_empty_list(dt_thumbtable_t *table)
static gboolean _dt_collection_get_hash(dt_thumbtable_t *table)
static void _event_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
gboolean dt_thumbtable_get_thumbnail_info(dt_thumbtable_t *table, int32_t imgid, dt_image_t *out)
Definition thumbtable.c:593
gboolean _update_row_ids(dt_thumbtable_t *table)
Definition thumbtable.c:470
static void _scrollbar_value_changed(GtkAdjustment *adjustment, gpointer user_data)
Definition thumbtable.c:222
static const dt_thumbtable_layout_ops_t * _ops_for_mode(dt_thumbtable_mode_t mode)
static void _thumbtable_schedule_update(dt_thumbtable_t *table)
Definition thumbtable.c:209
static void _parent_overlay_size_allocate(GtkWidget *widget, GtkAllocation *allocation, gpointer user_data)
Definition thumbtable.c:246
void _add_thumbnail_group_borders(dt_thumbtable_t *table, dt_thumbnail_t *thumb)
Definition thumbtable.c:625
void dt_thumbtable_stop(dt_thumbtable_t *table)
void dt_thumbtable_set_focus_regions(dt_thumbtable_t *table, gboolean enable)
Definition thumbtable.c:884
static void dt_thumbtable_scroll_to_rowid(dt_thumbtable_t *table, int rowid)
Definition thumbtable.c:383
void _update_grid_area(dt_thumbtable_t *table)
Definition thumbtable.c:485
int dt_thumbtable_scroll_to_active_rowid(dt_thumbtable_t *table)
Scroll to show the active row.
Definition thumbtable.c:434
gboolean dt_thumbtable_get_draw_group_borders(dt_thumbtable_t *table)
Definition thumbtable.c:914
int dt_thumbtable_thumb_cell_decoration(void)
Definition thumbtable.c:522
void dt_thumbtable_dispatch_over(dt_thumbtable_t *table, GdkEventType type, int32_t imgid)
Update the mouse-over image ID with conflict resolution.
static gboolean _thumbtable_clone_lut(dt_thumbtable_t *dst)
Definition thumbtable.c:73
void _alternative_mode(dt_thumbtable_t *table, gboolean enable)
gboolean dt_thumbtable_key_pressed_grid(GtkWidget *self, GdkEventKey *event, gpointer user_data)
void dt_thumbtable_set_overlays_mode(dt_thumbtable_t *table, dt_thumbnail_overlay_t over)
Set the overlay display mode for thumbnails.
static void _rowid_to_position(dt_thumbtable_t *table, int rowid, int *x, int *y)
Definition thumbtable.c:339
static gboolean _draw_callback(GtkWidget *widget, cairo_t *cr, gpointer user_data)
static void _dt_profile_change_callback(gpointer instance, int type, gpointer user_data)
Definition thumbtable.c:812
static gboolean _get_row_ids(dt_thumbtable_t *table, int *rowid_min, int *rowid_max)
Definition thumbtable.c:455
void dt_thumbtable_schedule_focus(dt_thumbtable_t *table, const gint priority)
Definition thumbtable.c:191
void dt_thumbtable_refresh_thumbnail_real(dt_thumbtable_t *table, int32_t imgid, gboolean reinit)
Definition thumbtable.c:922
void dt_thumbtable_select_all(dt_thumbtable_t *table)
Select all images in the current grid.
static void _dt_selection_changed_callback(gpointer instance, gpointer user_data)
Definition thumbtable.c:869
void dt_thumbtable_reset_collection(dt_thumbtable_t *table)
gboolean dt_thumbtable_key_released_grid(GtkWidget *self, GdkEventKey *event, gpointer user_data)
int dt_thumbtable_find_rowid_from_imgid(dt_thumbtable_t *table, const int32_t imgid)
Definition thumbtable.c:400
static void _event_dnd_end(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
static gboolean _set_thumb_position(dt_thumbtable_t *table, dt_thumbnail_t *thumb)
Definition thumbtable.c:354
static void _event_dnd_get(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data, const guint target_type, const guint time, gpointer user_data)
void dt_thumbtable_move_in_grid(dt_thumbtable_t *table, GdkEventKey *event, dt_thumbtable_direction_t direction, int origin_imgid)
void dt_thumbtable_set_parent(dt_thumbtable_t *table, dt_thumbtable_mode_t mode)
static gchar * _thumbs_get_overlays_class(dt_thumbnail_overlay_t over)
void dt_thumbtable_queue_update(dt_thumbtable_t *table)
Definition thumbtable.c:217
dt_thumbtable_t * dt_thumbtable_new(dt_thumbtable_mode_t mode)
Create a new thumbnail table widget.
void dt_thumbtable_set_draw_group_borders(dt_thumbtable_t *table, gboolean enable)
Definition thumbtable.c:906
void _mouse_over_image_callback(gpointer instance, gpointer user_data)
Definition thumbtable.c:272
void dt_thumbtable_select_range(dt_thumbtable_t *table, const int rowid)
Select a range of images in the collection.
static void _dt_image_info_changed_callback(gpointer instance, gpointer imgs, gpointer user_data)
Definition thumbtable.c:956
static void _refresh_highlights(dt_thumbtable_t *table)
Definition thumbtable.c:824
dt_thumbnail_t * _find_thumb_by_imgid(dt_thumbtable_t *table, const int32_t imgid)
Definition thumbtable.c:587
void dt_thumbtable_update_parent(dt_thumbtable_t *table)
A widget to manage and display image thumbnails in Ansel's lighttable and filmstrip views.
dt_thumbtable_mode_t
Display modes for the thumbnail table.
Definition thumbtable.h:62
@ DT_THUMBTABLE_MODE_FILMSTRIP
Definition thumbtable.h:65
@ DT_THUMBTABLE_MODE_FILEMANAGER
Definition thumbtable.h:64
#define dt_thumbtable_refresh_thumbnail(table, imgid, reinit)
Definition thumbtable.h:292
void dt_thumbtable_info_cleanup(void)
void dt_thumbtable_info_debug_assert_matches_cache(const dt_image_t *sql_info)
void dt_thumbtable_info_seed_image_cache(const dt_image_t *info)
sqlite3_stmt * dt_thumbtable_info_get_collection_stmt(void)
static gboolean dt_thumbtable_info_is_grouped(const dt_image_t info)
Private interface shared between the thumbtable engine (thumbtable.c) and its two frontends (filemana...
dt_thumbtable_direction_t
@ DT_TT_MOVE_LEFT
@ DT_TT_MOVE_RIGHT
@ DT_TT_MOVE_DOWN
@ DT_TT_MOVE_NEXT_PAGE
@ DT_TT_MOVE_UP
@ DT_TT_MOVE_START
@ DT_TT_MOVE_PREVIOUS_PAGE
@ DT_TT_MOVE_END
char * dt_get_help_url(char *name)
gchar * dt_util_glist_to_str(const gchar *separator, GList *items)
Definition utility.c:166
void dt_view_image_info_update(int32_t imgid)
Definition view.c:1501