]> Pileus Git - ~andy/gtk/blob - gtk/gtkmodelmenu.c
GtkAccelLabel: add manual accel API
[~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   GtkMenuShell      *shell;
37   guint              update_idle;
38   GSList            *connected;
39   gboolean           with_separators;
40   gint               n_items;
41   gchar             *action_namespace;
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                                                  const gchar *action_namespace,
53                                                  gboolean with_separators);
54
55 static void
56 gtk_model_menu_binding_free (gpointer data)
57 {
58   GtkModelMenuBinding *binding = data;
59
60   /* disconnect all existing signal handlers */
61   while (binding->connected)
62     {
63       g_signal_handlers_disconnect_by_func (binding->connected->data, gtk_model_menu_binding_items_changed, binding);
64       g_object_unref (binding->connected->data);
65
66       binding->connected = g_slist_delete_link (binding->connected, binding->connected);
67     }
68
69   g_object_unref (binding->model);
70   g_free (binding->action_namespace);
71
72   g_slice_free (GtkModelMenuBinding, binding);
73 }
74
75 static void
76 gtk_model_menu_binding_append_item (GtkModelMenuBinding  *binding,
77                                     GMenuModel           *model,
78                                     const gchar          *action_namespace,
79                                     gint                  item_index,
80                                     gchar               **heading)
81 {
82   GMenuModel *section;
83
84   if ((section = g_menu_model_get_item_link (model, item_index, "section")))
85     {
86       gchar *section_namespace = NULL;
87
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", &section_namespace);
90
91       if (action_namespace)
92         {
93           gchar *namespace = g_strjoin (".", action_namespace, section_namespace, NULL);
94           gtk_model_menu_binding_append_model (binding, section, namespace, FALSE);
95           g_free (namespace);
96         }
97       else
98         {
99           gtk_model_menu_binding_append_model (binding, section, section_namespace, FALSE);
100         }
101
102       g_free (section_namespace);
103       g_object_unref (section);
104     }
105   else
106     {
107       GtkMenuItem *item;
108
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));
112       binding->n_items++;
113     }
114 }
115
116 static void
117 gtk_model_menu_binding_append_model (GtkModelMenuBinding *binding,
118                                      GMenuModel          *model,
119                                      const gchar         *action_namespace,
120                                      gboolean             with_separators)
121 {
122   gint n, i;
123
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));
126
127   /* Deciding if we should show a separator is a bit difficult.  There
128    * are two types of separators:
129    *
130    *  - section headings (when sections have 'label' property)
131    *
132    *  - normal separators automatically put between sections
133    *
134    * The easiest way to think about it is that a section usually has a
135    * separator (or heading) immediately before it.
136    *
137    * There are three exceptions to this general rule:
138    *
139    *  - empty sections don't get separators or headings
140    *
141    *  - sections only get separators and headings at the toplevel of a
142    *    menu (ie: no separators on nested sections or in menubars)
143    *
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)
146    *
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.
153    */
154
155   n = g_menu_model_get_n_items (model);
156
157   for (i = 0; i < n; i++)
158     {
159       gint our_position = binding->n_items;
160       gchar *heading = NULL;
161
162       gtk_model_menu_binding_append_item (binding, model, action_namespace, i, &heading);
163
164       if (with_separators && our_position < binding->n_items)
165         {
166           GtkWidget *separator = NULL;
167
168           if (heading)
169             {
170               separator = gtk_menu_item_new_with_label (heading);
171               gtk_widget_set_sensitive (separator, FALSE);
172             }
173           else if (our_position > 0)
174             separator = gtk_separator_menu_item_new ();
175
176           if (separator)
177             {
178               gtk_menu_shell_insert (binding->shell, separator, our_position);
179               gtk_widget_show (separator);
180               binding->n_items++;
181             }
182         }
183
184       g_free (heading);
185     }
186 }
187
188 static void
189 gtk_model_menu_binding_populate (GtkModelMenuBinding *binding)
190 {
191   GList *children;
192
193   /* remove current children */
194   children = gtk_container_get_children (GTK_CONTAINER (binding->shell));
195   while (children)
196     {
197       gtk_container_remove (GTK_CONTAINER (binding->shell), children->data);
198       children = g_list_delete_link (children, children);
199     }
200
201   binding->n_items = 0;
202
203   /* add new items from the model */
204   gtk_model_menu_binding_append_model (binding, binding->model, binding->action_namespace, binding->with_separators);
205 }
206
207 static gboolean
208 gtk_model_menu_binding_handle_changes (gpointer user_data)
209 {
210   GtkModelMenuBinding *binding = user_data;
211
212   /* disconnect all existing signal handlers */
213   while (binding->connected)
214     {
215       g_signal_handlers_disconnect_by_func (binding->connected->data, gtk_model_menu_binding_items_changed, binding);
216       g_object_unref (binding->connected->data);
217
218       binding->connected = g_slist_delete_link (binding->connected, binding->connected);
219     }
220
221   gtk_model_menu_binding_populate (binding);
222
223   binding->update_idle = 0;
224
225   g_object_unref (binding->shell);
226
227   return G_SOURCE_REMOVE;
228 }
229
230 static void
231 gtk_model_menu_binding_items_changed (GMenuModel *model,
232                                       gint        position,
233                                       gint        removed,
234                                       gint        added,
235                                       gpointer    user_data)
236 {
237   GtkModelMenuBinding *binding = user_data;
238
239   if (binding->update_idle == 0)
240     {
241       binding->update_idle = gdk_threads_add_idle (gtk_model_menu_binding_handle_changes, user_data);
242       g_object_ref (binding->shell);
243     }
244 }
245
246 static void
247 gtk_model_menu_bind (GtkMenuShell *shell,
248                      GMenuModel   *model,
249                      const gchar  *action_namespace,
250                      gboolean      with_separators)
251 {
252   GtkModelMenuBinding *binding;
253
254   binding = g_slice_new (GtkModelMenuBinding);
255   binding->model = g_object_ref (model);
256   binding->shell = shell;
257   binding->update_idle = 0;
258   binding->connected = NULL;
259   binding->with_separators = with_separators;
260   binding->action_namespace = g_strdup (action_namespace);
261
262   g_object_set_data_full (G_OBJECT (shell), "gtk-model-menu-binding", binding, gtk_model_menu_binding_free);
263 }
264
265
266 static void
267 gtk_model_menu_populate (GtkMenuShell *shell)
268 {
269   GtkModelMenuBinding *binding;
270
271   binding = (GtkModelMenuBinding*) g_object_get_data (G_OBJECT (shell), "gtk-model-menu-binding");
272
273   gtk_model_menu_binding_populate (binding);
274 }
275
276 GtkWidget *
277 gtk_model_menu_create_menu (GMenuModel    *model,
278                             const gchar   *action_namespace)
279 {
280   GtkWidget *menu;
281
282   menu = gtk_menu_new ();
283
284   gtk_model_menu_bind (GTK_MENU_SHELL (menu), model, action_namespace, TRUE);
285   gtk_model_menu_populate (GTK_MENU_SHELL (menu));
286
287   return menu;
288 }
289
290 /**
291  * gtk_menu_new_from_model:
292  * @model: a #GMenuModel
293  *
294  * Creates a #GtkMenu and populates it with menu items and
295  * submenus according to @model.
296  *
297  * The created menu items are connected to actions found in the
298  * #GtkApplicationWindow to which the menu belongs - typically
299  * by means of being attached to a widget (see gtk_menu_attach_to_widget())
300  * that is contained within the #GtkApplicationWindows widget hierarchy.
301  *
302  * Returns: a new #GtkMenu
303  *
304  * Since: 3.4
305  */
306 GtkWidget *
307 gtk_menu_new_from_model (GMenuModel *model)
308 {
309   GtkWidget *menu;
310
311   menu = gtk_menu_new ();
312   gtk_model_menu_bind (GTK_MENU_SHELL (menu), model, NULL, TRUE);
313   gtk_model_menu_populate (GTK_MENU_SHELL (menu));
314
315   return menu;
316 }
317
318 GtkWidget *
319 gtk_model_menu_create_menu_bar (GMenuModel *model)
320 {
321   GtkWidget *menubar;
322
323   menubar = gtk_menu_bar_new ();
324
325   gtk_model_menu_bind (GTK_MENU_SHELL (menubar), model, NULL, FALSE);
326   gtk_model_menu_populate (GTK_MENU_SHELL (menubar));
327
328   return menubar;
329 }
330
331 /**
332  * gtk_menu_bar_new_from_model:
333  * @model: a #GMenuModel
334  *
335  * Creates a new #GtkMenuBar and populates it with menu items
336  * and submenus according to @model.
337  *
338  * The created menu items are connected to actions found in the
339  * #GtkApplicationWindow to which the menu bar belongs - typically
340  * by means of being contained within the #GtkApplicationWindows
341  * widget hierarchy.
342  *
343  * Returns: a new #GtkMenuBar
344  *
345  * Since: 3.4
346  */
347 GtkWidget *
348 gtk_menu_bar_new_from_model (GMenuModel *model)
349 {
350   GtkWidget *menubar;
351
352   menubar = gtk_menu_bar_new ();
353
354   gtk_model_menu_bind (GTK_MENU_SHELL (menubar), model, NULL, FALSE);
355   gtk_model_menu_populate (GTK_MENU_SHELL (menubar));
356
357   return menubar;
358 }