diff --git a/app/display/gimpimagewindow.c b/app/display/gimpimagewindow.c index 19677a8777..70fe8ae814 100644 --- a/app/display/gimpimagewindow.c +++ b/app/display/gimpimagewindow.c @@ -348,7 +348,7 @@ gimp_image_window_constructed (GObject *object) GimpImageWindowPrivate *private = GIMP_IMAGE_WINDOW_GET_PRIVATE (window); GimpMenuFactory *menu_factory; GimpGuiConfig *config; - GMenuModel *model; + GimpMenuModel *model; G_OBJECT_CLASS (parent_class)->constructed (object); @@ -386,8 +386,7 @@ gimp_image_window_constructed (GObject *object) gtk_widget_show (private->main_vbox); /* Create the menubar */ - model = gimp_ui_manager_get_model (private->menubar_manager, - "/image-menubar"); + model = gimp_ui_manager_get_model (private->menubar_manager, "/image-menubar"); #ifdef GDK_WINDOWING_QUARTZ /* macOS has its native menubar system, and this should support it. It means @@ -395,7 +394,8 @@ gimp_image_window_constructed (GObject *object) * TODO: Since the .ui file has no title/labels, I should edit the model to * extract titles from actions. */ - gtk_application_set_menubar (private->gimp->app, G_MENU_MODEL (model)); + gtk_application_set_menubar (GTK_APPLICATION (private->gimp->app), + G_MENU_MODEL (model)); #else private->menubar = gimp_menu_bar_new (model, private->menubar_manager); g_object_unref (model); diff --git a/app/widgets/gimpaction.c b/app/widgets/gimpaction.c index 31a5349cea..17b226d4f9 100644 --- a/app/widgets/gimpaction.c +++ b/app/widgets/gimpaction.c @@ -428,6 +428,7 @@ gimp_action_set_sensitive (GimpAction *action, gimp_action_update_proxy_tooltip (action, NULL); } + g_object_notify (G_OBJECT (action), "enabled"); } gboolean diff --git a/app/widgets/gimpmenu.c b/app/widgets/gimpmenu.c index e96f69e11a..ada0e5c50a 100644 --- a/app/widgets/gimpmenu.c +++ b/app/widgets/gimpmenu.c @@ -33,6 +33,7 @@ #include "gimpenumaction.h" #include "gimphelp-ids.h" #include "gimpmenu.h" +#include "gimpmenumodel.h" #include "gimpmenushell.h" #include "gimpprocedureaction.h" #include "gimpradioaction.h" @@ -69,7 +70,7 @@ static void gimp_menu_iface_init (GimpMenuShellInterface *ifac static void gimp_menu_finalize (GObject *object); static void gimp_menu_append (GimpMenuShell *shell, - GMenuModel *model); + GimpMenuModel *model); static void gimp_menu_add_ui (GimpMenuShell *shell, const gchar **paths, const gchar *action_name, @@ -170,7 +171,7 @@ gimp_menu_finalize (GObject *object) static void gimp_menu_append (GimpMenuShell *shell, - GMenuModel *model) + GimpMenuModel *model) { static GtkRadioMenuItem *group = NULL; GimpMenu *menu = GIMP_MENU (shell); @@ -179,7 +180,7 @@ gimp_menu_append (GimpMenuShell *shell, g_return_if_fail (GTK_IS_CONTAINER (shell)); - n_items = g_menu_model_get_n_items (model); + n_items = g_menu_model_get_n_items (G_MENU_MODEL (model)); for (gint i = 0; i < n_items; i++) { GMenuModel *subsection; @@ -187,10 +188,10 @@ gimp_menu_append (GimpMenuShell *shell, gchar *label = NULL; gchar *action_name = NULL; - subsection = g_menu_model_get_item_link (model, i, G_MENU_LINK_SECTION); - submenu = g_menu_model_get_item_link (model, i, G_MENU_LINK_SUBMENU); - g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_LABEL, "s", &label); - g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_ACTION, "s", &action_name); + subsection = g_menu_model_get_item_link (G_MENU_MODEL (model), i, G_MENU_LINK_SECTION); + submenu = g_menu_model_get_item_link (G_MENU_MODEL (model), i, G_MENU_LINK_SUBMENU); + g_menu_model_get_item_attribute (G_MENU_MODEL (model), i, G_MENU_ATTRIBUTE_LABEL, "s", &label); + g_menu_model_get_item_attribute (G_MENU_MODEL (model), i, G_MENU_ATTRIBUTE_ACTION, "s", &action_name); if (subsection != NULL) { @@ -202,7 +203,7 @@ gimp_menu_append (GimpMenuShell *shell, gtk_container_add (GTK_CONTAINER (shell), item); gtk_widget_show (item); - gimp_menu_append (shell, subsection); + gimp_menu_append (shell, GIMP_MENU_MODEL (subsection)); item = gtk_separator_menu_item_new (); gtk_container_add (GTK_CONTAINER (shell), item); @@ -240,7 +241,7 @@ gimp_menu_append (GimpMenuShell *shell, subcontainer = gimp_menu_new (manager); gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), subcontainer); - gimp_menu_append (GIMP_MENU_SHELL (subcontainer), submenu); + gimp_menu_append (GIMP_MENU_SHELL (subcontainer), GIMP_MENU_MODEL (submenu)); gtk_widget_show (subcontainer); g_tree_insert (menu->priv->submenus, @@ -263,7 +264,7 @@ gimp_menu_append (GimpMenuShell *shell, subcontainer = gimp_menu_new (manager); gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), subcontainer); - gimp_menu_append (GIMP_MENU_SHELL (subcontainer), submenu); + gimp_menu_append (GIMP_MENU_SHELL (subcontainer), GIMP_MENU_MODEL (submenu)); gtk_widget_show (subcontainer); g_tree_insert (menu->priv->submenus, diff --git a/app/widgets/gimpmenubar.c b/app/widgets/gimpmenubar.c index cde75cccd3..9e34dd3753 100644 --- a/app/widgets/gimpmenubar.c +++ b/app/widgets/gimpmenubar.c @@ -32,6 +32,7 @@ #include "gimpaction.h" #include "gimpmenu.h" #include "gimpmenubar.h" +#include "gimpmenumodel.h" #include "gimpmenushell.h" #include "gimpradioaction.h" #include "gimpuimanager.h" @@ -57,9 +58,7 @@ enum struct _GimpMenuBarPrivate { - GMenuModel *model; - - GTree *menus; + GTree *menus; }; @@ -67,19 +66,10 @@ struct _GimpMenuBarPrivate static void gimp_menu_bar_iface_init (GimpMenuShellInterface *iface); -static void gimp_menu_bar_constructed (GObject *object); static void gimp_menu_bar_dispose (GObject *object); -static void gimp_menu_bar_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec); -static void gimp_menu_bar_get_property (GObject *object, - guint property_id, - GValue *value, - GParamSpec *pspec); static void gimp_menu_bar_append (GimpMenuShell *shell, - GMenuModel *model); + GimpMenuModel *model); static void gimp_menu_bar_add_ui (GimpMenuShell *shell, const gchar **paths, const gchar *action_name, @@ -101,19 +91,11 @@ gimp_menu_bar_class_init (GimpMenuBarClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); - object_class->constructed = gimp_menu_bar_constructed; object_class->dispose = gimp_menu_bar_dispose; - object_class->get_property = gimp_menu_bar_get_property; - object_class->set_property = gimp_menu_bar_set_property; + object_class->get_property = gimp_menu_shell_get_property; + object_class->set_property = gimp_menu_shell_set_property; gimp_menu_shell_install_properties (object_class); - - g_object_class_install_property (object_class, PROP_MODEL, - g_param_spec_object ("model", - NULL, NULL, - G_TYPE_MENU_MODEL, - GIMP_PARAM_READWRITE | - G_PARAM_CONSTRUCT)); } static void @@ -134,67 +116,19 @@ gimp_menu_bar_init (GimpMenuBar *bar) gimp_menu_shell_init (GIMP_MENU_SHELL (bar)); } -static void -gimp_menu_bar_constructed (GObject *object) -{ - G_OBJECT_CLASS (parent_class)->constructed (object); -} - static void gimp_menu_bar_dispose (GObject *object) { GimpMenuBar *bar = GIMP_MENU_BAR (object); - g_clear_object (&bar->priv->model); g_clear_pointer (&bar->priv->menus, g_tree_unref); G_OBJECT_CLASS (parent_class)->dispose (object); } -static void -gimp_menu_bar_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec) -{ - GimpMenuBar *bar = GIMP_MENU_BAR (object); - - switch (property_id) - { - case PROP_MODEL: - bar->priv->model = g_value_dup_object (value); - gimp_menu_shell_fill (GIMP_MENU_SHELL (bar), bar->priv->model, "ui-added", FALSE); - break; - - default: - gimp_menu_shell_set_property (object, property_id, value, pspec); - break; - } -} - -static void -gimp_menu_bar_get_property (GObject *object, - guint property_id, - GValue *value, - GParamSpec *pspec) -{ - GimpMenuBar *bar = GIMP_MENU_BAR (object); - - switch (property_id) - { - case PROP_MODEL: - g_value_set_object (value, bar->priv->model); - break; - - default: - gimp_menu_shell_get_property (object, property_id, value, pspec); - break; - } -} - static void gimp_menu_bar_append (GimpMenuShell *shell, - GMenuModel *model) + GimpMenuModel *model) { GimpMenuBar *bar = GIMP_MENU_BAR (shell); GimpUIManager *manager = gimp_menu_shell_get_manager (GIMP_MENU_SHELL (shell)); @@ -202,7 +136,7 @@ gimp_menu_bar_append (GimpMenuShell *shell, g_return_if_fail (GTK_IS_CONTAINER (shell)); - n_items = g_menu_model_get_n_items (model); + n_items = g_menu_model_get_n_items (G_MENU_MODEL (model)); for (gint i = 0; i < n_items; i++) { GMenuModel *subsection; @@ -210,10 +144,10 @@ gimp_menu_bar_append (GimpMenuShell *shell, gchar *label = NULL; gchar *action_name = NULL; - subsection = g_menu_model_get_item_link (model, i, G_MENU_LINK_SECTION); - submenu = g_menu_model_get_item_link (model, i, G_MENU_LINK_SUBMENU); - g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_LABEL, "s", &label); - g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_ACTION, "s", &action_name); + subsection = g_menu_model_get_item_link (G_MENU_MODEL (model), i, G_MENU_LINK_SECTION); + submenu = g_menu_model_get_item_link (G_MENU_MODEL (model), i, G_MENU_LINK_SUBMENU); + g_menu_model_get_item_attribute (G_MENU_MODEL (model), i, G_MENU_ATTRIBUTE_LABEL, "s", &label); + g_menu_model_get_item_attribute (G_MENU_MODEL (model), i, G_MENU_ATTRIBUTE_ACTION, "s", &action_name); if (submenu != NULL) { @@ -231,7 +165,7 @@ gimp_menu_bar_append (GimpMenuShell *shell, subcontainer = gimp_menu_new (manager); gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), subcontainer); - GIMP_MENU_SHELL_GET_INTERFACE (subcontainer)->append (GIMP_MENU_SHELL (subcontainer), submenu); + gimp_menu_shell_append (GIMP_MENU_SHELL (subcontainer), GIMP_MENU_MODEL (submenu)); gtk_widget_show (subcontainer); g_tree_insert (bar->priv->menus, @@ -305,7 +239,7 @@ gimp_menu_bar_add_ui (GimpMenuShell *shell, /* Public functions */ GtkWidget * -gimp_menu_bar_new (GMenuModel *model, +gimp_menu_bar_new (GimpMenuModel *model, GimpUIManager *manager) { g_return_val_if_fail (GIMP_IS_UI_MANAGER (manager) && diff --git a/app/widgets/gimpmenubar.h b/app/widgets/gimpmenubar.h index f255914e20..40e699f758 100644 --- a/app/widgets/gimpmenubar.h +++ b/app/widgets/gimpmenubar.h @@ -48,7 +48,7 @@ struct _GimpMenuBarClass GType gimp_menu_bar_get_type (void) G_GNUC_CONST; -GtkWidget * gimp_menu_bar_new (GMenuModel *model, +GtkWidget * gimp_menu_bar_new (GimpMenuModel *model, GimpUIManager *manager); diff --git a/app/widgets/gimpmenumodel.c b/app/widgets/gimpmenumodel.c new file mode 100644 index 0000000000..d6e522db3e --- /dev/null +++ b/app/widgets/gimpmenumodel.c @@ -0,0 +1,865 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpmenu_model.c + * Copyright (C) 2023 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include "widgets-types.h" + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "core/gimp.h" + +#include "gimpaction.h" +#include "gimpmenumodel.h" +#include "gimpmenushell.h" +#include "gimpuimanager.h" + + +/** + * GimpMenuModel: + * + * GimpMenuModel implements GMenuModel. We initialize an object from another + * GMenuModel (usually a GMenu), auto-fill with various data from the + * GimpAction, when they are not in GAction API, e.g. labels, but action + * visibility. + * + * The object will also synchronize automatically with changes from the actions, + * but also GimpUIManager for dynamic contents and will trigger an + * "items-changed" when necessary. This allows for such variant to be used in + * GTK API which has no knowledge of GimpAction or GimpUIManager enhancements. + */ + + +enum +{ + PROP_0, + PROP_MANAGER, + PROP_MODEL, + PROP_PATH, + PROP_IS_SECTION, +}; + +struct _GimpMenuModelPrivate +{ + GimpUIManager *manager; + GMenuModel *model; + + gchar *path; + gboolean is_section; + + GList *items; + gint last_n_items; +}; + + +static void gimp_menu_model_finalize (GObject *object); +static void gimp_menu_model_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_menu_model_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); + +static GVariant * gimp_menu_model_get_item_attribute_value (GMenuModel *model, + gint item_index, + const gchar *attribute, + const GVariantType *expected_type); +static void gimp_menu_model_get_item_attributes (GMenuModel *model, + gint item_index, + GHashTable **attributes); +static GMenuModel * gimp_menu_model_get_item_link (GMenuModel *model, + gint item_index, + const gchar *link); +static void gimp_menu_model_get_item_links (GMenuModel *model, + gint item_index, + GHashTable **links); +static gint gimp_menu_model_get_n_items (GMenuModel *model); +static gboolean gimp_menu_model_is_mutable (GMenuModel *model); + +static void gimp_menu_model_initialize (GimpMenuModel *model, + GMenuModel *gmodel); +static void gimp_menu_model_update (GimpMenuModel *model); + +static GMenuItem * gimp_menu_model_get_item (GimpMenuModel *model, + gint idx); + +static void gimp_menu_model_action_notify_visible (GimpAction *action, + GParamSpec *pspec, + GimpMenuModel *model); +static void gimp_menu_model_action_notify_label (GimpAction *action, + GParamSpec *pspec, + GMenuItem *item); + +static gboolean gimp_menu_model_ui_added (GimpUIManager *manager, + const gchar *path, + const gchar *action_name, + const gchar *placeholder_key, + gboolean top, + GimpMenuModel *model); +static gboolean gimp_menu_model_ui_removed (GimpUIManager *manager, + const gchar *path, + const gchar *action_name, + GimpMenuModel *model); + + +G_DEFINE_TYPE_WITH_CODE (GimpMenuModel, gimp_menu_model, G_TYPE_MENU_MODEL, G_ADD_PRIVATE (GimpMenuModel)) + +#define parent_class gimp_menu_model_parent_class + + +/* Class functions */ + +static void +gimp_menu_model_class_init (GimpMenuModelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GMenuModelClass *model_class = G_MENU_MODEL_CLASS (klass); + + object_class->finalize = gimp_menu_model_finalize; + object_class->get_property = gimp_menu_model_get_property; + object_class->set_property = gimp_menu_model_set_property; + + model_class->get_item_attribute_value = gimp_menu_model_get_item_attribute_value; + model_class->get_item_attributes = gimp_menu_model_get_item_attributes; + model_class->get_item_link = gimp_menu_model_get_item_link; + model_class->get_item_links = gimp_menu_model_get_item_links; + model_class->get_n_items = gimp_menu_model_get_n_items; + model_class->is_mutable = gimp_menu_model_is_mutable; + + g_object_class_install_property (object_class, PROP_MANAGER, + g_param_spec_object ("manager", + NULL, NULL, + GIMP_TYPE_UI_MANAGER, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, PROP_MODEL, + g_param_spec_object ("model", + NULL, NULL, + G_TYPE_MENU_MODEL, + GIMP_PARAM_READWRITE)); + g_object_class_install_property (object_class, PROP_PATH, + g_param_spec_string ("path", + NULL, NULL, NULL, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, PROP_IS_SECTION, + g_param_spec_boolean ("section", + NULL, NULL, FALSE, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_menu_model_init (GimpMenuModel *model) +{ + model->priv = gimp_menu_model_get_instance_private (model); + + model->priv->items = NULL; + model->priv->last_n_items = 0; + model->priv->path = 0; + model->priv->is_section = FALSE; +} + +static void +gimp_menu_model_finalize (GObject *object) +{ + GimpMenuModel *model = GIMP_MENU_MODEL (object); + + g_clear_object (&model->priv->model); + g_list_free_full (model->priv->items, g_object_unref); + g_free (model->priv->path); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +void +gimp_menu_model_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpMenuModel *model = GIMP_MENU_MODEL (object); + + switch (property_id) + { + case PROP_MANAGER: + g_value_set_object (value, model->priv->manager); + break; + case PROP_MODEL: + g_value_set_object (value, model->priv->model); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +void +gimp_menu_model_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpMenuModel *model = GIMP_MENU_MODEL (object); + + switch (property_id) + { + case PROP_MANAGER: + g_set_weak_pointer (&model->priv->manager, g_value_get_object (value)); + break; + case PROP_MODEL: + model->priv->model = g_value_dup_object (value); + gimp_menu_model_initialize (model, model->priv->model); + break; + case PROP_PATH: + model->priv->path = g_value_dup_string (value); + break; + case PROP_IS_SECTION: + model->priv->is_section = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static GVariant* +gimp_menu_model_get_item_attribute_value (GMenuModel *model, + gint item_index, + const gchar *attribute, + const GVariantType *expected_type) +{ + GimpMenuModel *m = GIMP_MENU_MODEL (model); + GMenuItem *item; + + item = gimp_menu_model_get_item (m, item_index); + + return g_menu_item_get_attribute_value (item, attribute, expected_type); +} + +static void +gimp_menu_model_get_item_attributes (GMenuModel *model, + gint item_index, + GHashTable **attributes) +{ + GimpMenuModel *m = GIMP_MENU_MODEL (model); + GMenuItem *item; + GVariant *value; + + item = gimp_menu_model_get_item (m, item_index); + + *attributes = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, + (GDestroyNotify) g_variant_unref); + + value = g_menu_item_get_attribute_value (item, G_MENU_ATTRIBUTE_LABEL, NULL); + if (value) + g_hash_table_insert (*attributes, G_MENU_ATTRIBUTE_LABEL, value); + + value = g_menu_item_get_attribute_value (item, G_MENU_ATTRIBUTE_ACTION, NULL); + if (value) + g_hash_table_insert (*attributes, G_MENU_ATTRIBUTE_ACTION, value); + + value = g_menu_item_get_attribute_value (item, G_MENU_ATTRIBUTE_ICON, NULL); + if (value) + g_hash_table_insert (*attributes, G_MENU_ATTRIBUTE_ICON, value); + + value = g_menu_item_get_attribute_value (item, G_MENU_LINK_SUBMENU, NULL); + if (value) + g_hash_table_insert (*attributes, G_MENU_LINK_SUBMENU, value); + + value = g_menu_item_get_attribute_value (item, G_MENU_LINK_SECTION, NULL); + if (value) + g_hash_table_insert (*attributes, G_MENU_LINK_SECTION, value); +} + +static GMenuModel* +gimp_menu_model_get_item_link (GMenuModel *model, + gint item_index, + const gchar *link) +{ + GimpMenuModel *m = GIMP_MENU_MODEL (model); + GMenuItem *item; + + item = gimp_menu_model_get_item (m, item_index); + + return g_menu_item_get_link (item, link); +} + + +static void +gimp_menu_model_get_item_links (GMenuModel *model, + gint item_index, + GHashTable **links) +{ + GimpMenuModel *m = GIMP_MENU_MODEL (model); + GMenuModel *subsection; + GMenuModel *submenu; + GMenuItem *item; + + *links = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, + (GDestroyNotify) g_object_unref); + + item = gimp_menu_model_get_item (m, item_index); + subsection = g_menu_item_get_link (item, G_MENU_LINK_SECTION); + submenu = g_menu_item_get_link (item, G_MENU_LINK_SUBMENU); + + if (subsection) + g_hash_table_insert (*links, G_MENU_LINK_SECTION, g_object_ref (subsection)); + if (submenu) + g_hash_table_insert (*links, G_MENU_LINK_SUBMENU, g_object_ref (submenu)); + + g_clear_object (&subsection); + g_clear_object (&submenu); +} + +static gint +gimp_menu_model_get_n_items (GMenuModel *model) +{ + GimpMenuModel *m = GIMP_MENU_MODEL (model); + GApplication *app = m->priv->manager->gimp->app; + gint len = 0; + + for (GList *iter = m->priv->items; iter; iter = iter->next) + { + const gchar *action_name = NULL; + GMenuModel *subsection; + GMenuModel *submenu; + + subsection = g_menu_item_get_link (iter->data, G_MENU_LINK_SECTION); + submenu = g_menu_item_get_link (iter->data, G_MENU_LINK_SUBMENU); + + if (subsection || submenu) + { + len++; + } + /* Count neither placeholders (items with no action_name), nor invisible + * actions. + */ + else if (g_menu_item_get_attribute (iter->data, + G_MENU_ATTRIBUTE_ACTION, + "&s", &action_name)) + { + GAction *action; + + action = g_action_map_lookup_action (G_ACTION_MAP (app), + action_name + 4); + if (gimp_action_is_visible (GIMP_ACTION (action))) + len++; + } + g_clear_object (&subsection); + g_clear_object (&submenu); + } + + m->priv->last_n_items = len; + + return len; +} + +static gboolean +gimp_menu_model_is_mutable (GMenuModel* model) +{ + return TRUE; +} + + +/* Public functions. */ + +GimpMenuModel * +gimp_menu_model_new (GimpUIManager *manager, + GMenuModel *model) +{ + g_return_val_if_fail (GIMP_IS_UI_MANAGER (manager), NULL); + + return g_object_new (GIMP_TYPE_MENU_MODEL, + "manager", manager, + "model", model, + NULL); +} + +GimpMenuModel * +gimp_menu_model_get_submodel (GimpMenuModel *model, + const gchar *path) +{ + GimpMenuModel *submodel; + gchar *submenus; + gchar *submenu; + gchar *subsubmenus; + + submodel = g_object_ref (model); + + if (path == NULL) + return submodel; + + submenus = g_strdup (path); + subsubmenus = submenus; + + while (subsubmenus && strlen (subsubmenus) > 0) + { + gint n_items; + gint i; + + submenu = subsubmenus; + while (*submenu == '/') + submenu++; + + subsubmenus = strstr (submenu, "/"); + if (subsubmenus) + *(subsubmenus++) = '\0'; + + if (strlen (submenu) == 0) + break; + + n_items = g_menu_model_get_n_items (G_MENU_MODEL (submodel)); + for (i = 0; i < n_items; i++) + { + GMenuModel *subsubmodel; + gchar *label = NULL; + gchar *canon_label = NULL; + + subsubmodel = g_menu_model_get_item_link (G_MENU_MODEL (submodel), i, G_MENU_LINK_SUBMENU); + g_menu_model_get_item_attribute (G_MENU_MODEL (submodel), i, G_MENU_ATTRIBUTE_LABEL, "s", &label); + if (label) + canon_label = gimp_menu_shell_make_canonical_path (label); + + if (subsubmodel && g_strcmp0 (canon_label, submenu) == 0) + { + g_object_unref (submodel); + submodel = GIMP_MENU_MODEL (subsubmodel); + + g_free (label); + g_free (canon_label); + break; + } + g_clear_object (&subsubmodel); + g_free (label); + g_free (canon_label); + } + g_return_val_if_fail (i < n_items, NULL); + } + + g_free (submenus); + + return submodel; +} + +const gchar * +gimp_menu_model_get_path (GimpMenuModel *model) +{ + return model->priv->path; +} + + +/* Private functions. */ + +static GimpMenuModel * +gimp_menu_model_new_section (GimpUIManager *manager, + GMenuModel *model, + const gchar *path) +{ + g_return_val_if_fail (GIMP_IS_UI_MANAGER (manager), NULL); + + return g_object_new (GIMP_TYPE_MENU_MODEL, + "manager", manager, + "model", model, + "path", path, + "section", TRUE, + NULL); +} + +static GimpMenuModel * +gimp_menu_model_new_submenu (GimpUIManager *manager, + GMenuModel *model, + const gchar *path) +{ + g_return_val_if_fail (GIMP_IS_UI_MANAGER (manager), NULL); + + return g_object_new (GIMP_TYPE_MENU_MODEL, + "manager", manager, + "model", model, + "path", path, + NULL); +} + +static void +gimp_menu_model_initialize (GimpMenuModel *model, + GMenuModel *gmodel) +{ + GApplication *app = model->priv->manager->gimp->app; + gint n_items; + + g_return_if_fail (GIMP_IS_MENU_MODEL (model)); + g_return_if_fail (G_IS_MENU_MODEL (gmodel)); + + n_items = g_menu_model_get_n_items (gmodel); + for (int i = 0; i < n_items; i++) + { + GMenuModel *subsection; + GMenuModel *submenu; + gchar *label = NULL; + gchar *action_name = NULL; + GMenuItem *item = NULL; + + subsection = g_menu_model_get_item_link (G_MENU_MODEL (gmodel), i, G_MENU_LINK_SECTION); + submenu = g_menu_model_get_item_link (G_MENU_MODEL (gmodel), i, G_MENU_LINK_SUBMENU); + g_menu_model_get_item_attribute (G_MENU_MODEL (gmodel), i, + G_MENU_ATTRIBUTE_LABEL, "s", &label); + g_menu_model_get_item_attribute (G_MENU_MODEL (gmodel), i, + G_MENU_ATTRIBUTE_ACTION, "s", &action_name); + + if (subsection != NULL) + { + GimpMenuModel *submodel; + + submodel = gimp_menu_model_new_section (model->priv->manager, subsection, + model->priv->path); + item = g_menu_item_new_section (label, G_MENU_MODEL (submodel)); + g_object_unref (submodel); + } + else if (submenu != NULL) + { + GimpMenuModel *submodel; + gchar *canon_label; + gchar *path; + + g_return_if_fail (label != NULL); + canon_label = gimp_menu_shell_make_canonical_path (label); + path = g_strdup_printf ("%s/%s", + model->priv->path ? model->priv->path : "", + canon_label); + g_free (canon_label); + + submodel = gimp_menu_model_new_submenu (model->priv->manager, submenu, path); + item = g_menu_item_new_submenu (label, G_MENU_MODEL (submodel)); + + g_object_unref (submodel); + g_free (path); + } + else + { + item = g_menu_item_new_from_model (G_MENU_MODEL (gmodel), i); + + if (action_name) + { + GAction *action; + + action = g_action_map_lookup_action (G_ACTION_MAP (app), + action_name + 4); + g_signal_connect_object (action, + "notify::visible", + G_CALLBACK (gimp_menu_model_action_notify_visible), + model, 0); + + g_menu_item_set_label (item, gimp_action_get_label (GIMP_ACTION (action))); + g_signal_connect_object (action, + "notify::label", + G_CALLBACK (gimp_menu_model_action_notify_label), + item, 0); + } + } + + if (item) + model->priv->items = g_list_append (model->priv->items, item); + + g_free (label); + g_free (action_name); + g_clear_object (&subsection); + g_clear_object (&submenu); + } + + if (! model->priv->is_section) + { + g_signal_connect_object (model->priv->manager, "ui-added", + G_CALLBACK (gimp_menu_model_ui_added), + model, 0); + g_signal_connect_object (model->priv->manager, "ui-removed", + G_CALLBACK (gimp_menu_model_ui_removed), + model, 0); + gimp_ui_manager_foreach_ui (model->priv->manager, + (GimpUIMenuCallback) gimp_menu_model_ui_added, + model); + } +} + +static void +gimp_menu_model_update (GimpMenuModel *model) +{ + gint last_n_items; + gint n_items; + + g_return_if_fail (GIMP_IS_MENU_MODEL (model)); + + last_n_items = model->priv->last_n_items; + n_items = gimp_menu_model_get_n_items (G_MENU_MODEL (model)); + + /* A bit "lazy" but should make sure we don't mess up. */ + g_menu_model_items_changed (G_MENU_MODEL (model), 0, last_n_items, n_items); +} + +static GMenuItem * +gimp_menu_model_get_item (GimpMenuModel *model, + gint idx) +{ + GimpMenuModel *m = GIMP_MENU_MODEL (model); + GApplication *app = m->priv->manager->gimp->app; + gint cur = -1; + + for (GList *iter = m->priv->items; iter; iter = iter->next) + { + const gchar *action_name = NULL; + GMenuModel *subsection; + GMenuModel *submenu; + + subsection = g_menu_item_get_link (iter->data, G_MENU_LINK_SECTION); + submenu = g_menu_item_get_link (iter->data, G_MENU_LINK_SUBMENU); + + if (subsection || submenu) + { + cur++; + } + /* Count neither placeholders (items with no action_name), nor invisible + * actions. + */ + else if (g_menu_item_get_attribute (iter->data, + G_MENU_ATTRIBUTE_ACTION, + "&s", &action_name)) + { + GAction *action; + + action = g_action_map_lookup_action (G_ACTION_MAP (app), + action_name + 4); + if (gimp_action_is_visible (GIMP_ACTION (action))) + cur++; + } + + g_clear_object (&subsection); + g_clear_object (&submenu); + + if (cur == idx) + return iter->data; + } + + return NULL; +} + +static void +gimp_menu_model_action_notify_visible (GimpAction *action, + GParamSpec *pspec, + GimpMenuModel *model) +{ + gimp_menu_model_update (model); +} + +static void +gimp_menu_model_action_notify_label (GimpAction *action, + GParamSpec *pspec, + GMenuItem *item) +{ + g_return_if_fail (GIMP_IS_ACTION (action)); + g_return_if_fail (G_IS_MENU_ITEM (item)); + + g_menu_item_set_label (item, gimp_action_get_label (action)); +} + +static gboolean +gimp_menu_model_ui_added (GimpUIManager *manager, + const gchar *path, + const gchar *action_name, + const gchar *placeholder_key, + gboolean top, + GimpMenuModel *model) +{ + gboolean added = FALSE; + + if (g_strcmp0 (path, model->priv->path) == 0) + { + GApplication *app = model->priv->manager->gimp->app; + GAction *action; + gchar *detailed_action_name; + GMenuItem *item; + + action = g_action_map_lookup_action (G_ACTION_MAP (app), action_name); + + g_return_val_if_fail (action != NULL, FALSE); + + added = TRUE; + + g_signal_handlers_disconnect_by_func (action, + G_CALLBACK (gimp_menu_model_action_notify_visible), + model); + detailed_action_name = g_strdup_printf ("app.%s", g_action_get_name (action)); + item = g_menu_item_new (gimp_action_get_label (GIMP_ACTION (action)), detailed_action_name); + /* TODO: add also G_MENU_ATTRIBUTE_ICON attribute? */ + g_free (detailed_action_name); + + if (placeholder_key) + { + GList *placeholder = NULL; + GMenuModel *subsection = NULL; + + for (GList *iter = model->priv->items; iter; iter = iter->next) + { + const gchar *label; + + subsection = g_menu_item_get_link (iter->data, G_MENU_LINK_SECTION); + + if (subsection != NULL) + { + if (gimp_menu_model_ui_added (manager, path, action_name, + placeholder_key, top, + GIMP_MENU_MODEL (subsection))) + break; + else + g_clear_object (&subsection); + } + else if (g_menu_item_get_attribute (iter->data, + G_MENU_ATTRIBUTE_LABEL, + "&s", &label) && + g_strcmp0 (label, placeholder_key) == 0) + { + placeholder = iter; + break; + } + } + + if (placeholder) + { + if (top) + model->priv->items = g_list_insert_before (model->priv->items, + placeholder, item); + else + model->priv->items = g_list_insert_before (model->priv->items, + placeholder->next, item); + } + else + { + added = FALSE; + + if (subsection == NULL && ! model->priv->is_section) + g_warning ("%s: no placeholder item '%s'.", G_STRFUNC, placeholder_key); + } + /* else: added in a subsection. */ + + g_clear_object (&subsection); + } + else if (top) + { + model->priv->items = g_list_prepend (model->priv->items, item); + } + else + { + model->priv->items = g_list_append (model->priv->items, item); + } + + g_signal_connect_object (action, + "notify::visible", + G_CALLBACK (gimp_menu_model_action_notify_visible), + model, 0); + g_signal_connect_object (action, + "notify::label", + G_CALLBACK (gimp_menu_model_action_notify_label), + item, 0); + + gimp_menu_model_update (model); + } + + return added; +} + +static gboolean +gimp_menu_model_ui_removed (GimpUIManager *manager, + const gchar *path, + const gchar *action_name, + GimpMenuModel *model) +{ + gboolean removed = FALSE; + + if (g_strcmp0 (path, model->priv->path) == 0) + { + GApplication *app = model->priv->manager->gimp->app; + GMenuItem *item = NULL; + GMenuModel *subsection = NULL; + GAction *action; + + action = g_action_map_lookup_action (G_ACTION_MAP (app), action_name); + + g_return_val_if_fail (action != NULL, FALSE); + + removed = TRUE; + + for (GList *iter = model->priv->items; iter; iter = iter->next) + { + const gchar *action; + + subsection = g_menu_item_get_link (iter->data, G_MENU_LINK_SECTION); + + if (subsection != NULL) + { + if (gimp_menu_model_ui_removed (manager, path, action_name, + GIMP_MENU_MODEL (subsection))) + break; + else + g_clear_object (&subsection); + } + else if (g_menu_item_get_attribute (iter->data, + G_MENU_ATTRIBUTE_ACTION, + "&s", &action) && + /* "action" attribute will start with "app." prefix. */ + g_strcmp0 (action + 4, action_name) == 0) + { + item = iter->data; + model->priv->items = g_list_delete_link (model->priv->items, iter); + break; + } + } + + if (item) + { + g_signal_handlers_disconnect_by_func (action, + G_CALLBACK (gimp_menu_model_action_notify_visible), + model); + g_signal_handlers_disconnect_by_func (action, + G_CALLBACK (gimp_menu_model_action_notify_label), + item); + g_object_unref (item); + } + else + { + removed = FALSE; + + if (subsection == NULL && ! model->priv->is_section) + g_warning ("%s: no item for action name '%s'.", G_STRFUNC, action_name); + } + /* else: removed in a subsection. */ + + g_clear_object (&subsection); + + gimp_menu_model_update (model); + } + + return removed; +} diff --git a/app/widgets/gimpmenumodel.h b/app/widgets/gimpmenumodel.h new file mode 100644 index 0000000000..11128f1721 --- /dev/null +++ b/app/widgets/gimpmenumodel.h @@ -0,0 +1,60 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpmenu_model.h + * Copyright (C) 2023 Jehan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_MENU_MODEL_H__ +#define __GIMP_MENU_MODEL_H__ + + +#define GIMP_TYPE_MENU_MODEL (gimp_menu_model_get_type ()) +#define GIMP_MENU_MODEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MENU_MODEL, GimpMenuModel)) +#define GIMP_MENU_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MENU_MODEL, GimpMenuModelClass)) +#define GIMP_IS_MENU_MODEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_MENU_MODEL)) +#define GIMP_IS_MENU_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MENU_MODEL)) +#define GIMP_MENU_MODEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MENU_MODEL, GimpMenuModelClass)) + + +typedef struct _GimpMenuModelPrivate GimpMenuModelPrivate; +typedef struct _GimpMenuModelClass GimpMenuModelClass; + +struct _GimpMenuModel +{ + GMenuModel parent_instance; + + GimpMenuModelPrivate *priv; +}; + +struct _GimpMenuModelClass +{ + GMenuModelClass parent_class; +}; + + +GType gimp_menu_model_get_type (void) G_GNUC_CONST; + +GimpMenuModel * gimp_menu_model_new (GimpUIManager *manager, + GMenuModel *model); + +GimpMenuModel * gimp_menu_model_get_submodel (GimpMenuModel *model, + const gchar *path); + +const gchar * gimp_menu_model_get_path (GimpMenuModel *model); + + +#endif /* __GIMP_MENU_MODEL_H__ */ diff --git a/app/widgets/gimpmenushell.c b/app/widgets/gimpmenushell.c index abc9afbdcc..5902646a1b 100644 --- a/app/widgets/gimpmenushell.c +++ b/app/widgets/gimpmenushell.c @@ -28,6 +28,7 @@ #include "widgets-types.h" #include "gimpmenu.h" +#include "gimpmenumodel.h" #include "gimpmenushell.h" #include "gimpuimanager.h" @@ -39,8 +40,8 @@ typedef struct _GimpMenuShellPrivate GimpMenuShellPrivate; struct _GimpMenuShellPrivate { GimpUIManager *manager; - gchar *update_signal; - gchar **path_prefix; + GimpMenuModel *model; + gchar *path_prefix; GRegex *path_regex; }; @@ -50,19 +51,14 @@ static GimpMenuShellPrivate * gimp_menu_shell_get_private (GimpMenuShell *menu_shell); static void gimp_menu_shell_private_finalize (GimpMenuShellPrivate *priv); -static void gimp_menu_shell_ui_added (GimpUIManager *manager, - const gchar *path, - const gchar *action_name, - const gchar *placeholder_key, - gboolean top, - GimpMenuShell *shell); -static void gimp_menu_shell_ui_removed (GimpUIManager *manager, - const gchar *path, - const gchar *action_name, +static void gimp_menu_shell_model_changed (GMenuModel *self, + gint position, + gint removed, + gint added, GimpMenuShell *shell); static void gimp_menu_shell_append_model_drop_top (GimpMenuShell *shell, - GMenuModel *model); + GimpMenuModel *model); static gchar ** gimp_menu_shell_break_path (GimpMenuShell *shell, const gchar *path); @@ -84,65 +80,67 @@ gimp_menu_shell_default_init (GimpMenuShellInterface *iface) GIMP_TYPE_UI_MANAGER, GIMP_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_interface_install_property (iface, + g_param_spec_object ("model", + NULL, NULL, + GIMP_TYPE_MENU_MODEL, + GIMP_PARAM_READWRITE | + G_PARAM_READWRITE)); } -/* public functions */ +/* Public functions. */ void gimp_menu_shell_fill (GimpMenuShell *shell, - GMenuModel *model, - const gchar *update_signal, + GimpMenuModel *model, gboolean drop_top_submenu) { - GimpMenuShellPrivate *priv; - gchar **path_prefix; - g_return_if_fail (GTK_IS_CONTAINER (shell)); gtk_container_foreach (GTK_CONTAINER (shell), (GtkCallback) gtk_widget_destroy, NULL); - priv = GET_PRIVATE (shell); - g_clear_pointer (&priv->path_prefix, g_strfreev); - path_prefix = g_object_get_data (G_OBJECT (model), "gimp-ui-manager-model-paths"); - priv->path_prefix = g_strdupv (path_prefix); - if (drop_top_submenu) gimp_menu_shell_append_model_drop_top (shell, model); else - GIMP_MENU_SHELL_GET_INTERFACE (shell)->append (shell, model); - - if (update_signal != NULL) - { - if (priv->update_signal != NULL) - { - g_free (priv->update_signal); - g_signal_handlers_disconnect_by_func (priv->manager, - G_CALLBACK (gimp_menu_shell_ui_added), - shell); - g_signal_handlers_disconnect_by_func (priv->manager, - G_CALLBACK (gimp_menu_shell_ui_removed), - shell); - } - - priv->update_signal = g_strdup (update_signal); - g_signal_connect_object (priv->manager, update_signal, - G_CALLBACK (gimp_menu_shell_ui_added), - shell, 0); - g_signal_connect_object (priv->manager, "ui-removed", - G_CALLBACK (gimp_menu_shell_ui_removed), - shell, 0); - gimp_ui_manager_foreach_ui (priv->manager, - (GimpUIMenuCallback) gimp_menu_shell_ui_added, - shell); - } + gimp_menu_shell_append (shell, model); } /* Protected functions. */ +void +gimp_menu_shell_append (GimpMenuShell *shell, + GimpMenuModel *model) +{ + GimpMenuShellPrivate *priv = GET_PRIVATE (shell); + + g_free (priv->path_prefix); + priv->path_prefix = g_strdup (gimp_menu_model_get_path (GIMP_MENU_MODEL (model))); + + if (priv->model) + g_signal_handlers_disconnect_by_func (priv->model, + G_CALLBACK (gimp_menu_shell_model_changed), + shell); + + if (priv->model != model) + { + g_clear_object (&priv->model); + priv->model = g_object_ref (model); + } + + if (model) + { + GIMP_MENU_SHELL_GET_INTERFACE (shell)->append (shell, model); + + g_signal_connect_object (priv->model, "items-changed", + G_CALLBACK (gimp_menu_shell_model_changed), + shell, 0); + } +} + void gimp_menu_shell_init (GimpMenuShell *shell) { @@ -152,8 +150,8 @@ gimp_menu_shell_init (GimpMenuShell *shell) priv = GET_PRIVATE (shell); - priv->update_signal = NULL; priv->path_prefix = NULL; + priv->model = NULL; priv->path_regex = g_regex_new ("/+", 0, 0, NULL); } @@ -169,6 +167,7 @@ void gimp_menu_shell_install_properties (GObjectClass *klass) { g_object_class_override_property (klass, GIMP_MENU_SHELL_PROP_MANAGER, "manager"); + g_object_class_override_property (klass, GIMP_MENU_SHELL_PROP_MODEL, "model"); } void @@ -186,6 +185,9 @@ gimp_menu_shell_get_property (GObject *object, case GIMP_MENU_SHELL_PROP_MANAGER: g_value_set_object (value, priv->manager); break; + case GIMP_MENU_SHELL_PROP_MODEL: + g_value_set_object (value, priv->model); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -199,15 +201,18 @@ gimp_menu_shell_set_property (GObject *object, const GValue *value, GParamSpec *pspec) { - GimpMenuShellPrivate *priv; + GimpMenuShellPrivate *priv = GET_PRIVATE (object); + GimpMenuShell *shell = GIMP_MENU_SHELL (object); - priv = GET_PRIVATE (object); switch (property_id) { case GIMP_MENU_SHELL_PROP_MANAGER: g_set_weak_pointer (&priv->manager, g_value_get_object (value)); break; + case GIMP_MENU_SHELL_PROP_MODEL: + gimp_menu_shell_append (shell, g_value_get_object (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -265,84 +270,64 @@ gimp_menu_shell_get_private (GimpMenuShell *menu_shell) static void gimp_menu_shell_private_finalize (GimpMenuShellPrivate *priv) { - g_free (priv->update_signal); - g_clear_pointer (&priv->path_prefix, g_strfreev); + g_free (priv->path_prefix); g_clear_pointer (&priv->path_regex, g_regex_unref); + g_clear_object (&priv->model); g_slice_free (GimpMenuShellPrivate, priv); } static void -gimp_menu_shell_ui_added (GimpUIManager *manager, - const gchar *path, - const gchar *action_name, - const gchar *placeholder_key, - gboolean top, - GimpMenuShell *shell) +gimp_menu_shell_model_changed (GMenuModel *model, + gint position, + gint removed, + gint added, + GimpMenuShell *shell) { - gchar **paths = NULL; - gint paths_idx = 0; + GimpMenuShellPrivate *priv = GET_PRIVATE (shell); - if (GIMP_MENU_SHELL_GET_INTERFACE (shell)->add_ui && - gimp_menu_shell_is_subpath (shell, path, &paths, &paths_idx)) - GIMP_MENU_SHELL_GET_INTERFACE (shell)->add_ui (shell, - (const gchar **) (paths + paths_idx), - action_name, - placeholder_key, top); - - g_strfreev (paths); -} - -static void -gimp_menu_shell_ui_removed (GimpUIManager *manager, - const gchar *path, - const gchar *action_name, - GimpMenuShell *shell) -{ - gchar **paths = NULL; - gint paths_idx = 0; - - if (GIMP_MENU_SHELL_GET_INTERFACE (shell)->remove_ui && - gimp_menu_shell_is_subpath (shell, path, &paths, &paths_idx)) - GIMP_MENU_SHELL_GET_INTERFACE (shell)->remove_ui (shell, - (const gchar **) paths + paths_idx, - action_name); - - g_strfreev (paths); + /* Lazy approach to handle model changes. A better implementation would only + * remove or delete the changed items using: + * GIMP_MENU_SHELL_GET_INTERFACE (shell)->add_ui() + * GIMP_MENU_SHELL_GET_INTERFACE (shell)->remove_ui + * TODO + */ + gtk_container_foreach (GTK_CONTAINER (shell), + (GtkCallback) gtk_widget_destroy, + NULL); + GIMP_MENU_SHELL_GET_INTERFACE (shell)->append (shell, priv->model); } static void gimp_menu_shell_append_model_drop_top (GimpMenuShell *shell, - GMenuModel *model) + GimpMenuModel *model) { - GMenuModel *submenu = NULL; + GimpMenuShellPrivate *priv = GET_PRIVATE (shell); + GMenuModel *submenu = NULL; g_return_if_fail (GTK_IS_CONTAINER (shell)); - if (g_menu_model_get_n_items (model) == 1) + if (g_menu_model_get_n_items (G_MENU_MODEL (model)) == 1) { - GimpMenuShellPrivate *priv = GET_PRIVATE (shell); - gchar *label = NULL; + gchar *label = NULL; - submenu = g_menu_model_get_item_link (model, 0, G_MENU_LINK_SUBMENU); + submenu = g_menu_model_get_item_link (G_MENU_MODEL (model), 0, G_MENU_LINK_SUBMENU); if (submenu) { - GStrvBuilder *paths_builder = g_strv_builder_new (); + gchar *path_prefix; g_menu_model_get_item_attribute (submenu, 0, G_MENU_ATTRIBUTE_LABEL, "s", &label); - g_strv_builder_addv (paths_builder, (const char **) priv->path_prefix); - g_strv_builder_add (paths_builder, label); - g_strfreev (priv->path_prefix); - priv->path_prefix = g_strv_builder_end (paths_builder); - g_strv_builder_unref (paths_builder); + path_prefix = g_strdup_printf ("%s/%s", priv->path_prefix, label); + g_free (priv->path_prefix); + priv->path_prefix = path_prefix; } g_free (label); } - GIMP_MENU_SHELL_GET_INTERFACE (shell)->append (shell, submenu != NULL ? submenu : model); + gimp_menu_shell_append (shell, submenu != NULL ? submenu : model); g_clear_object (&submenu); } @@ -390,7 +375,7 @@ gimp_menu_shell_is_subpath (GimpMenuShell *shell, gint *index) { GimpMenuShellPrivate *priv; - gboolean is_subpath = TRUE; + gboolean is_subpath = TRUE; *index = 0; *paths = gimp_menu_shell_break_path (shell, path); @@ -398,18 +383,23 @@ gimp_menu_shell_is_subpath (GimpMenuShell *shell, if (priv->path_prefix) { - *index = g_strv_length (priv->path_prefix); + gchar **path_prefix; + + path_prefix = gimp_menu_shell_break_path (shell, priv->path_prefix); + *index = g_strv_length (path_prefix); is_subpath = FALSE; if (*index <= g_strv_length (*paths)) { gint i; for (i = 0; i < *index; i ++) - if (g_strcmp0 (priv->path_prefix[i], (*paths)[i]) != 0) + if (g_strcmp0 (path_prefix[i], (*paths)[i]) != 0) break; is_subpath = (i == *index); } + + g_strfreev (path_prefix); } return is_subpath; diff --git a/app/widgets/gimpmenushell.h b/app/widgets/gimpmenushell.h index 8a19170044..0267e83dce 100644 --- a/app/widgets/gimpmenushell.h +++ b/app/widgets/gimpmenushell.h @@ -32,8 +32,9 @@ enum { GIMP_MENU_SHELL_PROP_0, GIMP_MENU_SHELL_PROP_MANAGER, + GIMP_MENU_SHELL_PROP_MODEL, - GIMP_MENU_SHELL_PROP_LAST = GIMP_MENU_SHELL_PROP_MANAGER, + GIMP_MENU_SHELL_PROP_LAST = GIMP_MENU_SHELL_PROP_MODEL, }; typedef struct _GimpMenuShellInterface GimpMenuShellInterface; @@ -44,8 +45,8 @@ struct _GimpMenuShellInterface /* Virtual functions. */ - void (* append) (GimpMenuShell *shell, - GMenuModel *model); + void (* append) (GimpMenuShell *shell, + GimpMenuModel *model); void (* add_ui) (GimpMenuShell *shell, const gchar **paths, const gchar *action_name, @@ -60,13 +61,15 @@ struct _GimpMenuShellInterface GType gimp_menu_shell_get_type (void) G_GNUC_CONST; void gimp_menu_shell_fill (GimpMenuShell *shell, - GMenuModel *model, - const gchar *update_signal, + GimpMenuModel *model, gboolean drop_top_submenu); /* Protected functions. */ +void gimp_menu_shell_append (GimpMenuShell *shell, + GimpMenuModel *model); + void gimp_menu_shell_init (GimpMenuShell *shell); void gimp_menu_shell_install_properties (GObjectClass *klass); void gimp_menu_shell_get_property (GObject *object, diff --git a/app/widgets/gimptexteditor.c b/app/widgets/gimptexteditor.c index dbcb2898ae..c27959eced 100644 --- a/app/widgets/gimptexteditor.c +++ b/app/widgets/gimptexteditor.c @@ -139,7 +139,7 @@ gimp_text_editor_new (const gchar *title, GimpTextEditor *editor; GtkWidget *content_area; GtkWidget *toolbar; - GMenuModel *toolbar_model; + GimpMenuModel *toolbar_model; GtkWidget *style_editor; GtkWidget *scrolled_window; gboolean use_header_bar; diff --git a/app/widgets/gimptoolbar.c b/app/widgets/gimptoolbar.c index f210ab4826..f45bb60652 100644 --- a/app/widgets/gimptoolbar.c +++ b/app/widgets/gimptoolbar.c @@ -35,6 +35,7 @@ #include "gimpradioaction.h" #include "gimptoggleaction.h" #include "gimptoolbar.h" +#include "gimpmenumodel.h" #include "gimpmenushell.h" #include "gimpuimanager.h" @@ -53,36 +54,13 @@ * actions as buttons). */ -enum -{ - PROP_0 = GIMP_MENU_SHELL_PROP_LAST, - PROP_MODEL -}; - - -struct _GimpToolbarPrivate -{ - GMenuModel *model; -}; - /* local function prototypes */ static void gimp_toolbar_iface_init (GimpMenuShellInterface *iface); -static void gimp_toolbar_constructed (GObject *object); -static void gimp_toolbar_dispose (GObject *object); -static void gimp_toolbar_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec); -static void gimp_toolbar_get_property (GObject *object, - guint property_id, - GValue *value, - GParamSpec *pspec); - static void gimp_toolbar_append (GimpMenuShell *shell, - GMenuModel *model); + GimpMenuModel *model); static void gimp_toolbar_add_ui (GimpMenuShell *shell, const gchar **paths, const gchar *action_name, @@ -108,7 +86,6 @@ static void gimp_toolbar_action_notify_visible (GimpAction *act G_DEFINE_TYPE_WITH_CODE (GimpToolbar, gimp_toolbar, GTK_TYPE_TOOLBAR, - G_ADD_PRIVATE (GimpToolbar) G_IMPLEMENT_INTERFACE (GIMP_TYPE_MENU_SHELL, gimp_toolbar_iface_init)) #define parent_class gimp_toolbar_parent_class @@ -121,19 +98,10 @@ gimp_toolbar_class_init (GimpToolbarClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); - object_class->constructed = gimp_toolbar_constructed; - object_class->dispose = gimp_toolbar_dispose; - object_class->get_property = gimp_toolbar_get_property; - object_class->set_property = gimp_toolbar_set_property; + object_class->get_property = gimp_menu_shell_get_property; + object_class->set_property = gimp_menu_shell_set_property; gimp_menu_shell_install_properties (object_class); - - g_object_class_install_property (object_class, PROP_MODEL, - g_param_spec_object ("model", - NULL, NULL, - G_TYPE_MENU_MODEL, - GIMP_PARAM_READWRITE | - G_PARAM_CONSTRUCT)); } static void @@ -146,78 +114,19 @@ gimp_toolbar_iface_init (GimpMenuShellInterface *iface) static void gimp_toolbar_init (GimpToolbar *bar) { - bar->priv = gimp_toolbar_get_instance_private (bar); - gimp_menu_shell_init (GIMP_MENU_SHELL (bar)); } -static void -gimp_toolbar_constructed (GObject *object) -{ - G_OBJECT_CLASS (parent_class)->constructed (object); -} - -static void -gimp_toolbar_dispose (GObject *object) -{ - GimpToolbar *bar = GIMP_TOOLBAR (object); - - g_clear_object (&bar->priv->model); - - G_OBJECT_CLASS (parent_class)->dispose (object); -} - -static void -gimp_toolbar_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec) -{ - GimpToolbar *bar = GIMP_TOOLBAR (object); - - switch (property_id) - { - case PROP_MODEL: - bar->priv->model = g_value_dup_object (value); - gimp_menu_shell_fill (GIMP_MENU_SHELL (bar), bar->priv->model, "ui-added", FALSE); - break; - - default: - gimp_menu_shell_set_property (object, property_id, value, pspec); - break; - } -} - -static void -gimp_toolbar_get_property (GObject *object, - guint property_id, - GValue *value, - GParamSpec *pspec) -{ - GimpToolbar *bar = GIMP_TOOLBAR (object); - - switch (property_id) - { - case PROP_MODEL: - g_value_set_object (value, bar->priv->model); - break; - - default: - gimp_menu_shell_get_property (object, property_id, value, pspec); - break; - } -} - static void gimp_toolbar_append (GimpMenuShell *shell, - GMenuModel *model) + GimpMenuModel *model) { GimpToolbar *toolbar = GIMP_TOOLBAR (shell); gint n_items; g_return_if_fail (GTK_IS_CONTAINER (shell)); - n_items = g_menu_model_get_n_items (model); + n_items = g_menu_model_get_n_items (G_MENU_MODEL (model)); for (gint i = 0; i < n_items; i++) { GMenuModel *subsection; @@ -225,10 +134,10 @@ gimp_toolbar_append (GimpMenuShell *shell, gchar *label = NULL; gchar *action_name = NULL; - subsection = g_menu_model_get_item_link (model, i, G_MENU_LINK_SECTION); - submenu = g_menu_model_get_item_link (model, i, G_MENU_LINK_SUBMENU); - g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_LABEL, "s", &label); - g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_ACTION, "s", &action_name); + subsection = g_menu_model_get_item_link (G_MENU_MODEL (model), i, G_MENU_LINK_SECTION); + submenu = g_menu_model_get_item_link (G_MENU_MODEL (model), i, G_MENU_LINK_SUBMENU); + g_menu_model_get_item_attribute (G_MENU_MODEL (model), i, G_MENU_ATTRIBUTE_LABEL, "s", &label); + g_menu_model_get_item_attribute (G_MENU_MODEL (model), i, G_MENU_ATTRIBUTE_ACTION, "s", &action_name); if (subsection != NULL) { @@ -238,7 +147,7 @@ gimp_toolbar_append (GimpMenuShell *shell, gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1); gtk_widget_show (GTK_WIDGET (item)); - gimp_toolbar_append (shell, subsection); + gimp_toolbar_append (shell, GIMP_MENU_MODEL (subsection)); item = gtk_separator_tool_item_new (); gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1); @@ -290,7 +199,7 @@ gimp_toolbar_add_ui (GimpMenuShell *shell, /* Public functions */ GtkWidget * -gimp_toolbar_new (GMenuModel *model, +gimp_toolbar_new (GimpMenuModel *model, GimpUIManager *manager) { g_return_val_if_fail (GIMP_IS_UI_MANAGER (manager) && diff --git a/app/widgets/gimptoolbar.h b/app/widgets/gimptoolbar.h index e44f75a225..64b1c51485 100644 --- a/app/widgets/gimptoolbar.h +++ b/app/widgets/gimptoolbar.h @@ -48,7 +48,7 @@ struct _GimpToolbarClass GType gimp_toolbar_get_type (void) G_GNUC_CONST; -GtkWidget * gimp_toolbar_new (GMenuModel *model, +GtkWidget * gimp_toolbar_new (GimpMenuModel *model, GimpUIManager *manager); diff --git a/app/widgets/gimpuimanager.c b/app/widgets/gimpuimanager.c index 3c9fa3f9bf..769ddd73c2 100644 --- a/app/widgets/gimpuimanager.c +++ b/app/widgets/gimpuimanager.c @@ -38,6 +38,7 @@ #include "gimphelp.h" #include "gimphelp-ids.h" #include "gimpmenu.h" +#include "gimpmenumodel.h" #include "gimpmenushell.h" #include "gimptoggleaction.h" #include "gimpuimanager.h" @@ -108,8 +109,6 @@ static GtkWidget *find_widget_under_pointer (GdkWindow *window, gint *x, gint *y); -static void gimp_ui_manager_fill_model (GimpUIManager *manager, - GMenuModel *model); static void gimp_ui_manager_menu_item_free (GimpUIManagerMenuItem *item); static void gimp_ui_manager_popup_hidden (GtkMenuShell *popup, @@ -478,16 +477,16 @@ gimp_ui_manager_get_widget (GimpUIManager *manager, return gtk_ui_manager_get_widget ((GtkUIManager *) manager, path); } -GMenuModel * +GimpMenuModel * gimp_ui_manager_get_model (GimpUIManager *manager, const gchar *path) { GimpUIManagerUIEntry *entry; - GMenuModel *model; + GimpMenuModel *model; + GMenuModel *gmodel; + GimpMenuModel *submodel; gchar *root; gchar *submenus; - GStrvBuilder *paths_builder = g_strv_builder_new (); - GStrv paths; root = g_strdup (path); submenus = strstr (root + 1, "/"); @@ -545,66 +544,21 @@ gimp_ui_manager_get_model (GimpUIManager *manager, /* The model is owned by the builder which I have to keep around. */ entry->builder = gtk_builder_new_from_file (filename); - gimp_ui_manager_fill_model (manager, - G_MENU_MODEL (gtk_builder_get_object (entry->builder, root))); - g_free (filename); g_free (full_basename); } - model = G_MENU_MODEL (gtk_builder_get_object (entry->builder, root)); + gmodel = G_MENU_MODEL (gtk_builder_get_object (entry->builder, root)); - g_return_val_if_fail (G_IS_MENU (model), NULL); + g_return_val_if_fail (G_IS_MENU (gmodel), NULL); - g_object_ref (model); - while (submenus != NULL) - { - const gchar *submenu = submenus; - gint n_items; - gint i; + model = gimp_menu_model_new (manager, gmodel); - submenus = strstr (submenu + 1, "/"); - if (submenus != NULL) - { - *submenus = '\0'; - if (*(++submenus) == '\0') - submenus = NULL; - } + submodel = gimp_menu_model_get_submodel (model, submenus); - n_items = g_menu_model_get_n_items (model); - for (i = 0; i < n_items; i++) - { - GMenuModel *submodel; - gchar *label = NULL; - gchar *canon_label = NULL; - - submodel = g_menu_model_get_item_link (model, i, G_MENU_LINK_SUBMENU); - g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_LABEL, "s", &label); - if (label) - canon_label = gimp_menu_shell_make_canonical_path (label); - - if (submodel && g_strcmp0 (canon_label, submenu) == 0) - { - g_strv_builder_add (paths_builder, canon_label); - g_object_unref (model); - model = submodel; - g_free (label); - break; - } - g_clear_object (&submodel); - g_free (label); - g_free (canon_label); - } - g_return_val_if_fail (i < n_items, NULL); - } - - paths = g_strv_builder_end (paths_builder); - g_strv_builder_unref (paths_builder); - g_object_set_data_full (G_OBJECT (model), "gimp-ui-manager-model-paths", - paths, (GDestroyNotify) g_strfreev); g_free (root); - return model; + return submodel; } void @@ -801,19 +755,19 @@ gimp_ui_manager_ui_popup_at_widget (GimpUIManager *manager, GDestroyNotify popdown_func, gpointer popdown_data) { - GMenuModel *model; - GtkWidget *menu; + GimpMenuModel *model; + GtkWidget *menu; g_return_if_fail (GIMP_IS_UI_MANAGER (manager)); g_return_if_fail (ui_path != NULL); g_return_if_fail (GTK_IS_WIDGET (widget)); - menu = gimp_menu_new (manager); + menu = gimp_menu_new (manager); gtk_menu_attach_to_widget (GTK_MENU (menu), widget, NULL); model = gimp_ui_manager_get_model (manager, ui_path); g_return_if_fail (model != NULL); - gimp_menu_shell_fill (GIMP_MENU_SHELL (menu), model, "ui-added", TRUE); + gimp_menu_shell_fill (GIMP_MENU_SHELL (menu), model, TRUE); g_object_unref (model); if (! menu) @@ -821,13 +775,13 @@ gimp_ui_manager_ui_popup_at_widget (GimpUIManager *manager, if (child_ui_manager != NULL && child_ui_path != NULL) { - GMenuModel *child_model; - GtkWidget *child_menu; + GimpMenuModel *child_model; + GtkWidget *child_menu; /* TODO GMenu: the "icon" attribute set in the .ui file should be visible. */ child_model = gimp_ui_manager_get_model (child_ui_manager, child_ui_path); child_menu = gimp_menu_new (child_ui_manager); - gimp_menu_shell_fill (GIMP_MENU_SHELL (child_menu), child_model, "ui-added", FALSE); + gimp_menu_shell_fill (GIMP_MENU_SHELL (child_menu), child_model, FALSE); g_object_unref (child_model); gimp_menu_merge (GIMP_MENU (menu), GIMP_MENU (child_menu), TRUE); @@ -859,8 +813,8 @@ gimp_ui_manager_ui_popup_at_pointer (GimpUIManager *manager, GDestroyNotify popdown_func, gpointer popdown_data) { - GMenuModel *model; - GtkWidget *menu; + GimpMenuModel *model; + GtkWidget *menu; g_return_if_fail (GIMP_IS_UI_MANAGER (manager)); g_return_if_fail (ui_path != NULL); @@ -868,7 +822,7 @@ gimp_ui_manager_ui_popup_at_pointer (GimpUIManager *manager, model = gimp_ui_manager_get_model (manager, ui_path); menu = gimp_menu_new (manager); gtk_menu_attach_to_widget (GTK_MENU (menu), attached_widget, NULL); - gimp_menu_shell_fill (GIMP_MENU_SHELL (menu), model, "ui-added", TRUE); + gimp_menu_shell_fill (GIMP_MENU_SHELL (menu), model, TRUE); g_object_unref (model); if (! menu) @@ -901,8 +855,8 @@ gimp_ui_manager_ui_popup_at_rect (GimpUIManager *manager, GDestroyNotify popdown_func, gpointer popdown_data) { - GMenuModel *model; - GtkWidget *menu; + GimpMenuModel *model; + GtkWidget *menu; g_return_if_fail (GIMP_IS_UI_MANAGER (manager)); g_return_if_fail (ui_path != NULL); @@ -910,7 +864,7 @@ gimp_ui_manager_ui_popup_at_rect (GimpUIManager *manager, model = gimp_ui_manager_get_model (manager, ui_path); menu = gimp_menu_new (manager); gtk_menu_attach_to_widget (GTK_MENU (menu), attached_widget, NULL); - gimp_menu_shell_fill (GIMP_MENU_SHELL (menu), model, "ui-added", TRUE); + gimp_menu_shell_fill (GIMP_MENU_SHELL (menu), model, TRUE); g_object_unref (model); if (! menu) @@ -1401,55 +1355,6 @@ find_widget_under_pointer (GdkWindow *window, return event_widget; } -static void -gimp_ui_manager_fill_model (GimpUIManager *manager, - GMenuModel *model) -{ - gint n_items; - - g_return_if_fail (G_IS_MENU_MODEL (model)); - - n_items = g_menu_model_get_n_items (model); - for (int i = 0; i < n_items; i++) - { - GMenuModel *subsection; - GMenuModel *submenu; - gchar *label = NULL; - gchar *action_name = NULL; - - subsection = g_menu_model_get_item_link (model, i, G_MENU_LINK_SECTION); - submenu = g_menu_model_get_item_link (model, i, G_MENU_LINK_SUBMENU); - g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_LABEL, "s", &label); - g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_ACTION, "s", &action_name); - - if (subsection != NULL) - { - gimp_ui_manager_fill_model (manager, subsection); - } - else if (submenu != NULL) - { - gimp_ui_manager_fill_model (manager, submenu); - } - else if (action_name != NULL) - { - /* Update the label, unless it's a placeholder (no action name). */ - GApplication *app; - GAction *action; - - app = manager->gimp->app; - action = g_action_map_lookup_action (G_ACTION_MAP (app), action_name + 4); - g_menu_remove (G_MENU (model), i); - g_menu_insert (G_MENU (model), i, - gimp_action_get_label (GIMP_ACTION (action)), - action_name); - } - g_free (label); - g_free (action_name); - g_clear_object (&subsection); - g_clear_object (&submenu); - } -} - static void gimp_ui_manager_menu_item_free (GimpUIManagerMenuItem *item) { diff --git a/app/widgets/gimpuimanager.h b/app/widgets/gimpuimanager.h index 8d7463dc04..ebeb4cf839 100644 --- a/app/widgets/gimpuimanager.h +++ b/app/widgets/gimpuimanager.h @@ -110,7 +110,7 @@ GtkAccelGroup * gimp_ui_manager_get_accel_group (GimpUIManager *manager); GtkWidget * gimp_ui_manager_get_widget (GimpUIManager *manager, const gchar *path); -GMenuModel * gimp_ui_manager_get_model (GimpUIManager *manager, +GimpMenuModel * gimp_ui_manager_get_model (GimpUIManager *manager, const gchar *path); void gimp_ui_manager_remove_ui (GimpUIManager *manager, diff --git a/app/widgets/meson.build b/app/widgets/meson.build index df27613023..baeecd4c91 100644 --- a/app/widgets/meson.build +++ b/app/widgets/meson.build @@ -142,6 +142,7 @@ libappwidgets_sources = [ 'gimpmenushell.c', 'gimpmenudock.c', 'gimpmenufactory.c', + 'gimpmenumodel.c', 'gimpmessagebox.c', 'gimpmessagedialog.c', 'gimpmeter.c', diff --git a/app/widgets/widgets-types.h b/app/widgets/widgets-types.h index 8ba3fff160..d69119ff7a 100644 --- a/app/widgets/widgets-types.h +++ b/app/widgets/widgets-types.h @@ -212,6 +212,7 @@ typedef struct _GimpLayerModeComboBox GimpLayerModeComboBox; typedef struct _GimpMessageBox GimpMessageBox; typedef struct _GimpMenu GimpMenu; typedef struct _GimpMenuBar GimpMenuBar; +typedef struct _GimpMenuModel GimpMenuModel; typedef struct _GimpMenuShell GimpMenuShell; typedef struct _GimpMeter GimpMeter; typedef struct _GimpModifiersEditor GimpModifiersEditor;