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, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
20 * Author: Matthias Clasen <mclasen@redhat.com>
21 * Ryan Lortie <desrt@desrt.ca>
26 #include "gtkmodelmenu.h"
28 #include "gtkseparatormenuitem.h"
29 #include "gtkmodelmenuitem.h"
32 GActionObservable *actions;
37 gboolean with_separators;
39 } GtkModelMenuBinding;
42 gtk_model_menu_binding_items_changed (GMenuModel *model,
47 static void gtk_model_menu_binding_append_model (GtkModelMenuBinding *binding,
49 gboolean with_separators);
52 gtk_model_menu_binding_free (gpointer data)
54 GtkModelMenuBinding *binding = data;
56 /* disconnect all existing signal handlers */
57 while (binding->connected)
59 g_signal_handlers_disconnect_by_func (binding->connected->data, gtk_model_menu_binding_items_changed, binding);
60 g_object_unref (binding->connected->data);
62 binding->connected = g_slist_delete_link (binding->connected, binding->connected);
65 g_object_unref (binding->actions);
66 g_object_unref (binding->model);
70 gtk_model_menu_binding_append_item (GtkModelMenuBinding *binding,
77 if ((section = g_menu_model_get_item_link (model, item_index, "section")))
79 g_menu_model_get_item_attribute (model, item_index, "label", "s", &heading);
80 gtk_model_menu_binding_append_model (binding, section, FALSE);
86 item = gtk_model_menu_item_new (model, item_index, binding->actions);
87 gtk_menu_shell_append (binding->shell, GTK_WIDGET (item));
88 gtk_widget_show (GTK_WIDGET (item));
94 gtk_model_menu_binding_append_model (GtkModelMenuBinding *binding,
96 gboolean with_separators)
100 g_signal_connect (model, "items-changed", G_CALLBACK (gtk_model_menu_binding_items_changed), binding);
101 binding->connected = g_slist_prepend (binding->connected, g_object_ref (model));
103 /* Deciding if we should show a separator is a bit difficult. There
104 * are two types of separators:
106 * - section headings (when sections have 'label' property)
108 * - normal separators automatically put between sections
110 * The easiest way to think about it is that a section usually has a
111 * separator (or heading) immediately before it.
113 * There are three exceptions to this general rule:
115 * - empty sections don't get separators or headings
117 * - sections only get separators and headings at the toplevel of a
118 * menu (ie: no separators on nested sections or in menubars)
120 * - the first section in the menu doesn't get a normal separator,
121 * but it can get a header (if it's not empty)
123 * Unfortunately, we cannot simply check the size of the section in
124 * order to determine if we should place a header: the section may
125 * contain other sections that are themselves empty. Instead, we need
126 * to append the section, and check if we ended up with any actual
127 * content. If we did, then we need to insert before that content.
128 * We use 'our_position' to keep track of this.
131 n = g_menu_model_get_n_items (model);
133 for (i = 0; i < n; i++)
135 gint our_position = binding->n_items;
136 gchar *heading = NULL;
138 gtk_model_menu_binding_append_item (binding, model, i, &heading);
140 if (with_separators && our_position < binding->n_items)
142 GtkWidget *separator = NULL;
146 separator = gtk_menu_item_new_with_label (heading);
147 gtk_widget_set_sensitive (separator, FALSE);
149 else if (our_position > 0)
150 separator = gtk_separator_menu_item_new ();
154 gtk_menu_shell_insert (binding->shell, separator, our_position);
155 gtk_widget_show (separator);
165 gtk_model_menu_binding_populate (GtkModelMenuBinding *binding)
169 /* remove current children */
170 children = gtk_container_get_children (GTK_CONTAINER (binding->shell));
173 gtk_container_remove (GTK_CONTAINER (binding->shell), children->data);
174 children = g_list_delete_link (children, children);
177 binding->n_items = 0;
179 /* add new items from the model */
180 gtk_model_menu_binding_append_model (binding, binding->model, binding->with_separators);
184 gtk_model_menu_binding_handle_changes (gpointer user_data)
186 GtkModelMenuBinding *binding = user_data;
188 /* disconnect all existing signal handlers */
189 while (binding->connected)
191 g_signal_handlers_disconnect_by_func (binding->connected->data, gtk_model_menu_binding_items_changed, binding);
192 g_object_unref (binding->connected->data);
194 binding->connected = g_slist_delete_link (binding->connected, binding->connected);
197 gtk_model_menu_binding_populate (binding);
199 binding->update_idle = 0;
201 g_object_unref (binding->shell);
203 return G_SOURCE_REMOVE;
207 gtk_model_menu_binding_items_changed (GMenuModel *model,
213 GtkModelMenuBinding *binding = user_data;
215 if (binding->update_idle == 0)
217 binding->update_idle = gdk_threads_add_idle (gtk_model_menu_binding_handle_changes, user_data);
218 g_object_ref (binding->shell);
223 gtk_model_menu_bind (GtkMenuShell *shell,
225 GActionObservable *actions,
226 gboolean with_separators)
228 GtkModelMenuBinding *binding;
230 binding = g_slice_new (GtkModelMenuBinding);
231 binding->model = g_object_ref (model);
232 binding->actions = g_object_ref (actions);
233 binding->shell = shell;
234 binding->update_idle = 0;
235 binding->connected = NULL;
236 binding->with_separators = with_separators;
238 g_object_set_data_full (G_OBJECT (shell), "gtk-model-menu-binding", binding, gtk_model_menu_binding_free);
239 gtk_model_menu_binding_populate (binding);
243 gtk_model_menu_create_menu (GMenuModel *model,
244 GActionObservable *actions)
248 menu = gtk_menu_new ();
249 gtk_model_menu_bind (GTK_MENU_SHELL (menu), model, actions, TRUE);
255 gtk_model_menu_create_menu_bar (GMenuModel *model,
256 GActionObservable *actions)
260 menubar = gtk_menu_bar_new ();
261 gtk_model_menu_bind (GTK_MENU_SHELL (menubar), model, actions, FALSE);