2 * Copyright © 2011 Red Hat, Inc.
3 * Copyright © 2011 Canonical Limited
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the licence, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
18 * Author: Matthias Clasen <mclasen@redhat.com>
19 * Ryan Lortie <desrt@desrt.ca>
24 #include "gtkmenushell.h"
25 #include "gtkmenubar.h"
28 #include "gtkseparatormenuitem.h"
29 #include "gtkmodelmenuitem.h"
30 #include "gtkapplicationprivate.h"
32 #define MODEL_MENU_WIDGET_DATA "gtk-model-menu-widget-data"
39 gboolean with_separators;
41 gchar *action_namespace;
42 } GtkModelMenuBinding;
45 gtk_model_menu_binding_items_changed (GMenuModel *model,
50 static void gtk_model_menu_binding_append_model (GtkModelMenuBinding *binding,
52 const gchar *action_namespace,
53 gboolean with_separators);
56 gtk_model_menu_binding_free (gpointer data)
58 GtkModelMenuBinding *binding = data;
60 /* disconnect all existing signal handlers */
61 while (binding->connected)
63 g_signal_handlers_disconnect_by_func (binding->connected->data, gtk_model_menu_binding_items_changed, binding);
64 g_object_unref (binding->connected->data);
66 binding->connected = g_slist_delete_link (binding->connected, binding->connected);
69 g_object_unref (binding->model);
70 g_free (binding->action_namespace);
72 g_slice_free (GtkModelMenuBinding, binding);
76 gtk_model_menu_binding_append_item (GtkModelMenuBinding *binding,
78 const gchar *action_namespace,
84 if ((section = g_menu_model_get_item_link (model, item_index, "section")))
86 gchar *section_namespace = NULL;
88 g_menu_model_get_item_attribute (model, item_index, "label", "s", heading);
89 g_menu_model_get_item_attribute (model, item_index, "action-namespace", "s", §ion_namespace);
93 gchar *namespace = g_strjoin (".", action_namespace, section_namespace, NULL);
94 gtk_model_menu_binding_append_model (binding, section, namespace, FALSE);
99 gtk_model_menu_binding_append_model (binding, section, section_namespace, FALSE);
102 g_free (section_namespace);
103 g_object_unref (section);
109 item = gtk_model_menu_item_new (model, item_index, action_namespace);
110 gtk_menu_shell_append (binding->shell, GTK_WIDGET (item));
111 gtk_widget_show (GTK_WIDGET (item));
117 gtk_model_menu_binding_append_model (GtkModelMenuBinding *binding,
119 const gchar *action_namespace,
120 gboolean with_separators)
124 g_signal_connect (model, "items-changed", G_CALLBACK (gtk_model_menu_binding_items_changed), binding);
125 binding->connected = g_slist_prepend (binding->connected, g_object_ref (model));
127 /* Deciding if we should show a separator is a bit difficult. There
128 * are two types of separators:
130 * - section headings (when sections have 'label' property)
132 * - normal separators automatically put between sections
134 * The easiest way to think about it is that a section usually has a
135 * separator (or heading) immediately before it.
137 * There are three exceptions to this general rule:
139 * - empty sections don't get separators or headings
141 * - sections only get separators and headings at the toplevel of a
142 * menu (ie: no separators on nested sections or in menubars)
144 * - the first section in the menu doesn't get a normal separator,
145 * but it can get a header (if it's not empty)
147 * Unfortunately, we cannot simply check the size of the section in
148 * order to determine if we should place a header: the section may
149 * contain other sections that are themselves empty. Instead, we need
150 * to append the section, and check if we ended up with any actual
151 * content. If we did, then we need to insert before that content.
152 * We use 'our_position' to keep track of this.
155 n = g_menu_model_get_n_items (model);
157 for (i = 0; i < n; i++)
159 gint our_position = binding->n_items;
160 gchar *heading = NULL;
162 gtk_model_menu_binding_append_item (binding, model, action_namespace, i, &heading);
164 if (with_separators && our_position < binding->n_items)
166 GtkWidget *separator = NULL;
170 separator = gtk_menu_item_new_with_label (heading);
171 gtk_widget_set_sensitive (separator, FALSE);
173 else if (our_position > 0)
174 separator = gtk_separator_menu_item_new ();
178 gtk_menu_shell_insert (binding->shell, separator, our_position);
179 gtk_widget_show (separator);
189 gtk_model_menu_binding_populate (GtkModelMenuBinding *binding)
193 /* remove current children */
194 children = gtk_container_get_children (GTK_CONTAINER (binding->shell));
197 gtk_container_remove (GTK_CONTAINER (binding->shell), children->data);
198 children = g_list_delete_link (children, children);
201 binding->n_items = 0;
203 /* add new items from the model */
204 gtk_model_menu_binding_append_model (binding, binding->model, binding->action_namespace, binding->with_separators);
208 gtk_model_menu_binding_handle_changes (gpointer user_data)
210 GtkModelMenuBinding *binding = user_data;
212 /* disconnect all existing signal handlers */
213 while (binding->connected)
215 g_signal_handlers_disconnect_by_func (binding->connected->data, gtk_model_menu_binding_items_changed, binding);
216 g_object_unref (binding->connected->data);
218 binding->connected = g_slist_delete_link (binding->connected, binding->connected);
221 gtk_model_menu_binding_populate (binding);
223 binding->update_idle = 0;
225 g_object_unref (binding->shell);
227 return G_SOURCE_REMOVE;
231 gtk_model_menu_binding_items_changed (GMenuModel *model,
237 GtkModelMenuBinding *binding = user_data;
239 if (binding->update_idle == 0)
241 binding->update_idle = gdk_threads_add_idle (gtk_model_menu_binding_handle_changes, user_data);
242 g_object_ref (binding->shell);
247 * gtk_menu_shell_bind_model:
248 * @menu_shell: a #GtkMenuShell
249 * @model: (allow-none): the #GMenuModel to bind to or %NULL to remove
251 * @action_namespace: (allow-none): the namespace for actions in @model
252 * @with_separators: %TRUE if toplevel items in @shell should have
253 * separators between them
255 * Establishes a binding between a #GtkMenuShell and a #GMenuModel.
257 * The contents of @shell are removed and then refilled with menu items
258 * according to @model. When @model changes, @shell is updated.
259 * Calling this function twice on @shell with different @model will
260 * cause the first binding to be replaced with a binding to the new
261 * model. If @model is %NULL then any previous binding is undone and
262 * all children are removed.
264 * @with_separators determines if toplevel items (eg: sections) have
265 * separators inserted between them. This is typically desired for
266 * menus but doesn't make sense for menubars.
268 * If @action_namespace is non-%NULL then the effect is as if all
269 * actions mentioned in the @model have their names prefixed with the
270 * namespace, plus a dot. For example, if the action "quit" is
271 * mentioned and @action_namespace is "app" then the effective action
272 * name is "app.quit".
274 * For most cases you are probably better off using
275 * gtk_menu_new_from_model() or gtk_menu_bar_new_from_model() or just
276 * directly passing the #GMenuModel to gtk_application_set_app_menu() or
277 * gtk_application_set_menu_bar().
282 gtk_menu_shell_bind_model (GtkMenuShell *shell,
284 const gchar *action_namespace,
285 gboolean with_separators)
287 g_return_if_fail (GTK_IS_MENU_SHELL (shell));
288 g_return_if_fail (model == NULL || G_IS_MENU_MODEL (model));
292 GtkModelMenuBinding *binding;
294 binding = g_slice_new (GtkModelMenuBinding);
295 binding->model = g_object_ref (model);
296 binding->shell = shell;
297 binding->update_idle = 0;
298 binding->connected = NULL;
299 binding->with_separators = with_separators;
300 binding->action_namespace = g_strdup (action_namespace);
302 g_object_set_data_full (G_OBJECT (shell), "gtk-model-menu-binding", binding, gtk_model_menu_binding_free);
304 gtk_model_menu_binding_populate (binding);
311 /* break existing binding */
312 g_object_set_data (G_OBJECT (shell), "gtk-model-menu-binding", NULL);
314 /* remove all children */
315 children = gtk_container_get_children (GTK_CONTAINER (shell));
318 gtk_container_remove (GTK_CONTAINER (shell), children->data);
319 children = g_list_delete_link (children, children);