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