Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
libs/tools/lighttable.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2011, 2013 Henrik Andersson.
4 Copyright (C) 2012 Petr Styblo.
5 Copyright (C) 2012 Richard Wonka.
6 Copyright (C) 2012-2014, 2016-2017 Tobias Ellinghaus.
7 Copyright (C) 2013 johannes hanika.
8 Copyright (C) 2013 Pascal de Bruijn.
9 Copyright (C) 2013-2014, 2016 Roman Lebedev.
10 Copyright (C) 2014, 2019-2022 Aldric Renaudin.
11 Copyright (C) 2018 Mario Lueder.
12 Copyright (C) 2018 Rikard Öxler.
13 Copyright (C) 2019, 2022-2023, 2025 Aurélien PIERRE.
14 Copyright (C) 2019, 2021 Bill Ferguson.
15 Copyright (C) 2019 Edgardo Hoszowski.
16 Copyright (C) 2019-2021 Pascal Obry.
17 Copyright (C) 2020-2021 Chris Elston.
18 Copyright (C) 2021-2022 Diederik Ter Rahe.
19 Copyright (C) 2021 Ralf Brown.
20 Copyright (C) 2022 Martin Bařinka.
21 Copyright (C) 2022 Nicolas Auffray.
22
23 darktable is free software: you can redistribute it and/or modify
24 it under the terms of the GNU General Public License as published by
25 the Free Software Foundation, either version 3 of the License, or
26 (at your option) any later version.
27
28 darktable is distributed in the hope that it will be useful,
29 but WITHOUT ANY WARRANTY; without even the implied warranty of
30 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 GNU General Public License for more details.
32
33 You should have received a copy of the GNU General Public License
34 along with darktable. If not, see <http://www.gnu.org/licenses/>.
35*/
36
37#include "common/darktable.h"
38#include <gdk/gdkkeysyms.h>
39
40#include "common/collection.h"
41#include "common/debug.h"
42#include "common/selection.h"
43#include "control/conf.h"
44#include "control/control.h"
45#include "dtgtk/button.h"
46#include "dtgtk/thumbtable.h"
47#include "dtgtk/togglebutton.h"
48#include "gui/actions/menu.h"
49
50#include "gui/gtk.h"
51#include "libs/lib.h"
52#include "libs/lib_api.h"
53
54DT_MODULE(1)
55
62
63/* set columns proxy function */
64static void _lib_lighttable_set_columns(dt_lib_module_t *self, gint columns);
65
66/* columns slider change callback */
67static void _lib_lighttable_columns_slider_changed(GtkWidget *widget, gpointer user_data);
68
69static void _set_columns(dt_lib_module_t *self, int columns);
70
71const char *name(struct dt_lib_module_t *self)
72{
73 return _("lighttable");
74}
75
76const char **views(dt_lib_module_t *self)
77{
78 static const char *v[] = {"lighttable", NULL};
79 return v;
80}
81
86
88{
89 return 0;
90}
91
93{
94 return 1001;
95}
96
97gboolean _columns_in_action(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
98 GdkModifierType modifier, gpointer data)
99{
100 dt_lib_module_t *self = (dt_lib_module_t *)data;
101 int current_level = dt_conf_get_int("plugins/lighttable/images_in_row");
102 int new_level = CLAMP(current_level - 1, 1, 12);
103 _lib_lighttable_set_columns(self, new_level);
104 dt_conf_set_int("plugins/lighttable/images_in_row_backup", new_level);
105 return TRUE;
106}
107
108gboolean _columns_out_action(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval,
109 GdkModifierType modifier, gpointer data)
110{
111 dt_lib_module_t *self = (dt_lib_module_t *)data;
112 int current_level = dt_conf_get_int("plugins/lighttable/images_in_row");
113 int new_level = CLAMP(current_level + 1, 1, 12);
114 _lib_lighttable_set_columns(self, new_level);
115 dt_conf_set_int("plugins/lighttable/images_in_row_backup", new_level);
116 return TRUE;
117}
118
119static void _dt_collection_changed_callback(gpointer instance, dt_collection_change_t query_change,
120 dt_collection_properties_t changed_property, gpointer imgs,
121 const int next, gpointer user_data)
122{
123 if(IS_NULL_PTR(user_data)) return;
124 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
125
127 {
128 int current_level = dt_conf_get_int("plugins/lighttable/images_in_row");
130
131 switch(num_images)
132 {
133 case 1:
134 case 2:
135 case 3:
136 case 4:
137 case 5:
138 _lib_lighttable_set_columns(self, num_images);
139 dt_conf_set_int("plugins/lighttable/images_in_row_backup", current_level);
140 break;
141 case 6:
143 dt_conf_set_int("plugins/lighttable/images_in_row_backup", current_level);
144 break;
145 case 7:
146 case 8:
148 dt_conf_set_int("plugins/lighttable/images_in_row_backup", current_level);
149 break;
150 case 9:
151 case 10:
152 case 11:
153 case 12:
154 case 13:
155 case 14:
156 case 15:
158 dt_conf_set_int("plugins/lighttable/images_in_row_backup", current_level);
159 break;
160 default:
161 if(dt_conf_key_exists("plugins/lighttable/images_in_row_backup"))
162 _lib_lighttable_set_columns(self, dt_conf_get_int("plugins/lighttable/images_in_row_backup"));
163 }
164 }
165 else if(dt_conf_key_exists("plugins/lighttable/images_in_row_backup"))
166 {
167 _lib_lighttable_set_columns(self, dt_conf_get_int("plugins/lighttable/images_in_row_backup"));
168 }
169
170 // Reset zoom
172}
173
174static gboolean _zoom_combobox_changed(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data)
175{
176 const int level = GPOINTER_TO_INT(get_custom_data(GTK_WIDGET(user_data)));
178 return TRUE;
179}
180
181static gboolean _zoom_checked(GtkWidget *widget)
182{
183 const int level = GPOINTER_TO_INT(get_custom_data(widget));
185}
186
187
188// Ctrl + Scroll changes the number of columns
189static gboolean _thumbtable_scroll(GtkWidget *widget, GdkEventScroll *event, gpointer data)
190{
191 dt_lib_module_t *self = (dt_lib_module_t *)data;
192
193 if(dt_modifier_is(event->state, GDK_CONTROL_MASK))
194 {
195 int scroll_y;
196 dt_gui_get_scroll_unit_deltas(event, NULL, &scroll_y);
197
198 int current_level = dt_conf_get_int("plugins/lighttable/images_in_row");
199 int new_level = CLAMP(current_level + CLAMP(scroll_y, -1, 1), 1, 12);
200
201 _lib_lighttable_set_columns(self, new_level);
202 dt_conf_set_int("plugins/lighttable/images_in_row_backup", new_level);
203 return TRUE;
204 }
205 return FALSE;
206}
207
208static gboolean _focus_toggle_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data)
209{
211 gboolean state = dt_thumbtable_get_focus_regions(table);
213 return TRUE;
214}
215
221
222static gboolean focus_peaking_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data)
223{
225 gboolean focus_peaking = dt_thumbtable_get_focus_peaking(table);
226 dt_thumbtable_set_focus_peaking(table, !focus_peaking);
227 return TRUE;
228}
229
235
236void append_thumbnails(GtkWidget **menus, GList **lists, const dt_menus_t index, GtkAccelGroup *accel_group)
237{
238 // Focusing options
239 add_generic_sub_menu_entry(menus, lists, _("Overlay focus zones"), index, NULL, _focus_toggle_callback, _focus_checked, NULL,
240 NULL, 0, 0, accel_group);
241
242 add_generic_sub_menu_entry(menus, lists, _("Overlay focus peaking"), index, NULL, focus_peaking_callback,
243 focus_peaking_checked_callback, NULL, NULL, GDK_KEY_p,
244 GDK_CONTROL_MASK | GDK_SHIFT_MASK, accel_group);
245
246 // Zoom
247 add_generic_top_submenu_entry(menus, lists, _("Zoom"), index, accel_group);
248 GtkWidget *parent = get_last_widget(lists);
249 add_generic_sub_sub_menu_entry(menus, parent, lists, _("Fit"), index, GINT_TO_POINTER(0), _zoom_combobox_changed,
250 _zoom_checked, NULL, NULL, 0, 0, accel_group);
251 add_generic_sub_sub_menu_entry(menus, parent, lists, _("50 %"), index, GINT_TO_POINTER(1), _zoom_combobox_changed,
252 _zoom_checked, NULL, NULL, 0, 0, accel_group);
253 add_generic_sub_sub_menu_entry(menus, parent, lists, _("100 %"), index, GINT_TO_POINTER(2), _zoom_combobox_changed,
254 _zoom_checked, NULL, NULL, 0, 0, accel_group);
255 add_generic_sub_sub_menu_entry(menus, parent, lists, _("200 %"), index, GINT_TO_POINTER(3), _zoom_combobox_changed,
256 _zoom_checked, NULL, NULL, 0, 0, accel_group);
257}
258
260{
261 /* initialize ui widgets */
263 self->data = (void *)d;
264
265 self->widget = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
266 dt_gui_add_class(self->widget, "lighttable_box");
267 gtk_widget_set_halign(self->widget, GTK_ALIGN_END);
268 gtk_widget_set_hexpand(self->widget, FALSE);
269
270 // Thumbnail menu
271 GtkAccelGroup *accel_group = darktable.gui->accels->lighttable_accels;
272 GtkWidget *menu_bar = gtk_menu_bar_new();
273 GtkWidget *menus[1];
274 const int index = 0;
275 d->menu_items = NULL;
276 add_generic_top_menu_entry(menu_bar, menus, &d->menu_items, index, _("_Thumbnails"), accel_group, "Lighttable");
277 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(menu_bar), FALSE, FALSE, 0);
278 append_thumbnails(menus, &d->menu_items, index, accel_group);
279
280 // dumb empty flexible spacer at the end
281 GtkWidget *spacer = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
282 gtk_box_pack_start(GTK_BOX(self->widget), spacer, TRUE, TRUE, 0);
283
284 GtkWidget *label = gtk_label_new(C_("quickfilter", "Columns"));
285 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(label), FALSE, FALSE, 0);
286
287 d->columns = gtk_spin_button_new_with_range(1., 12., 1.);
288 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(d->columns), FALSE, FALSE, 0);
289 gtk_spin_button_set_value(GTK_SPIN_BUTTON(d->columns), dt_conf_get_int("plugins/lighttable/images_in_row"));
291
292 g_signal_connect(G_OBJECT(d->columns), "value-changed", G_CALLBACK(_lib_lighttable_columns_slider_changed), self);
293
294 dt_accels_new_lighttable_action(_columns_in_action, self, N_("Lighttable/Actions"), N_("Zoom in the thumbtable grid"),
295 GDK_KEY_plus, GDK_CONTROL_MASK, _("Triggers the action"));
296 dt_accels_new_lighttable_action(_columns_out_action, self, N_("Lighttable/Actions"), N_("Zoom out the thumbtable grid"),
297 GDK_KEY_minus, GDK_CONTROL_MASK, _("Triggers the action"));
298
300 G_CALLBACK(_dt_collection_changed_callback), self);
301
302 _lib_lighttable_columns_slider_changed(d->columns, self); // the slider defaults to 1 and GTK doesn't
303 // fire a value-changed signal when setting
304 // it to 1 => empty text box
305
306 // Wire a scroll event handler on thumbtable here. This avoids us a proxy
308 d->scroll_handler_id
309 = g_signal_connect(G_OBJECT(table->scroll_window), "scroll-event", G_CALLBACK(_thumbtable_scroll), self);
310}
311
313{
314 if(IS_NULL_PTR(self->data)) return;
316
318
319 if(d->scroll_handler_id > 0)
320 {
322 if(!IS_NULL_PTR(table) && !IS_NULL_PTR(table->scroll_window))
323 g_signal_handler_disconnect(G_OBJECT(table->scroll_window), d->scroll_handler_id);
324 d->scroll_handler_id = 0;
325 }
326
327 GList *menu_widgets = NULL;
328 for(GList *iter = d->menu_items; iter; iter = g_list_next(iter))
329 {
330 dt_menu_entry_t *entry = (dt_menu_entry_t *)iter->data;
331 if(IS_NULL_PTR(entry) || IS_NULL_PTR(entry->widget) || !GTK_IS_WIDGET(entry->widget)) continue;
332 menu_widgets = g_list_prepend(menu_widgets, g_object_ref(entry->widget));
333 }
334
335 for(GList *iter = menu_widgets; iter; iter = g_list_next(iter))
336 {
337 GtkWidget *widget = GTK_WIDGET(iter->data);
338 if(GTK_IS_WIDGET(widget)) gtk_widget_destroy(widget);
339 g_object_unref(widget);
340 }
341 g_list_free(menu_widgets);
342
343 g_list_free(d->menu_items);
344 d->menu_items = NULL;
345
346 dt_free(self->data);
347}
348
349static void _set_columns(dt_lib_module_t *self, int columns)
350{
351 dt_conf_set_int("plugins/lighttable/images_in_row", columns);
352
353 // Use the coordinated grid configuration function that properly orders:
354 // 1. Grid reconfiguration with new column count
355 // 2. Thumbnail updates and resizing
356 // 3. Scrolling to active selection
357 // This prevents partial updates and ensures synchronization.
359}
360
361static void _lib_lighttable_columns_slider_changed(GtkWidget *widget, gpointer user_data)
362{
363 dt_lib_module_t *self = (dt_lib_module_t *)user_data;
365 const int cols = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(d->columns));
366 _set_columns(self, cols);
367 dt_conf_set_int("plugins/lighttable/images_in_row_backup", cols);
368}
369
370static void _lib_lighttable_set_columns(dt_lib_module_t *self, gint columns)
371{
373 gtk_spin_button_set_value(GTK_SPIN_BUTTON(d->columns), columns);
374 _set_columns(self, columns);
375}
376
377// clang-format off
378// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
379// vim: shiftwidth=2 expandtab tabstop=2 cindent
380// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
381// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
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_change_t
Definition collection.h:147
char * name
int dt_conf_key_exists(const char *key)
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
darktable_t darktable
Definition darktable.c:181
#define DT_MODULE(MODVER)
Definition darktable.h:140
#define dt_free(ptr)
Definition darktable.h:456
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
gboolean dt_gui_get_scroll_unit_deltas(const GdkEventScroll *event, int *delta_x, int *delta_y)
Definition gtk.c:219
void dt_accels_disconnect_on_text_input(GtkWidget *widget)
Disconnects accels when a text or search entry gets the focus, and reconnects them when it looses it....
Definition gtk.c:3225
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:133
#define dt_accels_new_lighttable_action(a, b, c, d, e, f, g)
Definition gtk.h:432
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
const float v
static void _lib_lighttable_columns_slider_changed(GtkWidget *widget, gpointer user_data)
static gboolean focus_peaking_checked_callback()
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)
void append_thumbnails(GtkWidget **menus, GList **lists, const dt_menus_t index, GtkAccelGroup *accel_group)
gboolean _columns_out_action(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval, GdkModifierType modifier, gpointer data)
static void _set_columns(dt_lib_module_t *self, int columns)
void gui_cleanup(dt_lib_module_t *self)
static gboolean focus_peaking_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data)
static gboolean _focus_toggle_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data)
gboolean _columns_in_action(GtkAccelGroup *accel_group, GObject *accelerable, guint keyval, GdkModifierType modifier, gpointer data)
static gboolean _zoom_checked(GtkWidget *widget)
gboolean _focus_checked(GtkWidget *widget)
uint32_t container(dt_lib_module_t *self)
void gui_init(dt_lib_module_t *self)
int position()
static void _lib_lighttable_set_columns(dt_lib_module_t *self, gint columns)
const char ** views(dt_lib_module_t *self)
int expandable(dt_lib_module_t *self)
static gboolean _thumbtable_scroll(GtkWidget *widget, GdkEventScroll *event, gpointer data)
static gboolean _zoom_combobox_changed(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data)
void add_generic_sub_menu_entry(GtkWidget **menus, GList **lists, const gchar *label, const dt_menus_t index, void *data, gboolean(*action_callback)(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data), gboolean(*checked_callback)(GtkWidget *widget), gboolean(*active_callback)(GtkWidget *widget), gboolean(*sensitive_callback)(GtkWidget *widget), guint key_val, GdkModifierType mods, GtkAccelGroup *accel_group)
Definition menu.c:521
void add_generic_sub_sub_menu_entry(GtkWidget **menus, GtkWidget *parent, GList **lists, const gchar *label, const dt_menus_t index, void *data, gboolean(*action_callback)(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data), gboolean(*checked_callback)(GtkWidget *widget), gboolean(*active_callback)(GtkWidget *widget), gboolean(*sensitive_callback)(GtkWidget *widget), guint key_val, GdkModifierType mods, GtkAccelGroup *accel_group)
Definition menu.c:566
GtkWidget * get_last_widget(GList **list)
Definition menu.c:618
void * get_custom_data(GtkWidget *widget)
Definition menu.c:612
void add_generic_top_menu_entry(GtkWidget *menu_bar, GtkWidget **menus, GList **lists, const dt_menus_t index, gchar *label, GtkAccelGroup *accel_group, const char *accel_path_prefix)
Definition menu.c:460
void add_generic_top_submenu_entry(GtkWidget **menus, GList **lists, const gchar *label, const dt_menus_t index, GtkAccelGroup *accel_group)
Definition menu.c:493
dt_menus_t
Definition menu.h:42
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:368
@ 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:357
struct _GtkWidget GtkWidget
Definition splash.h:29
const float uint32_t state[4]
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_collection_t * collection
Definition darktable.h:781
struct dt_control_signal_t * signals
Definition darktable.h:774
GtkAccelGroup * lighttable_accels
dt_accels_t * accels
Definition gtk.h:194
dt_ui_t * ui
Definition gtk.h:164
gboolean culling_mode
Definition gtk.h:187
GModule *void * data
Definition lib.h:80
GtkWidget * widget
Definition lib.h:84
Definition menu.h:65
GtkWidget * widget
Definition menu.h:66
GtkWidget * scroll_window
Definition thumbtable.h:144
dt_thumbtable_t * thumbtable_lighttable
void dt_thumbtable_set_zoom(dt_thumbtable_t *table, dt_thumbtable_zoom_t level)
gboolean dt_thumbtable_get_focus_peaking(dt_thumbtable_t *table)
void dt_thumbtable_apply_grid_configuration(dt_thumbtable_t *table)
Apply grid configuration changes with proper event synchronization.
Definition thumbtable.c:286
gboolean dt_thumbtable_get_focus_regions(dt_thumbtable_t *table)
void dt_thumbtable_set_focus_peaking(dt_thumbtable_t *table, gboolean enable)
void dt_thumbtable_set_focus_regions(dt_thumbtable_t *table, gboolean enable)
dt_thumbtable_zoom_t dt_thumbtable_get_zoom(dt_thumbtable_t *table)
A widget to manage and display image thumbnails in Ansel's lighttable and filmstrip views.
@ DT_UI_CONTAINER_PANEL_TOP_SECOND_ROW