Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
src/gui/actions/styles.c
Go to the documentation of this file.
1/*
2 This file is part of the Ansel project.
3 Copyright (C) 2026 Aurélien PIERRE.
4 Copyright (C) 2026 Guillaume Stutin.
5
6 Ansel is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 Ansel is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
18*/
19
21#include "gui/actions/menu.h"
22#ifdef __APPLE__
23#include "osx/osx.h" // dt_osx_disallow_fullscreen(), used under GDK_WINDOWING_QUARTZ below
24#endif
26#include "gui/application.h"
27#include "gui/styles.h"
28#include "common/act_on.h"
31#include "common/styles.h"
33#include "develop/dev_history.h"
35#include "common/conf.h"
36#include "control/signal.h"
37#include "libs/lib.h"
38
39#include <glib.h>
40#include "widgets/label.h"
41
42static GtkWidget **_styles_menus = NULL;
43static GList **_styles_lists = NULL;
47
48static gboolean _styles_menu_disabled(GtkWidget *widget)
49{
50 return FALSE;
51}
52
53static gboolean _styles_apply_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval,
54 GdkModifierType mods, gpointer user_data)
55{
56 const char *style_name = get_custom_data(GTK_WIDGET(user_data));
57 if(IS_NULL_PTR(style_name) || !*style_name) return FALSE;
58
59 if(dt_conf_get_bool("history/style/ask"))
60 {
61 gchar *title = g_strdup_printf(_("Apply style \"%s\" — merge settings"), style_name);
62 const gboolean ok = dt_gui_merge_options_dialog(title,
63 "history/style/mode",
64 "history/style/copy_iop_order",
65 "history/style/ask",
66 dt_styles_has_module_order(style_name));
67 dt_free(title);
68 if(!ok) return FALSE;
69 }
70
71 GList *imgs = dt_act_on_get_images();
72 const gboolean duplicate = dt_conf_get_bool("ui_last/styles_create_duplicate");
73 gboolean is_darkroom_image_in_list = dt_dev_history_is_image_in_dev(imgs);
74
75 if(is_darkroom_image_in_list)
76 {
77 dt_develop_t *const dev = dt_dev_get_global();
78 imgs = g_list_remove(imgs, GINT_TO_POINTER(dev->image_storage.id));
80 const gboolean applied = dt_history_style_on_image(dev->image_storage.id, style_name, duplicate);
82 if(applied)
84 }
85
86 if(imgs) dt_history_style_on_list(imgs, style_name, duplicate);
87
88 g_list_free(imgs);
89 imgs = NULL;
90
91 return TRUE;
92}
93
95{
97}
98
99static gboolean _styles_create_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval,
100 GdkModifierType mods, gpointer user_data)
101{
102 const int32_t imgid = dt_act_on_get_first_image();
103 if(imgid <= 0) return FALSE;
104
106 return TRUE;
107}
108
109static void _close_styles_popup(GtkWidget *dialog, gint response_id, gpointer data)
110{
111 dt_gui_get_global()->styles_popup.module = (GtkWidget *)g_object_ref(dt_gui_get_global()->styles_popup.module);
112 GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dt_gui_get_global()->styles_popup.window));
113 gtk_container_remove(GTK_CONTAINER(content), dt_gui_get_global()->styles_popup.module);
115}
116
117static gboolean _styles_open_popup_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval,
118 GdkModifierType mods, gpointer user_data)
119{
120 if(dt_gui_get_global()->styles_popup.window)
121 {
122 gtk_window_present_with_time(GTK_WINDOW(dt_gui_get_global()->styles_popup.window), GDK_CURRENT_TIME);
123 return TRUE;
124 }
125
126 dt_lib_module_t *module = dt_lib_get_module("styles");
127 if(IS_NULL_PTR(module)) return TRUE;
128
131 : dt_lib_gui_get_expander(module);
132 if(IS_NULL_PTR(w)) return TRUE;
133
134 dt_gui_get_global()->styles_popup.module = w;
135
136 GtkWidget *dialog = gtk_dialog_new();
137#ifdef GDK_WINDOWING_QUARTZ
139 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER_ON_PARENT);
140#endif
141 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CANCEL);
142 gtk_window_set_modal(GTK_WINDOW(dialog), FALSE);
143 gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(dt_gui_main_window()));
144 gtk_window_set_title(GTK_WINDOW(dialog), _("Ansel - Styles"));
145 g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(_close_styles_popup), NULL);
146
148 dt_gui_add_help_link(w, dt_get_help_url(module->plugin_name));
149 gtk_widget_set_size_request(w, DT_PIXEL_APPLY_DPI(450), -1);
150
151 GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
152 gtk_box_pack_start(GTK_BOX(content), w, TRUE, TRUE, 0);
153 gtk_widget_set_visible(w, TRUE);
154 gtk_widget_show_all(dialog);
155
157 return TRUE;
158}
159
160static gchar *_styles_build_tooltip(const dt_style_t *style)
161{
162 char *items_string = dt_styles_get_item_list_as_string(style->name);
163 gchar *tooltip = NULL;
164
165 if(items_string && *items_string)
166 {
167 if(style->description && *style->description)
168 {
169 gchar *desc = g_markup_escape_text(style->description, -1);
170 tooltip = g_strconcat("<b>", desc, "</b>\n", items_string, NULL);
171 dt_free(desc);
172 }
173 else
174 {
175 tooltip = g_strdup(items_string);
176 }
177 }
178 else if(style->description && *style->description)
179 {
180 tooltip = g_markup_escape_text(style->description, -1);
181 }
182
183 dt_free(items_string);
184 return tooltip;
185}
186
187static GtkWidget *_styles_get_submenu(GtkWidget **menus, GList **lists, GHashTable *submenus,
188 GtkWidget *parent, const gchar *path, const gchar *label,
189 const dt_menus_t index)
190{
191 if(IS_NULL_PTR(parent) || IS_NULL_PTR(path) || !*path || IS_NULL_PTR(label) || !*label)
192 return parent;
193
194 GtkWidget *submenu = g_hash_table_lookup(submenus, path);
195 if(submenu) return submenu;
196
197 gchar *menu_label = g_markup_escape_text(label, -1);
198 if(IS_NULL_PTR(menu_label)) menu_label = g_strdup("");
199
200 submenu = gtk_menu_new();
201 gtk_menu_set_accel_group(GTK_MENU(submenu), dt_gui_get_accels()->global_accels);
202
203 gchar *clean_label = strip_markup(menu_label);
204 gchar *accel_path = dt_accels_build_path(gtk_menu_get_accel_path(GTK_MENU(parent)), clean_label);
205 gtk_menu_set_accel_path(GTK_MENU(submenu), accel_path);
206 dt_free(accel_path);
207 dt_free(clean_label);
208
209 dt_menu_entry_t *entry = set_menu_entry(menus, lists, menu_label, index, GTK_MENU(parent), NULL,
210 NULL, NULL, NULL, NULL, 0, 0,
211 dt_gui_get_accels()->global_accels);
212 gtk_menu_item_set_submenu(GTK_MENU_ITEM(entry->widget), submenu);
213 gtk_menu_shell_append(GTK_MENU_SHELL(parent), entry->widget);
214
215 g_hash_table_insert(submenus, g_strdup(path), submenu);
216 dt_free(menu_label);
217 return submenu;
218}
219
220static void _styles_add_menu_entry(GtkWidget **menus, GList **lists, GtkWidget *parent, const dt_menus_t index,
221 const gchar *label, const gchar *tooltip, const gchar *style_name)
222{
223 dt_menu_entry_t *entry = set_menu_entry(menus, lists, label, index, GTK_MENU(parent), NULL,
225 0, 0, dt_gui_get_accels()->global_accels);
226
227 gtk_menu_shell_append(GTK_MENU_SHELL(parent), entry->widget);
228 gtk_menu_item_set_reserve_indicator(GTK_MENU_ITEM(entry->widget), TRUE);
229 g_object_set_data_full(G_OBJECT(entry->widget), "custom-data", g_strdup(style_name), g_free);
230
231 if(tooltip)
232 gtk_widget_set_tooltip_markup(entry->widget, tooltip);
233}
234
235static gboolean _styles_history_prepend_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval,
236 GdkModifierType mods, gpointer user_data)
237{
238 dt_conf_set_int("history/style/mode", DT_HISTORY_MERGE_PREPEND);
239 return TRUE;
240}
241
243{
244 return dt_conf_get_int("history/style/mode") == DT_HISTORY_MERGE_PREPEND;
245}
246
247static gboolean _styles_history_append_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval,
248 GdkModifierType mods, gpointer user_data)
249{
250 dt_conf_set_int("history/style/mode", DT_HISTORY_MERGE_APPEND);
251 return TRUE;
252}
253
255{
256 return dt_conf_get_int("history/style/mode") == DT_HISTORY_MERGE_APPEND;
257}
258
259static gboolean _styles_history_replace_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval,
260 GdkModifierType mods, gpointer user_data)
261{
262 dt_conf_set_int("history/style/mode", DT_HISTORY_MERGE_REPLACE);
263 return TRUE;
264}
265
267{
268 return dt_conf_get_int("history/style/mode") == DT_HISTORY_MERGE_REPLACE;
269}
270
271static gboolean _styles_copy_iop_order_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval,
272 GdkModifierType mods, gpointer user_data)
273{
274 dt_conf_set_bool("history/style/copy_iop_order", !dt_conf_get_bool("history/style/copy_iop_order"));
275 return TRUE;
276}
277
279{
280 return dt_conf_get_bool("history/style/copy_iop_order");
281}
282
283static gboolean _styles_ask_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval,
284 GdkModifierType mods, gpointer user_data)
285{
286 dt_conf_set_bool("history/style/ask", !dt_conf_get_bool("history/style/ask"));
287 return TRUE;
288}
289
291{
292 return dt_conf_get_bool("history/style/ask");
293}
294
295static void _styles_menu_clear(void)
296{
298
300 GList *children = gtk_container_get_children(GTK_CONTAINER(menu));
301 for(GList *child = children; child; child = g_list_next(child))
302 gtk_widget_destroy(GTK_WIDGET(child->data));
303 g_list_free(children);
304 children = NULL;
305
306 if(*_styles_lists)
307 {
308 // Menu entries are owned by their GtkMenuItem: set_menu_entry() frees the
309 // dt_menu_entry_t from the widget "destroy" signal. Only the temporary list
310 // nodes need to be released here after destroying the menu children above.
311 g_list_free(*_styles_lists);
312 *_styles_lists = NULL;
313 }
314}
315
316static gboolean _styles_menu_rebuild_idle(gpointer user_data)
317{
319 {
321 return G_SOURCE_REMOVE;
322 }
323
326 gtk_widget_show_all(_styles_menus[_styles_index]);
327 gtk_widget_queue_resize(_styles_menus[_styles_index]);
328
330 return G_SOURCE_REMOVE;
331}
332
333static void _styles_menu_rebuild_callback(gpointer instance, gpointer user_data)
334{
337}
338
339
340void append_styles(GtkWidget **menus, GList **lists, const dt_menus_t index)
341{
342 _styles_menus = menus;
343 _styles_lists = lists;
344 _styles_index = index;
346 {
348 G_CALLBACK(_styles_menu_rebuild_callback), NULL);
350 }
351
352 GList *styles = dt_styles_get_list("");
353
354 if(IS_NULL_PTR(styles))
355 {
356 add_sub_menu_entry(menus, lists, _("No styles available"), index, NULL, NULL, NULL, NULL,
358 }
359 else
360 {
361 GHashTable *submenus = g_hash_table_new_full(g_str_hash, g_str_equal, dt_free_gpointer, NULL);
362
363 for(GList *iter = styles; iter; iter = g_list_next(iter))
364 {
365 dt_style_t *style = (dt_style_t *)iter->data;
366 if(IS_NULL_PTR(style) || IS_NULL_PTR(style->name)) continue;
367
368 gchar **split = g_strsplit(style->name, "|", -1);
369 gchar *tooltip = _styles_build_tooltip(style);
370
371 int leaf = -1;
372 for(int i = 0; split[i]; i++)
373 if(split[i][0] != '\0')
374 leaf = i;
375
376 GtkWidget *parent_menu = menus[index];
377 GString *submenu_path = g_string_new(NULL);
378 for(int i = 0; i < leaf; i++)
379 {
380 if(split[i][0] == '\0') continue;
381
382 if(submenu_path->len > 0)
383 g_string_append_c(submenu_path, '|');
384 g_string_append(submenu_path, split[i]);
385 parent_menu = _styles_get_submenu(menus, lists, submenus, parent_menu,
386 submenu_path->str, split[i], index);
387 }
388
389 gchar *label = g_markup_escape_text(leaf >= 0 ? split[leaf] : style->name, -1);
390 if(IS_NULL_PTR(label)) label = g_strdup("");
391 _styles_add_menu_entry(menus, lists, parent_menu, index, label, tooltip, style->name);
392
393 g_string_free(submenu_path, TRUE);
394 dt_free(label);
396 g_strfreev(split);
397 }
398
399 g_hash_table_destroy(submenus);
400 g_list_free_full(styles, dt_style_free);
401 styles = NULL;
402 }
403
404 add_menu_separator(menus[index]);
405
406 add_top_submenu_entry(menus, lists, _("History pasting mode"), index);
407 GtkWidget *parent = get_last_widget(lists);
408
409 add_sub_sub_menu_entry(menus, parent, lists, _("Prepend"), index, NULL,
411 gtk_widget_set_tooltip_text(get_last_widget(lists),
412 _("Apply style BEFORE the current history.\n"
413 "CURRENT EDITS are applied afterwards and win conflicts."));
414
415 add_sub_sub_menu_entry(menus, parent, lists, _("Append"), index, NULL,
417 gtk_widget_set_tooltip_text(get_last_widget(lists),
418 _("Apply style AFTER the current history.\n"
419 "STYLE EDITS are applied afterwards and win conflicts."));
420
421 add_sub_sub_menu_entry(menus, parent, lists, _("Replace"), index, NULL,
423 gtk_widget_set_tooltip_text(get_last_widget(lists),
424 _("Discard the current history and replace it entirely with the style."));
425
426 add_top_submenu_entry(menus, lists, _("Nodes pasting mode"), index);
427 parent = get_last_widget(lists);
428
429 add_sub_sub_menu_entry(menus, parent, lists, _("Copy module order"), index, NULL,
431
432 add_sub_menu_entry(menus, lists, _("Ask merge settings before apply"), index, NULL,
434
435 add_menu_separator(menus[index]);
436 add_sub_menu_entry(menus, lists, _("Create new style..."), index, NULL,
438
439 add_sub_menu_entry(menus, lists, _("Manage styles..."), index, NULL,
440 _styles_open_popup_callback, NULL, NULL, NULL, 0, 0);
441}
442
443// clang-format off
444// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
445// vim: shiftwidth=2 expandtab tabstop=2 cindent
446// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
447// clang-format on
gchar * dt_accels_build_path(const gchar *scope, const gchar *feature)
Build the accel path of feature under scope.
Handle default and user-set shortcuts (accelerators)
int dt_act_on_get_images_nb(const gboolean only_visible, const gboolean force)
Definition act_on.c:53
GList * dt_act_on_get_images()
Definition act_on.c:38
int32_t dt_act_on_get_first_image()
Definition act_on.c:67
GtkWidget * dt_gui_main_window(void)
struct dt_accels_t * dt_gui_get_accels(void)
void dt_gui_add_help_link(GtkWidget *widget, char *link)
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
void dt_conf_set_bool(const char *name, int val)
int dt_conf_get_bool(const char *name)
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
Integer for name, clamped to the bounds declared in the XML.
struct dt_develop_t * dt_dev_get_global(void)
Definition darktable.c:531
struct dt_gui_gtk_t * dt_gui_get_global(void)
Definition darktable.c:526
void dt_apply_dev_history_update(dt_develop_t *dev)
Reload the current darkroom history and refresh every dependent GUI.
gboolean dt_dev_history_is_image_in_dev(GList *imgs)
Check whether any image of the list is the one currently loaded in the darkroom.
void dt_dev_undo_start_record(dt_develop_t *dev)
Definition develop.c:1921
void dt_dev_undo_end_record(dt_develop_t *dev)
Definition develop.c:1932
void dt_gui_styles_dialog_new(int32_t imgid)
gboolean dt_history_style_on_image(const int32_t imgid, const char *name, const gboolean duplicate)
gboolean dt_history_style_on_list(const GList *list, const char *name, const gboolean duplicate)
@ DT_HISTORY_MERGE_REPLACE
@ DT_HISTORY_MERGE_PREPEND
@ DT_HISTORY_MERGE_APPEND
gboolean dt_gui_merge_options_dialog(const char *title, const char *mode_key, const char *iop_order_key, const char *ask_key, const gboolean iop_order_available)
Show a modal dialog to pick merge mode and pipeline order before a paste or style apply.
const char * tooltip
Definition image.h:319
gchar * strip_markup(const char *s)
Remove Pango/Gtk markup and accel mnemonics from a text label. If markup parsing fails,...
Definition label.c:87
void dt_lib_gui_set_expanded(dt_lib_module_t *module, gboolean expanded)
Definition lib.c:852
GtkWidget * dt_lib_gui_get_expander(dt_lib_module_t *module)
Definition lib.c:1050
#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 macros.h:96
static void dt_free_gpointer(gpointer ptr)
g_free() one pointer, with the signature GDestroyNotify wants.
Definition mem_alloc.h:184
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
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:597
dt_menu_entry_t * set_menu_entry(GtkWidget **menus, GList **items_list, const gchar *label, dt_menus_t menu_index, GtkMenu *parent, 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:327
GtkWidget * get_last_widget(GList **list)
Definition menu.c:631
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:555
void * get_custom_data(GtkWidget *widget)
Definition menu.c:625
void add_top_submenu_entry(GtkWidget **menus, GList **lists, const gchar *label, const dt_menus_t index)
Definition menu.c:528
void add_menu_separator(GtkWidget *menu)
Definition menu.c:611
gboolean has_active_images()
Definition menu.c:649
dt_menus_t
Definition menu.h:42
@ DT_MENU_STYLES
Definition menu.h:47
void dt_osx_disallow_fullscreen(GtkWidget *widget)
Definition osx.mm:105
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:619
@ DT_SIGNAL_STYLE_CHANGED
This signal is raised when a style is added/deleted/changed
Definition signal.h:151
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:400
char * dt_styles_get_item_list_as_string(const char *name)
void dt_style_free(gpointer data)
gboolean dt_styles_has_module_order(const char *name)
GList * dt_styles_get_list(const char *filter)
static GList ** _styles_lists
static dt_menus_t _styles_index
static void _styles_menu_rebuild_callback(gpointer instance, gpointer user_data)
static GtkWidget ** _styles_menus
static gboolean _styles_ask_checked_callback(GtkWidget *widget)
static gboolean _styles_history_prepend_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data)
static GtkWidget * _styles_get_submenu(GtkWidget **menus, GList **lists, GHashTable *submenus, GtkWidget *parent, const gchar *path, const gchar *label, const dt_menus_t index)
static gboolean _styles_apply_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data)
static gboolean _styles_ask_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data)
static gboolean _styles_history_prepend_checked_callback(GtkWidget *widget)
static gboolean _styles_copy_iop_order_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data)
static void _close_styles_popup(GtkWidget *dialog, gint response_id, gpointer data)
static gboolean _styles_open_popup_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data)
static gboolean _styles_signal_connected
static gboolean _styles_menu_disabled(GtkWidget *widget)
static gboolean _styles_history_append_checked_callback(GtkWidget *widget)
void append_styles(GtkWidget **menus, GList **lists, const dt_menus_t index)
static void _styles_add_menu_entry(GtkWidget **menus, GList **lists, GtkWidget *parent, const dt_menus_t index, const gchar *label, const gchar *tooltip, const gchar *style_name)
static guint _styles_menu_rebuild_source
static gboolean _styles_history_append_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data)
static gboolean _styles_copy_iop_order_checked_callback(GtkWidget *widget)
static gchar * _styles_build_tooltip(const dt_style_t *style)
static gboolean _styles_create_sensitive_callback(GtkWidget *widget)
static gboolean _styles_create_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data)
static gboolean _styles_history_replace_checked_callback(GtkWidget *widget)
static void _styles_menu_clear(void)
static gboolean _styles_menu_rebuild_idle(gpointer user_data)
static gboolean _styles_history_replace_callback(GtkAccelGroup *group, GObject *acceleratable, guint keyval, GdkModifierType mods, gpointer user_data)
dt_image_t image_storage
Definition develop.h:225
struct dt_gui_gtk_t::@35 styles_popup
GtkWidget * window
int32_t id
Definition image.h:410
Definition menu.h:65
GtkWidget * widget
Definition menu.h:66
gchar * description
gchar * name
char * dt_get_help_url(char *name)
#define DT_PIXEL_APPLY_DPI(value)