]> Pileus Git - ~andy/gtk/blob - tests/testgmenu.c
Adapt to api changes in GMenuModel
[~andy/gtk] / tests / testgmenu.c
1 /* testgmenu.c
2  * Copyright (C) 2011  Red Hat, Inc.
3  * Written by Matthias Clasen
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 #include <stdlib.h>
22 #include <string.h>
23 #include <gio/gio.h>
24 #include <gtk/gtk.h>
25
26 /* TODO
27  *
28  * - Labeled sections
29  *
30  * - Focus changes. Verify that stopping subscriptions works.
31  *
32  * - Other attributes. What about icons ?
33  */
34
35 /* MenuHolder {{{1 */
36
37 typedef struct {
38   GMenuModel   *model;
39   GActionGroup *group;
40   GtkWidget    *menu;
41   gboolean      items_changed;
42 } MenuHolder;
43
44 /* Menumodel callbacks {{{2 */
45
46 static void
47 connect_to_items_changed (GMenuModel *model,
48                           GCallback   callback,
49                           gpointer    data)
50 {
51   gint i;
52   GMenuModel *m;
53   GMenuLinkIter *iter;
54
55   if (!g_object_get_data (G_OBJECT (model), "handler-connected"))
56     {
57       g_signal_connect (model, "items-changed", callback, data);
58       g_object_set_data (G_OBJECT (model), "handler-connected", GINT_TO_POINTER (1));
59     }
60   for (i = 0; i < g_menu_model_get_n_items (model); i++)
61     {
62       iter = g_menu_model_iterate_item_links (model, i);
63       while (g_menu_link_iter_next (iter))
64         {
65           m = g_menu_link_iter_get_value (iter);
66           connect_to_items_changed (m, callback, data);
67           g_object_unref (m);
68         }
69       g_object_unref (iter);
70     }
71 }
72
73 static void
74 items_changed (GMenuModel *model,
75                gint        position,
76                gint        removed,
77                gint        added,
78                MenuHolder *holder)
79 {
80   g_print ("Received GMenuModel::items-changed\n");
81   holder->items_changed = TRUE;
82   connect_to_items_changed (model, G_CALLBACK (items_changed), holder);
83 }
84
85  /* Actiongroup callbacks {{{2 */
86
87 typedef struct {
88   GActionGroup *group;
89   gchar        *name;
90   gchar        *target;
91   gulong        enabled_changed_id;
92   gulong        state_changed_id;
93 } ActionData;
94
95 static void
96 action_data_free (gpointer data)
97 {
98   ActionData *a = data;
99
100   if (a->enabled_changed_id)
101     g_signal_handler_disconnect (a->group, a->enabled_changed_id);
102
103   if (a->state_changed_id)
104     g_signal_handler_disconnect (a->group, a->state_changed_id);
105
106   g_object_unref (a->group);
107   g_free (a->name);
108   g_free (a->target);
109
110   g_free (a);
111 }
112
113 static void
114 enabled_changed (GActionGroup *group,
115                  const gchar  *action_name,
116                  gboolean      enabled,
117                  GtkWidget    *widget)
118 {
119   gtk_widget_set_sensitive (widget, enabled);
120 }
121
122 static void
123 toggle_state_changed (GActionGroup     *group,
124                       const gchar      *name,
125                       GVariant         *state,
126                       GtkCheckMenuItem *w)
127 {
128   gtk_check_menu_item_set_active (w, g_variant_get_boolean (state));
129 }
130
131 static void
132 radio_state_changed (GActionGroup     *group,
133                      const gchar      *name,
134                      GVariant         *state,
135                      GtkCheckMenuItem *w)
136 {
137   ActionData *a;
138   gboolean b;
139
140   a = g_object_get_data (G_OBJECT (w), "action");
141   b = g_strcmp0 (a->target, g_variant_get_string (state, NULL)) == 0;
142
143   gtk_check_menu_item_set_active (w, b);
144 }
145
146 /* Menuitem callbacks {{{2 */
147
148 static void
149 item_activated (GtkWidget *w,
150                 gpointer   data)
151 {
152   ActionData *a;
153
154   a = g_object_get_data (G_OBJECT (w), "action");
155   g_action_group_activate_action (a->group, a->name, NULL);
156 }
157
158 static void
159 toggle_item_toggled (GtkCheckMenuItem *w,
160                      gpointer          data)
161 {
162   ActionData *a;
163   gboolean b;
164
165   a = g_object_get_data (G_OBJECT (w), "action");
166   b = gtk_check_menu_item_get_active (w);
167   g_action_group_change_action_state (a->group, a->name,
168                                       g_variant_new_boolean (b));
169 }
170
171 static void
172 radio_item_toggled (GtkCheckMenuItem *w,
173                     gpointer          data)
174 {
175   ActionData *a;
176   GVariant *v;
177
178   a = g_object_get_data (G_OBJECT (w), "action");
179   if (gtk_check_menu_item_get_active (w))
180     {
181       g_action_group_change_action_state (a->group, a->name,
182                                           g_variant_new_string (a->target));
183     }
184   else
185     {
186       v = g_action_group_get_action_state (a->group, a->name);
187       if (g_strcmp0 (g_variant_get_string (v, NULL), a->target) == 0)
188         gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w), TRUE);
189       g_variant_unref (v);
190     }
191 }
192
193 /* GtkMenu construction {{{2 */
194
195 static GtkWidget *
196 create_menuitem_from_model (GMenuModel   *model,
197                             gint          item,
198                             GActionGroup *group)
199 {
200   GtkWidget *w;
201   gchar *label;
202   gchar *action;
203   gchar *target;
204   gchar *s;
205   ActionData *a;
206   const GVariantType *type;
207   GVariant *v;
208
209   g_menu_model_get_item_attribute (model, item, G_MENU_ATTRIBUTE_LABEL, "s", &label);
210
211   action = NULL;
212   g_menu_model_get_item_attribute (model, item, G_MENU_ATTRIBUTE_ACTION, "s", &action);
213
214   if (action != NULL)
215     type = g_action_group_get_action_state_type (group, action);
216   else
217     type = NULL;
218
219   if (type == NULL)
220     w = gtk_menu_item_new_with_mnemonic (label);
221   else if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN))
222     w = gtk_check_menu_item_new_with_label (label);
223   else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING))
224     {
225       w = gtk_check_menu_item_new_with_label (label);
226       gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (w), TRUE);
227     }
228   else
229     g_assert_not_reached ();
230
231   if (action != NULL)
232     {
233       a = g_new0 (ActionData, 1);
234       a->group = g_object_ref (group);
235       a->name = g_strdup (action);
236       g_object_set_data_full (G_OBJECT (w), "action", a, action_data_free);
237
238       if (!g_action_group_get_action_enabled (group, action))
239         gtk_widget_set_sensitive (w, FALSE);
240
241       s = g_strconcat ("action-enabled-changed::", action, NULL);
242       a->enabled_changed_id = g_signal_connect (group, s,
243                                                 G_CALLBACK (enabled_changed), w);
244       g_free (s);
245
246       if (type == NULL)
247         g_signal_connect (w, "activate", G_CALLBACK (item_activated), NULL);
248       else if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN))
249         {
250           g_signal_connect (w, "toggled", G_CALLBACK (toggle_item_toggled), NULL);
251           s = g_strconcat ("action-state-changed::", action, NULL);
252           a->state_changed_id = g_signal_connect (group, s,
253                                                   G_CALLBACK (toggle_state_changed), w);
254           g_free (s);
255           v = g_action_group_get_action_state (group, action);
256           gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w),
257                                           g_variant_get_boolean (v));
258           g_variant_unref (v);
259         }
260       else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING))
261         {
262           g_signal_connect (w, "toggled", G_CALLBACK (radio_item_toggled), NULL);
263           s = g_strconcat ("action-state-changed::", action, NULL);
264           a->state_changed_id = g_signal_connect (group, s,
265                                                   G_CALLBACK (radio_state_changed), w);
266           g_free (s);
267           g_menu_model_get_item_attribute (model, item, G_MENU_ATTRIBUTE_TARGET, "s", &target);
268           a->target = g_strdup (target);
269           v = g_action_group_get_action_state (group, action);
270           gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w),
271                                           g_strcmp0 (g_variant_get_string (v, NULL), target) == 0);
272           g_variant_unref (v);
273           g_free (target);
274         }
275       else
276         g_assert_not_reached ();
277     }
278
279   g_free (label);
280   g_free (action);
281
282   return w;
283 }
284
285 static GtkWidget *create_menu_from_model (GMenuModel   *model,
286                                           GActionGroup *group);
287
288 static void
289 append_items_from_model (GtkWidget    *menu,
290                          GMenuModel   *model,
291                          GActionGroup *group,
292                          gboolean     *need_separator)
293 {
294   gint n;
295   gint i;
296   GtkWidget *w;
297   GtkWidget *menuitem;
298   GtkWidget *submenu;
299   GMenuModel *m;
300
301   n = g_menu_model_get_n_items (model);
302
303   if (*need_separator && n > 0)
304     {
305       w = gtk_separator_menu_item_new ();
306       gtk_widget_show (w);
307       gtk_menu_shell_append (GTK_MENU_SHELL (menu), w);
308
309       *need_separator = FALSE;
310     }
311
312   for (i = 0; i < n; i++)
313     {
314       if ((m = g_menu_model_get_item_link (model, i, G_MENU_LINK_SECTION)))
315         {
316           append_items_from_model (menu, m, group, need_separator);
317           g_object_unref (m);
318           continue;
319         }
320
321       menuitem = create_menuitem_from_model (model, i, group);
322
323       if ((m = g_menu_model_get_item_link (model, i, G_MENU_LINK_SUBMENU)))
324         {
325           submenu = create_menu_from_model (m, group);
326           gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem), submenu);
327           g_object_unref (m);
328         }
329
330       gtk_widget_show (menuitem);
331       gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
332
333       *need_separator = TRUE;
334     }
335 }
336
337 static GtkWidget *
338 create_menu_from_model (GMenuModel   *model,
339                         GActionGroup *group)
340 {
341   GtkWidget *w;
342   gboolean need_separator;
343
344   w = gtk_menu_new ();
345   need_separator = FALSE;
346   append_items_from_model (w, model, group, &need_separator);
347
348   return w;
349 }
350
351 /* }}}2 */
352
353 MenuHolder *
354 menu_holder_new (GMenuModel   *model,
355                  GActionGroup *group)
356 {
357   MenuHolder *holder;
358
359   holder = g_new (MenuHolder, 1);
360   holder->model = g_object_ref (model);
361   holder->group = g_object_ref (group);
362   holder->menu = create_menu_from_model (model, group);
363   holder->items_changed = FALSE;
364
365   connect_to_items_changed (model, G_CALLBACK (items_changed), holder);
366
367   return holder;
368 }
369
370 GtkWidget *
371 menu_holder_get_menu (MenuHolder *holder)
372 {
373   if (holder->items_changed)
374     {
375       holder->items_changed = FALSE;
376       gtk_widget_destroy (holder->menu);
377       holder->menu = create_menu_from_model (holder->model, holder->group);
378     }
379
380   return holder->menu;
381 }
382
383 /* The example menu {{{1 */
384
385 static const gchar menu_markup[] =
386   "<menu id='edit-menu'>\n"
387   "  <section>\n"
388   "    <item action='undo'>\n"
389   "      <attribute name='label' translatable='yes' context='Stock label'>'_Undo'</attribute>\n"
390   "    </item>\n"
391   "    <item label='Redo' action='redo'/>\n"
392   "  </section>\n"
393   "  <section></section>\n"
394   "  <section label='Copy &amp; Paste'>\n"
395   "    <item label='Cut' action='cut'/>\n"
396   "    <item label='Copy' action='copy'/>\n"
397   "    <item label='Paste' action='paste'/>\n"
398   "  </section>\n"
399   "  <section>\n"
400   "    <item label='Bold' action='bold'/>\n"
401   "    <submenu label='Language'>\n"
402   "      <item label='Latin' action='lang' target='latin'/>\n"
403   "      <item label='Greek' action='lang' target='greek'/>\n"
404   "      <item label='Urdu'  action='lang' target='urdu'/>\n"
405   "    </submenu>\n"
406   "  </section>\n"
407   "</menu>\n";
408
409 static void
410 start_element (GMarkupParseContext *context,
411                const gchar         *element_name,
412                const gchar        **attribute_names,
413                const gchar        **attribute_values,
414                gpointer             user_data,
415                GError             **error)
416 {
417   if (strcmp (element_name, "menu") == 0)
418     g_menu_markup_parser_start_menu (context, "gtk30", NULL);
419 }
420
421 static void
422 end_element (GMarkupParseContext *context,
423              const gchar         *element_name,
424              gpointer             user_data,
425              GError             **error)
426 {
427   GMenu **menu = user_data;
428
429   if (strcmp (element_name, "menu") == 0)
430     *menu = g_menu_markup_parser_end_menu (context);
431 }
432
433 static const GMarkupParser parser = {
434    start_element, end_element, NULL, NULL, NULL
435 };
436
437 static GMenuModel *
438 get_model (void)
439 {
440   GMarkupParseContext *context;
441   GMenu *menu = NULL;
442   GError *error = NULL;
443
444   context = g_markup_parse_context_new (&parser, 0, &menu, NULL);
445   if (!g_markup_parse_context_parse (context, menu_markup, -1, &error))
446     {
447        g_warning ("menu parsing failed: %s\n", error->message);
448        exit (1);
449     }
450   g_markup_parse_context_free (context);
451   g_assert (menu);
452
453    return G_MENU_MODEL (menu);
454 }
455
456  /* The example actions {{{1 */
457
458 static void
459 activate_action (GSimpleAction *action, GVariant *parameter, gpointer user_data)
460 {
461   g_print ("Action %s activated\n", g_action_get_name (G_ACTION (action)));
462 }
463
464 static void
465 toggle_changed (GSimpleAction *action, GVariant *value, gpointer user_data)
466 {
467   g_print ("Toggle action %s state changed to %d\n",
468            g_action_get_name (G_ACTION (action)),
469            g_variant_get_boolean (value));
470
471   g_simple_action_set_state (action, value);
472 }
473
474 static void
475 radio_changed (GSimpleAction *action, GVariant *value, gpointer user_data)
476 {
477   g_print ("Radio action %s state changed to %s\n",
478            g_action_get_name (G_ACTION (action)),
479            g_variant_get_string (value, NULL));
480
481   g_simple_action_set_state (action, value);
482 }
483
484 static GActionEntry actions[] = {
485   { "undo",  activate_action, NULL, NULL,      NULL },
486   { "redo",  activate_action, NULL, NULL,      NULL },
487   { "cut",   activate_action, NULL, NULL,      NULL },
488   { "copy",  activate_action, NULL, NULL,      NULL },
489   { "paste", activate_action, NULL, NULL,      NULL },
490   { "bold",  NULL,            NULL, "true",    toggle_changed },
491   { "lang",  NULL,            NULL, "'latin'", radio_changed },
492 };
493
494 static GActionGroup *
495 get_group (void)
496 {
497   GSimpleActionGroup *group;
498
499   group = g_simple_action_group_new ();
500
501   g_simple_action_group_add_entries (group, actions, G_N_ELEMENTS (actions), NULL);
502
503   return G_ACTION_GROUP (group);
504 }
505  
506 /* The action treeview {{{1 */
507
508 static void
509 enabled_cell_func (GtkTreeViewColumn *column,
510                    GtkCellRenderer   *cell,
511                    GtkTreeModel      *model,
512                    GtkTreeIter       *iter,
513                    gpointer           data)
514 {
515   GActionGroup *group = data;
516   gchar *name;
517   gboolean enabled;
518
519   gtk_tree_model_get (model, iter, 0, &name, -1);
520   enabled = g_action_group_get_action_enabled (group, name);
521   g_free (name);
522
523   gtk_cell_renderer_toggle_set_active (GTK_CELL_RENDERER_TOGGLE (cell), enabled);
524 }
525
526 static void
527 state_cell_func (GtkTreeViewColumn *column,
528                  GtkCellRenderer   *cell,
529                  GtkTreeModel      *model,
530                  GtkTreeIter       *iter,
531                  gpointer           data)
532 {
533   GActionGroup *group = data;
534   gchar *name;
535   GVariant *state;
536
537   gtk_tree_model_get (model, iter, 0, &name, -1);
538   state = g_action_group_get_action_state (group, name);
539   g_free (name);
540
541   gtk_cell_renderer_set_visible (cell, FALSE);
542   g_object_set (cell, "mode", GTK_CELL_RENDERER_MODE_INERT, NULL);
543
544   if (state &&
545       g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN) &&
546       GTK_IS_CELL_RENDERER_TOGGLE (cell))
547     {
548       gtk_cell_renderer_set_visible (cell, TRUE);
549       g_object_set (cell, "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL);
550       gtk_cell_renderer_toggle_set_active (GTK_CELL_RENDERER_TOGGLE (cell),
551                                            g_variant_get_boolean (state));
552     }
553   else if (state &&
554            g_variant_is_of_type (state, G_VARIANT_TYPE_STRING) &&
555            GTK_IS_CELL_RENDERER_COMBO (cell))
556     {
557       gtk_cell_renderer_set_visible (cell, TRUE);
558       g_object_set (cell, "mode", GTK_CELL_RENDERER_MODE_EDITABLE, NULL);
559       g_object_set (cell, "text", g_variant_get_string (state, NULL), NULL);
560     }
561
562   if (state)
563     g_variant_unref (state);
564 }
565
566 static void
567 enabled_cell_toggled (GtkCellRendererToggle *cell,
568                       const gchar           *path_str,
569                       GtkTreeModel          *model)
570 {
571   GActionGroup *group;
572   GAction *action;
573   gchar *name;
574   GtkTreePath *path;
575   GtkTreeIter iter;
576   gboolean enabled;
577
578   group = g_object_get_data (G_OBJECT (model), "group");
579   path = gtk_tree_path_new_from_string (path_str);
580   gtk_tree_model_get_iter (model, &iter, path);
581   gtk_tree_model_get (model, &iter, 0, &name, -1);
582
583   enabled = g_action_group_get_action_enabled (group, name);
584   action = g_simple_action_group_lookup (G_SIMPLE_ACTION_GROUP (group), name);
585   g_simple_action_set_enabled (G_SIMPLE_ACTION (action), !enabled);
586
587   gtk_tree_model_row_changed (model, path, &iter);
588
589   g_free (name);
590   gtk_tree_path_free (path);
591 }
592
593 static void
594 state_cell_toggled (GtkCellRendererToggle *cell,
595                     const gchar           *path_str,
596                     GtkTreeModel          *model)
597 {
598   GActionGroup *group;
599   GAction *action;
600   gchar *name;
601   GtkTreePath *path;
602   GtkTreeIter iter;
603   GVariant *state;
604
605   group = g_object_get_data (G_OBJECT (model), "group");
606   path = gtk_tree_path_new_from_string (path_str);
607   gtk_tree_model_get_iter (model, &iter, path);
608   gtk_tree_model_get (model, &iter, 0, &name, -1);
609
610   state = g_action_group_get_action_state (group, name);
611   action = g_simple_action_group_lookup (G_SIMPLE_ACTION_GROUP (group), name);
612   if (state && g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN))
613     {
614       gboolean b;
615
616       b = g_variant_get_boolean (state);
617       g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (!b));
618     }
619   else
620     {
621       /* nothing to do */
622     }
623
624   gtk_tree_model_row_changed (model, path, &iter);
625
626   g_free (name);
627   gtk_tree_path_free (path);
628   if (state)
629     g_variant_unref (state);
630 }
631
632 static void
633 state_cell_edited (GtkCellRendererCombo  *cell,
634                    const gchar           *path_str,
635                    const gchar           *new_text,
636                    GtkTreeModel          *model)
637 {
638   GActionGroup *group;
639   GAction *action;
640   gchar *name;
641   GtkTreePath *path;
642   GtkTreeIter iter;
643
644   group = g_object_get_data (G_OBJECT (model), "group");
645   path = gtk_tree_path_new_from_string (path_str);
646   gtk_tree_model_get_iter (model, &iter, path);
647   gtk_tree_model_get (model, &iter, 0, &name, -1);
648   action = g_simple_action_group_lookup (G_SIMPLE_ACTION_GROUP (group), name);
649   g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_string (new_text));
650
651   gtk_tree_model_row_changed (model, path, &iter);
652
653   g_free (name);
654   gtk_tree_path_free (path);
655 }
656
657 static GtkWidget *
658 create_action_treeview (GActionGroup *group)
659 {
660   GtkWidget *tv;
661   GtkListStore *store;
662   GtkListStore *values;
663   GtkTreeIter iter;
664   GtkTreeViewColumn *column;
665   GtkCellRenderer *cell;
666   gchar **actions;
667   gint i;
668
669   store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
670   actions = g_action_group_list_actions (group);
671   for (i = 0; actions[i]; i++)
672     {
673       gtk_list_store_append (store, &iter);
674       gtk_list_store_set (store, &iter, 0, actions[i], -1);
675     }
676   g_strfreev (actions);
677   g_object_set_data (G_OBJECT (store), "group", group);
678
679   tv = gtk_tree_view_new ();
680
681   g_signal_connect_swapped (group, "action-enabled-changed",
682                             G_CALLBACK (gtk_widget_queue_draw), tv);
683   g_signal_connect_swapped (group, "action-state-changed",
684                             G_CALLBACK (gtk_widget_queue_draw), tv);
685
686   gtk_tree_view_set_model (GTK_TREE_VIEW (tv), GTK_TREE_MODEL (store));
687
688   cell = gtk_cell_renderer_text_new ();
689   column = gtk_tree_view_column_new_with_attributes ("Action", cell,
690                                                      "text", 0,
691                                                      NULL);
692   gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
693
694   column = gtk_tree_view_column_new ();
695   gtk_tree_view_column_set_title (column, "Enabled");
696   cell = gtk_cell_renderer_toggle_new ();
697   gtk_tree_view_column_pack_start (column, cell, FALSE);
698   gtk_tree_view_column_set_cell_data_func (column, cell, enabled_cell_func, group, NULL);
699   g_signal_connect (cell, "toggled", G_CALLBACK (enabled_cell_toggled), store);
700   gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
701
702   column = gtk_tree_view_column_new ();
703   gtk_tree_view_column_set_title (column, "State");
704   cell = gtk_cell_renderer_toggle_new ();
705   gtk_tree_view_column_pack_start (column, cell, FALSE);
706   gtk_tree_view_column_set_cell_data_func (column, cell, state_cell_func, group, NULL);
707   g_signal_connect (cell, "toggled", G_CALLBACK (state_cell_toggled), store);
708   cell = gtk_cell_renderer_combo_new ();
709   values = gtk_list_store_new (1, G_TYPE_STRING);
710   gtk_list_store_append (values, &iter);
711   gtk_list_store_set (values, &iter, 0, "latin", -1);
712   gtk_list_store_append (values, &iter);
713   gtk_list_store_set (values, &iter, 0, "greek", -1);
714   gtk_list_store_append (values, &iter);
715   gtk_list_store_set (values, &iter, 0, "urdu", -1);
716   gtk_list_store_append (values, &iter);
717   gtk_list_store_set (values, &iter, 0, "sumerian", -1);
718   g_object_set (cell,
719                 "has-entry", FALSE,
720                 "model", values,
721                 "text-column", 0,
722                 "editable", TRUE,
723                 NULL);
724   gtk_tree_view_column_pack_start (column, cell, FALSE);
725   gtk_tree_view_column_set_cell_data_func (column, cell, state_cell_func, group, NULL);
726   g_signal_connect (cell, "edited", G_CALLBACK (state_cell_edited), store);
727   gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
728
729   return tv;
730 }
731
732 /* Dynamic menu changes {{{1 */
733
734 static void
735 toggle_sumerian (GtkToggleButton *button, gpointer data)
736 {
737   GMenuModel *model;
738   gboolean adding;
739   GMenuModel *m;
740
741   model = g_object_get_data (G_OBJECT (button), "model");
742
743   adding = gtk_toggle_button_get_active (button);
744
745   m = g_menu_model_get_item_link (model, g_menu_model_get_n_items (model) - 1, G_MENU_LINK_SECTION);
746   m = g_menu_model_get_item_link (m, g_menu_model_get_n_items (m) - 1, G_MENU_LINK_SUBMENU);
747   if (adding)
748     g_menu_append (G_MENU (m), "Sumerian", "lang::sumerian");
749   else
750     g_menu_remove (G_MENU (m), g_menu_model_get_n_items (m) - 1);
751 }
752
753 static void
754 action_list_add (GtkTreeModel *store,
755                  const gchar  *action)
756 {
757   GtkTreeIter iter;
758
759   gtk_list_store_append (GTK_LIST_STORE (store), &iter);
760   gtk_list_store_set (GTK_LIST_STORE (store), &iter, 0, action, -1);
761 }
762
763 static void
764 action_list_remove (GtkTreeModel *store,
765                     const gchar  *action)
766 {
767   GtkTreeIter iter;
768   gchar *text;
769
770   gtk_tree_model_get_iter_first (store, &iter);
771   do {
772     gtk_tree_model_get (store, &iter, 0, &text, -1);
773     if (g_strcmp0 (action, text) == 0)
774       {
775         g_free (text);
776         gtk_list_store_remove (GTK_LIST_STORE (store), &iter);
777         break;
778       }
779     g_free (text);
780   } while (gtk_tree_model_iter_next (store, &iter));
781 }
782
783 static void
784 toggle_italic (GtkToggleButton *button, gpointer data)
785 {
786   GMenuModel *model;
787   GActionGroup *group;
788   GSimpleAction *action;
789   gboolean adding;
790   GMenuModel *m;
791   GtkTreeView *tv = data;
792   GtkTreeModel *store;
793
794   model = g_object_get_data (G_OBJECT (button), "model");
795   group = g_object_get_data (G_OBJECT (button), "group");
796
797   store = gtk_tree_view_get_model (tv);
798
799   adding = gtk_toggle_button_get_active (button);
800
801   m = g_menu_model_get_item_link (model, g_menu_model_get_n_items (model) - 1, G_MENU_LINK_SECTION);
802   if (adding)
803     {
804       action = g_simple_action_new_stateful ("italic", NULL, g_variant_new_boolean (FALSE));
805       g_simple_action_group_insert (G_SIMPLE_ACTION_GROUP (group), G_ACTION (action));
806       g_signal_connect (action, "change-state", G_CALLBACK (toggle_changed), NULL);
807       g_object_unref (action);
808       action_list_add (store, "italic");
809       g_menu_insert (G_MENU (m), 1, "Italic", "italic");
810     }
811   else
812     {
813       g_simple_action_group_remove (G_SIMPLE_ACTION_GROUP (group), "italic");
814       action_list_remove (store, "italic");
815       g_menu_remove (G_MENU (m), 1);
816     }
817 }
818
819 static void
820 toggle_speed (GtkToggleButton *button, gpointer data)
821 {
822   GMenuModel *model;
823   GActionGroup *group;
824   GSimpleAction *action;
825   gboolean adding;
826   GMenuModel *m;
827   GMenu *submenu;
828   GtkTreeView *tv = data;
829   GtkTreeModel *store;
830
831   model = g_object_get_data (G_OBJECT (button), "model");
832   group = g_object_get_data (G_OBJECT (button), "group");
833
834   store = gtk_tree_view_get_model (tv);
835
836   adding = gtk_toggle_button_get_active (button);
837
838   m = g_menu_model_get_item_link (model, 1, G_MENU_LINK_SECTION);
839   if (adding)
840     {
841       action = g_simple_action_new ("faster", NULL);
842       g_simple_action_group_insert (G_SIMPLE_ACTION_GROUP (group), G_ACTION (action));
843       g_signal_connect (action, "activate", G_CALLBACK (activate_action), NULL);
844       g_object_unref (action);
845
846       action = g_simple_action_new ("slower", NULL);
847       g_simple_action_group_insert (G_SIMPLE_ACTION_GROUP (group), G_ACTION (action));
848       g_signal_connect (action, "activate", G_CALLBACK (activate_action), NULL);
849       g_object_unref (action);
850
851       action_list_add (store, "faster");
852       action_list_add (store, "slower");
853
854       submenu = g_menu_new ();
855       g_menu_append (submenu, "Faster", "faster");
856       g_menu_append (submenu, "Slower", "slower");
857       g_menu_append_submenu (G_MENU (m), "Speed", G_MENU_MODEL (submenu));
858     }
859   else
860     {
861       g_simple_action_group_remove (G_SIMPLE_ACTION_GROUP (group), "faster");
862       g_simple_action_group_remove (G_SIMPLE_ACTION_GROUP (group), "slower");
863
864       action_list_remove (store, "faster");
865       action_list_remove (store, "slower");
866
867       g_menu_remove (G_MENU (m), g_menu_model_get_n_items (m) - 1);
868     }
869 }
870 static GtkWidget *
871 create_add_remove_buttons (GActionGroup *group,
872                            GMenuModel   *model,
873                            GtkWidget    *treeview)
874 {
875   GtkWidget *box;
876   GtkWidget *button;
877
878   box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
879
880   button = gtk_check_button_new_with_label ("Add Italic");
881   gtk_container_add (GTK_CONTAINER (box), button);
882
883   g_object_set_data  (G_OBJECT (button), "group", group);
884   g_object_set_data  (G_OBJECT (button), "model", model);
885
886   g_signal_connect (button, "toggled",
887                     G_CALLBACK (toggle_italic), treeview);
888
889   button = gtk_check_button_new_with_label ("Add Sumerian");
890   gtk_container_add (GTK_CONTAINER (box), button);
891
892   g_object_set_data  (G_OBJECT (button), "group", group);
893   g_object_set_data  (G_OBJECT (button), "model", model);
894
895   g_signal_connect (button, "toggled",
896                     G_CALLBACK (toggle_sumerian), NULL);
897
898   button = gtk_check_button_new_with_label ("Add Speed");
899   gtk_container_add (GTK_CONTAINER (box), button);
900
901   g_object_set_data  (G_OBJECT (button), "group", group);
902   g_object_set_data  (G_OBJECT (button), "model", model);
903
904   g_signal_connect (button, "toggled",
905                     G_CALLBACK (toggle_speed), treeview);
906   return box;
907 }
908
909 /* main {{{1 */
910
911 static void
912 button_clicked (GtkButton  *button,
913                 MenuHolder *holder)
914 {
915   GtkWidget *menu;
916
917   menu = menu_holder_get_menu (holder);
918   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 0, 0);
919 }
920
921 #define BUS_NAME "org.gtk.TestMenus"
922 #define OBJ_PATH "/org/gtk/TestMenus"
923
924 int
925 main (int argc, char *argv[])
926 {
927   GtkWidget *window;
928   GtkWidget *box;
929   GtkWidget *button;
930   GtkWidget *tv;
931   GtkWidget *buttons;
932   MenuHolder *holder;
933   GMenuModel *model;
934   GActionGroup *group;
935   GDBusConnection *bus;
936   GError *error = NULL;
937   gboolean do_export = FALSE;
938   gboolean do_import = FALSE;
939   GOptionEntry entries[] = {
940     { "export", 0, 0, G_OPTION_ARG_NONE, &do_export, "Export actions and menus over D-Bus", NULL },
941     { "import", 0, 0, G_OPTION_ARG_NONE, &do_import, "Use exported actions and menus", NULL },
942     { NULL, }
943   };
944
945   gtk_init_with_args (&argc, &argv, NULL, entries, NULL, NULL);
946
947   if (do_export && do_import)
948     {
949        g_error ("can't have it both ways\n");
950        exit (1);
951     }
952
953   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
954   box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
955   gtk_container_add (GTK_CONTAINER (window), box);
956
957   bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
958
959   if (do_import)
960     {
961       g_print ("Getting menus from the bus...\n");
962       model = (GMenuModel*)g_menu_proxy_get (bus, BUS_NAME, OBJ_PATH);
963       g_print ("Getting actions from the bus...\n");
964       group = (GActionGroup*)g_dbus_action_group_new_sync (bus, BUS_NAME, OBJ_PATH, 0, NULL, NULL);
965     }
966   else
967     {
968       group = get_group ();
969       model = get_model ();
970
971       tv = create_action_treeview (group);
972       gtk_container_add (GTK_CONTAINER (box), tv);
973       buttons = create_add_remove_buttons (group, model, tv);
974       gtk_container_add (GTK_CONTAINER (box), buttons);
975     }
976
977   if (do_export)
978     {
979       g_print ("Exporting menus on the bus...\n");
980       if (!g_menu_exporter_export (bus, OBJ_PATH, model, &error))
981         {
982           g_warning ("Menu export failed: %s", error->message);
983           exit (1);
984         }
985       g_print ("Exporting actions on the bus...\n");
986       if (!g_action_group_exporter_export (bus, OBJ_PATH, group, &error))
987         {
988           g_warning ("Action export failed: %s", error->message);
989           exit (1);
990         }
991       g_bus_own_name_on_connection (bus, BUS_NAME, 0, NULL, NULL, NULL, NULL);
992     }
993   else
994     {
995       holder = menu_holder_new (model, group);
996       button = gtk_button_new_with_label ("Click here");
997       g_signal_connect (button, "clicked",
998                         G_CALLBACK (button_clicked), holder);
999       gtk_container_add (GTK_CONTAINER (box), button);
1000     }
1001
1002   gtk_widget_show_all (window);
1003
1004   gtk_main ();
1005
1006   return 0;
1007 }
1008
1009 /* Epilogue {{{1 */
1010 /* vim:set foldmethod=marker: */