]> Pileus Git - ~andy/gtk/blob - gtk/gtkmodelmenu.c
390923fdb46bf86d5ddffd7a27829303fe25c820
[~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, see <http://www.gnu.org/licenses/>.
17  *
18  * Author: Matthias Clasen <mclasen@redhat.com>
19  *         Ryan Lortie <desrt@desrt.ca>
20  */
21
22 #include "config.h"
23
24 #include "gtkmodelmenu.h"
25
26 #include "gtkmenu.h"
27 #include "gtkmenubar.h"
28 #include "gtkseparatormenuitem.h"
29 #include "gtkmodelmenuitem.h"
30 #include "gtkapplicationprivate.h"
31
32 #define MODEL_MENU_WIDGET_DATA "gtk-model-menu-widget-data"
33
34 typedef struct {
35   GMenuModel        *model;
36   GtkAccelGroup     *accels;
37   GtkMenuShell      *shell;
38   guint              update_idle;
39   GSList            *connected;
40   gboolean           with_separators;
41   gint               n_items;
42 } GtkModelMenuBinding;
43
44 static void
45 gtk_model_menu_binding_items_changed (GMenuModel *model,
46                                       gint        position,
47                                       gint        removed,
48                                       gint        added,
49                                       gpointer    user_data);
50 static void gtk_model_menu_binding_append_model (GtkModelMenuBinding *binding,
51                                                  GMenuModel *model,
52                                                  gboolean with_separators);
53
54 static void
55 gtk_model_menu_binding_free (gpointer data)
56 {
57   GtkModelMenuBinding *binding = data;
58
59   /* disconnect all existing signal handlers */
60   while (binding->connected)
61     {
62       g_signal_handlers_disconnect_by_func (binding->connected->data, gtk_model_menu_binding_items_changed, binding);
63       g_object_unref (binding->connected->data);
64
65       binding->connected = g_slist_delete_link (binding->connected, binding->connected);
66     }
67
68   g_object_unref (binding->model);
69
70   g_slice_free (GtkModelMenuBinding, binding);
71 }
72
73 static void
74 gtk_model_menu_binding_append_item (GtkModelMenuBinding  *binding,
75                                     GMenuModel           *model,
76                                     gint                  item_index,
77                                     gchar               **heading)
78 {
79   GMenuModel *section;
80
81   if ((section = g_menu_model_get_item_link (model, item_index, "section")))
82     {
83       g_menu_model_get_item_attribute (model, item_index, "label", "s", heading);
84       gtk_model_menu_binding_append_model (binding, section, FALSE);
85       g_object_unref (section);
86     }
87   else
88     {
89       GtkMenuItem *item;
90
91       item = gtk_model_menu_item_new (model, item_index, binding->accels);
92       gtk_menu_shell_append (binding->shell, GTK_WIDGET (item));
93       gtk_widget_show (GTK_WIDGET (item));
94       binding->n_items++;
95     }
96 }
97
98 static void
99 gtk_model_menu_binding_append_model (GtkModelMenuBinding *binding,
100                                      GMenuModel          *model,
101                                      gboolean             with_separators)
102 {
103   gint n, i;
104
105   g_signal_connect (model, "items-changed", G_CALLBACK (gtk_model_menu_binding_items_changed), binding);
106   binding->connected = g_slist_prepend (binding->connected, g_object_ref (model));
107
108   /* Deciding if we should show a separator is a bit difficult.  There
109    * are two types of separators:
110    *
111    *  - section headings (when sections have 'label' property)
112    *
113    *  - normal separators automatically put between sections
114    *
115    * The easiest way to think about it is that a section usually has a
116    * separator (or heading) immediately before it.
117    *
118    * There are three exceptions to this general rule:
119    *
120    *  - empty sections don't get separators or headings
121    *
122    *  - sections only get separators and headings at the toplevel of a
123    *    menu (ie: no separators on nested sections or in menubars)
124    *
125    *  - the first section in the menu doesn't get a normal separator,
126    *    but it can get a header (if it's not empty)
127    *
128    * Unfortunately, we cannot simply check the size of the section in
129    * order to determine if we should place a header: the section may
130    * contain other sections that are themselves empty.  Instead, we need
131    * to append the section, and check if we ended up with any actual
132    * content.  If we did, then we need to insert before that content.
133    * We use 'our_position' to keep track of this.
134    */
135
136   n = g_menu_model_get_n_items (model);
137
138   for (i = 0; i < n; i++)
139     {
140       gint our_position = binding->n_items;
141       gchar *heading = NULL;
142
143       gtk_model_menu_binding_append_item (binding, model, i, &heading);
144
145       if (with_separators && our_position < binding->n_items)
146         {
147           GtkWidget *separator = NULL;
148
149           if (heading)
150             {
151               separator = gtk_menu_item_new_with_label (heading);
152               gtk_widget_set_sensitive (separator, FALSE);
153             }
154           else if (our_position > 0)
155             separator = gtk_separator_menu_item_new ();
156
157           if (separator)
158             {
159               gtk_menu_shell_insert (binding->shell, separator, our_position);
160               gtk_widget_show (separator);
161               binding->n_items++;
162             }
163         }
164
165       g_free (heading);
166     }
167 }
168
169 static void
170 gtk_model_menu_binding_populate (GtkModelMenuBinding *binding)
171 {
172   GList *children;
173
174   /* remove current children */
175   children = gtk_container_get_children (GTK_CONTAINER (binding->shell));
176   while (children)
177     {
178       gtk_container_remove (GTK_CONTAINER (binding->shell), children->data);
179       children = g_list_delete_link (children, children);
180     }
181
182   binding->n_items = 0;
183
184   /* add new items from the model */
185   gtk_model_menu_binding_append_model (binding, binding->model, binding->with_separators);
186 }
187
188 static gboolean
189 gtk_model_menu_binding_handle_changes (gpointer user_data)
190 {
191   GtkModelMenuBinding *binding = user_data;
192
193   /* disconnect all existing signal handlers */
194   while (binding->connected)
195     {
196       g_signal_handlers_disconnect_by_func (binding->connected->data, gtk_model_menu_binding_items_changed, binding);
197       g_object_unref (binding->connected->data);
198
199       binding->connected = g_slist_delete_link (binding->connected, binding->connected);
200     }
201
202   gtk_model_menu_binding_populate (binding);
203
204   binding->update_idle = 0;
205
206   g_object_unref (binding->shell);
207
208   return G_SOURCE_REMOVE;
209 }
210
211 static void
212 gtk_model_menu_binding_items_changed (GMenuModel *model,
213                                       gint        position,
214                                       gint        removed,
215                                       gint        added,
216                                       gpointer    user_data)
217 {
218   GtkModelMenuBinding *binding = user_data;
219
220   if (binding->update_idle == 0)
221     {
222       binding->update_idle = gdk_threads_add_idle (gtk_model_menu_binding_handle_changes, user_data);
223       g_object_ref (binding->shell);
224     }
225 }
226
227 static void
228 gtk_model_menu_bind (GtkMenuShell *shell,
229                      GMenuModel   *model,
230                      gboolean      with_separators)
231 {
232   GtkModelMenuBinding *binding;
233
234   binding = g_slice_new (GtkModelMenuBinding);
235   binding->model = g_object_ref (model);
236   binding->accels = NULL;
237   binding->shell = shell;
238   binding->update_idle = 0;
239   binding->connected = NULL;
240   binding->with_separators = with_separators;
241
242   g_object_set_data_full (G_OBJECT (shell), "gtk-model-menu-binding", binding, gtk_model_menu_binding_free);
243 }
244
245
246 static void
247 gtk_model_menu_populate (GtkMenuShell  *shell,
248                          GtkAccelGroup *accels)
249 {
250   GtkModelMenuBinding *binding;
251
252   binding = (GtkModelMenuBinding*) g_object_get_data (G_OBJECT (shell), "gtk-model-menu-binding");
253
254   binding->accels = accels;
255
256   gtk_model_menu_binding_populate (binding);
257 }
258
259 GtkWidget *
260 gtk_model_menu_create_menu (GMenuModel    *model,
261                             GtkAccelGroup *accels)
262 {
263   GtkWidget *menu;
264
265   menu = gtk_menu_new ();
266   gtk_menu_set_accel_group (GTK_MENU (menu), accels);
267
268   gtk_model_menu_bind (GTK_MENU_SHELL (menu), model, TRUE);
269   gtk_model_menu_populate (GTK_MENU_SHELL (menu), accels);
270
271   return menu;
272 }
273
274 /**
275  * gtk_menu_new_from_model:
276  * @model: a #GMenuModel
277  *
278  * Creates a #GtkMenu and populates it with menu items and
279  * submenus according to @model.
280  *
281  * The created menu items are connected to actions found in the
282  * #GtkApplicationWindow to which the menu belongs - typically
283  * by means of being attached to a widget (see gtk_menu_attach_to_widget())
284  * that is contained within the #GtkApplicationWindows widget hierarchy.
285  *
286  * Returns: a new #GtkMenu
287  *
288  * Since: 3.4
289  */
290 GtkWidget *
291 gtk_menu_new_from_model (GMenuModel *model)
292 {
293   GtkWidget *menu;
294
295   menu = gtk_menu_new ();
296   gtk_model_menu_bind (GTK_MENU_SHELL (menu), model, TRUE);
297   gtk_model_menu_populate (GTK_MENU_SHELL (menu), NULL);
298
299   return menu;
300 }
301
302 GtkWidget *
303 gtk_model_menu_create_menu_bar (GMenuModel    *model,
304                                 GtkAccelGroup *accels)
305 {
306   GtkWidget *menubar;
307
308   menubar = gtk_menu_bar_new ();
309
310   gtk_model_menu_bind (GTK_MENU_SHELL (menubar), model, FALSE);
311   gtk_model_menu_populate (GTK_MENU_SHELL (menubar), accels);
312
313   return menubar;
314 }
315
316 /**
317  * gtk_menu_bar_new_from_model:
318  * @model: a #GMenuModel
319  *
320  * Creates a new #GtkMenuBar and populates it with menu items
321  * and submenus according to @model.
322  *
323  * The created menu items are connected to actions found in the
324  * #GtkApplicationWindow to which the menu bar belongs - typically
325  * by means of being contained within the #GtkApplicationWindows
326  * widget hierarchy.
327  *
328  * Returns: a new #GtkMenuBar
329  *
330  * Since: 3.4
331  */
332 GtkWidget *
333 gtk_menu_bar_new_from_model (GMenuModel *model)
334 {
335   GtkWidget *menubar;
336
337   menubar = gtk_menu_bar_new ();
338
339   gtk_model_menu_bind (GTK_MENU_SHELL (menubar), model, FALSE);
340   gtk_model_menu_populate (GTK_MENU_SHELL (menubar), NULL);
341
342   return menubar;
343 }