]> Pileus Git - ~andy/gtk/blob - gtk/gtkmodelmenu.c
a9979cd03138554b57be9c0cce9957c8b7ffdb92
[~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 "gtkmenu.h"
29 #include "gtkmenubar.h"
30 #include "gtkseparatormenuitem.h"
31 #include "gtkmodelmenuitem.h"
32
33 typedef struct {
34   GActionObservable *actions;
35   GMenuModel        *model;
36   GtkMenuShell      *shell;
37   guint              update_idle;
38   GSList            *connected;
39   gboolean           with_separators;
40   gint               n_items;
41 } GtkModelMenuBinding;
42
43 static void
44 gtk_model_menu_binding_items_changed (GMenuModel *model,
45                                       gint        position,
46                                       gint        removed,
47                                       gint        added,
48                                       gpointer    user_data);
49 static void gtk_model_menu_binding_append_model (GtkModelMenuBinding *binding,
50                                                  GMenuModel *model,
51                                                  gboolean with_separators);
52
53 static void
54 gtk_model_menu_binding_free (gpointer data)
55 {
56   GtkModelMenuBinding *binding = data;
57
58   /* disconnect all existing signal handlers */
59   while (binding->connected)
60     {
61       g_signal_handlers_disconnect_by_func (binding->connected->data, gtk_model_menu_binding_items_changed, binding);
62       g_object_unref (binding->connected->data);
63
64       binding->connected = g_slist_delete_link (binding->connected, binding->connected);
65     }
66
67   g_object_unref (binding->actions);
68   g_object_unref (binding->model);
69 }
70
71 static void
72 gtk_model_menu_binding_append_item (GtkModelMenuBinding  *binding,
73                                     GMenuModel           *model,
74                                     gint                  item_index,
75                                     gchar               **heading)
76 {
77   GMenuModel *section;
78
79   if ((section = g_menu_model_get_item_link (model, item_index, "section")))
80     {
81       g_menu_model_get_item_attribute (model, item_index, "label", "s", &heading);
82       gtk_model_menu_binding_append_model (binding, section, FALSE);
83     }
84   else
85     {
86       GtkMenuItem *item;
87
88       item = gtk_model_menu_item_new (model, item_index, binding->actions);
89       gtk_menu_shell_append (binding->shell, GTK_WIDGET (item));
90       gtk_widget_show (GTK_WIDGET (item));
91       binding->n_items++;
92     }
93 }
94
95 static void
96 gtk_model_menu_binding_append_model (GtkModelMenuBinding *binding,
97                                      GMenuModel          *model,
98                                      gboolean             with_separators)
99 {
100   gint n, i;
101
102   g_signal_connect (model, "items-changed", G_CALLBACK (gtk_model_menu_binding_items_changed), binding);
103   binding->connected = g_slist_prepend (binding->connected, g_object_ref (model));
104
105   /* Deciding if we should show a separator is a bit difficult.  There
106    * are two types of separators:
107    *
108    *  - section headings (when sections have 'label' property)
109    *
110    *  - normal separators automatically put between sections
111    *
112    * The easiest way to think about it is that a section usually has a
113    * separator (or heading) immediately before it.
114    *
115    * There are three exceptions to this general rule:
116    *
117    *  - empty sections don't get separators or headings
118    *
119    *  - sections only get separators and headings at the toplevel of a
120    *    menu (ie: no separators on nested sections or in menubars)
121    *
122    *  - the first section in the menu doesn't get a normal separator,
123    *    but it can get a header (if it's not empty)
124    *
125    * Unfortunately, we cannot simply check the size of the section in
126    * order to determine if we should place a header: the section may
127    * contain other sections that are themselves empty.  Instead, we need
128    * to append the section, and check if we ended up with any actual
129    * content.  If we did, then we need to insert before that content.
130    * We use 'our_position' to keep track of this.
131    */
132
133   n = g_menu_model_get_n_items (model);
134
135   for (i = 0; i < n; i++)
136     {
137       gint our_position = binding->n_items;
138       gchar *heading = NULL;
139
140       gtk_model_menu_binding_append_item (binding, model, i, &heading);
141
142       if (with_separators && our_position < binding->n_items)
143         {
144           GtkWidget *separator = NULL;
145
146           if (heading)
147             {
148               separator = gtk_menu_item_new_with_label (heading);
149               gtk_widget_set_sensitive (separator, FALSE);
150             }
151           else if (our_position > 0)
152             separator = gtk_separator_menu_item_new ();
153
154           if (separator)
155             {
156               gtk_menu_shell_insert (binding->shell, separator, our_position);
157               gtk_widget_show (separator);
158               binding->n_items++;
159             }
160         }
161
162       g_free (heading);
163     }
164 }
165
166 static void
167 gtk_model_menu_binding_populate (GtkModelMenuBinding *binding)
168 {
169   GList *children;
170
171   /* remove current children */
172   children = gtk_container_get_children (GTK_CONTAINER (binding->shell));
173   while (children)
174     {
175       gtk_container_remove (GTK_CONTAINER (binding->shell), children->data);
176       children = g_list_delete_link (children, children);
177     }
178
179   binding->n_items = 0;
180
181   /* add new items from the model */
182   gtk_model_menu_binding_append_model (binding, binding->model, binding->with_separators);
183 }
184
185 static gboolean
186 gtk_model_menu_binding_handle_changes (gpointer user_data)
187 {
188   GtkModelMenuBinding *binding = user_data;
189
190   /* disconnect all existing signal handlers */
191   while (binding->connected)
192     {
193       g_signal_handlers_disconnect_by_func (binding->connected->data, gtk_model_menu_binding_items_changed, binding);
194       g_object_unref (binding->connected->data);
195
196       binding->connected = g_slist_delete_link (binding->connected, binding->connected);
197     }
198
199   gtk_model_menu_binding_populate (binding);
200
201   binding->update_idle = 0;
202
203   g_object_unref (binding->shell);
204
205   return G_SOURCE_REMOVE;
206 }
207
208 static void
209 gtk_model_menu_binding_items_changed (GMenuModel *model,
210                                       gint        position,
211                                       gint        removed,
212                                       gint        added,
213                                       gpointer    user_data)
214 {
215   GtkModelMenuBinding *binding = user_data;
216
217   if (binding->update_idle == 0)
218     {
219       binding->update_idle = gdk_threads_add_idle (gtk_model_menu_binding_handle_changes, user_data);
220       g_object_ref (binding->shell);
221     }
222 }
223
224 void
225 gtk_model_menu_bind (GtkMenuShell      *shell,
226                      GMenuModel        *model,
227                      GActionObservable *actions,
228                      gboolean           with_separators)
229 {
230   GtkModelMenuBinding *binding;
231
232   binding = g_slice_new (GtkModelMenuBinding);
233   binding->model = g_object_ref (model);
234   binding->actions = g_object_ref (actions);
235   binding->shell = shell;
236   binding->update_idle = 0;
237   binding->connected = NULL;
238   binding->with_separators = with_separators;
239
240   g_object_set_data_full (G_OBJECT (shell), "gtk-model-menu-binding", binding, gtk_model_menu_binding_free);
241   gtk_model_menu_binding_populate (binding);
242 }
243
244 GtkWidget *
245 gtk_model_menu_create_menu (GMenuModel        *model,
246                             GActionObservable *actions)
247 {
248   GtkWidget *menu;
249
250   menu = gtk_menu_new ();
251   gtk_model_menu_bind (GTK_MENU_SHELL (menu), model, actions, TRUE);
252
253   return menu;
254 }
255
256 GtkWidget *
257 gtk_model_menu_create_menu_bar (GMenuModel        *model,
258                                 GActionObservable *actions)
259 {
260   GtkWidget *menubar;
261
262   menubar = gtk_menu_bar_new ();
263   gtk_model_menu_bind (GTK_MENU_SHELL (menubar), model, actions, FALSE);
264
265   return menubar;
266 }
267