]> Pileus Git - ~andy/gtk/blob - gtk/gtkapplicationwindow.c
GtkApplicationWindow: Use dispose for GObjects
[~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_dispose (GObject *object)
362 {
363   GtkApplicationWindow *window = GTK_APPLICATION_WINDOW (object);
364
365   g_clear_object (&window->priv->menubar);
366   g_clear_object (&window->priv->actions);
367
368   G_OBJECT_CLASS (gtk_application_window_parent_class)
369     ->dispose (object);
370 }
371
372 static void
373 gtk_application_window_init (GtkApplicationWindow *window)
374 {
375   window->priv = G_TYPE_INSTANCE_GET_PRIVATE (window, GTK_TYPE_APPLICATION_WINDOW, GtkApplicationWindowPrivate);
376
377   window->priv->actions = g_simple_action_group_new ();
378
379   /* window->priv->actions is the one and only ref on the group, so when
380    * we dispose, the action group will die, disconnecting all signals.
381    */
382   g_signal_connect_swapped (window->priv->actions, "action-added",
383                             G_CALLBACK (g_action_group_action_added), window);
384   g_signal_connect_swapped (window->priv->actions, "action-enabled-changed",
385                             G_CALLBACK (g_action_group_action_enabled_changed), window);
386   g_signal_connect_swapped (window->priv->actions, "action-state-changed",
387                             G_CALLBACK (g_action_group_action_state_changed), window);
388   g_signal_connect_swapped (window->priv->actions, "action-removed",
389                             G_CALLBACK (g_action_group_action_removed), window);
390 }
391
392 static void
393 gtk_application_window_class_init (GtkApplicationWindowClass *class)
394 {
395   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class);
396   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
397   GObjectClass *object_class = G_OBJECT_CLASS (class);
398
399   container_class->forall = gtk_application_window_real_forall_internal;
400   widget_class->get_preferred_height = gtk_application_window_real_get_preferred_height;
401   widget_class->get_preferred_height_for_width = gtk_application_window_real_get_preferred_height_for_width;
402   widget_class->get_preferred_width = gtk_application_window_real_get_preferred_width;
403   widget_class->get_preferred_width_for_height = gtk_application_window_real_get_preferred_width_for_height;
404   widget_class->size_allocate = gtk_application_window_real_size_allocate;
405   widget_class->realize = gtk_application_window_real_realize;
406   widget_class->map = gtk_application_window_real_map;
407   object_class->get_property = gtk_application_window_get_property;
408   object_class->set_property = gtk_application_window_set_property;
409   object_class->dispose = gtk_application_window_dispose;
410
411   gtk_application_window_properties[PROP_SHOW_APP_MENU] =
412     g_param_spec_boolean ("show-app-menu", "show application menu",
413                           "TRUE if the application menu should be included in the menubar at the top of the window",
414                           FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
415   g_object_class_install_properties (object_class, N_PROPS, gtk_application_window_properties);
416   g_type_class_add_private (class, sizeof (GtkApplicationWindowPrivate));
417 }
418
419 GtkWidget *
420 gtk_application_window_new (GtkApplication *application)
421 {
422   g_return_val_if_fail (GTK_IS_APPLICATION (application), NULL);
423
424   return g_object_new (GTK_TYPE_APPLICATION_WINDOW,
425                        "application", application,
426                        NULL);
427 }
428
429 gboolean
430 gtk_application_window_get_show_app_menu (GtkApplicationWindow *window)
431 {
432   return window->priv->override_show_app_menu;
433 }
434
435 static void
436 recalculate_app_menu_state (GtkApplicationWindow   *window)
437 {
438   if ((window->priv->did_override_show_app_menu
439        && window->priv->override_show_app_menu)
440       || window->priv->default_show_app_menu)
441     {
442       GtkWidget *menubar;
443       GtkWidget *item;
444
445       item = gtk_menu_item_new_with_label ("Application");
446       gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), gtk_application_window_get_app_menu (window));
447
448       menubar = gtk_menu_bar_new ();
449       window->priv->menubar = g_object_ref_sink (menubar);
450       gtk_menu_shell_append (GTK_MENU_SHELL (menubar), item);
451       gtk_widget_set_parent (menubar, GTK_WIDGET (window));
452       gtk_widget_show_all (menubar);
453     }
454   else
455     {
456       gtk_widget_unparent (GTK_WIDGET (window->priv->menubar));
457       g_object_unref (window->priv->menubar);
458     }
459 }
460
461 void
462 gtk_application_window_set_show_app_menu (GtkApplicationWindow *window,
463                                           gboolean              show_app_menu)
464 {
465   show_app_menu = !!show_app_menu;
466   window->priv->did_override_show_app_menu = TRUE;
467   if (window->priv->override_show_app_menu != show_app_menu)
468     {
469       window->priv->override_show_app_menu = show_app_menu;
470       recalculate_app_menu_state (window);
471       g_object_notify_by_pspec (G_OBJECT (window), gtk_application_window_properties[PROP_SHOW_APP_MENU]);
472     }
473 }
474
475 /* GtkMenu construction {{{1 */
476
477 typedef struct {
478   GActionGroup *group;
479   gchar        *name;
480   gchar        *target;
481   gulong        enabled_changed_id;
482   gulong        state_changed_id;
483   gulong        activate_handler;
484 } ActionData;
485
486 static void
487 action_data_free (gpointer data)
488 {
489   ActionData *a = data;
490
491   if (a->enabled_changed_id)
492     g_signal_handler_disconnect (a->group, a->enabled_changed_id);
493
494   if (a->state_changed_id)
495     g_signal_handler_disconnect (a->group, a->state_changed_id);
496
497   g_object_unref (a->group);
498   g_free (a->name);
499   g_free (a->target);
500
501   g_free (a);
502 }
503
504 static void
505 enabled_changed (GActionGroup *group,
506                  const gchar  *action_name,
507                  gboolean      enabled,
508                  GtkWidget    *widget)
509 {
510   gtk_widget_set_sensitive (widget, enabled);
511 }
512
513 static void
514 item_activated (GtkWidget *w,
515                 gpointer   data)
516 {
517   ActionData *a;
518   GVariant *parameter;
519
520   a = g_object_get_data (G_OBJECT (w), "action");
521   if (a->target)
522     parameter = g_variant_ref_sink (g_variant_new_string (a->target));
523   else
524     parameter = NULL;
525   g_action_group_activate_action (a->group, a->name, parameter);
526   if (parameter)
527     g_variant_unref (parameter);
528 }
529
530 static void
531 toggle_state_changed (GActionGroup     *group,
532                       const gchar      *name,
533                       GVariant         *state,
534                       GtkCheckMenuItem *w)
535 {
536   ActionData *a;
537
538   a = g_object_get_data (G_OBJECT (w), "action");
539   g_signal_handler_block (w, a->activate_handler);
540   gtk_check_menu_item_set_active (w, g_variant_get_boolean (state));
541   g_signal_handler_unblock (w, a->activate_handler);
542 }
543
544 static void
545 radio_state_changed (GActionGroup     *group,
546                      const gchar      *name,
547                      GVariant         *state,
548                      GtkCheckMenuItem *w)
549 {
550   ActionData *a;
551   gboolean b;
552
553   a = g_object_get_data (G_OBJECT (w), "action");
554   g_signal_handler_block (w, a->activate_handler);
555   b = g_strcmp0 (a->target, g_variant_get_string (state, NULL)) == 0;
556   gtk_check_menu_item_set_active (w, b);
557   g_signal_handler_unblock (w, a->activate_handler);
558 }
559
560 static GtkWidget *
561 create_menuitem_from_model (GMenuModel   *model,
562                             gint          item,
563                             GActionGroup *group)
564 {
565   GtkWidget *w;
566   gchar *label;
567   gchar *action;
568   gchar *target;
569   gchar *s;
570   ActionData *a;
571   const GVariantType *type;
572   GVariant *v;
573
574   label = NULL;
575   g_menu_model_get_item_attribute (model, item, G_MENU_ATTRIBUTE_LABEL, "s", &label);
576
577   action = NULL;
578   g_menu_model_get_item_attribute (model, item, G_MENU_ATTRIBUTE_ACTION, "s", &action);
579
580   if (action != NULL)
581     type = g_action_group_get_action_state_type (group, action);
582   else
583     type = NULL;
584
585   if (type == NULL)
586     w = gtk_menu_item_new_with_mnemonic (label);
587   else if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN))
588     w = gtk_check_menu_item_new_with_label (label);
589   else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING))
590     {
591       w = gtk_check_menu_item_new_with_label (label);
592       gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (w), TRUE);
593     }
594   else
595     g_assert_not_reached ();
596
597   if (action != NULL)
598     {
599       a = g_new0 (ActionData, 1);
600       a->group = g_object_ref (group);
601       a->name = g_strdup (action);
602       g_object_set_data_full (G_OBJECT (w), "action", a, action_data_free);
603
604       if (!g_action_group_get_action_enabled (group, action))
605         gtk_widget_set_sensitive (w, FALSE);
606
607       s = g_strconcat ("action-enabled-changed::", action, NULL);
608       a->enabled_changed_id = g_signal_connect (group, s,
609                                                 G_CALLBACK (enabled_changed), w);
610       g_free (s);
611       a->activate_handler = g_signal_connect (w, "activate",
612                                               G_CALLBACK (item_activated), NULL);
613
614       if (type == NULL)
615         {
616           /* all set */
617         }
618       else if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN))
619         {
620           s = g_strconcat ("action-state-changed::", action, NULL);
621           a->state_changed_id = g_signal_connect (group, s,
622                                                   G_CALLBACK (toggle_state_changed), w);
623           g_free (s);
624           v = g_action_group_get_action_state (group, action);
625           gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w),
626                                           g_variant_get_boolean (v));
627           g_variant_unref (v);
628         }
629       else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING))
630         {
631           s = g_strconcat ("action-state-changed::", action, NULL);
632           a->state_changed_id = g_signal_connect (group, s,
633                                                   G_CALLBACK (radio_state_changed), w);
634           g_free (s);
635           g_menu_model_get_item_attribute (model, item, G_MENU_ATTRIBUTE_TARGET, "s", &target);
636           a->target = g_strdup (target);
637           v = g_action_group_get_action_state (group, action);
638           gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w),
639                                           g_strcmp0 (g_variant_get_string (v, NULL), target) == 0);
640           g_variant_unref (v);
641           g_free (target);
642         }
643       else
644         g_assert_not_reached ();
645     }
646
647   g_free (label);
648   g_free (action);
649
650   return w;
651 }
652
653 static void populate_menu_from_model (GtkMenuShell *menu,
654                                       GMenuModel   *model,
655                                       GActionGroup *group);
656
657 static void
658 append_items_from_model (GtkMenuShell *menu,
659                          GMenuModel   *model,
660                          GActionGroup *group,
661                          gboolean     *need_separator,
662                          const gchar  *heading)
663 {
664   gint n;
665   gint i;
666   GtkWidget *w;
667   GtkWidget *menuitem;
668   GtkWidget *submenu;
669   GMenuModel *m;
670   gchar *label;
671
672   n = g_menu_model_get_n_items (model);
673
674   if (*need_separator && n > 0)
675     {
676       w = gtk_separator_menu_item_new ();
677       gtk_widget_show (w);
678       gtk_menu_shell_append (menu, w);
679       *need_separator = FALSE;
680     }
681
682   if (heading != NULL)
683     {
684       w = gtk_menu_item_new_with_label (heading);
685       gtk_widget_show (w);
686       gtk_widget_set_sensitive (w, FALSE);
687       gtk_menu_shell_append (GTK_MENU_SHELL (menu), w);
688     }
689
690   for (i = 0; i < n; i++)
691     {
692       if ((m = g_menu_model_get_item_link (model, i, G_MENU_LINK_SECTION)))
693         {
694           label = NULL;
695           g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_LABEL, "s", &label);
696           append_items_from_model (menu, m, group, need_separator, label);
697           g_object_unref (m);
698           g_free (label);
699           continue;
700         }
701
702       if (*need_separator)
703         {
704           w = gtk_separator_menu_item_new ();
705           gtk_widget_show (w);
706           gtk_menu_shell_append (menu, w);
707           *need_separator = FALSE;
708         }
709
710       menuitem = create_menuitem_from_model (model, i, group);
711
712       if ((m = g_menu_model_get_item_link (model, i, G_MENU_LINK_SUBMENU)))
713         {
714           submenu = gtk_menu_new ();
715           populate_menu_from_model (GTK_MENU_SHELL (submenu), m, group);
716           gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem), submenu);
717           g_object_unref (m);
718         }
719
720       gtk_widget_show (menuitem);
721       gtk_menu_shell_append (menu, menuitem);
722
723       *need_separator = TRUE;
724     }
725 }
726
727 static void
728 populate_menu_from_model (GtkMenuShell *menu,
729                           GMenuModel   *model,
730                           GActionGroup *group)
731 {
732   gboolean need_separator;
733
734   need_separator = FALSE;
735   append_items_from_model (menu, model, group, &need_separator, NULL);
736 }
737
738 typedef struct {
739   GtkApplication *application;
740   GtkMenuShell   *menu;
741   guint           update_idle;
742   GHashTable     *connected;
743 } ItemsChangedData;
744
745 static void
746 free_items_changed_data (gpointer data)
747 {
748   ItemsChangedData *d = data;
749
750   g_object_unref (d->application);
751
752   if (d->update_idle != 0)
753     g_source_remove (d->update_idle);
754
755   g_hash_table_unref (d->connected);
756
757   g_free (d);
758 }
759
760 static gboolean
761 repopulate_menu (gpointer data)
762 {
763   ItemsChangedData *d = data;
764   GList *children, *l;
765   GtkWidget *child;
766   GMenuModel *model;
767
768   /* remove current children */
769   children = gtk_container_get_children (GTK_CONTAINER (d->menu));
770   for (l = children; l; l = l->next)
771     {
772       child = l->data;
773       gtk_container_remove (GTK_CONTAINER (d->menu), child);
774     }
775   g_list_free (children);
776
777   /* repopulate */
778   model = g_application_get_menu (G_APPLICATION (d->application));
779   populate_menu_from_model (d->menu, model, G_ACTION_GROUP (d->application));
780
781   d->update_idle = 0;
782
783   return FALSE;
784 }
785
786 static void
787 connect_to_items_changed (GMenuModel *model,
788                           GCallback   callback,
789                           gpointer    data)
790 {
791   ItemsChangedData *d = data;
792   gint i;
793   GMenuModel *m;
794   GMenuLinkIter *iter;
795
796   if (!g_hash_table_lookup (d->connected, model))
797     {
798       g_signal_connect (model, "items-changed", callback, data);
799       g_hash_table_insert (d->connected, model, model);
800     }
801
802   for (i = 0; i < g_menu_model_get_n_items (model); i++)
803     {
804       iter = g_menu_model_iterate_item_links (model, i);
805       while (g_menu_link_iter_next (iter))
806         {
807           m = g_menu_link_iter_get_value (iter);
808           connect_to_items_changed (m, callback, data);
809           g_object_unref (m);
810         }
811       g_object_unref (iter);
812     }
813 }
814
815 static void
816 items_changed (GMenuModel *model,
817                gint        position,
818                gint        removed,
819                gint        added,
820                gpointer    data)
821 {
822   ItemsChangedData *d = data;
823
824   if (d->update_idle == 0)
825     d->update_idle = gdk_threads_add_idle (repopulate_menu, data);
826   connect_to_items_changed (model, G_CALLBACK (items_changed), data);
827 }
828
829 /**
830  * gtk_application_window_get_app_menu:
831  * @application: a #GtkApplication
832  *
833  * Populates a menu widget from a menu model that is
834  * associated with @application. See g_application_set_menu().
835  * The menu items will be connected to action of @application,
836  * as indicated by the menu model. The menus contents will be
837  * updated automatically in response to menu model changes.
838  *
839  * It is the callers responsibility to add the menu at a
840  * suitable place in the widget hierarchy.
841  *
842  * This function returns %NULL if @application has no associated
843  * menu model. It also returns %NULL if the menu model is
844  * represented outside the application, e.g. by an application
845  * menu in the desktop shell.
846  *
847  * @menu may be a #GtkMenu or a #GtkMenuBar.
848  *
849  * Returns: A #GtkMenu that has been populated from the
850  *     #GMenuModel that is associated with @application,
851  *     or %NULL
852  */
853 GtkWidget *
854 gtk_application_window_get_app_menu (GtkApplicationWindow *window)
855 {
856   GtkApplication *application;
857   GtkWidget *menu;
858   GMenuModel *model;
859   ItemsChangedData *data;
860   GActionMuxer *muxer;
861
862   application = gtk_window_get_application (GTK_WINDOW (window));
863
864   model = g_application_get_menu (G_APPLICATION (application));
865
866   if (!model)
867     return NULL;
868
869   menu = gtk_menu_new ();
870
871   muxer = g_action_muxer_new ();
872   g_action_muxer_insert (muxer, "app", G_ACTION_GROUP (application));
873   g_action_muxer_insert (muxer, "win", G_ACTION_GROUP (window));
874   populate_menu_from_model (GTK_MENU_SHELL (menu), model, G_ACTION_GROUP (muxer));
875   g_object_unref (muxer);
876
877   data = g_new (ItemsChangedData, 1);
878   data->application = g_object_ref (application);
879   data->menu = GTK_MENU_SHELL (menu);
880   data->update_idle = 0;
881   data->connected = g_hash_table_new (NULL, NULL);
882
883   g_object_set_data_full (G_OBJECT (menu), "gtk-application-menu-data",
884                           data, free_items_changed_data);
885
886   connect_to_items_changed (model, G_CALLBACK (items_changed), data);
887
888   return menu;
889 }