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 "gtkmodelmenu.h"
27 #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"
35 GActionObservable *actions;
37 GtkAccelGroup *accels;
41 gboolean with_separators;
43 } GtkModelMenuBinding;
46 gtk_model_menu_binding_items_changed (GMenuModel *model,
51 static void gtk_model_menu_binding_append_model (GtkModelMenuBinding *binding,
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);
70 g_object_unref (binding->actions);
71 g_object_unref (binding->model);
73 g_slice_free (GtkModelMenuBinding, binding);
77 gtk_model_menu_binding_append_item (GtkModelMenuBinding *binding,
84 if ((section = g_menu_model_get_item_link (model, item_index, "section")))
86 g_menu_model_get_item_attribute (model, item_index, "label", "s", heading);
87 gtk_model_menu_binding_append_model (binding, section, FALSE);
93 item = gtk_model_menu_item_new (model, item_index, binding->actions, binding->accels);
94 gtk_menu_shell_append (binding->shell, GTK_WIDGET (item));
95 gtk_widget_show (GTK_WIDGET (item));
101 gtk_model_menu_binding_append_model (GtkModelMenuBinding *binding,
103 gboolean with_separators)
107 g_signal_connect (model, "items-changed", G_CALLBACK (gtk_model_menu_binding_items_changed), binding);
108 binding->connected = g_slist_prepend (binding->connected, g_object_ref (model));
110 /* Deciding if we should show a separator is a bit difficult. There
111 * are two types of separators:
113 * - section headings (when sections have 'label' property)
115 * - normal separators automatically put between sections
117 * The easiest way to think about it is that a section usually has a
118 * separator (or heading) immediately before it.
120 * There are three exceptions to this general rule:
122 * - empty sections don't get separators or headings
124 * - sections only get separators and headings at the toplevel of a
125 * menu (ie: no separators on nested sections or in menubars)
127 * - the first section in the menu doesn't get a normal separator,
128 * but it can get a header (if it's not empty)
130 * Unfortunately, we cannot simply check the size of the section in
131 * order to determine if we should place a header: the section may
132 * contain other sections that are themselves empty. Instead, we need
133 * to append the section, and check if we ended up with any actual
134 * content. If we did, then we need to insert before that content.
135 * We use 'our_position' to keep track of this.
138 n = g_menu_model_get_n_items (model);
140 for (i = 0; i < n; i++)
142 gint our_position = binding->n_items;
143 gchar *heading = NULL;
145 gtk_model_menu_binding_append_item (binding, model, i, &heading);
147 if (with_separators && our_position < binding->n_items)
149 GtkWidget *separator = NULL;
153 separator = gtk_menu_item_new_with_label (heading);
154 gtk_widget_set_sensitive (separator, FALSE);
156 else if (our_position > 0)
157 separator = gtk_separator_menu_item_new ();
161 gtk_menu_shell_insert (binding->shell, separator, our_position);
162 gtk_widget_show (separator);
172 gtk_model_menu_binding_populate (GtkModelMenuBinding *binding)
176 /* remove current children */
177 children = gtk_container_get_children (GTK_CONTAINER (binding->shell));
180 gtk_container_remove (GTK_CONTAINER (binding->shell), children->data);
181 children = g_list_delete_link (children, children);
184 binding->n_items = 0;
186 /* add new items from the model */
187 gtk_model_menu_binding_append_model (binding, binding->model, binding->with_separators);
191 gtk_model_menu_binding_handle_changes (gpointer user_data)
193 GtkModelMenuBinding *binding = user_data;
195 /* disconnect all existing signal handlers */
196 while (binding->connected)
198 g_signal_handlers_disconnect_by_func (binding->connected->data, gtk_model_menu_binding_items_changed, binding);
199 g_object_unref (binding->connected->data);
201 binding->connected = g_slist_delete_link (binding->connected, binding->connected);
204 gtk_model_menu_binding_populate (binding);
206 binding->update_idle = 0;
208 g_object_unref (binding->shell);
210 return G_SOURCE_REMOVE;
214 gtk_model_menu_binding_items_changed (GMenuModel *model,
220 GtkModelMenuBinding *binding = user_data;
222 if (binding->update_idle == 0)
224 binding->update_idle = gdk_threads_add_idle (gtk_model_menu_binding_handle_changes, user_data);
225 g_object_ref (binding->shell);
230 gtk_model_menu_bind (GtkMenuShell *shell,
232 gboolean with_separators)
234 GtkModelMenuBinding *binding;
236 binding = g_slice_new (GtkModelMenuBinding);
237 binding->model = g_object_ref (model);
238 binding->actions = NULL;
239 binding->accels = NULL;
240 binding->shell = shell;
241 binding->update_idle = 0;
242 binding->connected = NULL;
243 binding->with_separators = with_separators;
245 g_object_set_data_full (G_OBJECT (shell), "gtk-model-menu-binding", binding, gtk_model_menu_binding_free);
250 gtk_model_menu_populate (GtkMenuShell *shell,
251 GActionObservable *actions,
252 GtkAccelGroup *accels)
254 GtkModelMenuBinding *binding;
256 binding = (GtkModelMenuBinding*) g_object_get_data (G_OBJECT (shell), "gtk-model-menu-binding");
258 binding->actions = g_object_ref (actions);
259 binding->accels = accels;
261 gtk_model_menu_binding_populate (binding);
265 gtk_model_menu_create_menu (GMenuModel *model,
266 GActionObservable *actions,
267 GtkAccelGroup *accels)
271 menu = gtk_menu_new ();
272 gtk_menu_set_accel_group (GTK_MENU (menu), accels);
274 gtk_model_menu_bind (GTK_MENU_SHELL (menu), model, TRUE);
275 gtk_model_menu_populate (GTK_MENU_SHELL (menu), actions, accels);
281 gtk_model_menu_connect_app_window (GtkMenu *menu,
282 GtkApplicationWindow *window)
284 GActionObservable *actions;
285 GtkAccelGroup *accels;
287 actions = gtk_application_window_get_observable (window);
288 accels = gtk_application_window_get_accel_group (window);
290 gtk_menu_set_accel_group (menu, accels);
291 gtk_model_menu_populate (GTK_MENU_SHELL (menu), actions, accels);
295 attach_widget_hierarchy_changed (GtkWidget *attach_widget,
296 GtkWidget *previous_toplevel,
300 GtkMenu *menu = user_data;
302 toplevel = gtk_widget_get_toplevel (attach_widget);
303 if (GTK_IS_APPLICATION_WINDOW (toplevel))
304 gtk_model_menu_connect_app_window (menu, GTK_APPLICATION_WINDOW (toplevel));
308 notify_attach (GtkMenu *menu,
312 GtkWidget *attach_widget, *toplevel;
314 attach_widget = g_object_get_data (G_OBJECT (menu), MODEL_MENU_WIDGET_DATA);
315 if (attach_widget != NULL)
317 g_signal_handlers_disconnect_by_func (attach_widget, attach_widget_hierarchy_changed, menu);
318 g_object_set_data (G_OBJECT (menu), MODEL_MENU_WIDGET_DATA, NULL);
321 attach_widget = gtk_menu_get_attach_widget (menu);
325 toplevel = gtk_widget_get_toplevel (attach_widget);
326 if (GTK_IS_APPLICATION_WINDOW (toplevel))
328 gtk_model_menu_connect_app_window (menu, GTK_APPLICATION_WINDOW (toplevel));
332 g_object_set_data (G_OBJECT (menu), MODEL_MENU_WIDGET_DATA, attach_widget);
333 g_signal_connect_object (attach_widget, "hierarchy-changed",
334 G_CALLBACK (attach_widget_hierarchy_changed), menu, 0);
339 * gtk_menu_new_from_model:
340 * @model: a #GMenuModel
342 * Creates a #GtkMenu and populates it with menu items and
343 * submenus according to @model.
345 * The created menu items are connected to actions found in the
346 * #GtkApplicationWindow to which the menu belongs - typically
347 * by means of being attached to a widget (see gtk_menu_attach_to_widget())
348 * that is contained within the #GtkApplicationWindows widget hierarchy.
350 * Returns: a new #GtkMenu
355 gtk_menu_new_from_model (GMenuModel *model)
359 menu = gtk_menu_new ();
360 gtk_model_menu_bind (GTK_MENU_SHELL (menu), model, TRUE);
361 g_signal_connect (menu, "notify::attach-widget",
362 G_CALLBACK (notify_attach), NULL);
368 gtk_model_menu_create_menu_bar (GMenuModel *model,
369 GActionObservable *actions,
370 GtkAccelGroup *accels)
374 menubar = gtk_menu_bar_new ();
376 gtk_model_menu_bind (GTK_MENU_SHELL (menubar), model, FALSE);
377 gtk_model_menu_populate (GTK_MENU_SHELL (menubar), actions, accels);
383 hierarchy_changed (GtkMenuShell *shell,
384 GObject *previous_toplevel,
388 GActionObservable *actions;
389 GtkAccelGroup *accels;
391 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (shell));
392 if (GTK_IS_APPLICATION_WINDOW (toplevel))
394 actions = gtk_application_window_get_observable (GTK_APPLICATION_WINDOW (toplevel));
395 accels = gtk_application_window_get_accel_group (GTK_APPLICATION_WINDOW (toplevel));
397 gtk_model_menu_populate (shell, actions, accels);
402 * gtk_menu_bar_new_from_model:
403 * @model: a #GMenuModel
405 * Creates a new #GtkMenuBar and populates it with menu items
406 * and submenus according to @model.
408 * The created menu items are connected to actions found in the
409 * #GtkApplicationWindow to which the menu bar belongs - typically
410 * by means of being contained within the #GtkApplicationWindows
413 * Returns: a new #GtkMenuBar
418 gtk_menu_bar_new_from_model (GMenuModel *model)
422 menubar = gtk_menu_bar_new ();
424 gtk_model_menu_bind (GTK_MENU_SHELL (menubar), model, FALSE);
426 g_signal_connect (menubar, "hierarchy-changed",
427 G_CALLBACK (hierarchy_changed), NULL);