diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 03f3e3113a..481fda2155 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -500,6 +500,7 @@ gtk_private_h_sources = \ gtkmenuprivate.h \ gtkmenuitemprivate.h \ gtkmenushellprivate.h \ + gtkmenutracker.h \ gtkmnemonichash.h \ gtkmodelmenuitem.h \ gtkmodifierstyle.h \ @@ -762,11 +763,11 @@ gtk_base_c_sources = \ gtkmenubutton.c \ gtkmenuitem.c \ gtkmenushell.c \ + gtkmenutracker.c \ gtkmenutoolbutton.c \ gtkmessagedialog.c \ gtkmisc.c \ gtkmnemonichash.c \ - gtkmodelmenu.c \ gtkmodelmenuitem.c \ gtkmodifierstyle.c \ gtkmodules.c \ diff --git a/gtk/gtkmenushell.c b/gtk/gtkmenushell.c index b127ae6e1a..ad5d2d274a 100644 --- a/gtk/gtkmenushell.c +++ b/gtk/gtkmenushell.c @@ -51,6 +51,7 @@ #include "gtkmain.h" #include "gtkintl.h" #include "gtktypebuiltins.h" +#include "gtkmodelmenuitem.h" #include "deprecated/gtktearoffmenuitem.h" @@ -529,7 +530,10 @@ gtk_menu_shell_finalize (GObject *object) static void gtk_menu_shell_dispose (GObject *object) { - gtk_menu_shell_deactivate (GTK_MENU_SHELL (object)); + GtkMenuShell *menu_shell = GTK_MENU_SHELL (object); + + g_clear_pointer (&menu_shell->priv->tracker, gtk_menu_tracker_free); + gtk_menu_shell_deactivate (menu_shell); G_OBJECT_CLASS (gtk_menu_shell_parent_class)->dispose (object); } @@ -2022,3 +2026,104 @@ gtk_menu_shell_get_parent_shell (GtkMenuShell *menu_shell) return menu_shell->priv->parent_menu_shell; } + +static void +gtk_menu_shell_tracker_insert_func (gint position, + GMenuModel *model, + gint item_index, + const gchar *action_namespace, + gboolean is_separator, + gpointer user_data) +{ + GtkMenuShell *menu_shell = user_data; + GtkWidget *item; + + if (is_separator) + { + gchar *label; + + item = gtk_separator_menu_item_new (); + + if (g_menu_model_get_item_attribute (model, item_index, G_MENU_ATTRIBUTE_LABEL, "s", &label)) + { + gtk_menu_item_set_label (GTK_MENU_ITEM (item), label); + g_free (label); + } + } + else + item = gtk_model_menu_item_new (model, item_index, action_namespace); + + gtk_menu_shell_insert (menu_shell, item, position); + gtk_widget_show (item); +} + +static void +gtk_menu_shell_tracker_remove_func (gint position, + gpointer user_data) +{ + GtkMenuShell *menu_shell = user_data; + GtkWidget *child; + + child = g_list_nth_data (menu_shell->priv->children, position); + /* We use destroy here because in the case of an item with a submenu, + * the attached-to from the submenu holds a ref on the item and a + * simple gtk_container_remove() isn't good enough to break that. + */ + gtk_widget_destroy (child); +} + +/** + * gtk_menu_shell_bind_model: + * @menu_shell: a #GtkMenuShell + * @model: (allow-none): the #GMenuModel to bind to or %NULL to remove + * binding + * @action_namespace: (allow-none): the namespace for actions in @model + * @with_separators: %TRUE if toplevel items in @shell should have + * separators between them + * + * Establishes a binding between a #GtkMenuShell and a #GMenuModel. + * + * The contents of @shell are removed and then refilled with menu items + * according to @model. When @model changes, @shell is updated. + * Calling this function twice on @shell with different @model will + * cause the first binding to be replaced with a binding to the new + * model. If @model is %NULL then any previous binding is undone and + * all children are removed. + * + * @with_separators determines if toplevel items (eg: sections) have + * separators inserted between them. This is typically desired for + * menus but doesn't make sense for menubars. + * + * If @action_namespace is non-%NULL then the effect is as if all + * actions mentioned in the @model have their names prefixed with the + * namespace, plus a dot. For example, if the action "quit" is + * mentioned and @action_namespace is "app" then the effective action + * name is "app.quit". + * + * For most cases you are probably better off using + * gtk_menu_new_from_model() or gtk_menu_bar_new_from_model() or just + * directly passing the #GMenuModel to gtk_application_set_app_menu() or + * gtk_application_set_menu_bar(). + * + * Since: 3.6 + */ +void +gtk_menu_shell_bind_model (GtkMenuShell *menu_shell, + GMenuModel *model, + const gchar *action_namespace, + gboolean with_separators) +{ + g_return_if_fail (GTK_IS_MENU_SHELL (menu_shell)); + g_return_if_fail (model == NULL || G_IS_MENU_MODEL (model)); + + g_clear_pointer (&menu_shell->priv->tracker, gtk_menu_tracker_free); + + while (menu_shell->priv->children) + gtk_container_remove (GTK_CONTAINER (menu_shell), menu_shell->priv->children->data); + + if (model) + menu_shell->priv->tracker = gtk_menu_tracker_new (model, with_separators, action_namespace, + gtk_menu_shell_tracker_insert_func, + gtk_menu_shell_tracker_remove_func, + menu_shell); +} diff --git a/gtk/gtkmenushellprivate.h b/gtk/gtkmenushellprivate.h index 9ef04f3a21..40a858bd1c 100644 --- a/gtk/gtkmenushellprivate.h +++ b/gtk/gtkmenushellprivate.h @@ -22,7 +22,7 @@ #include #include #include - +#include G_BEGIN_DECLS @@ -38,6 +38,7 @@ struct _GtkMenuShellPrivate GList *children; GtkWidget *active_menu_item; GtkWidget *parent_menu_shell; + GtkMenuTracker *tracker; // if bound to a GMenuModel guint button; guint32 activate_time; diff --git a/gtk/gtkmenutracker.c b/gtk/gtkmenutracker.c new file mode 100644 index 0000000000..f49457f115 --- /dev/null +++ b/gtk/gtkmenutracker.c @@ -0,0 +1,434 @@ +/* + * Copyright © 2013 Canonical Limited + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Ryan Lortie + */ + +#include "config.h" + +#include "gtkmenutracker.h" + +typedef struct _GtkMenuTrackerSection GtkMenuTrackerSection; + +struct _GtkMenuTracker +{ + GtkMenuTrackerInsertFunc insert_func; + GtkMenuTrackerRemoveFunc remove_func; + gpointer user_data; + + GtkMenuTrackerSection *toplevel; +}; + +struct _GtkMenuTrackerSection +{ + GMenuModel *model; + GSList *items; + gchar *action_namespace; + + guint with_separators : 1; + guint has_separator : 1; + + gulong handler; +}; + +static GtkMenuTrackerSection * gtk_menu_tracker_section_new (GtkMenuTracker *tracker, + GMenuModel *model, + gboolean with_separators, + gint offset, + const gchar *action_namespace); +static void gtk_menu_tracker_section_free (GtkMenuTrackerSection *section); + +static GtkMenuTrackerSection * +gtk_menu_tracker_section_find_model (GtkMenuTrackerSection *section, + GMenuModel *model, + gint *offset) +{ + GSList *item; + + if (section->has_separator) + (*offset)++; + + if (section->model == model) + return section; + + for (item = section->items; item; item = item->next) + { + GtkMenuTrackerSection *subsection = item->data; + + if (subsection) + { + GtkMenuTrackerSection *found_section; + + found_section = gtk_menu_tracker_section_find_model (subsection, model, offset); + + if (found_section) + return found_section; + } + else + (*offset)++; + } + + return FALSE; +} + +/* this is responsible for syncing the showing of a separator for a + * single subsection (and its children). + * + * we only ever show separators if we have _actual_ children (ie: we do + * not show a separator if the section contains only empty child + * sections). it's difficult to determine this on-the-fly, so we have + * this separate function to come back later and figure it out. + * + * 'section' is that section. + * + * 'tracker' is passed in so that we can emit callbacks when we decide + * to add/remove separators. + * + * 'offset' is passed in so we know which position to emit in our + * callbacks. ie: if we add a separator right at the top of this + * section then we would emit it with this offset. deeper inside, we + * adjust accordingly. + * + * could_have_separator is true in two situations: + * + * - our parent section had with_separators defined and we are not the + * first section (ie: we should add a separator if we have content in + * order to divide us from the items above) + * + * - if we had a 'label' attribute set for this section + * + * parent_model and parent_index are passed in so that we can give them + * to the insertion callback so that it can see the label (and anything + * else that happens to be defined on the section). + * + * we iterate each item in ourselves. for subsections, we recursively + * run ourselves to sync separators. after we are done, we notice if we + * have any items in us or if we are completely empty and sync if our + * separator is shown or not. + */ +static gint +gtk_menu_tracker_section_sync_separators (GtkMenuTrackerSection *section, + GtkMenuTracker *tracker, + gint offset, + gboolean could_have_separator, + GMenuModel *parent_model, + gint parent_index) +{ + gboolean should_have_separator; + gint n_items = 0; + GSList *item; + gint i = 0; + + for (item = section->items; item; item = item->next) + { + GtkMenuTrackerSection *subsection = item->data; + + if (subsection) + { + gboolean could_have_separator; + + could_have_separator = (section->with_separators && i > 0) || + g_menu_model_get_item_attribute (section->model, i, "label", "s", NULL); + + n_items += gtk_menu_tracker_section_sync_separators (subsection, tracker, offset + n_items, + could_have_separator, section->model, i); + } + else + n_items++; + + i++; + } + + should_have_separator = could_have_separator && n_items != 0; + + if (should_have_separator > section->has_separator) + { + /* Add a separator */ + (* tracker->insert_func) (offset, parent_model, parent_index, NULL, TRUE, tracker->user_data); + section->has_separator = TRUE; + } + else if (should_have_separator < section->has_separator) + { + /* Remove a separator */ + (* tracker->remove_func) (offset, tracker->user_data); + section->has_separator = FALSE; + } + + n_items += section->has_separator; + + return n_items; +} + +static gint +gtk_menu_tracker_section_measure (GtkMenuTrackerSection *section) +{ + GSList *item; + gint n_items; + + if (section == NULL) + return 1; + + n_items = 0; + + if (section->has_separator) + n_items++; + + for (item = section->items; item; item = item->next) + n_items += gtk_menu_tracker_section_measure (item->data); + + return n_items; +} + +static void +gtk_menu_tracker_remove_items (GtkMenuTracker *tracker, + GSList **change_point, + gint offset, + gint n_items) +{ + gint i; + + for (i = 0; i < n_items; i++) + { + GtkMenuTrackerSection *subsection; + gint n; + + subsection = (*change_point)->data; + *change_point = g_slist_delete_link (*change_point, *change_point); + + n = gtk_menu_tracker_section_measure (subsection); + gtk_menu_tracker_section_free (subsection); + + while (n--) + (* tracker->remove_func) (offset, tracker->user_data); + } +} + +static void +gtk_menu_tracker_add_items (GtkMenuTracker *tracker, + GtkMenuTrackerSection *section, + GSList **change_point, + gint offset, + GMenuModel *model, + gint position, + gint n_items) +{ + while (n_items--) + { + GMenuModel *submenu; + + submenu = g_menu_model_get_item_link (model, position + n_items, G_MENU_LINK_SECTION); + g_assert (submenu != model); + if (submenu != NULL) + { + GtkMenuTrackerSection *subsection; + gchar *action_namespace = NULL; + + g_menu_model_get_item_attribute (model, position + n_items, + G_MENU_ATTRIBUTE_ACTION_NAMESPACE, "s", &action_namespace); + + if (section->action_namespace) + { + gchar *namespace; + + namespace = g_strjoin (".", section->action_namespace, action_namespace, NULL); + subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, offset, namespace); + g_free (namespace); + } + else + subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, offset, section->action_namespace); + + *change_point = g_slist_prepend (*change_point, subsection); + g_free (action_namespace); + g_object_unref (submenu); + } + else + { + (* tracker->insert_func) (offset, model, position + n_items, + section->action_namespace, FALSE, tracker->user_data); + *change_point = g_slist_prepend (*change_point, NULL); + } + } +} + +static void +gtk_menu_tracker_model_changed (GMenuModel *model, + gint position, + gint removed, + gint added, + gpointer user_data) +{ + GtkMenuTracker *tracker = user_data; + GtkMenuTrackerSection *section; + GSList **change_point; + gint offset = 0; + gint i; + + /* First find which section the changed model corresponds to, and the + * position of that section within the overall menu. + */ + section = gtk_menu_tracker_section_find_model (tracker->toplevel, model, &offset); + + /* Next, seek through that section to the change point. This gives us + * the correct GSList** to make the change to and also finds the final + * offset at which we will make the changes (by measuring the number + * of items within each item of the section before the change point). + */ + change_point = §ion->items; + for (i = 0; i < position; i++) + { + offset += gtk_menu_tracker_section_measure ((*change_point)->data); + change_point = &(*change_point)->next; + } + + /* We remove items in order and add items in reverse order. This + * means that the offset used for all inserts and removes caused by a + * single change will be the same. + * + * This also has a performance advantage: GtkMenuShell stores the + * menu items in a linked list. In the case where we are creating a + * menu for the first time, adding the items in reverse order means + * that we only ever insert at index zero, prepending the list. This + * means that we can populate in O(n) time instead of O(n^2) that we + * would do by appending. + */ + gtk_menu_tracker_remove_items (tracker, change_point, offset, removed); + gtk_menu_tracker_add_items (tracker, section, change_point, offset, model, position, added); + + /* The offsets for insertion/removal of separators will be all over + * the place, however... + */ + gtk_menu_tracker_section_sync_separators (tracker->toplevel, tracker, 0, FALSE, NULL, 0); +} + +static void +gtk_menu_tracker_section_free (GtkMenuTrackerSection *section) +{ + if (section == NULL) + return; + + g_signal_handler_disconnect (section->model, section->handler); + g_slist_free_full (section->items, (GDestroyNotify) gtk_menu_tracker_section_free); + g_free (section->action_namespace); + g_object_unref (section->model); + g_slice_free (GtkMenuTrackerSection, section); +} + +static GtkMenuTrackerSection * +gtk_menu_tracker_section_new (GtkMenuTracker *tracker, + GMenuModel *model, + gboolean with_separators, + gint offset, + const gchar *action_namespace) +{ + GtkMenuTrackerSection *section; + + section = g_slice_new0 (GtkMenuTrackerSection); + section->model = g_object_ref (model); + section->with_separators = with_separators; + section->action_namespace = g_strdup (action_namespace); + + gtk_menu_tracker_add_items (tracker, section, §ion->items, offset, model, 0, g_menu_model_get_n_items (model)); + section->handler = g_signal_connect (model, "items-changed", G_CALLBACK (gtk_menu_tracker_model_changed), tracker); + + return section; +} + +/*< private > + * gtk_menu_tracker_new: + * @model: the model to flatten + * @with_separators: if the toplevel should have separators (ie: TRUE + * for menus, FALSE for menubars) + * @action_namespace: the passed-in action namespace + * @insert_func: insert callback + * @remove_func: remove callback + * @user_data user data for callbacks + * + * Creates a GtkMenuTracker for @model, holding a ref on @model for as + * long as the tracker is alive. + * + * This flattens out the model, merging sections and inserting + * separators where appropriate. It monitors for changes and performs + * updates on the fly. It also handles action_namespace for subsections + * (but you will need to handle it yourself for submenus). + * + * When the tracker is first created, @insert_func will be called many + * times to populate the menu with the initial contents of @model + * (unless it is empty), before gtk_menu_tracker_new() returns. For + * this reason, the menu that is using the tracker ought to be empty + * when it creates the tracker. + * + * Future changes to @model will result in more calls to @insert_func + * and @remove_func. + * + * The position argument to both functions is the linear 0-based + * position in the menu at which the item in question should be inserted + * or removed. + * + * For @insert_func, @model and @item_index are used to get the + * information about the menu item to insert. @action_namespace is the + * action namespace that actions referred to from that item should place + * themselves in. Note that if the item is a submenu and the + * "action-namespace" attribute is defined on the item, it will _not_ be + * applied to the @action_namespace argument as it is meant for the + * items inside of the submenu, not the submenu item itself. + * + * @is_separator is set to %TRUE in case the item being added is a + * separator. @model and @item_index will still be meaningfully set in + * this case -- to the section menu item corresponding to the separator. + * This is useful if the section specifies a label, for example. If + * there is an "action-namespace" attribute on this menu item then it + * should be ignored by the consumer because #GtkMenuTracker has already + * handled it. + * + * When using #GtkMenuTracker there is no need to hold onto @model or + * monitor it for changes. The model will be unreffed when + * gtk_menu_tracker_free() is called. + */ +GtkMenuTracker * +gtk_menu_tracker_new (GMenuModel *model, + gboolean with_separators, + const gchar *action_namespace, + GtkMenuTrackerInsertFunc insert_func, + GtkMenuTrackerRemoveFunc remove_func, + gpointer user_data) +{ + GtkMenuTracker *tracker; + + tracker = g_slice_new (GtkMenuTracker); + tracker->insert_func = insert_func; + tracker->remove_func = remove_func; + tracker->user_data = user_data; + + tracker->toplevel = gtk_menu_tracker_section_new (tracker, model, with_separators, 0, action_namespace); + gtk_menu_tracker_section_sync_separators (tracker->toplevel, tracker, 0, FALSE, NULL, 0); + + return tracker; +} + +/*< private > + * gtk_menu_tracker_free: + * @tracker: a #GtkMenuTracker + * + * Frees the tracker, ... + */ +void +gtk_menu_tracker_free (GtkMenuTracker *tracker) +{ + gtk_menu_tracker_section_free (tracker->toplevel); + g_slice_free (GtkMenuTracker, tracker); +} diff --git a/gtk/gtkmenutracker.h b/gtk/gtkmenutracker.h new file mode 100644 index 0000000000..51810a104c --- /dev/null +++ b/gtk/gtkmenutracker.h @@ -0,0 +1,51 @@ +/* + * Copyright © 2013 Canonical Limited + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Ryan Lortie + */ + +#ifndef __GTK_MENU_TRACKER_H__ +#define __GTK_MENU_TRACKER_H__ + +#include + +typedef struct _GtkMenuTracker GtkMenuTracker; + +typedef void (* GtkMenuTrackerInsertFunc) (gint position, + GMenuModel *model, + gint item_index, + const gchar *action_namespace, + gboolean is_separator, + gpointer user_data); + +typedef void (* GtkMenuTrackerRemoveFunc) (gint position, + gpointer user_data); + + +G_GNUC_INTERNAL +GtkMenuTracker * gtk_menu_tracker_new (GMenuModel *model, + gboolean with_separators, + const gchar *action_namespace, + GtkMenuTrackerInsertFunc insert_func, + GtkMenuTrackerRemoveFunc remove_func, + gpointer user_data); + +G_GNUC_INTERNAL +void gtk_menu_tracker_free (GtkMenuTracker *tracker); + +#endif /* __GTK_MENU_TRACKER_H__ */ diff --git a/gtk/gtkmodelmenu.c b/gtk/gtkmodelmenu.c deleted file mode 100644 index 0c3cc5e96c..0000000000 --- a/gtk/gtkmodelmenu.c +++ /dev/null @@ -1,322 +0,0 @@ -/* - * Copyright © 2011 Red Hat, Inc. - * Copyright © 2011 Canonical Limited - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the licence, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see . - * - * Author: Matthias Clasen - * Ryan Lortie - */ - -#include "config.h" - -#include "gtkmenushell.h" -#include "gtkmenubar.h" -#include "gtkmenu.h" - -#include "gtkseparatormenuitem.h" -#include "gtkmodelmenuitem.h" -#include "gtkapplicationprivate.h" - -#define MODEL_MENU_WIDGET_DATA "gtk-model-menu-widget-data" - -typedef struct { - GMenuModel *model; - GtkMenuShell *shell; - guint update_idle; - GSList *connected; - gboolean with_separators; - gint n_items; - gchar *action_namespace; -} GtkModelMenuBinding; - -static void -gtk_model_menu_binding_items_changed (GMenuModel *model, - gint position, - gint removed, - gint added, - gpointer user_data); -static void gtk_model_menu_binding_append_model (GtkModelMenuBinding *binding, - GMenuModel *model, - const gchar *action_namespace, - gboolean with_separators); - -static void -gtk_model_menu_binding_free (gpointer data) -{ - GtkModelMenuBinding *binding = data; - - /* disconnect all existing signal handlers */ - while (binding->connected) - { - g_signal_handlers_disconnect_by_func (binding->connected->data, gtk_model_menu_binding_items_changed, binding); - g_object_unref (binding->connected->data); - - binding->connected = g_slist_delete_link (binding->connected, binding->connected); - } - - g_object_unref (binding->model); - g_free (binding->action_namespace); - - g_slice_free (GtkModelMenuBinding, binding); -} - -static void -gtk_model_menu_binding_append_item (GtkModelMenuBinding *binding, - GMenuModel *model, - const gchar *action_namespace, - gint item_index, - gchar **heading) -{ - GMenuModel *section; - - if ((section = g_menu_model_get_item_link (model, item_index, "section"))) - { - gchar *section_namespace = NULL; - - g_menu_model_get_item_attribute (model, item_index, "label", "s", heading); - g_menu_model_get_item_attribute (model, item_index, "action-namespace", "s", §ion_namespace); - - if (action_namespace) - { - gchar *namespace = g_strjoin (".", action_namespace, section_namespace, NULL); - gtk_model_menu_binding_append_model (binding, section, namespace, FALSE); - g_free (namespace); - } - else - { - gtk_model_menu_binding_append_model (binding, section, section_namespace, FALSE); - } - - g_free (section_namespace); - g_object_unref (section); - } - else - { - GtkMenuItem *item; - - item = gtk_model_menu_item_new (model, item_index, action_namespace); - gtk_menu_shell_append (binding->shell, GTK_WIDGET (item)); - gtk_widget_show (GTK_WIDGET (item)); - binding->n_items++; - } -} - -static void -gtk_model_menu_binding_append_model (GtkModelMenuBinding *binding, - GMenuModel *model, - const gchar *action_namespace, - gboolean with_separators) -{ - gint n, i; - - g_signal_connect (model, "items-changed", G_CALLBACK (gtk_model_menu_binding_items_changed), binding); - binding->connected = g_slist_prepend (binding->connected, g_object_ref (model)); - - /* Deciding if we should show a separator is a bit difficult. There - * are two types of separators: - * - * - section headings (when sections have 'label' property) - * - * - normal separators automatically put between sections - * - * The easiest way to think about it is that a section usually has a - * separator (or heading) immediately before it. - * - * There are three exceptions to this general rule: - * - * - empty sections don't get separators or headings - * - * - sections only get separators and headings at the toplevel of a - * menu (ie: no separators on nested sections or in menubars) - * - * - the first section in the menu doesn't get a normal separator, - * but it can get a header (if it's not empty) - * - * Unfortunately, we cannot simply check the size of the section in - * order to determine if we should place a header: the section may - * contain other sections that are themselves empty. Instead, we need - * to append the section, and check if we ended up with any actual - * content. If we did, then we need to insert before that content. - * We use 'our_position' to keep track of this. - */ - - n = g_menu_model_get_n_items (model); - - for (i = 0; i < n; i++) - { - gint our_position = binding->n_items; - gchar *heading = NULL; - - gtk_model_menu_binding_append_item (binding, model, action_namespace, i, &heading); - - if (with_separators && our_position < binding->n_items) - { - GtkWidget *separator = NULL; - - if (heading) - { - separator = gtk_menu_item_new_with_label (heading); - gtk_widget_set_sensitive (separator, FALSE); - } - else if (our_position > 0) - separator = gtk_separator_menu_item_new (); - - if (separator) - { - gtk_menu_shell_insert (binding->shell, separator, our_position); - gtk_widget_show (separator); - binding->n_items++; - } - } - - g_free (heading); - } -} - -static void -gtk_model_menu_binding_populate (GtkModelMenuBinding *binding) -{ - GList *children; - - /* remove current children */ - children = gtk_container_get_children (GTK_CONTAINER (binding->shell)); - while (children) - { - gtk_container_remove (GTK_CONTAINER (binding->shell), children->data); - children = g_list_delete_link (children, children); - } - - binding->n_items = 0; - - /* add new items from the model */ - gtk_model_menu_binding_append_model (binding, binding->model, binding->action_namespace, binding->with_separators); -} - -static gboolean -gtk_model_menu_binding_handle_changes (gpointer user_data) -{ - GtkModelMenuBinding *binding = user_data; - - /* disconnect all existing signal handlers */ - while (binding->connected) - { - g_signal_handlers_disconnect_by_func (binding->connected->data, gtk_model_menu_binding_items_changed, binding); - g_object_unref (binding->connected->data); - - binding->connected = g_slist_delete_link (binding->connected, binding->connected); - } - - gtk_model_menu_binding_populate (binding); - - binding->update_idle = 0; - - g_object_unref (binding->shell); - - return G_SOURCE_REMOVE; -} - -static void -gtk_model_menu_binding_items_changed (GMenuModel *model, - gint position, - gint removed, - gint added, - gpointer user_data) -{ - GtkModelMenuBinding *binding = user_data; - - if (binding->update_idle == 0) - { - binding->update_idle = gdk_threads_add_idle (gtk_model_menu_binding_handle_changes, user_data); - g_object_ref (binding->shell); - } -} - -/** - * gtk_menu_shell_bind_model: - * @menu_shell: a #GtkMenuShell - * @model: (allow-none): the #GMenuModel to bind to or %NULL to remove - * binding - * @action_namespace: (allow-none): the namespace for actions in @model - * @with_separators: %TRUE if toplevel items in @shell should have - * separators between them - * - * Establishes a binding between a #GtkMenuShell and a #GMenuModel. - * - * The contents of @shell are removed and then refilled with menu items - * according to @model. When @model changes, @shell is updated. - * Calling this function twice on @shell with different @model will - * cause the first binding to be replaced with a binding to the new - * model. If @model is %NULL then any previous binding is undone and - * all children are removed. - * - * @with_separators determines if toplevel items (eg: sections) have - * separators inserted between them. This is typically desired for - * menus but doesn't make sense for menubars. - * - * If @action_namespace is non-%NULL then the effect is as if all - * actions mentioned in the @model have their names prefixed with the - * namespace, plus a dot. For example, if the action "quit" is - * mentioned and @action_namespace is "app" then the effective action - * name is "app.quit". - * - * For most cases you are probably better off using - * gtk_menu_new_from_model() or gtk_menu_bar_new_from_model() or just - * directly passing the #GMenuModel to gtk_application_set_app_menu() or - * gtk_application_set_menu_bar(). - * - * Since: 3.6 - */ -void -gtk_menu_shell_bind_model (GtkMenuShell *shell, - GMenuModel *model, - const gchar *action_namespace, - gboolean with_separators) -{ - g_return_if_fail (GTK_IS_MENU_SHELL (shell)); - g_return_if_fail (model == NULL || G_IS_MENU_MODEL (model)); - - if (model) - { - GtkModelMenuBinding *binding; - - binding = g_slice_new (GtkModelMenuBinding); - binding->model = g_object_ref (model); - binding->shell = shell; - binding->update_idle = 0; - binding->connected = NULL; - binding->with_separators = with_separators; - binding->action_namespace = g_strdup (action_namespace); - - g_object_set_data_full (G_OBJECT (shell), "gtk-model-menu-binding", binding, gtk_model_menu_binding_free); - - gtk_model_menu_binding_populate (binding); - } - - else - { - GList *children; - - /* break existing binding */ - g_object_set_data (G_OBJECT (shell), "gtk-model-menu-binding", NULL); - - /* remove all children */ - children = gtk_container_get_children (GTK_CONTAINER (shell)); - while (children) - { - gtk_container_remove (GTK_CONTAINER (shell), children->data); - children = g_list_delete_link (children, children); - } - } -} diff --git a/gtk/gtkmodelmenuitem.c b/gtk/gtkmodelmenuitem.c index 6d902c2352..a71f2ecbae 100644 --- a/gtk/gtkmodelmenuitem.c +++ b/gtk/gtkmodelmenuitem.c @@ -275,7 +275,7 @@ gtk_model_menu_item_class_init (GtkModelMenuItemClass *class) 0, 2, 0, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)); } -GtkMenuItem * +GtkWidget * gtk_model_menu_item_new (GMenuModel *model, gint item_index, const gchar *action_namespace) @@ -286,5 +286,5 @@ gtk_model_menu_item_new (GMenuModel *model, gtk_model_menu_item_setup (item, model, item_index, action_namespace); - return GTK_MENU_ITEM (item); + return GTK_WIDGET (item); } diff --git a/gtk/gtkmodelmenuitem.h b/gtk/gtkmodelmenuitem.h index 655f07341b..3f24163d5b 100644 --- a/gtk/gtkmodelmenuitem.h +++ b/gtk/gtkmodelmenuitem.h @@ -34,7 +34,7 @@ G_GNUC_INTERNAL GType gtk_model_menu_item_get_type (void) G_GNUC_CONST; G_GNUC_INTERNAL -GtkMenuItem * gtk_model_menu_item_new (GMenuModel *model, +GtkWidget * gtk_model_menu_item_new (GMenuModel *model, gint item_index, const gchar *action_namespace);