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