diff --git a/data/themes/darktable.css b/data/themes/darktable.css index 9bd94f2d7d2d..b41606a54031 100644 --- a/data/themes/darktable.css +++ b/data/themes/darktable.css @@ -911,6 +911,22 @@ menuitem label, margin: 0; } +/*------------------------------------------------ + - Popup menu - + ----------------------------------------------*/ +popover.menu { + padding: 0; +} + +popover.menu modelbutton { + padding: 0.1em 0.5em; +} + +popover.menu modelbutton:hover { + background-color: @selected_bg_color; +} + + tooltip, #range-current { @@ -949,6 +965,23 @@ combobox separator min-height: 0.07em; } +/*------------------------------------------------ + - Popup menu - + ----------------------------------------------*/ +popover.menu { + padding: 0; +} + +popover.menu modelbutton { + padding: 0.1em 0.5em; +} + +popover.menu modelbutton:hover, +popover.menu modelbutton:hover * { + background-color: @selected_bg_color; +} + + /*---------------------- - GTK Notebooks tabs - ----------------------*/ diff --git a/src/common/presets.c b/src/common/presets.c index 22460f9a5931..843f2e47dfaf 100644 --- a/src/common/presets.c +++ b/src/common/presets.c @@ -25,6 +25,8 @@ #include "develop/imageop.h" #include "libs/lib.h" +#include +#include #include #include #include @@ -548,6 +550,84 @@ GtkWidget *dt_insert_preset_in_menu_hierarchy(const char *name, return mi; } +static void _menu_shell_insert_sorted2(GMenu *menu, + GMenuItem *item, + const gchar *name) +{ + GMenuModel *model = G_MENU_MODEL(menu); + + int num = g_menu_model_get_n_items(model); + gboolean found = FALSE; + int i; + for(i = 0; i < num; i++) + { + gchar *item_label = NULL; + GVariant *attr = g_menu_model_get_item_attribute_value(model, i, "label", G_VARIANT_TYPE_STRING); + if(attr) + { + item_label = g_variant_dup_string(attr, NULL); + g_variant_unref(attr); + if(g_utf8_collate(item_label, name) > 0) found = TRUE; + g_free(item_label); + if(found) break; + } + } + + g_menu_insert_item(menu, i, item); +} + +void dt_insert_preset_in_menu_hierarchy2(const char *name, + const char *action, + GSList **menu_path, + GMenu *mainmenu, + GMenu **submenu, + gchar ***prev_split, + gboolean isdefault) +{ + gchar *local_name = dt_util_localize_segmented_name(name, FALSE); + gchar **split = g_strsplit(local_name, "|", -1); + gchar **s = split; + gchar **p = *prev_split; + GSList *mpath = *menu_path; + GMenuItem *mi; + g_free(local_name); + + for(; p && *(p+1) && *(s+1) && !g_strcmp0(*s, *p); p++, s++) + ; + + for(; p && *(p+1); p++) + { + mpath = g_slist_delete_link(mpath, mpath); // pop + *submenu = mpath ? mpath->data : mainmenu; + } + + for(; *(s+1); s++) + { + GMenu *sm = g_menu_new(); + GMenuItem *smi = g_menu_item_new_submenu(*s, G_MENU_MODEL(sm)); + _menu_shell_insert_sorted2(*submenu, smi, *s); + *submenu = sm; + mpath = g_slist_prepend(mpath, sm); // push + } + + *menu_path = mpath; + g_strfreev(*prev_split); + *prev_split = split; + + if(isdefault) + { + gchar *label = g_strdup_printf("%s %s", *s, _("(default)")); + mi = g_menu_item_new(label, action); + _menu_shell_insert_sorted2(*submenu, mi, label); + g_free(label); + } + else + { + mi = g_menu_item_new(*s, action); + _menu_shell_insert_sorted2(*submenu, mi, *s); + } +} + // clang-format off // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py // vim: shiftwidth=2 expandtab tabstop=2 cindent diff --git a/src/common/presets.h b/src/common/presets.h index 4945cd908392..6e6abc7e0442 100644 --- a/src/common/presets.h +++ b/src/common/presets.h @@ -65,6 +65,14 @@ GtkWidget *dt_insert_preset_in_menu_hierarchy(const char *name, gboolean isdefault, gboolean writeprotect); +void dt_insert_preset_in_menu_hierarchy2(const char *name, + const char *action, + GSList **menu_path2, + GMenu *mainmenu2, + GMenu **submenu2, + gchar ***prev_split, + gboolean isdefault); + // clang-format off // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py // vim: shiftwidth=2 expandtab tabstop=2 cindent diff --git a/src/gui/gtk.c b/src/gui/gtk.c index e498c0c9488a..ccae48f746d0 100644 --- a/src/gui/gtk.c +++ b/src/gui/gtk.c @@ -5072,6 +5072,22 @@ void dt_gui_dialog_restore_size(GtkDialog *dialog, const char *conf) g_signal_connect(dialog, "configure-event", G_CALLBACK(_resize_dialog), (gpointer)conf); } +GtkWidget *dt_gui_popover_menu_from_model(GtkWidget *parent, GMenu *menu) +{ + GtkWidget *popover_menu; + +#if GTK_CHECK_VERSION(4, 0, 0) + popover_menu = gtk_popover_menu_new_from_model_full(G_MENU_MODEL(menu), + GTK_POPOVER_MENU_NESTED); + gtk_widget_set_parent(popover_menu, parent); +#else + popover_menu = gtk_popover_new_from_model(parent, G_MENU_MODEL(menu)); +#endif + + return popover_menu; +} + + // clang-format off // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py // vim: shiftwidth=2 expandtab tabstop=2 cindent diff --git a/src/gui/gtk.h b/src/gui/gtk.h index 324a6d2b4094..8098ac429947 100644 --- a/src/gui/gtk.h +++ b/src/gui/gtk.h @@ -659,6 +659,9 @@ void dt_gui_dialog_restore_size(GtkDialog *dialog, // returns the session type at runtime dt_gui_session_type_t dt_gui_get_session_type(void); +// Popover menu +GtkWidget *dt_gui_popover_menu_from_model(GtkWidget *parent, GMenu *menu); + #if !defined(__cplusplus) #undef G_CALLBACK diff --git a/src/libs/collect.c b/src/libs/collect.c index 4aa1342e1a44..7ee92cf15cac 100644 --- a/src/libs/collect.c +++ b/src/libs/collect.c @@ -96,6 +96,8 @@ typedef struct dt_lib_collect_t gboolean inited; GtkWidget *history_box; + + GSimpleActionGroup *action_group; } dt_lib_collect_t; typedef struct dt_lib_collect_params_rule_t @@ -3076,9 +3078,41 @@ static gboolean entry_focus_in_callback(GtkWidget *w, return FALSE; } -static void menuitem_mode(GtkMenuItem *menuitem, - dt_lib_collect_rule_t *d) +static gboolean _process_variant_params(GVariant *parameter, + gpointer userdata, + dt_lib_collect_mode_t *mode, + dt_lib_collect_rule_t **d) { + const gsize nb = g_variant_n_children(parameter); + + if(nb != 2) + return FALSE; + + dt_lib_collect_t *m = (dt_lib_collect_t *)userdata; + + GVariant *v = g_variant_get_child_value(parameter, 0); + *mode = g_variant_get_int32(v); + g_variant_unref(v); + + v = g_variant_get_child_value(parameter, 1); + const int rule_index = g_variant_get_int32(v); + g_variant_unref(v); + + *d = &m->rule[rule_index]; + + return TRUE; +} + +static void _menuitem_mode(GSimpleAction *action, + GVariant *parameter, + gpointer userdata) +{ + dt_lib_collect_mode_t mode; + dt_lib_collect_rule_t *d = NULL; + + if(!_process_variant_params(parameter, userdata, &mode, &d)) + return; + // add next row with and operator const int _a = dt_conf_get_int("plugins/lighttable/collect/num_rules"); const int active = CLAMP(_a, 1, MAX_RULES); @@ -3087,8 +3121,6 @@ static void menuitem_mode(GtkMenuItem *menuitem, { char confname[200] = { 0 }; snprintf(confname, sizeof(confname), "plugins/lighttable/collect/mode%1d", active); - const dt_lib_collect_mode_t mode = - GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menuitem), "menuitem_mode")); dt_conf_set_int(confname, mode); snprintf(confname, sizeof(confname), "plugins/lighttable/collect/string%1d", active); @@ -3103,17 +3135,22 @@ static void menuitem_mode(GtkMenuItem *menuitem, DT_COLLECTION_PROP_UNDEF, NULL); } -static void menuitem_mode_change(GtkMenuItem *menuitem, - dt_lib_collect_rule_t *d) +static void _menuitem_mode_change(GSimpleAction *action, + GVariant *parameter, + gpointer userdata) { + dt_lib_collect_mode_t mode; + dt_lib_collect_rule_t *d = NULL; + + if(!_process_variant_params(parameter, userdata, &mode, &d)) + return; + // add next row with and operator const int num = d->num + 1; if(num < MAX_RULES && num > 0) { char confname[200] = { 0 }; snprintf(confname, sizeof(confname), "plugins/lighttable/collect/mode%1d", num); - const dt_lib_collect_mode_t mode = - GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menuitem), "menuitem_mode")); dt_conf_set_int(confname, mode); } dt_lib_collect_t *c = get_collect(d); @@ -3336,9 +3373,15 @@ static void _metadata_changed(gpointer instance, } } -static void menuitem_clear(GtkMenuItem *menuitem, - dt_lib_collect_rule_t *d) +static void _menuitem_clear(GSimpleAction *simple, + GVariant *parameter, + gpointer userdata) { + dt_lib_collect_t *m = (dt_lib_collect_t*) userdata; + + const int index = g_variant_get_int32(parameter); + dt_lib_collect_rule_t *d = &m->rule[index]; + // remove this row, or if 1st, clear text entry box const int _a = dt_conf_get_int("plugins/lighttable/collect/num_rules"); const int active = CLAMP(_a, 1, MAX_RULES); @@ -3383,72 +3426,58 @@ static void menuitem_clear(GtkMenuItem *menuitem, DT_COLLECTION_PROP_UNDEF, NULL); } -static gboolean popup_button_callback(GtkWidget *widget, +static gboolean popup_button_callback(GtkWidget *button, GdkEventButton *event, dt_lib_collect_rule_t *d) { if(event->button != 1) return FALSE; - GtkWidget *menu = gtk_menu_new(); - GtkWidget *mi; + GMenu *menu = g_menu_new(); const int _a = dt_conf_get_int("plugins/lighttable/collect/num_rules"); const int active = CLAMP(_a, 1, MAX_RULES); - mi = gtk_menu_item_new_with_label(_("clear this rule")); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); - g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(menuitem_clear), d); - + gchar *action; + + action = g_strdup_printf("collect.clear(%d)", d->num); + g_menu_append(menu, _("clear this rule"), action); + g_free(action); + if(d->num == active - 1) { - mi = gtk_menu_item_new_with_label(_("narrow down search")); - g_object_set_data(G_OBJECT(mi), "menuitem_mode", - GINT_TO_POINTER(DT_LIB_COLLECT_MODE_AND)); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); - g_signal_connect(G_OBJECT(mi), "activate", - G_CALLBACK(menuitem_mode), d); - - mi = gtk_menu_item_new_with_label(_("add more images")); - g_object_set_data(G_OBJECT(mi), "menuitem_mode", - GINT_TO_POINTER(DT_LIB_COLLECT_MODE_OR)); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); - g_signal_connect(G_OBJECT(mi), "activate", - G_CALLBACK(menuitem_mode), d); - - mi = gtk_menu_item_new_with_label(_("exclude images")); - g_object_set_data(G_OBJECT(mi), "menuitem_mode", - GINT_TO_POINTER(DT_LIB_COLLECT_MODE_AND_NOT)); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); - g_signal_connect(G_OBJECT(mi), "activate", - G_CALLBACK(menuitem_mode), d); + const char *fmt = "collect.mode((%d,%d))"; + + action = g_strdup_printf(fmt, DT_LIB_COLLECT_MODE_AND, d->num); + g_menu_append(menu, _("narrow down search"), action); + g_free(action); + + action = g_strdup_printf(fmt, DT_LIB_COLLECT_MODE_OR, d->num); + g_menu_append(menu, _("add more images"), action); + g_free(action); + + action = g_strdup_printf(fmt, DT_LIB_COLLECT_MODE_AND_NOT, d->num); + g_menu_append(menu, _("exclude images"), action); + g_free(action); } else if(d->num < active - 1) { - mi = gtk_menu_item_new_with_label(_("change to: and")); - g_object_set_data(G_OBJECT(mi), "menuitem_mode", - GINT_TO_POINTER(DT_LIB_COLLECT_MODE_AND)); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); - g_signal_connect(G_OBJECT(mi), "activate", - G_CALLBACK(menuitem_mode_change), d); - - mi = gtk_menu_item_new_with_label(_("change to: or")); - g_object_set_data(G_OBJECT(mi), "menuitem_mode", - GINT_TO_POINTER(DT_LIB_COLLECT_MODE_OR)); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); - g_signal_connect(G_OBJECT(mi), "activate", - G_CALLBACK(menuitem_mode_change), d); - - mi = gtk_menu_item_new_with_label(_("change to: except")); - g_object_set_data(G_OBJECT(mi), "menuitem_mode", - GINT_TO_POINTER(DT_LIB_COLLECT_MODE_AND_NOT)); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); - g_signal_connect(G_OBJECT(mi), "activate", - G_CALLBACK(menuitem_mode_change), d); - } + const char *fmt = "collect.modechange((%d,%d))"; - gtk_widget_show_all(GTK_WIDGET(menu)); + action = g_strdup_printf(fmt, DT_LIB_COLLECT_MODE_AND, d->num); + g_menu_append(menu, _("change to: and"), action); + g_free(action); - gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent *)event); + action = g_strdup_printf(fmt, DT_LIB_COLLECT_MODE_OR, d->num); + g_menu_append(menu, _("change to: or"), action); + g_free(action); + + action = g_strdup_printf(fmt, DT_LIB_COLLECT_MODE_AND_NOT, d->num); + g_menu_append(menu, _("change to: except"), action); + g_free(action); + } + + GtkWidget *popover_menu = dt_gui_popover_menu_from_model(button, menu); + gtk_popover_popup(GTK_POPOVER(popover_menu)); return TRUE; } @@ -3524,8 +3553,9 @@ static void _populate_collect_combo(GtkWidget *w) #undef ADD_COLLECT_ENTRY } -void _menuitem_preferences(GtkMenuItem *menuitem, - dt_lib_module_t *self) +static void _menuitem_preferences(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) { GtkWidget *win = dt_ui_main_window(darktable.gui->ui); GtkWidget *dialog = gtk_dialog_new_with_buttons @@ -3548,12 +3578,12 @@ void _menuitem_preferences(GtkMenuItem *menuitem, DT_COLLECTION_PROP_UNDEF, NULL); } -void set_preferences(void *menu, - dt_lib_module_t *self) +void set_preferences(GMenu *menu, GActionGroup *action_group, dt_lib_module_t *self) { - GtkWidget *mi = gtk_menu_item_new_with_label(_("preferences...")); - g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(_menuitem_preferences), self); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); + GSimpleAction *action = g_simple_action_new("preferences", NULL); + g_signal_connect(action, "activate", G_CALLBACK(_menuitem_preferences), self); + g_action_map_add_action(G_ACTION_MAP(action_group), G_ACTION(action)); + g_menu_append(menu, _("preferences..."), "presets.preferences"); } static gint _sort_model_func(GtkTreeModel *model, @@ -3807,6 +3837,17 @@ void gui_init(dt_lib_module_t *self) self->widget = dt_gui_vbox(); dt_gui_add_class(self->widget, "dt_spacing_sw"); + // setup the actions for this module + const GActionEntry entries[] = { + { "clear", _menuitem_clear, "i" }, + { "mode", _menuitem_mode, "(ii)" }, + { "modechange", _menuitem_mode_change, "(ii)" } + }; + + d->action_group = g_simple_action_group_new(); + g_action_map_add_action_entries(G_ACTION_MAP(d->action_group), entries, G_N_ELEMENTS(entries), d); + gtk_widget_insert_action_group(self->widget, "collect", G_ACTION_GROUP(d->action_group)); + d->active_rule = 0; d->nb_rules = 0; d->params = (dt_lib_collect_params_t *)malloc(sizeof(dt_lib_collect_params_t)); @@ -3961,6 +4002,8 @@ void gui_cleanup(dt_lib_module_t *self) g_object_unref(d->listfilter); g_object_unref(d->vmonitor); + g_object_unref(d->action_group); + /* TODO: Make sure we are cleaning up all allocations */ free(self->data); diff --git a/src/libs/export.c b/src/libs/export.c index 65a8330a9aae..7445a9cd9308 100644 --- a/src/libs/export.c +++ b/src/libs/export.c @@ -1274,8 +1274,11 @@ static void _on_storage_list_changed(gpointer instance, dt_bauhaus_combobox_set(d->storage, dt_imageio_get_index_of_storage(storage)); } -static void _menuitem_preferences(GtkMenuItem *menuitem, dt_lib_module_t *self) +static void _menuitem_preferences(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) { + dt_lib_module_t *self = (dt_lib_module_t *)user_data; dt_lib_export_t *d = self->data; const gchar *name = dt_bauhaus_combobox_get_text(d->storage); const gboolean ondisk = name @@ -1422,12 +1425,12 @@ static void _export_presets_changed_callback(gpointer instance, gpointer module, _fill_batch_export_list(self); } -void set_preferences(void *menu, dt_lib_module_t *self) +void set_preferences(GMenu *menu, GActionGroup *action_group, dt_lib_module_t *self) { - GtkWidget *mi = gtk_menu_item_new_with_label(_("preferences...")); - g_signal_connect(G_OBJECT(mi), "activate", - G_CALLBACK(_menuitem_preferences), self); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); + GSimpleAction *action = g_simple_action_new("preferences", NULL); + g_signal_connect(action, "activate", G_CALLBACK(_menuitem_preferences), self); + g_action_map_add_action(G_ACTION_MAP(action_group), G_ACTION(action)); + g_menu_append(menu, _("preferences..."), "presets.preferences"); } void gui_init(dt_lib_module_t *self) diff --git a/src/libs/lib.c b/src/libs/lib.c index ab755edd73ed..58a69b6b5fb3 100644 --- a/src/libs/lib.c +++ b/src/libs/lib.c @@ -25,15 +25,17 @@ #include "control/control.h" #include "dtgtk/button.h" #include "dtgtk/expander.h" -#include "dtgtk/icon.h" #include "gui/accelerators.h" #include "gui/drag_and_drop.h" #include "gui/gtk.h" #include "gui/presets.h" #include "gui/splash.h" -#ifdef GDK_WINDOWING_QUARTZ -#include "osx/osx.h" -#endif +#include +#include +#include +#include +#include +#include #include #include @@ -49,7 +51,25 @@ typedef struct dt_lib_presets_edit_dialog_t gint old_id; } dt_lib_presets_edit_dialog_t; -static gpointer _active_menu_item = NULL; + +static GtkWidget *_preset_popover = NULL; + +static void _menuitem_activate_preset(GSimpleAction *action, GVariant *parameter, gpointer user_data); +static void _menuitem_edit_preset(GSimpleAction *action, GVariant *parameter, gpointer user_data); +static void _menuitem_delete_preset(GSimpleAction *action, GVariant *parameter, gpointer user_data); +static void _menuitem_new_preset(GSimpleAction *action, GVariant *parameter, gpointer user_data); +static void _menuitem_update_preset(GSimpleAction *action, GVariant *parameter, gpointer user_data); +static void _menuitem_manage_presets(GSimpleAction *action, GVariant *parameter, gpointer user_data); + +// action entries for the preset menu items +static GActionEntry _action_entries[] = { + { "activate", _menuitem_activate_preset, "s", "''" }, + { "edit", _menuitem_edit_preset, NULL, NULL }, + { "delete", _menuitem_delete_preset, NULL, NULL }, + { "new", _menuitem_new_preset, NULL, NULL }, + { "update", _menuitem_update_preset, "s", NULL }, + { "manage", _menuitem_manage_presets, NULL, NULL } +}; static gchar *_get_lib_view_path(const dt_lib_module_t *module, const dt_view_t *cv, @@ -148,10 +168,41 @@ static void edit_preset(const char *name_in, (name, rowid, NULL, NULL, TRUE, TRUE, FALSE, GTK_WINDOW(window)); } -static void menuitem_update_preset(GtkMenuItem *menuitem, - dt_lib_module_info_t *minfo) +static dt_lib_module_info_t *_get_module_info(dt_lib_module_t *module) +{ + dt_lib_module_info_t *minfo = calloc(1, sizeof(dt_lib_module_info_t)); + minfo->plugin_name = g_strdup(module->plugin_name); + minfo->version = module->version(); + minfo->module = module; + minfo->params = module->get_params ? module->get_params(module, &minfo->params_size) : NULL; + if(!minfo->params) + { + // this is a valid case, for example in location.c when nothing got selected + // fprintf(stderr, "something went wrong: ¶ms=%p, size=%i\n", + // minfo->params, minfo->params_size); + minfo->params_size = 0; + } + + return minfo; +} + +static void _free_module_info2(gpointer user_data) { - char *name = g_object_get_data(G_OBJECT(menuitem), "dt-preset-name"); + dt_lib_module_info_t *minfo = (dt_lib_module_info_t *)user_data; + g_free(minfo->plugin_name); + free(minfo->params); + free(minfo); +} + +static void _menuitem_update_preset(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) + +{ + dt_lib_module_t *module = (dt_lib_module_t *)user_data; + dt_lib_module_info_t *minfo = _get_module_info(module); + + const gchar *name = g_variant_get_string(parameter, NULL); if(!dt_conf_get_bool("plugins/lighttable/preset/ask_before_delete_preset") || dt_gui_show_yes_no_dialog(_("update preset?"), "", @@ -179,11 +230,17 @@ static void menuitem_update_preset(GtkMenuItem *menuitem, DT_CONTROL_SIGNAL_RAISE(DT_SIGNAL_PRESETS_CHANGED, g_strdup(minfo->plugin_name)); } + + _free_module_info2(minfo); } -static void menuitem_new_preset(GtkMenuItem *menuitem, - dt_lib_module_info_t *minfo) +static void _menuitem_new_preset(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) { + dt_lib_module_t *module = (dt_lib_module_t *)user_data; + dt_lib_module_info_t *minfo = _get_module_info(module); + dt_lib_presets_remove(_("new preset"), minfo->plugin_name, minfo->version); // add new preset @@ -213,23 +270,38 @@ static void menuitem_new_preset(GtkMenuItem *menuitem, // then show edit dialog edit_preset(_("new preset"), minfo); + _free_module_info2(minfo); } -static void menuitem_edit_preset(GtkMenuItem *menuitem, - dt_lib_module_info_t *minfo) +static void _menuitem_edit_preset(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) { + dt_lib_module_t *module = (dt_lib_module_t *)user_data; + dt_lib_module_info_t *minfo = _get_module_info(module); + edit_preset(NULL, minfo); + _free_module_info2(minfo); } -static void menuitem_manage_presets(GtkMenuItem *menuitem, - dt_lib_module_info_t *minfo) +static void _menuitem_manage_presets(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) { - if(minfo->module->manage_presets) minfo->module->manage_presets(minfo->module); + dt_lib_module_t *module = (dt_lib_module_t *)user_data; + dt_lib_module_info_t *minfo = _get_module_info(module); + + if(module->manage_presets) module->manage_presets(module); + _free_module_info2(minfo); } -static void menuitem_delete_preset(GtkMenuItem *menuitem, - dt_lib_module_info_t *minfo) +static void _menuitem_delete_preset(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) { + dt_lib_module_t *module = (dt_lib_module_t *)user_data; + dt_lib_module_info_t *minfo = _get_module_info(module); + gchar *name = dt_lib_get_active_preset_name(minfo); if(name == NULL) return; @@ -239,13 +311,13 @@ static void menuitem_delete_preset(GtkMenuItem *menuitem, name)) { dt_action_rename_preset(&minfo->module->actions, name, NULL); - dt_lib_presets_remove(name, minfo->plugin_name, minfo->version); DT_CONTROL_SIGNAL_RAISE(DT_SIGNAL_PRESETS_CHANGED, g_strdup(minfo->plugin_name)); } g_free(name); + _free_module_info2(minfo); } gchar *dt_lib_presets_duplicate(const gchar *preset, @@ -404,45 +476,45 @@ void dt_lib_presets_update(const gchar *preset, sqlite3_finalize(stmt); } -static void _menuitem_activate_preset(GtkMenuItem *menuitem, - dt_lib_module_info_t *minfo) +static void _menuitem_activate_preset(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) { - dt_lib_presets_apply(g_object_get_data(G_OBJECT(menuitem), "dt-preset-name"), - minfo->plugin_name, minfo->version); -} + dt_lib_module_t *module = (dt_lib_module_t*) user_data; + + g_simple_action_set_state(action, parameter); -static gboolean _menuitem_button_preset(GtkMenuItem *menuitem, - GdkEventButton *event, - dt_lib_module_info_t *minfo) -{ - if(event->button == GDK_BUTTON_PRIMARY) return FALSE; + const gchar *preset_name = g_variant_get_string(parameter, NULL); + dt_lib_presets_apply(preset_name, + module->plugin_name, module->version()); - dt_shortcut_copy_lua((dt_action_t*)minfo->module, - g_object_get_data(G_OBJECT(menuitem), "dt-preset-name")); - return TRUE; + // close the menu + gtk_popover_popdown(GTK_POPOVER(_preset_popover)); } -static void free_module_info(GtkWidget *widget, - gpointer user_data) -{ - dt_lib_module_info_t *minfo = (dt_lib_module_info_t *)user_data; - g_free(minfo->plugin_name); - free(minfo->params); - free(minfo); -} +// static gboolean _menuitem_button_preset(GtkMenuItem *menuitem, +// GdkEventButton *event, +// dt_lib_module_info_t *minfo) +// { +// if(event->button == GDK_BUTTON_PRIMARY) return FALSE; +// +// dt_shortcut_copy_lua((dt_action_t*)minfo->module, +// g_object_get_data(G_OBJECT(menuitem), "dt-preset-name")); +// return TRUE; +// } -static void dt_lib_presets_popup_menu_show(dt_lib_module_info_t *minfo, - GtkWidget *w) +static void _dt_lib_presets_popup_menu_show(dt_lib_module_info_t *minfo, + GtkWidget *presets_button) { - GtkMenu *menu = GTK_MENU(gtk_menu_new()); + GMenu *menu = g_menu_new(); + + GActionGroup *action_group = gtk_widget_get_action_group(presets_button, "presets"); const gboolean hide_default = dt_conf_get_bool("plugins/lighttable/hide_default_presets"); const gboolean default_first = dt_conf_get_bool("modules/default_presets_first"); - g_signal_connect(G_OBJECT(menu), "destroy", G_CALLBACK(free_module_info), minfo); - - GtkWidget *mi; - int active_preset = -1, cnt = 0; + int cnt = 0; + gchar *active_preset_name = NULL; gboolean selected_writeprotect = FALSE; sqlite3_stmt *stmt; // order like the pref value @@ -462,8 +534,10 @@ static void dt_lib_presets_popup_menu_show(dt_lib_module_info_t *minfo, gboolean found = FALSE; int last_wp = -1; gchar **prev_split = NULL; - GtkWidget *submenu = GTK_WIDGET(menu); - GtkWidget *mainmenu = submenu; + + GMenu *submenu = menu; + GMenu *mainmenu = submenu; + GSList *menu_path = NULL; // stack of menuitems which are the parents of submenus on menu_stack while(sqlite3_step(stmt) == SQLITE_ROW) { @@ -480,8 +554,9 @@ static void dt_lib_presets_popup_menu_show(dt_lib_module_info_t *minfo, } else if(last_wp != writeprotect) { - last_wp = writeprotect; - gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new()); + mainmenu = g_menu_new(); + g_menu_append_section(menu, NULL, G_MENU_MODEL(mainmenu)); + *prev_split[0] = '\0'; // make first level mismatch so we start over } @@ -492,98 +567,86 @@ static void dt_lib_presets_popup_menu_show(dt_lib_module_info_t *minfo, if(darktable.gui->last_preset && strcmp(darktable.gui->last_preset, name) == 0) found = TRUE; - mi = dt_insert_preset_in_menu_hierarchy(name, - &menu_path, mainmenu, &submenu, &prev_split, - FALSE, writeprotect); - - // selected in bold: - // printf("comparing %d bytes to %d\n", op_params_size, minfo->params_size); - // for(int k=0;kparams, op_params, k);k++) - // printf("compare [%c %c] %d: - // %d\n", - // ((const char*)(minfo->params))[k], - // ((const char*)(op_params))[k], - // k, memcmp(minfo->params, op_params, k)); + gchar *action = g_strdup_printf("presets.activate::%s", name); + dt_insert_preset_in_menu_hierarchy2(name, + action, + &menu_path, + mainmenu, + &submenu, + &prev_split, + FALSE); + g_free(action); + + // active preset if(op_params_size == minfo->params_size && !memcmp(minfo->params, op_params, op_params_size)) { - active_preset = cnt; + active_preset_name = g_strdup(name); selected_writeprotect = writeprotect; - dt_gui_add_class(mi, "active_menu_item"); - gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(mi), TRUE); - g_set_weak_pointer(&_active_menu_item, mi); - // walk back up the menu hierarchy and highlight the entire path down to the current leaf - for(const GSList *mp = menu_path; mp; mp = g_slist_next(mp)) - dt_gui_add_class(gtk_bin_get_child(GTK_BIN(mp->data)), "active_menu_item"); + + // dt_gui_add_class(mi, "active_menu_item"); + // gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(mi), TRUE); + // g_set_weak_pointer(&_active_menu_item, mi); + // // walk back up the menu hierarchy and highlight the entire path down to the current leaf + // for(const GSList *mp = menu_path; mp; mp = g_slist_next(mp)) + // dt_gui_add_class(gtk_bin_get_child(GTK_BIN(mp->data)), "active_menu_item"); } - g_object_set_data_full(G_OBJECT(mi), "dt-preset-name", g_strdup(name), g_free); - g_object_set_data(G_OBJECT(mi), "dt-preset-module", minfo->module); - dt_action_define(&minfo->module->actions, "preset", name, mi, NULL); - - g_signal_connect(G_OBJECT(mi), "activate", - G_CALLBACK(_menuitem_activate_preset), minfo); - g_signal_connect(G_OBJECT(mi), "button-release-event", - G_CALLBACK(_menuitem_button_preset), minfo); - gtk_widget_set_tooltip_text(mi, (const char *)sqlite3_column_text(stmt, 3)); - gtk_widget_set_has_tooltip(mi, TRUE); + + // g_object_set_data_full(G_OBJECT(mi), "dt-preset-name", g_strdup(name), g_free); + // g_object_set_data(G_OBJECT(mi), "dt-preset-module", minfo->module); + + // dt_action_define(&minfo->module->actions, "preset", name, mi, NULL); // TODO: prüfen!!! + + // g_signal_connect(G_OBJECT(mi), "activate", + // G_CALLBACK(_menuitem_activate_preset), minfo); + // + // g_signal_connect(G_OBJECT(mi), "button-release-event", // TODO: prüfen, Lua !!! + // G_CALLBACK(_menuitem_button_preset), minfo); + + // gtk_widget_set_tooltip_text(mi, (const char *)sqlite3_column_text(stmt, 3)); + + cnt++; } sqlite3_finalize(stmt); if(cnt > 0) { - gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new()); + mainmenu = g_menu_new(); + g_menu_append_section(menu, NULL, G_MENU_MODEL(mainmenu)); cnt = 0; } if(minfo->module->manage_presets) { - mi = gtk_menu_item_new_with_label(_("manage presets...")); - g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(menuitem_manage_presets), minfo); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); + g_menu_append(mainmenu, _("manage presets..."), "presets.manage"); cnt++; } - else if(active_preset >= 0) // FIXME: this doesn't seem to work. + else if(active_preset_name) { if(!selected_writeprotect) { - mi = gtk_menu_item_new_with_label(_("edit this preset..")); - g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(menuitem_edit_preset), minfo); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); - - mi = gtk_menu_item_new_with_label(_("delete this preset")); - g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(menuitem_delete_preset), minfo); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); + g_menu_append(mainmenu, _("edit this preset..."), "presets.edit"); + g_menu_append(mainmenu, _("delete this preset"), "presets.delete"); cnt++; } } else { - mi = gtk_menu_item_new_with_label(_("store new preset..")); - if(minfo->params_size == 0) - { - gtk_widget_set_sensitive(mi, FALSE); - gtk_widget_set_tooltip_text(mi, _("nothing to save")); - } - else - g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(menuitem_new_preset), minfo); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); + g_menu_append(mainmenu, _("store new preset.."), "presets.new"); if(darktable.gui->last_preset && found) { - char *local_last_name = dt_util_localize_segmented_name(darktable.gui->last_preset, TRUE); - char *markup = g_markup_printf_escaped("%s %s", - _("update preset"), - local_last_name); + gchar *local_last_name = dt_util_localize_segmented_name(darktable.gui->last_preset, TRUE); + gchar *markup = g_markup_printf_escaped("%s %s", + _("update preset"), + local_last_name); + + gchar *action = g_strdup_printf("presets.update::%s", local_last_name); + g_menu_append(mainmenu, markup, action); + g_free(action); + g_free(local_last_name); - mi = gtk_menu_item_new_with_label(""); - gtk_widget_set_sensitive(mi, minfo->params_size > 0); - gtk_label_set_markup(GTK_LABEL(gtk_bin_get_child(GTK_BIN(mi))), markup); - g_object_set_data_full(G_OBJECT(mi), "dt-preset-name", - g_strdup(darktable.gui->last_preset), g_free); - g_signal_connect(G_OBJECT(mi), "activate", - G_CALLBACK(menuitem_update_preset), minfo); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); g_free(markup); } cnt++; @@ -591,14 +654,26 @@ static void dt_lib_presets_popup_menu_show(dt_lib_module_info_t *minfo, if(minfo->module->set_preferences) { - if(cnt>0) + if(cnt > 0) { - gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new()); + mainmenu = g_menu_new(); + g_menu_append_section(menu, NULL, G_MENU_MODEL(mainmenu)); } - minfo->module->set_preferences(GTK_MENU_SHELL(menu), minfo->module); + minfo->module->set_preferences(mainmenu, action_group, minfo->module); } - dt_gui_menu_popup(menu, w, GDK_GRAVITY_SOUTH_EAST, GDK_GRAVITY_NORTH_EAST); + // mark the active preset + GAction *action = g_action_map_lookup_action(G_ACTION_MAP(action_group), "activate"); + g_simple_action_set_state(G_SIMPLE_ACTION(action), + g_variant_new_string(active_preset_name? active_preset_name : "")); + + g_free(active_preset_name); + active_preset_name = NULL; + + // popup the menu + GtkWidget *popover_menu = dt_gui_popover_menu_from_model(presets_button, menu); + _preset_popover = popover_menu; + gtk_popover_popup(GTK_POPOVER(popover_menu)); } static int _lib_position(const dt_lib_module_t *module) @@ -932,7 +1007,8 @@ static gboolean _presets_popup_callback(GtkButton *button, // mi->params, mi->params_size); mi->params_size = 0; } - dt_lib_presets_popup_menu_show(mi, GTK_WIDGET(button)); + + _dt_lib_presets_popup_menu_show(mi, GTK_WIDGET(button)); if(button) dtgtk_button_set_active(DTGTK_BUTTON(button), FALSE); @@ -1307,6 +1383,11 @@ GtkWidget *dt_lib_gui_get_expander(dt_lib_module_t *module) && !module->set_preferences) gtk_widget_set_sensitive(GTK_WIDGET(module->presets_button), FALSE); + /* set the action group for the preset button */ + GSimpleActionGroup *action_group = g_simple_action_group_new(); + g_action_map_add_action_entries(G_ACTION_MAP(action_group), _action_entries, G_N_ELEMENTS(_action_entries), module); + gtk_widget_insert_action_group(module->presets_button, "presets", G_ACTION_GROUP(action_group)); + dt_action_define(&module->actions, NULL, NULL, module->presets_button, NULL); gtk_box_pack_end(GTK_BOX(header), module->presets_button, FALSE, FALSE, 0); diff --git a/src/libs/lib_api.h b/src/libs/lib_api.h index d2116a3d4504..c0f6bc008ee3 100644 --- a/src/libs/lib_api.h +++ b/src/libs/lib_api.h @@ -94,7 +94,7 @@ OPTIONAL(void *,get_params, struct dt_lib_module_t *self, int *size); OPTIONAL(int, set_params, struct dt_lib_module_t *self, const void *params, int size); OPTIONAL(void, init_presets, struct dt_lib_module_t *self); OPTIONAL(void, manage_presets, struct dt_lib_module_t *self); -OPTIONAL(void, set_preferences, void *menu, struct dt_lib_module_t *self); +OPTIONAL(void, set_preferences, GMenu *menu, GActionGroup *action_group, struct dt_lib_module_t *self); /** check if the module can autoapply presets. Default is FALSE */ DEFAULT(gboolean, preset_autoapply, struct dt_lib_module_t *self); diff --git a/src/libs/metadata.c b/src/libs/metadata.c index 1788ea87c6a3..6d0ea017d3a6 100644 --- a/src/libs/metadata.c +++ b/src/libs/metadata.c @@ -829,9 +829,11 @@ static void _fill_grid(dt_lib_module_t *self) dt_lib_gui_queue_update(self); } -static void _menuitem_preferences(GtkMenuItem *menuitem, - dt_lib_module_t *self) +static void _menuitem_preferences(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) { + dt_lib_module_t *self = (dt_lib_module_t *)user_data; dt_lib_metadata_t *d = (dt_lib_metadata_t *)self->data; GtkCellEditable *active_editable = NULL; @@ -1118,11 +1120,12 @@ static void _menuitem_preferences(GtkMenuItem *menuitem, gtk_widget_destroy(dialog); } -void set_preferences(void *menu, dt_lib_module_t *self) +void set_preferences(GMenu *menu, GActionGroup *action_group, dt_lib_module_t *self) { - GtkWidget *mi = gtk_menu_item_new_with_label(_("preferences...")); - g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(_menuitem_preferences), self); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); + GSimpleAction *action = g_simple_action_new("preferences", NULL); + g_signal_connect(action, "activate", G_CALLBACK(_menuitem_preferences), self); + g_action_map_add_action(G_ACTION_MAP(action_group), G_ACTION(action)); + g_menu_append(menu, _("preferences..."), "presets.preferences"); } void gui_init(dt_lib_module_t *self) diff --git a/src/libs/metadata_view.c b/src/libs/metadata_view.c index 0b4a5a4ef34a..cb9fea227349 100644 --- a/src/libs/metadata_view.c +++ b/src/libs/metadata_view.c @@ -1452,9 +1452,11 @@ static void _drag_data_inserted(GtkTreeModel *tree_model, _dndactive = TRUE; } -void _menuitem_preferences(GtkMenuItem *menuitem, - dt_lib_module_t *self) +static void _menuitem_preferences(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) { + dt_lib_module_t *self = (dt_lib_module_t *)user_data; dt_lib_metadata_view_t *d = self->data; GtkWidget *win = dt_ui_main_window(darktable.gui->ui); @@ -1570,12 +1572,12 @@ void _menuitem_preferences(GtkMenuItem *menuitem, gtk_widget_destroy(dialog); } -void set_preferences(void *menu, - dt_lib_module_t *self) +void set_preferences(GMenu *menu, GActionGroup *action_group, dt_lib_module_t *self) { - GtkWidget *mi = gtk_menu_item_new_with_label(_("preferences...")); - g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(_menuitem_preferences), self); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); + GSimpleAction *action = g_simple_action_new("preferences", NULL); + g_signal_connect(action, "activate", G_CALLBACK(_menuitem_preferences), self); + g_action_map_add_action(G_ACTION_MAP(action_group), G_ACTION(action)); + g_menu_append(menu, _("preferences..."), "presets.preferences"); } void *get_params(dt_lib_module_t *self, diff --git a/src/libs/recentcollect.c b/src/libs/recentcollect.c index ef2d291aad63..80ba5c4c10fa 100644 --- a/src/libs/recentcollect.c +++ b/src/libs/recentcollect.c @@ -223,8 +223,12 @@ static void _lib_recentcollection_updated(gpointer instance, dt_collection_chang } } -void _menuitem_preferences(GtkMenuItem *menuitem, dt_lib_module_t *self) +static void _menuitem_preferences(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) { + dt_lib_module_t *self = (dt_lib_module_t *)user_data; + char confname[200]; GtkWidget *win = dt_ui_main_window(darktable.gui->ui); GtkWidget *dialog = gtk_dialog_new_with_buttons(_("recent collections settings"), GTK_WINDOW(win), @@ -292,11 +296,12 @@ void _menuitem_preferences(GtkMenuItem *menuitem, dt_lib_module_t *self) gtk_widget_destroy(dialog); } -void set_preferences(void *menu, dt_lib_module_t *self) +void set_preferences(GMenu *menu, GActionGroup *action_group, dt_lib_module_t *self) { - GtkWidget *mi = gtk_menu_item_new_with_label(_("preferences...")); - g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(_menuitem_preferences), self); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); + GSimpleAction *action = g_simple_action_new("preferences", NULL); + g_signal_connect(action, "activate", G_CALLBACK(_menuitem_preferences), self); + g_action_map_add_action(G_ACTION_MAP(action_group), G_ACTION(action)); + g_menu_append(menu, _("preferences..."), "presets.preferences"); } void gui_reset(dt_lib_module_t *self) diff --git a/src/libs/styles.c b/src/libs/styles.c index 2e98e95a4643..6bb206eb93b0 100644 --- a/src/libs/styles.c +++ b/src/libs/styles.c @@ -1043,14 +1043,14 @@ void _menuitem_preferences(GtkMenuItem *menuitem, gtk_widget_destroy(dialog); } -void set_preferences(void *menu, dt_lib_module_t *self) +void set_preferences(GMenu *menu, GActionGroup *action_group, dt_lib_module_t *self) { - GtkWidget *mi = gtk_menu_item_new_with_label(_("preferences...")); - g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(_menuitem_preferences), self); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); + GSimpleAction *action = g_simple_action_new("preferences", NULL); + g_signal_connect(action, "activate", G_CALLBACK(_menuitem_preferences), self); + g_action_map_add_action(G_ACTION_MAP(action_group), G_ACTION(action)); + g_menu_append(menu, _("preferences..."), "presets.preferences"); } - // clang-format off // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py // vim: shiftwidth=2 expandtab tabstop=2 cindent diff --git a/src/libs/tagging.c b/src/libs/tagging.c index 5d09e1bfbbbe..b9e1a32d488e 100644 --- a/src/libs/tagging.c +++ b/src/libs/tagging.c @@ -3935,9 +3935,12 @@ static void _size_recent_tags_list() } } -void _menuitem_preferences(GtkMenuItem *menuitem, - dt_lib_module_t *self) +static void _menuitem_preferences(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) { + dt_lib_module_t *self = (dt_lib_module_t *)user_data; + GtkWidget *win = dt_ui_main_window(darktable.gui->ui); GtkWidget *dialog = gtk_dialog_new_with_buttons(_("tagging settings"), GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT, @@ -3963,11 +3966,12 @@ void _menuitem_preferences(GtkMenuItem *menuitem, } } -void set_preferences(void *menu, dt_lib_module_t *self) +void set_preferences(GMenu *menu, GActionGroup *action_group, dt_lib_module_t *self) { - GtkWidget *mi = gtk_menu_item_new_with_label(_("preferences...")); - g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(_menuitem_preferences), self); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); + GSimpleAction *action = g_simple_action_new("preferences", NULL); + g_signal_connect(action, "activate", G_CALLBACK(_menuitem_preferences), self); + g_action_map_add_action(G_ACTION_MAP(action_group), G_ACTION(action)); + g_menu_append(menu, _("preferences..."), "presets.preferences"); } static void _save_last_tag_used(const char *tagnames,