]> Pileus Git - ~andy/gtk/blob - gtk/gtkmodelmenu.c
Split off GMenuModel -> GtkMenuBar code
[~andy/gtk] / gtk / gtkmodelmenu.c
1 /*
2  * Copyright © 2011 Red Hat, Inc.
3  * Copyright © 2011 Canonical Limited
4  *
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.
9  *
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.
14  *
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.
19  *
20  * Author: Matthias Clasen <mclasen@redhat.com>
21  *         Ryan Lortie <desrt@desrt.ca>
22  */
23
24 #include "config.h"
25
26 #include "gtkmodelmenu.h"
27
28 #include "gtkseparatormenuitem.h"
29 #include "gtkmodelmenuitem.h"
30
31 typedef struct {
32   GActionObservable *actions;
33   GMenuModel        *model;
34   GtkMenuShell      *shell;
35   guint              update_idle;
36   GSList            *connected;
37   gboolean           with_separators;
38   gint               n_items;
39 } GtkModelMenuBinding;
40
41 static void
42 gtk_model_menu_binding_items_changed (GMenuModel *model,
43                                       gint        position,
44                                       gint        removed,
45                                       gint        added,
46                                       gpointer    user_data);
47 static void gtk_model_menu_binding_append_model (GtkModelMenuBinding *binding,
48                                                  GMenuModel *model,
49                                                  gboolean with_separators);
50
51 static void
52 gtk_model_menu_binding_free (gpointer data)
53 {
54   GtkModelMenuBinding *binding = data;
55
56   /* disconnect all existing signal handlers */
57   while (binding->connected)
58     {
59       g_signal_handlers_disconnect_by_func (binding->connected->data, gtk_model_menu_binding_items_changed, binding);
60       g_object_unref (binding->connected->data);
61
62       binding->connected = g_slist_delete_link (binding->connected, binding->connected);
63     }
64
65   g_object_unref (binding->actions);
66   g_object_unref (binding->model);
67 }
68
69 static void
70 gtk_model_menu_binding_append_item (GtkModelMenuBinding  *binding,
71                                     GMenuModel           *model,
72                                     gint                  item_index,
73                                     gchar               **heading)
74 {
75   GMenuModel *section;
76
77   if ((section = g_menu_model_get_item_link (model, item_index, "section")))
78     {
79       g_menu_model_get_item_attribute (model, item_index, "label", "s", &heading);
80       gtk_model_menu_binding_append_model (binding, section, FALSE);
81     }
82   else
83     {
84       GtkMenuItem *item;
85
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));
89       binding->n_items++;
90     }
91 }
92
93 static void
94 gtk_model_menu_binding_append_model (GtkModelMenuBinding *binding,
95                                      GMenuModel          *model,
96                                      gboolean             with_separators)
97 {
98   gint n, i;
99
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));
102
103   /* Deciding if we should show a separator is a bit difficult.  There
104    * are two types of separators:
105    *
106    *  - section headings (when sections have 'label' property)
107    *
108    *  - normal separators automatically put between sections
109    *
110    * The easiest way to think about it is that a section usually has a
111    * separator (or heading) immediately before it.
112    *
113    * There are three exceptions to this general rule:
114    *
115    *  - empty sections don't get separators or headings
116    *
117    *  - sections only get separators and headings at the toplevel of a
118    *    menu (ie: no separators on nested sections or in menubars)
119    *
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)
122    *
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.
129    */
130
131   n = g_menu_model_get_n_items (model);
132
133   for (i = 0; i < n; i++)
134     {
135       gint our_position = binding->n_items;
136       gchar *heading = NULL;
137
138       gtk_model_menu_binding_append_item (binding, model, i, &heading);
139
140       if (with_separators && our_position < binding->n_items)
141         {
142           GtkWidget *separator = NULL;
143
144           if (heading)
145             {
146               separator = gtk_menu_item_new_with_label (heading);
147               gtk_widget_set_sensitive (separator, FALSE);
148             }
149           else if (our_position > 0)
150             separator = gtk_separator_menu_item_new ();
151
152           if (separator)
153             {
154               gtk_menu_shell_insert (binding->shell, separator, our_position);
155               gtk_widget_show (separator);
156               binding->n_items++;
157             }
158         }
159
160       g_free (heading);
161     }
162 }
163
164 static void
165 gtk_model_menu_binding_populate (GtkModelMenuBinding *binding)
166 {
167   GList *children;
168
169   /* remove current children */
170   children = gtk_container_get_children (GTK_CONTAINER (binding->shell));
171   while (children)
172     {
173       gtk_container_remove (GTK_CONTAINER (binding->shell), children->data);
174       children = g_list_delete_link (children, children);
175     }
176
177   binding->n_items = 0;
178
179   /* add new items from the model */
180   gtk_model_menu_binding_append_model (binding, binding->model, binding->with_separators);
181 }
182
183 static gboolean
184 gtk_model_menu_binding_handle_changes (gpointer user_data)
185 {
186   GtkModelMenuBinding *binding = user_data;
187
188   /* disconnect all existing signal handlers */
189   while (binding->connected)
190     {
191       g_signal_handlers_disconnect_by_func (binding->connected->data, gtk_model_menu_binding_items_changed, binding);
192       g_object_unref (binding->connected->data);
193
194       binding->connected = g_slist_delete_link (binding->connected, binding->connected);
195     }
196
197   gtk_model_menu_binding_populate (binding);
198
199   binding->update_idle = 0;
200
201   g_object_unref (binding->shell);
202
203   return G_SOURCE_REMOVE;
204 }
205
206 static void
207 gtk_model_menu_binding_items_changed (GMenuModel *model,
208                                       gint        position,
209                                       gint        removed,
210                                       gint        added,
211                                       gpointer    user_data)
212 {
213   GtkModelMenuBinding *binding = user_data;
214
215   if (binding->update_idle == 0)
216     {
217       binding->update_idle = gdk_threads_add_idle (gtk_model_menu_binding_handle_changes, user_data);
218       g_object_ref (binding->shell);
219     }
220 }
221
222 void
223 gtk_model_menu_bind (GtkMenuShell      *shell,
224                      GMenuModel        *model,
225                      GActionObservable *actions,
226                      gboolean           with_separators)
227 {
228   GtkModelMenuBinding *binding;
229
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;
237
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);
240 }
241
242 GtkWidget *
243 gtk_model_menu_create_menu (GMenuModel        *model,
244                             GActionObservable *actions)
245 {
246   GtkWidget *menu;
247
248   menu = gtk_menu_new ();
249   gtk_model_menu_bind (GTK_MENU_SHELL (menu), model, actions, TRUE);
250
251   return menu;
252 }
253
254 GtkWidget *
255 gtk_model_menu_create_menu_bar (GMenuModel        *model,
256                                 GActionObservable *actions)
257 {
258   GtkWidget *menubar;
259
260   menubar = gtk_menu_bar_new ();
261   gtk_model_menu_bind (GTK_MENU_SHELL (menubar), model, actions, FALSE);
262
263   return menubar;
264 }
265