]> Pileus Git - ~andy/gtk/blob - gtk/gtkapplicationwindow.c
GtkApplicationWindow: wire up action activations
[~andy/gtk] / gtk / gtkapplicationwindow.c
1 /*
2  * Copyright © 2011 Canonical Limited
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the licence, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  *
19  * Author: Ryan Lortie <desrt@desrt.ca>
20  */
21
22 #include "config.h"
23
24 #include "gtkapplicationwindow.h"
25
26 #include "gtkseparatormenuitem.h"
27 #include "gtkcheckmenuitem.h"
28 #include "gtkmenubar.h"
29 #include "gactionmuxer.h"
30
31 struct _GtkApplicationWindowPrivate
32 {
33   GSimpleActionGroup *actions;
34   GtkMenuBar *menubar;
35
36   gboolean show_app_menu;
37 };
38
39 static gchar **
40 gtk_application_window_list_actions (GActionGroup *group)
41 {
42   GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (group);
43
44   return g_action_group_list_actions (G_ACTION_GROUP (window->priv->actions));
45 }
46
47 static gboolean
48 gtk_application_window_query_action (GActionGroup        *group,
49                                      const gchar         *action_name,
50                                      gboolean            *enabled,
51                                      const GVariantType **parameter_type,
52                                      const GVariantType **state_type,
53                                      GVariant           **state_hint,
54                                      GVariant           **state)
55 {
56   GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (group);
57
58   return g_action_group_query_action (G_ACTION_GROUP (window->priv->actions),
59                                       action_name, enabled, parameter_type, state_type, state_hint, state);
60 }
61
62 static void
63 gtk_application_window_activate_action (GActionGroup *group,
64                                         const gchar  *action_name,
65                                         GVariant     *parameter)
66 {
67   GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (group);
68
69   return g_action_group_activate_action (G_ACTION_GROUP (window->priv->actions), action_name, parameter);
70 }
71
72 static void
73 gtk_application_window_change_action_state (GActionGroup *group,
74                                             const gchar  *action_name,
75                                             GVariant     *state)
76 {
77   GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (group);
78
79   return g_action_group_change_action_state (G_ACTION_GROUP (window->priv->actions), action_name, state);
80 }
81
82 static GAction *
83 gtk_application_window_lookup_action (GActionMap *action_map,
84                                       const gchar *action_name)
85 {
86   GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (action_map);
87
88   return g_action_map_lookup_action (G_ACTION_MAP (window->priv->actions), action_name);
89 }
90
91 static void
92 gtk_application_window_add_action (GActionMap *action_map,
93                                    GAction    *action)
94 {
95   GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (action_map);
96
97   g_action_map_add_action (G_ACTION_MAP (window->priv->actions), action);
98 }
99
100 static void
101 gtk_application_window_remove_action (GActionMap  *action_map,
102                                       const gchar *action_name)
103 {
104   GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (action_map);
105
106   g_action_map_remove_action (G_ACTION_MAP (window->priv->actions), action_name);
107 }
108
109 static void
110 gtk_application_window_group_iface_init (GActionGroupInterface *iface)
111 {
112   iface->list_actions = gtk_application_window_list_actions;
113   iface->query_action = gtk_application_window_query_action;
114   iface->activate_action = gtk_application_window_activate_action;
115   iface->change_action_state = gtk_application_window_change_action_state;
116 }
117
118 static void
119 gtk_application_window_map_iface_init (GActionMapInterface *iface)
120 {
121   iface->lookup_action = gtk_application_window_lookup_action;
122   iface->add_action = gtk_application_window_add_action;
123   iface->remove_action = gtk_application_window_remove_action;
124 }
125
126 G_DEFINE_TYPE_WITH_CODE (GtkApplicationWindow, gtk_application_window, GTK_TYPE_WINDOW,
127                          G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, gtk_application_window_group_iface_init)
128                          G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_MAP, gtk_application_window_map_iface_init))
129
130 enum {
131   PROP_0,
132   PROP_SHOW_APP_MENU,
133   N_PROPS
134 };
135 static GParamSpec *gtk_application_window_properties[N_PROPS];
136
137 static void
138 gtk_application_window_real_get_preferred_height (GtkWidget *widget,
139                                                   gint      *minimum_height,
140                                                   gint      *natural_height)
141 {
142   GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (widget);
143
144   GTK_WIDGET_CLASS (gtk_application_window_parent_class)
145     ->get_preferred_height (widget, minimum_height, natural_height);
146
147   if (window->priv->menubar != NULL)
148     {
149       gint menubar_min_height, menubar_nat_height;
150
151       gtk_widget_get_preferred_height (GTK_WIDGET (window->priv->menubar), &menubar_min_height, &menubar_nat_height);
152       *minimum_height += menubar_min_height;
153       *natural_height += menubar_nat_height;
154     }
155 }
156
157 static void
158 gtk_application_window_real_get_preferred_height_for_width (GtkWidget *widget,
159                                                             gint       width,
160                                                             gint      *minimum_height,
161                                                             gint      *natural_height)
162 {
163   GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (widget);
164
165   GTK_WIDGET_CLASS (gtk_application_window_parent_class)
166     ->get_preferred_height_for_width (widget, width, minimum_height, natural_height);
167
168   if (window->priv->menubar != NULL)
169     {
170       gint menubar_min_height, menubar_nat_height;
171
172       gtk_widget_get_preferred_height_for_width (GTK_WIDGET (window->priv->menubar), width, &menubar_min_height, &menubar_nat_height);
173       *minimum_height += menubar_min_height;
174       *natural_height += menubar_nat_height;
175     }
176 }
177
178 static void
179 gtk_application_window_real_get_preferred_width (GtkWidget *widget,
180                                                  gint      *minimum_width,
181                                                  gint      *natural_width)
182 {
183   GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (widget);
184
185   GTK_WIDGET_CLASS (gtk_application_window_parent_class)
186     ->get_preferred_width (widget, minimum_width, natural_width);
187
188   if (window->priv->menubar != NULL)
189     {
190       gint menubar_min_width, menubar_nat_width;
191
192       gtk_widget_get_preferred_width (GTK_WIDGET (window->priv->menubar), &menubar_min_width, &menubar_nat_width);
193       *minimum_width = MAX (*minimum_width, menubar_min_width);
194       *natural_width = MAX (*natural_width, menubar_nat_width);
195     }
196 }
197
198 static void
199 gtk_application_window_real_get_preferred_width_for_height (GtkWidget *widget,
200                                                             gint       height,
201                                                             gint      *minimum_width,
202                                                             gint      *natural_width)
203 {
204   GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (widget);
205
206   GTK_WIDGET_CLASS (gtk_application_window_parent_class)
207     ->get_preferred_width_for_height (widget, height, minimum_width, natural_width);
208
209   if (window->priv->menubar != NULL)
210     {
211       gint menubar_min_width, menubar_nat_width;
212
213       gtk_widget_get_preferred_width_for_height (GTK_WIDGET (window->priv->menubar), height, &menubar_min_width, &menubar_nat_width);
214       *minimum_width = MAX (*minimum_width, menubar_min_width);
215       *natural_width = MAX (*natural_width, menubar_nat_width);
216     }
217 }
218
219 static void
220 gtk_application_window_real_size_allocate (GtkWidget     *widget,
221                                            GtkAllocation *allocation)
222 {
223   GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (widget);
224
225   if (window->priv->menubar != NULL)
226     {
227       GtkAllocation menubar_allocation = *allocation;
228       gint menubar_min_height, menubar_nat_height;
229       GtkWidget *child;
230
231       gtk_widget_get_preferred_height_for_width (GTK_WIDGET (window->priv->menubar), allocation->width, &menubar_min_height, &menubar_nat_height);
232
233       menubar_allocation.height = menubar_min_height;
234       gtk_widget_size_allocate (GTK_WIDGET (window->priv->menubar), &menubar_allocation);
235
236       child = gtk_bin_get_child (GTK_BIN (window));
237       if (child != NULL && gtk_widget_get_visible (child))
238         {
239           GtkAllocation child_allocation = *allocation;
240           gint border_width;
241
242           child_allocation.height = MAX (1, child_allocation.height - menubar_min_height);
243
244           border_width = gtk_container_get_border_width (GTK_CONTAINER (window));
245           child_allocation.x += border_width;
246           child_allocation.y += border_width + menubar_min_height;
247           child_allocation.width -= border_width * 2;
248           child_allocation.height -= border_width * 2 - menubar_min_height;
249           gtk_widget_size_allocate (child, &child_allocation);
250         }
251
252       gtk_widget_set_allocation (widget, allocation);
253     }
254   else
255     GTK_WIDGET_CLASS (gtk_application_window_parent_class)
256       ->size_allocate (widget, allocation);
257 }
258
259 static void
260 gtk_application_window_real_map (GtkWidget *widget)
261 {
262   GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (widget);
263
264   /* XXX could elimate this by tweaking gtk_window_map */
265   if (window->priv->menubar)
266     gtk_widget_map (GTK_WIDGET (window->priv->menubar));
267
268   GTK_WIDGET_CLASS (gtk_application_window_parent_class)
269     ->map (widget);
270 }
271
272 static void
273 gtk_application_window_real_forall_internal (GtkContainer *container,
274                                              gboolean      include_internal,
275                                              GtkCallback   callback,
276                                              gpointer      user_data)
277 {
278   GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (container);
279
280   if (window->priv->menubar)
281     callback (GTK_WIDGET (window->priv->menubar), user_data);
282
283   GTK_CONTAINER_CLASS (gtk_application_window_parent_class)
284     ->forall (container, include_internal, callback, user_data);
285 }
286
287
288 static void
289 gtk_application_window_get_property (GObject *object, guint prop_id,
290                                      GValue *value, GParamSpec *pspec)
291 {
292   GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (object);
293
294   switch (prop_id)
295     {
296     case PROP_SHOW_APP_MENU:
297       g_value_set_boolean (value, window->priv->show_app_menu);
298       break;
299
300     default:
301       g_assert_not_reached ();
302     }
303 }
304
305 static void
306 gtk_application_window_set_property (GObject *object, guint prop_id,
307                                      const GValue *value, GParamSpec *pspec)
308 {
309   GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (object);
310
311   switch (prop_id)
312     {
313     case PROP_SHOW_APP_MENU:
314       gtk_application_window_set_show_app_menu (window, g_value_get_boolean (value));
315       break;
316
317     default:
318       g_assert_not_reached ();
319     }
320 }
321
322 static void
323 gtk_application_window_finalize (GObject *object)
324 {
325   GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (object);
326
327   if (window->priv->menubar)
328     g_object_unref (window->priv->menubar);
329
330   g_object_unref (window->priv->actions);
331
332   G_OBJECT_CLASS (gtk_application_window_parent_class)
333     ->finalize (object);
334 }
335
336 static void
337 gtk_application_window_init (GtkApplicationWindow *window)
338 {
339   window->priv = G_TYPE_INSTANCE_GET_PRIVATE (window, GTK_TYPE_APPLICATION_WINDOW, GtkApplicationWindowPrivate);
340
341   window->priv->actions = g_simple_action_group_new ();
342
343   /* window->priv->actions is the one and only ref on the group, so when
344    * we finalize, the action group will die, disconnecting all signals.
345    */
346   g_signal_connect_swapped (window->priv->actions, "action-added",
347                             G_CALLBACK (g_action_group_action_added), window);
348   g_signal_connect_swapped (window->priv->actions, "action-enabled-changed",
349                             G_CALLBACK (g_action_group_action_enabled_changed), window);
350   g_signal_connect_swapped (window->priv->actions, "action-state-changed",
351                             G_CALLBACK (g_action_group_action_state_changed), window);
352   g_signal_connect_swapped (window->priv->actions, "action-removed",
353                             G_CALLBACK (g_action_group_action_removed), window);
354 }
355
356 static void
357 gtk_application_window_class_init (GtkApplicationWindowClass *class)
358 {
359   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class);
360   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
361   GObjectClass *object_class = G_OBJECT_CLASS (class);
362
363   container_class->forall = gtk_application_window_real_forall_internal;
364   widget_class->get_preferred_height = gtk_application_window_real_get_preferred_height;
365   widget_class->get_preferred_height_for_width = gtk_application_window_real_get_preferred_height_for_width;
366   widget_class->get_preferred_width = gtk_application_window_real_get_preferred_width;
367   widget_class->get_preferred_width_for_height = gtk_application_window_real_get_preferred_width_for_height;
368   widget_class->size_allocate = gtk_application_window_real_size_allocate;
369   widget_class->map = gtk_application_window_real_map;
370   object_class->get_property = gtk_application_window_get_property;
371   object_class->set_property = gtk_application_window_set_property;
372   object_class->finalize = gtk_application_window_finalize;
373
374   gtk_application_window_properties[PROP_SHOW_APP_MENU] =
375     g_param_spec_boolean ("show-app-menu", "show application menu",
376                           "TRUE if the application menu should be included in the menubar at the top of the window",
377                           FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
378   g_object_class_install_properties (object_class, N_PROPS, gtk_application_window_properties);
379   g_type_class_add_private (class, sizeof (GtkApplicationWindowPrivate));
380 }
381
382 GtkWidget *
383 gtk_application_window_new (GtkApplication *application)
384 {
385   g_return_val_if_fail (GTK_IS_APPLICATION (application), NULL);
386
387   return g_object_new (GTK_TYPE_APPLICATION_WINDOW,
388                        "application", application,
389                        NULL);
390 }
391
392 gboolean
393 gtk_application_window_get_show_app_menu (GtkApplicationWindow *window)
394 {
395   return window->priv->show_app_menu;
396 }
397
398 void
399 gtk_application_window_set_show_app_menu (GtkApplicationWindow *window,
400                                           gboolean              show_app_menu)
401 {
402   if (window->priv->show_app_menu != show_app_menu)
403     {
404       window->priv->show_app_menu = show_app_menu;
405       g_object_notify_by_pspec (G_OBJECT (window), gtk_application_window_properties[PROP_SHOW_APP_MENU]);
406
407       if (show_app_menu)
408         {
409           GtkWidget *menubar;
410           GtkWidget *item;
411
412           item = gtk_menu_item_new_with_label ("Application");
413           gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), gtk_application_window_get_app_menu (window));
414
415           menubar = gtk_menu_bar_new ();
416           window->priv->menubar = g_object_ref_sink (menubar);
417           gtk_menu_shell_append (GTK_MENU_SHELL (menubar), item);
418           gtk_widget_set_parent (menubar, GTK_WIDGET (window));
419           gtk_widget_show_all (menubar);
420         }
421       else
422         {
423           gtk_widget_unparent (GTK_WIDGET (window->priv->menubar));
424           g_object_unref (window->priv->menubar);
425         }
426     }
427 }
428
429 /* GtkMenu construction {{{1 */
430
431 typedef struct {
432   GActionGroup *group;
433   gchar        *name;
434   gchar        *target;
435   gulong        enabled_changed_id;
436   gulong        state_changed_id;
437   gulong        activate_handler;
438 } ActionData;
439
440 static void
441 action_data_free (gpointer data)
442 {
443   ActionData *a = data;
444
445   if (a->enabled_changed_id)
446     g_signal_handler_disconnect (a->group, a->enabled_changed_id);
447
448   if (a->state_changed_id)
449     g_signal_handler_disconnect (a->group, a->state_changed_id);
450
451   g_object_unref (a->group);
452   g_free (a->name);
453   g_free (a->target);
454
455   g_free (a);
456 }
457
458 static void
459 enabled_changed (GActionGroup *group,
460                  const gchar  *action_name,
461                  gboolean      enabled,
462                  GtkWidget    *widget)
463 {
464   gtk_widget_set_sensitive (widget, enabled);
465 }
466
467 static void
468 item_activated (GtkWidget *w,
469                 gpointer   data)
470 {
471   ActionData *a;
472   GVariant *parameter;
473
474   a = g_object_get_data (G_OBJECT (w), "action");
475   if (a->target)
476     parameter = g_variant_ref_sink (g_variant_new_string (a->target));
477   else
478     parameter = NULL;
479   g_action_group_activate_action (a->group, a->name, parameter);
480   if (parameter)
481     g_variant_unref (parameter);
482 }
483
484 static void
485 toggle_state_changed (GActionGroup     *group,
486                       const gchar      *name,
487                       GVariant         *state,
488                       GtkCheckMenuItem *w)
489 {
490   ActionData *a;
491
492   a = g_object_get_data (G_OBJECT (w), "action");
493   g_signal_handler_block (w, a->activate_handler);
494   gtk_check_menu_item_set_active (w, g_variant_get_boolean (state));
495   g_signal_handler_unblock (w, a->activate_handler);
496 }
497
498 static void
499 radio_state_changed (GActionGroup     *group,
500                      const gchar      *name,
501                      GVariant         *state,
502                      GtkCheckMenuItem *w)
503 {
504   ActionData *a;
505   gboolean b;
506
507   a = g_object_get_data (G_OBJECT (w), "action");
508   g_signal_handler_block (w, a->activate_handler);
509   b = g_strcmp0 (a->target, g_variant_get_string (state, NULL)) == 0;
510   gtk_check_menu_item_set_active (w, b);
511   g_signal_handler_unblock (w, a->activate_handler);
512 }
513
514 static GtkWidget *
515 create_menuitem_from_model (GMenuModel   *model,
516                             gint          item,
517                             GActionGroup *group)
518 {
519   GtkWidget *w;
520   gchar *label;
521   gchar *action;
522   gchar *target;
523   gchar *s;
524   ActionData *a;
525   const GVariantType *type;
526   GVariant *v;
527
528   label = NULL;
529   g_menu_model_get_item_attribute (model, item, G_MENU_ATTRIBUTE_LABEL, "s", &label);
530
531   action = NULL;
532   g_menu_model_get_item_attribute (model, item, G_MENU_ATTRIBUTE_ACTION, "s", &action);
533
534   if (action != NULL)
535     type = g_action_group_get_action_state_type (group, action);
536   else
537     type = NULL;
538
539   if (type == NULL)
540     w = gtk_menu_item_new_with_mnemonic (label);
541   else if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN))
542     w = gtk_check_menu_item_new_with_label (label);
543   else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING))
544     {
545       w = gtk_check_menu_item_new_with_label (label);
546       gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (w), TRUE);
547     }
548   else
549     g_assert_not_reached ();
550
551   if (action != NULL)
552     {
553       a = g_new0 (ActionData, 1);
554       a->group = g_object_ref (group);
555       a->name = g_strdup (action);
556       g_object_set_data_full (G_OBJECT (w), "action", a, action_data_free);
557
558       if (!g_action_group_get_action_enabled (group, action))
559         gtk_widget_set_sensitive (w, FALSE);
560
561       s = g_strconcat ("action-enabled-changed::", action, NULL);
562       a->enabled_changed_id = g_signal_connect (group, s,
563                                                 G_CALLBACK (enabled_changed), w);
564       g_free (s);
565       a->activate_handler = g_signal_connect (w, "activate",
566                                               G_CALLBACK (item_activated), NULL);
567
568       if (type == NULL)
569         {
570           /* all set */
571         }
572       else if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN))
573         {
574           s = g_strconcat ("action-state-changed::", action, NULL);
575           a->state_changed_id = g_signal_connect (group, s,
576                                                   G_CALLBACK (toggle_state_changed), w);
577           g_free (s);
578           v = g_action_group_get_action_state (group, action);
579           gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w),
580                                           g_variant_get_boolean (v));
581           g_variant_unref (v);
582         }
583       else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING))
584         {
585           s = g_strconcat ("action-state-changed::", action, NULL);
586           a->state_changed_id = g_signal_connect (group, s,
587                                                   G_CALLBACK (radio_state_changed), w);
588           g_free (s);
589           g_menu_model_get_item_attribute (model, item, G_MENU_ATTRIBUTE_TARGET, "s", &target);
590           a->target = g_strdup (target);
591           v = g_action_group_get_action_state (group, action);
592           gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w),
593                                           g_strcmp0 (g_variant_get_string (v, NULL), target) == 0);
594           g_variant_unref (v);
595           g_free (target);
596         }
597       else
598         g_assert_not_reached ();
599     }
600
601   g_free (label);
602   g_free (action);
603
604   return w;
605 }
606
607 static void populate_menu_from_model (GtkMenuShell *menu,
608                                       GMenuModel   *model,
609                                       GActionGroup *group);
610
611 static void
612 append_items_from_model (GtkMenuShell *menu,
613                          GMenuModel   *model,
614                          GActionGroup *group,
615                          gboolean     *need_separator,
616                          const gchar  *heading)
617 {
618   gint n;
619   gint i;
620   GtkWidget *w;
621   GtkWidget *menuitem;
622   GtkWidget *submenu;
623   GMenuModel *m;
624   gchar *label;
625
626   n = g_menu_model_get_n_items (model);
627
628   if (*need_separator && n > 0)
629     {
630       w = gtk_separator_menu_item_new ();
631       gtk_widget_show (w);
632       gtk_menu_shell_append (menu, w);
633       *need_separator = FALSE;
634     }
635
636   if (heading != NULL)
637     {
638       w = gtk_menu_item_new_with_label (heading);
639       gtk_widget_show (w);
640       gtk_widget_set_sensitive (w, FALSE);
641       gtk_menu_shell_append (GTK_MENU_SHELL (menu), w);
642     }
643
644   for (i = 0; i < n; i++)
645     {
646       if ((m = g_menu_model_get_item_link (model, i, G_MENU_LINK_SECTION)))
647         {
648           label = NULL;
649           g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_LABEL, "s", &label);
650           append_items_from_model (menu, m, group, need_separator, label);
651           g_object_unref (m);
652           g_free (label);
653           continue;
654         }
655
656       if (*need_separator)
657         {
658           w = gtk_separator_menu_item_new ();
659           gtk_widget_show (w);
660           gtk_menu_shell_append (menu, w);
661           *need_separator = FALSE;
662         }
663
664       menuitem = create_menuitem_from_model (model, i, group);
665
666       if ((m = g_menu_model_get_item_link (model, i, G_MENU_LINK_SUBMENU)))
667         {
668           submenu = gtk_menu_new ();
669           populate_menu_from_model (GTK_MENU_SHELL (submenu), m, group);
670           gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem), submenu);
671           g_object_unref (m);
672         }
673
674       gtk_widget_show (menuitem);
675       gtk_menu_shell_append (menu, menuitem);
676
677       *need_separator = TRUE;
678     }
679 }
680
681 static void
682 populate_menu_from_model (GtkMenuShell *menu,
683                           GMenuModel   *model,
684                           GActionGroup *group)
685 {
686   gboolean need_separator;
687
688   need_separator = FALSE;
689   append_items_from_model (menu, model, group, &need_separator, NULL);
690 }
691
692 typedef struct {
693   GtkApplication *application;
694   GtkMenuShell   *menu;
695   guint           update_idle;
696   GHashTable     *connected;
697 } ItemsChangedData;
698
699 static void
700 free_items_changed_data (gpointer data)
701 {
702   ItemsChangedData *d = data;
703
704   g_object_unref (d->application);
705
706   if (d->update_idle != 0)
707     g_source_remove (d->update_idle);
708
709   g_hash_table_unref (d->connected);
710
711   g_free (d);
712 }
713
714 static gboolean
715 repopulate_menu (gpointer data)
716 {
717   ItemsChangedData *d = data;
718   GList *children, *l;
719   GtkWidget *child;
720   GMenuModel *model;
721
722   /* remove current children */
723   children = gtk_container_get_children (GTK_CONTAINER (d->menu));
724   for (l = children; l; l = l->next)
725     {
726       child = l->data;
727       gtk_container_remove (GTK_CONTAINER (d->menu), child);
728     }
729   g_list_free (children);
730
731   /* repopulate */
732   model = g_application_get_menu (G_APPLICATION (d->application));
733   populate_menu_from_model (d->menu, model, G_ACTION_GROUP (d->application));
734
735   d->update_idle = 0;
736
737   return FALSE;
738 }
739
740 static void
741 connect_to_items_changed (GMenuModel *model,
742                           GCallback   callback,
743                           gpointer    data)
744 {
745   ItemsChangedData *d = data;
746   gint i;
747   GMenuModel *m;
748   GMenuLinkIter *iter;
749
750   if (!g_hash_table_lookup (d->connected, model))
751     {
752       g_signal_connect (model, "items-changed", callback, data);
753       g_hash_table_insert (d->connected, model, model);
754     }
755
756   for (i = 0; i < g_menu_model_get_n_items (model); i++)
757     {
758       iter = g_menu_model_iterate_item_links (model, i);
759       while (g_menu_link_iter_next (iter))
760         {
761           m = g_menu_link_iter_get_value (iter);
762           connect_to_items_changed (m, callback, data);
763           g_object_unref (m);
764         }
765       g_object_unref (iter);
766     }
767 }
768
769 static void
770 items_changed (GMenuModel *model,
771                gint        position,
772                gint        removed,
773                gint        added,
774                gpointer    data)
775 {
776   ItemsChangedData *d = data;
777
778   if (d->update_idle == 0)
779     d->update_idle = gdk_threads_add_idle (repopulate_menu, data);
780   connect_to_items_changed (model, G_CALLBACK (items_changed), data);
781 }
782
783 /**
784  * gtk_application_window_get_app_menu:
785  * @application: a #GtkApplication
786  *
787  * Populates a menu widget from a menu model that is
788  * associated with @application. See g_application_set_menu().
789  * The menu items will be connected to action of @application,
790  * as indicated by the menu model. The menus contents will be
791  * updated automatically in response to menu model changes.
792  *
793  * It is the callers responsibility to add the menu at a
794  * suitable place in the widget hierarchy.
795  *
796  * This function returns %NULL if @application has no associated
797  * menu model. It also returns %NULL if the menu model is
798  * represented outside the application, e.g. by an application
799  * menu in the desktop shell.
800  *
801  * @menu may be a #GtkMenu or a #GtkMenuBar.
802  *
803  * Returns: A #GtkMenu that has been populated from the
804  *     #GMenuModel that is associated with @application,
805  *     or %NULL
806  */
807 GtkWidget *
808 gtk_application_window_get_app_menu (GtkApplicationWindow *window)
809 {
810   GtkApplication *application;
811   GtkWidget *menu;
812   GMenuModel *model;
813   ItemsChangedData *data;
814   GActionMuxer *muxer;
815
816   application = gtk_window_get_application (GTK_WINDOW (window));
817
818   model = g_application_get_menu (G_APPLICATION (application));
819
820   if (!model)
821     return NULL;
822
823   menu = gtk_menu_new ();
824
825   muxer = g_action_muxer_new ();
826   g_action_muxer_insert (muxer, "app", G_ACTION_GROUP (application));
827   g_action_muxer_insert (muxer, "win", G_ACTION_GROUP (window));
828   populate_menu_from_model (GTK_MENU_SHELL (menu), model, G_ACTION_GROUP (muxer));
829   g_object_unref (muxer);
830
831   data = g_new (ItemsChangedData, 1);
832   data->application = g_object_ref (application);
833   data->menu = GTK_MENU_SHELL (menu);
834   data->update_idle = 0;
835   data->connected = g_hash_table_new (NULL, NULL);
836
837   g_object_set_data_full (G_OBJECT (menu), "gtk-application-menu-data",
838                           data, free_items_changed_data);
839
840   connect_to_items_changed (model, G_CALLBACK (items_changed), data);
841
842   return menu;
843 }