Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
file.c
Go to the documentation of this file.
1/*
2 This file is part of the Ansel project.
3 Copyright (C) 2023-2025 Aurélien PIERRE.
4 Copyright (C) 2023 lologor.
5 Copyright (C) 2023 Luca Zulberti.
6 Copyright (C) 2024, 2026 Guillaume Stutin.
7
8 Ansel is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 Ansel is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
20*/
21#include "common/darktable.h"
22#include "gui/actions/menu.h"
23#include "common/collection.h"
24#include "libs/collect.h"
25#include "common/import.h"
26#include "libs/lib.h"
27#include "control/control.h"
28
29
30static void pretty_print_collection(const char *buf, char *out, size_t outsize)
31{
32 memset(out, 0, outsize);
33
34 if(IS_NULL_PTR(buf) || buf[0] == '\0') return;
35
36 int num_rules = 0;
37 char str[400] = { 0 };
38 int mode, item;
39 int c;
40 sscanf(buf, "%d", &num_rules);
41 while(buf[0] != '\0' && buf[0] != ':') buf++;
42 if(buf[0] == ':') buf++;
43
44 for(int k = 0; k < num_rules; k++)
45 {
46 const int n = sscanf(buf, "%d:%d:%399[^$]", &mode, &item, str);
47
48 if(n == 3)
49 {
50 if(k > 0) switch(mode)
51 {
53 c = g_strlcpy(out, _(" and "), outsize);
54 out += c;
55 outsize -= c;
56 break;
58 c = g_strlcpy(out, _(" or "), outsize);
59 out += c;
60 outsize -= c;
61 break;
62 default: // case DT_LIB_COLLECT_MODE_AND_NOT:
63 c = g_strlcpy(out, _(" but not "), outsize);
64 out += c;
65 outsize -= c;
66 break;
67 }
68 int i = 0;
69 while(str[i] != '\0' && str[i] != '$') i++;
70 if(str[i] == '$') str[i] = '\0';
71
72 c = snprintf(out, outsize, "%s %s", item < DT_COLLECTION_PROP_LAST ? dt_collection_name(item) : "???",
73 item == 0 ? dt_image_film_roll_name(str) : str);
74 out += c;
75 outsize -= c;
76 }
77 while(buf[0] != '$' && buf[0] != '\0') buf++;
78 if(buf[0] == '$') buf++;
79 }
80}
81
82
83static gboolean update_collection_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data)
84{
85 // Grab the position of current menuitem in menu list
86 const int index = GPOINTER_TO_INT(get_custom_data(GTK_WIDGET(user_data)));
87
88 // Grab the corresponding config line
89 char confname[200];
90 snprintf(confname, sizeof(confname), "plugins/lighttable/recentcollect/line%1d", index);
91 const char *collection = dt_conf_get_string_const(confname);
92
93 snprintf(confname, sizeof(confname), "plugins/lighttable/recentcollect/pos%1d", index);
94
95 // Update the collection to the value defined in config line
96 dt_collection_deserialize(collection);
97
98 return TRUE;
99}
100
101
102void init_collection_line(gpointer instance,
103 dt_collection_change_t query_change,
104 dt_collection_properties_t changed_property, gpointer imgs, int next,
105 gpointer user_data)
106{
107 GtkWidget *widget = GTK_WIDGET(user_data);
108
109 // Grab the position of current menuitem in menu list
110 const int index = GPOINTER_TO_INT(get_custom_data(widget));
111
112 // Grab the corresponding config line
113 char confname[200];
114 snprintf(confname, sizeof(confname), "plugins/lighttable/recentcollect/line%1d", index);
115
116 // Get the human-readable name of the collection
117 const char *collection = dt_conf_get_string_const(confname);
118
119
120 if(collection && collection[0] != '\0')
121 {
122 char label[2048] = { 0 };
123 pretty_print_collection(collection, label, sizeof(label));
124 dt_capitalize_label(label);
125
126 // Update the menu entry label for current collection name. Escape it: a collection value
127 // can contain markup-significant characters (e.g. the < > operators in date/numeric rules).
128 GtkWidget *child = gtk_bin_get_child(GTK_BIN(widget));
129 gchar *escaped = g_markup_escape_text(label, -1);
130 gtk_label_set_markup(GTK_LABEL(child), escaped);
131 g_free(escaped);
132 }
133}
134
135void _close_export_popup(GtkWidget *dialog, gint response_id, gpointer data)
136{
137 // We need to increase the reference count of the module,
138 // then remove it from the popup before closing it,
139 // otherwise it gets destroyed along with it.
140 darktable.gui->export_popup.module = (GtkWidget *)g_object_ref(darktable.gui->export_popup.module);
141 GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(darktable.gui->export_popup.window));
142 gtk_container_remove(GTK_CONTAINER(content), darktable.gui->export_popup.module);
144}
145
146static gboolean export_files_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data)
147{
149 {
150 // if not NULL, we already have a popup open and can't re-instanciate a live GtkWidget
151 gtk_window_present_with_time(GTK_WINDOW(darktable.gui->export_popup.window), GDK_CURRENT_TIME);
152 return TRUE;
153 }
154
155 dt_lib_module_t *module = dt_lib_get_module("export");
156 if(IS_NULL_PTR(module)) return TRUE;
157
158 // get_expander actually builds the expander, it's not a getter despite what the name suggests.
159 // On first run we need to build, an the following runs, just fetch it
161 ? darktable.gui->export_popup.module
162 : dt_lib_gui_get_expander(module);
163 if(IS_NULL_PTR(w)) return TRUE;
164
165 // Save the module
166 darktable.gui->export_popup.module = w;
167
168 // Prepare the popup
169 GtkWidget *dialog = gtk_dialog_new();
170#ifdef GDK_WINDOWING_QUARTZ
171// TODO: On MacOS (at least on version 13) the dialog windows doesn't behave as expected. The dialog
172// needs to have a parent window. "set_parent_window" wasn't working, so set_transient_for is
173// the way to go. Still the window manager isn't dealing with the dialog properly, when the dialog
174// is shifted outside its parent. The dialog isn't visible any longer but still listed as a window
175// of the app.
177 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER_ON_PARENT);
178#endif
179 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CANCEL);
180 gtk_window_set_modal(GTK_WINDOW(dialog), FALSE);
181 gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)));
182 gtk_window_set_title(GTK_WINDOW(dialog), _("Ansel - Export images"));
183 g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(_close_export_popup), NULL);
184
185 // Ensure the module is expanded
187 dt_gui_add_help_link(w, dt_get_help_url(module->plugin_name));
188 gtk_widget_set_size_request(w, DT_PIXEL_APPLY_DPI(450), -1);
189
190 // Populate popup and fire everything
191 GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
192 gtk_box_pack_start(GTK_BOX(content), w, TRUE, TRUE, 0);
193 gtk_widget_set_visible(w, TRUE);
194 gtk_widget_show_all(dialog);
195
196 // Save the ref to the window. We don't reuse its content, we just need to know if it exists.
198
199 return TRUE;
200}
201
202
212
213
214void append_file(GtkWidget **menus, GList **lists, const dt_menus_t index)
215{
216 add_sub_menu_entry(menus, lists, _("Import..."), index, NULL, GET_ACCEL_WRAPPER(dt_images_import), NULL, NULL,
217 NULL, GDK_KEY_i, GDK_CONTROL_MASK | GDK_SHIFT_MASK);
218
219 add_sub_menu_entry(menus, lists, _("Export..."), index, NULL, export_files_callback, NULL, NULL,
220 NULL, GDK_KEY_e, GDK_CONTROL_MASK | GDK_SHIFT_MASK);
221
222 add_menu_separator(menus[index]);
223
224 add_top_submenu_entry(menus, lists, _("Recent collections"), index);
225 GtkWidget *parent = get_last_widget(lists);
226
227 for(int i = 0; i < NUM_LAST_COLLECTIONS; i++)
228 {
229 // Pass the position of current menuitem in list as custom-data pointer
230 gchar *item_label = g_strdup_printf(_("Most recent collection #%i"), i);
231 add_sub_sub_menu_entry(menus, parent, lists, item_label, index, GINT_TO_POINTER(i), update_collection_callback, NULL, NULL, NULL, 0, 0);
232 dt_free(item_label);
233
234 // Call init directly just this once
235 GtkWidget *this = get_last_widget(lists);
237
238 // Connect init to collection_changed signal for future updates
240 G_CALLBACK(init_collection_line), (gpointer)this);
241 }
242
243 add_menu_separator(menus[index]);
244
245 add_sub_menu_entry(menus, lists, _("Copy files on disk..."), index, NULL, GET_ACCEL_WRAPPER(dt_control_copy_images), NULL, NULL,
246 has_active_images, 0, 0);
247
248 add_sub_menu_entry(menus, lists, _("Move files on disk..."), index, NULL, GET_ACCEL_WRAPPER(dt_control_move_images), NULL, NULL,
249 has_active_images, 0, 0);
250
251 add_sub_menu_entry(menus, lists, _("Create a blended HDR"), index, NULL, GET_ACCEL_WRAPPER(dt_control_merge_hdr), NULL, NULL,
252 has_active_images, 0, 0);
253
254 add_menu_separator(menus[index]);
255
256 add_sub_menu_entry(menus, lists, _("Copy distant images locally"), index, NULL, GET_ACCEL_WRAPPER(dt_control_set_local_copy_images), NULL, NULL,
257 has_active_images, 0, 0);
258
259 add_sub_menu_entry(menus, lists, _("Resynchronize distant images"), index, NULL, GET_ACCEL_WRAPPER(dt_control_reset_local_copy_images), NULL, NULL,
260 has_active_images, 0, 0);
261
262 add_menu_separator(menus[index]);
263
264 add_no_accel_sub_menu_entry(menus, lists, _("Remove from library"), index, NULL, GET_ACCEL_WRAPPER(dt_control_remove_images), NULL, NULL,
265 has_active_image_in_lighttable, GDK_KEY_Delete, 0);
266
267 add_sub_menu_entry(menus, lists, _("Delete from disk"), index, NULL, GET_ACCEL_WRAPPER(dt_control_delete_images), NULL, NULL,
268 has_active_image_in_lighttable, GDK_KEY_Delete, GDK_SHIFT_MASK);
269
270 add_menu_separator(menus[index]);
271
272 add_sub_menu_entry(menus, lists, _("Quit"), index, NULL, GET_ACCEL_WRAPPER(dt_control_quit), NULL, NULL, NULL, GDK_KEY_q, GDK_CONTROL_MASK);
273}
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
@ DT_LIB_COLLECT_MODE_OR
Definition collect.h:38
@ DT_LIB_COLLECT_MODE_AND
Definition collect.h:37
const char * dt_collection_name(dt_collection_properties_t prop)
Definition collection.c:657
void dt_collection_deserialize(const char *buf)
dt_collection_properties_t
Definition collection.h:107
@ DT_COLLECTION_PROP_LAST
Definition collection.h:140
@ DT_COLLECTION_PROP_UNDEF
Definition collection.h:142
dt_collection_change_t
Definition collection.h:147
@ DT_COLLECTION_CHANGE_NONE
Definition collection.h:148
#define NUM_LAST_COLLECTIONS
Definition collection.h:53
const dt_colormatrix_t dt_aligned_pixel_t out
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
const char * dt_image_film_roll_name(const char *path)
const char * dt_conf_get_string_const(const char *name)
void dt_control_quit()
Definition control.c:433
void dt_control_delete_images()
void dt_control_move_images()
void dt_control_reset_local_copy_images()
void dt_control_set_local_copy_images()
gboolean dt_control_remove_images()
void dt_control_merge_hdr()
void dt_control_copy_images()
darktable_t darktable
Definition darktable.c:181
#define dt_free(ptr)
Definition darktable.h:456
#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
void init_collection_line(gpointer instance, dt_collection_change_t query_change, dt_collection_properties_t changed_property, gpointer imgs, int next, gpointer user_data)
Definition file.c:102
static gboolean export_files_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data)
Definition file.c:146
static gboolean update_collection_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data)
Definition file.c:83
void _close_export_popup(GtkWidget *dialog, gint response_id, gpointer data)
Definition file.c:135
static void pretty_print_collection(const char *buf, char *out, size_t outsize)
Definition file.c:30
void append_file(GtkWidget **menus, GList **lists, const dt_menus_t index)
Definition file.c:214
void dt_capitalize_label(gchar *text)
Definition gtk.c:3150
void dt_gui_add_help_link(GtkWidget *widget, char *link)
Definition gtk.c:2022
GtkWidget * dt_ui_main_window(dt_ui_t *ui)
get the main window widget
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
void dt_images_import()
Definition import.c:1496
void dt_lib_gui_set_expanded(dt_lib_module_t *module, gboolean expanded)
Definition lib.c:1064
GtkWidget * dt_lib_gui_get_expander(dt_lib_module_t *module)
Definition lib.c:1240
float *const restrict const size_t k
gboolean has_active_image_in_lighttable()
Definition menu.c:653
void add_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)
Definition menu.c:584
void add_no_accel_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)
Definition menu.c:553
GtkWidget * get_last_widget(GList **list)
Definition menu.c:618
void add_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)
Definition menu.c:542
void * get_custom_data(GtkWidget *widget)
Definition menu.c:612
void add_top_submenu_entry(GtkWidget **menus, GList **lists, const gchar *label, const dt_menus_t index)
Definition menu.c:515
void add_menu_separator(GtkWidget *menu)
Definition menu.c:598
gboolean has_active_images()
Definition menu.c:636
#define GET_ACCEL_WRAPPER(cb)
Definition menu.h:244
#define MAKE_ACCEL_WRAPPER(cb)
Definition menu.h:232
dt_menus_t
Definition menu.h:42
void dt_osx_disallow_fullscreen(GtkWidget *widget)
Definition osx.mm:104
@ 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
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_control_signal_t * signals
Definition darktable.h:774
dt_ui_t * ui
Definition gtk.h:164
struct dt_gui_gtk_t::@48 export_popup
GtkWidget * window
Definition gtk.h:235
char * dt_get_help_url(char *name)