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