]> Pileus Git - ~andy/gtk/blob - tests/testgmenu.c
testgmenu: Demonstrate how translatable labels work
[~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 (GMenuModelItem *item,
197                             GActionGroup   *group)
198 {
199   GtkWidget *w;
200   gchar *label;
201   gchar *action;
202   gchar *target;
203   gchar *s;
204   ActionData *a;
205   const GVariantType *type;
206   GVariant *v;
207
208   g_menu_model_item_get_attribute (item, G_MENU_ATTRIBUTE_LABEL, "s", &label);
209
210   action = NULL;
211   g_menu_model_item_get_attribute (item, G_MENU_ATTRIBUTE_ACTION, "s", &action);
212
213   if (action != NULL)
214     type = g_action_group_get_action_state_type (group, action);
215   else
216     type = NULL;
217
218   if (type == NULL)
219     w = gtk_menu_item_new_with_mnemonic (label);
220   else if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN))
221     w = gtk_check_menu_item_new_with_label (label);
222   else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING))
223     {
224       w = gtk_check_menu_item_new_with_label (label);
225       gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (w), TRUE);
226     }
227   else
228     g_assert_not_reached ();
229
230   if (action != NULL)
231     {
232       a = g_new0 (ActionData, 1);
233       a->group = g_object_ref (group);
234       a->name = g_strdup (action);
235       g_object_set_data_full (G_OBJECT (w), "action", a, action_data_free);
236
237       if (!g_action_group_get_action_enabled (group, action))
238         gtk_widget_set_sensitive (w, FALSE);
239
240       s = g_strconcat ("action-enabled-changed::", action, NULL);
241       a->enabled_changed_id = g_signal_connect (group, s,
242                                                 G_CALLBACK (enabled_changed), w);
243       g_free (s);
244
245       if (type == NULL)
246         g_signal_connect (w, "activate", G_CALLBACK (item_activated), NULL);
247       else if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN))
248         {
249           g_signal_connect (w, "toggled", G_CALLBACK (toggle_item_toggled), NULL);
250           s = g_strconcat ("action-state-changed::", action, NULL);
251           a->state_changed_id = g_signal_connect (group, s,
252                                                   G_CALLBACK (toggle_state_changed), w);
253           g_free (s);
254           v = g_action_group_get_action_state (group, action);
255           gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w),
256                                           g_variant_get_boolean (v));
257           g_variant_unref (v);
258         }
259       else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING))
260         {
261           g_signal_connect (w, "toggled", G_CALLBACK (radio_item_toggled), NULL);
262           s = g_strconcat ("action-state-changed::", action, NULL);
263           a->state_changed_id = g_signal_connect (group, s,
264                                                   G_CALLBACK (radio_state_changed), w);
265           g_free (s);
266           g_menu_model_item_get_attribute (item, G_MENU_ATTRIBUTE_TARGET, "s", &target);
267           a->target = g_strdup (target);
268           v = g_action_group_get_action_state (group, action);
269           gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w),
270                                           g_strcmp0 (g_variant_get_string (v, NULL), target) == 0);
271           g_variant_unref (v);
272           g_free (target);
273         }
274       else
275         g_assert_not_reached ();
276     }
277
278   g_free (label);
279   g_free (action);
280
281   return w;
282 }
283
284 static GtkWidget *create_menu_from_model (GMenuModel   *model,
285                                           GActionGroup *group);
286
287 static void
288 append_items_from_model (GtkWidget    *menu,
289                          GMenuModel   *model,
290                          GActionGroup *group,
291                          gboolean     *need_separator)
292 {
293   gint n;
294   gint i;
295   GtkWidget *w;
296   GtkWidget *menuitem;
297   GtkWidget *submenu;
298   GMenuModelItem item;
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       g_menu_model_get_item (model, i, &item);
315
316       if ((m = g_menu_model_item_get_link (&item, G_MENU_LINK_SECTION)))
317         {
318           append_items_from_model (menu, m, group, need_separator);
319           g_object_unref (m);
320           continue;
321         }
322
323       menuitem = create_menuitem_from_model (&item, group);
324
325       if ((m = g_menu_model_item_get_link (&item, G_MENU_LINK_SUBMENU)))
326         {
327           submenu = create_menu_from_model (m, group);
328           gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem), submenu);
329           g_object_unref (m);
330         }
331
332       gtk_widget_show (menuitem);
333       gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
334
335       *need_separator = TRUE;
336     }
337 }
338
339 static GtkWidget *
340 create_menu_from_model (GMenuModel   *model,
341                         GActionGroup *group)
342 {
343   GtkWidget *w;
344   gboolean need_separator;
345
346   w = gtk_menu_new ();
347   need_separator = FALSE;
348   append_items_from_model (w, model, group, &need_separator);
349
350   return w;
351 }
352
353 /* }}}2 */
354
355 MenuHolder *
356 menu_holder_new (GMenuModel   *model,
357                  GActionGroup *group)
358 {
359   MenuHolder *holder;
360
361   holder = g_new (MenuHolder, 1);
362   holder->model = g_object_ref (model);
363   holder->group = g_object_ref (group);
364   holder->menu = create_menu_from_model (model, group);
365   holder->items_changed = FALSE;
366
367   connect_to_items_changed (model, G_CALLBACK (items_changed), holder);
368
369   return holder;
370 }
371
372 GtkWidget *
373 menu_holder_get_menu (MenuHolder *holder)
374 {
375   if (holder->items_changed)
376     {
377       holder->items_changed = FALSE;
378       gtk_widget_destroy (holder->menu);
379       holder->menu = create_menu_from_model (holder->model, holder->group);
380     }
381
382   return holder->menu;
383 }
384
385 /* The example menu {{{1 */
386
387 static const gchar menu_markup[] =
388   "<menu id='edit-menu'>\n"
389   "  <section>\n"
390   "    <item action='undo'>\n"
391   "      <attribute name='label' translatable='yes' context='Stock label'>'_Undo'</attribute>\n"
392   "    </item>\n"
393   "    <item label='Redo' action='redo'/>\n"
394   "  </section>\n"
395   "  <section></section>\n"
396   "  <section label='Copy &amp; Paste'>\n"
397   "    <item label='Cut' action='cut'/>\n"
398   "    <item label='Copy' action='copy'/>\n"
399   "    <item label='Paste' action='paste'/>\n"
400   "  </section>\n"
401   "  <section>\n"
402   "    <item label='Bold' action='bold'/>\n"
403   "    <submenu label='Language'>\n"
404   "      <item label='Latin' action='lang' target='latin'/>\n"
405   "      <item label='Greek' action='lang' target='greek'/>\n"
406   "      <item label='Urdu'  action='lang' target='urdu'/>\n"
407   "    </submenu>\n"
408   "  </section>\n"
409   "</menu>\n";
410
411 static void
412 start_element (GMarkupParseContext *context,
413                const gchar         *element_name,
414                const gchar        **attribute_names,
415                const gchar        **attribute_values,
416                gpointer             user_data,
417                GError             **error)
418 {
419   if (strcmp (element_name, "menu") == 0)
420     g_menu_markup_parser_start_menu (context, "gtk30", NULL);
421 }
422
423 static void
424 end_element (GMarkupParseContext *context,
425              const gchar         *element_name,
426              gpointer             user_data,
427              GError             **error)
428 {
429   GMenu **menu = user_data;
430
431   if (strcmp (element_name, "menu") == 0)
432     *menu = g_menu_markup_parser_end_menu (context);
433 }
434
435 static const GMarkupParser parser = {
436    start_element, end_element, NULL, NULL, NULL
437 };
438
439 static GMenuModel *
440 get_model (void)
441 {
442   GMarkupParseContext *context;
443   GMenu *menu = NULL;
444   GError *error = NULL;
445
446   context = g_markup_parse_context_new (&parser, 0, &menu, NULL);
447   if (!g_markup_parse_context_parse (context, menu_markup, -1, &error))
448     {
449        g_warning ("menu parsing failed: %s\n", error->message);
450        exit (1);
451     }
452   g_markup_parse_context_free (context);
453   g_assert (menu);
454
455    return G_MENU_MODEL (menu);
456 }
457
458  /* The example actions {{{1 */
459
460 static void
461 activate_action (GSimpleAction *action, GVariant *parameter, gpointer user_data)
462 {
463   g_print ("Action %s activated\n", g_action_get_name (G_ACTION (action)));
464 }
465
466 static void
467 toggle_changed (GSimpleAction *action, GVariant *value, gpointer user_data)
468 {
469   g_print ("Toggle action %s state changed to %d\n",
470            g_action_get_name (G_ACTION (action)),
471            g_variant_get_boolean (value));
472
473   g_simple_action_set_state (action, value);
474 }
475
476 static void
477 radio_changed (GSimpleAction *action, GVariant *value, gpointer user_data)
478 {
479   g_print ("Radio action %s state changed to %s\n",
480            g_action_get_name (G_ACTION (action)),
481            g_variant_get_string (value, NULL));
482
483   g_simple_action_set_state (action, value);
484 }
485
486 static GActionEntry actions[] = {
487   { "undo",  activate_action, NULL, NULL,      NULL },
488   { "redo",  activate_action, NULL, NULL,      NULL },
489   { "cut",   activate_action, NULL, NULL,      NULL },
490   { "copy",  activate_action, NULL, NULL,      NULL },
491   { "paste", activate_action, NULL, NULL,      NULL },
492   { "bold",  NULL,            NULL, "true",    toggle_changed },
493   { "lang",  NULL,            NULL, "'latin'", radio_changed },
494 };
495
496 static GActionGroup *
497 get_group (void)
498 {
499   GSimpleActionGroup *group;
500
501   group = g_simple_action_group_new ();
502
503   g_simple_action_group_add_entries (group, actions, G_N_ELEMENTS (actions), NULL);
504
505   return G_ACTION_GROUP (group);
506 }
507  
508 /* The action treeview {{{1 */
509
510 static void
511 enabled_cell_func (GtkTreeViewColumn *column,
512                    GtkCellRenderer   *cell,
513                    GtkTreeModel      *model,
514                    GtkTreeIter       *iter,
515                    gpointer           data)
516 {
517   GActionGroup *group = data;
518   gchar *name;
519   gboolean enabled;
520
521   gtk_tree_model_get (model, iter, 0, &name, -1);
522   enabled = g_action_group_get_action_enabled (group, name);
523   g_free (name);
524
525   gtk_cell_renderer_toggle_set_active (GTK_CELL_RENDERER_TOGGLE (cell), enabled);
526 }
527
528 static void
529 state_cell_func (GtkTreeViewColumn *column,
530                  GtkCellRenderer   *cell,
531                  GtkTreeModel      *model,
532                  GtkTreeIter       *iter,
533                  gpointer           data)
534 {
535   GActionGroup *group = data;
536   gchar *name;
537   GVariant *state;
538
539   gtk_tree_model_get (model, iter, 0, &name, -1);
540   state = g_action_group_get_action_state (group, name);
541   g_free (name);
542
543   gtk_cell_renderer_set_visible (cell, FALSE);
544   g_object_set (cell, "mode", GTK_CELL_RENDERER_MODE_INERT, NULL);
545
546   if (state &&
547       g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN) &&
548       GTK_IS_CELL_RENDERER_TOGGLE (cell))
549     {
550       gtk_cell_renderer_set_visible (cell, TRUE);
551       g_object_set (cell, "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL);
552       gtk_cell_renderer_toggle_set_active (GTK_CELL_RENDERER_TOGGLE (cell),
553                                            g_variant_get_boolean (state));
554     }
555   else if (state &&
556            g_variant_is_of_type (state, G_VARIANT_TYPE_STRING) &&
557            GTK_IS_CELL_RENDERER_COMBO (cell))
558     {
559       gtk_cell_renderer_set_visible (cell, TRUE);
560       g_object_set (cell, "mode", GTK_CELL_RENDERER_MODE_EDITABLE, NULL);
561       g_object_set (cell, "text", g_variant_get_string (state, NULL), NULL);
562     }
563
564   if (state)
565     g_variant_unref (state);
566 }
567
568 static void
569 enabled_cell_toggled (GtkCellRendererToggle *cell,
570                       const gchar           *path_str,
571                       GtkTreeModel          *model)
572 {
573   GActionGroup *group;
574   GAction *action;
575   gchar *name;
576   GtkTreePath *path;
577   GtkTreeIter iter;
578   gboolean enabled;
579
580   group = g_object_get_data (G_OBJECT (model), "group");
581   path = gtk_tree_path_new_from_string (path_str);
582   gtk_tree_model_get_iter (model, &iter, path);
583   gtk_tree_model_get (model, &iter, 0, &name, -1);
584
585   enabled = g_action_group_get_action_enabled (group, name);
586   action = g_simple_action_group_lookup (G_SIMPLE_ACTION_GROUP (group), name);
587   g_simple_action_set_enabled (G_SIMPLE_ACTION (action), !enabled);
588
589   gtk_tree_model_row_changed (model, path, &iter);
590
591   g_free (name);
592   gtk_tree_path_free (path);
593 }
594
595 static void
596 state_cell_toggled (GtkCellRendererToggle *cell,
597                     const gchar           *path_str,
598                     GtkTreeModel          *model)
599 {
600   GActionGroup *group;
601   GAction *action;
602   gchar *name;
603   GtkTreePath *path;
604   GtkTreeIter iter;
605   GVariant *state;
606
607   group = g_object_get_data (G_OBJECT (model), "group");
608   path = gtk_tree_path_new_from_string (path_str);
609   gtk_tree_model_get_iter (model, &iter, path);
610   gtk_tree_model_get (model, &iter, 0, &name, -1);
611
612   state = g_action_group_get_action_state (group, name);
613   action = g_simple_action_group_lookup (G_SIMPLE_ACTION_GROUP (group), name);
614   if (state && g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN))
615     {
616       gboolean b;
617
618       b = g_variant_get_boolean (state);
619       g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (!b));
620     }
621   else
622     {
623       /* nothing to do */
624     }
625
626   gtk_tree_model_row_changed (model, path, &iter);
627
628   g_free (name);
629   gtk_tree_path_free (path);
630   if (state)
631     g_variant_unref (state);
632 }
633
634 static void
635 state_cell_edited (GtkCellRendererCombo  *cell,
636                    const gchar           *path_str,
637                    const gchar           *new_text,
638                    GtkTreeModel          *model)
639 {
640   GActionGroup *group;
641   GAction *action;
642   gchar *name;
643   GtkTreePath *path;
644   GtkTreeIter iter;
645
646   group = g_object_get_data (G_OBJECT (model), "group");
647   path = gtk_tree_path_new_from_string (path_str);
648   gtk_tree_model_get_iter (model, &iter, path);
649   gtk_tree_model_get (model, &iter, 0, &name, -1);
650   action = g_simple_action_group_lookup (G_SIMPLE_ACTION_GROUP (group), name);
651   g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_string (new_text));
652
653   gtk_tree_model_row_changed (model, path, &iter);
654
655   g_free (name);
656   gtk_tree_path_free (path);
657 }
658
659 static GtkWidget *
660 create_action_treeview (GActionGroup *group)
661 {
662   GtkWidget *tv;
663   GtkListStore *store;
664   GtkListStore *values;
665   GtkTreeIter iter;
666   GtkTreeViewColumn *column;
667   GtkCellRenderer *cell;
668   gchar **actions;
669   gint i;
670
671   store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
672   actions = g_action_group_list_actions (group);
673   for (i = 0; actions[i]; i++)
674     {
675       gtk_list_store_append (store, &iter);
676       gtk_list_store_set (store, &iter, 0, actions[i], -1);
677     }
678   g_strfreev (actions);
679   g_object_set_data (G_OBJECT (store), "group", group);
680
681   tv = gtk_tree_view_new ();
682
683   g_signal_connect_swapped (group, "action-enabled-changed",
684                             G_CALLBACK (gtk_widget_queue_draw), tv);
685   g_signal_connect_swapped (group, "action-state-changed",
686                             G_CALLBACK (gtk_widget_queue_draw), tv);
687
688   gtk_tree_view_set_model (GTK_TREE_VIEW (tv), GTK_TREE_MODEL (store));
689
690   cell = gtk_cell_renderer_text_new ();
691   column = gtk_tree_view_column_new_with_attributes ("Action", cell,
692                                                      "text", 0,
693                                                      NULL);
694   gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
695
696   column = gtk_tree_view_column_new ();
697   gtk_tree_view_column_set_title (column, "Enabled");
698   cell = gtk_cell_renderer_toggle_new ();
699   gtk_tree_view_column_pack_start (column, cell, FALSE);
700   gtk_tree_view_column_set_cell_data_func (column, cell, enabled_cell_func, group, NULL);
701   g_signal_connect (cell, "toggled", G_CALLBACK (enabled_cell_toggled), store);
702   gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
703
704   column = gtk_tree_view_column_new ();
705   gtk_tree_view_column_set_title (column, "State");
706   cell = gtk_cell_renderer_toggle_new ();
707   gtk_tree_view_column_pack_start (column, cell, FALSE);
708   gtk_tree_view_column_set_cell_data_func (column, cell, state_cell_func, group, NULL);
709   g_signal_connect (cell, "toggled", G_CALLBACK (state_cell_toggled), store);
710   cell = gtk_cell_renderer_combo_new ();
711   values = gtk_list_store_new (1, G_TYPE_STRING);
712   gtk_list_store_append (values, &iter);
713   gtk_list_store_set (values, &iter, 0, "latin", -1);
714   gtk_list_store_append (values, &iter);
715   gtk_list_store_set (values, &iter, 0, "greek", -1);
716   gtk_list_store_append (values, &iter);
717   gtk_list_store_set (values, &iter, 0, "urdu", -1);
718   gtk_list_store_append (values, &iter);
719   gtk_list_store_set (values, &iter, 0, "sumerian", -1);
720   g_object_set (cell,
721                 "has-entry", FALSE,
722                 "model", values,
723                 "text-column", 0,
724                 "editable", TRUE,
725                 NULL);
726   gtk_tree_view_column_pack_start (column, cell, FALSE);
727   gtk_tree_view_column_set_cell_data_func (column, cell, state_cell_func, group, NULL);
728   g_signal_connect (cell, "edited", G_CALLBACK (state_cell_edited), store);
729   gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
730
731   return tv;
732 }
733
734 /* Dynamic menu changes {{{1 */
735
736 static void
737 toggle_sumerian (GtkToggleButton *button, gpointer data)
738 {
739   GMenuModel *model;
740   gboolean adding;
741   GMenuModel *m;
742
743   model = g_object_get_data (G_OBJECT (button), "model");
744
745   adding = gtk_toggle_button_get_active (button);
746
747   m = g_menu_model_get_item_link (model, g_menu_model_get_n_items (model) - 1, G_MENU_LINK_SECTION);
748   m = g_menu_model_get_item_link (m, g_menu_model_get_n_items (m) - 1, G_MENU_LINK_SUBMENU);
749   if (adding)
750     g_menu_append (G_MENU (m), "Sumerian", "lang::sumerian");
751   else
752     g_menu_remove (G_MENU (m), g_menu_model_get_n_items (m) - 1);
753 }
754
755 static void
756 action_list_add (GtkTreeModel *store,
757                  const gchar  *action)
758 {
759   GtkTreeIter iter;
760
761   gtk_list_store_append (GTK_LIST_STORE (store), &iter);
762   gtk_list_store_set (GTK_LIST_STORE (store), &iter, 0, action, -1);
763 }
764
765 static void
766 action_list_remove (GtkTreeModel *store,
767                     const gchar  *action)
768 {
769   GtkTreeIter iter;
770   gchar *text;
771
772   gtk_tree_model_get_iter_first (store, &iter);
773   do {
774     gtk_tree_model_get (store, &iter, 0, &text, -1);
775     if (g_strcmp0 (action, text) == 0)
776       {
777         g_free (text);
778         gtk_list_store_remove (GTK_LIST_STORE (store), &iter);
779         break;
780       }
781     g_free (text);
782   } while (gtk_tree_model_iter_next (store, &iter));
783 }
784
785 static void
786 toggle_italic (GtkToggleButton *button, gpointer data)
787 {
788   GMenuModel *model;
789   GActionGroup *group;
790   GSimpleAction *action;
791   gboolean adding;
792   GMenuModel *m;
793   GtkTreeView *tv = data;
794   GtkTreeModel *store;
795
796   model = g_object_get_data (G_OBJECT (button), "model");
797   group = g_object_get_data (G_OBJECT (button), "group");
798
799   store = gtk_tree_view_get_model (tv);
800
801   adding = gtk_toggle_button_get_active (button);
802
803   m = g_menu_model_get_item_link (model, g_menu_model_get_n_items (model) - 1, G_MENU_LINK_SECTION);
804   if (adding)
805     {
806       action = g_simple_action_new_stateful ("italic", NULL, g_variant_new_boolean (FALSE));
807       g_simple_action_group_insert (G_SIMPLE_ACTION_GROUP (group), G_ACTION (action));
808       g_signal_connect (action, "change-state", G_CALLBACK (toggle_changed), NULL);
809       g_object_unref (action);
810       action_list_add (store, "italic");
811       g_menu_insert (G_MENU (m), 1, "Italic", "italic");
812     }
813   else
814     {
815       g_simple_action_group_remove (G_SIMPLE_ACTION_GROUP (group), "italic");
816       action_list_remove (store, "italic");
817       g_menu_remove (G_MENU (m), 1);
818     }
819 }
820
821 static void
822 toggle_speed (GtkToggleButton *button, gpointer data)
823 {
824   GMenuModel *model;
825   GActionGroup *group;
826   GSimpleAction *action;
827   gboolean adding;
828   GMenuModel *m;
829   GMenu *submenu;
830   GtkTreeView *tv = data;
831   GtkTreeModel *store;
832
833   model = g_object_get_data (G_OBJECT (button), "model");
834   group = g_object_get_data (G_OBJECT (button), "group");
835
836   store = gtk_tree_view_get_model (tv);
837
838   adding = gtk_toggle_button_get_active (button);
839
840   m = g_menu_model_get_item_link (model, 1, G_MENU_LINK_SECTION);
841   if (adding)
842     {
843       action = g_simple_action_new ("faster", NULL);
844       g_simple_action_group_insert (G_SIMPLE_ACTION_GROUP (group), G_ACTION (action));
845       g_signal_connect (action, "activate", G_CALLBACK (activate_action), NULL);
846       g_object_unref (action);
847
848       action = g_simple_action_new ("slower", NULL);
849       g_simple_action_group_insert (G_SIMPLE_ACTION_GROUP (group), G_ACTION (action));
850       g_signal_connect (action, "activate", G_CALLBACK (activate_action), NULL);
851       g_object_unref (action);
852
853       action_list_add (store, "faster");
854       action_list_add (store, "slower");
855
856       submenu = g_menu_new ();
857       g_menu_append (submenu, "Faster", "faster");
858       g_menu_append (submenu, "Slower", "slower");
859       g_menu_append_submenu (G_MENU (m), "Speed", G_MENU_MODEL (submenu));
860     }
861   else
862     {
863       g_simple_action_group_remove (G_SIMPLE_ACTION_GROUP (group), "faster");
864       g_simple_action_group_remove (G_SIMPLE_ACTION_GROUP (group), "slower");
865
866       action_list_remove (store, "faster");
867       action_list_remove (store, "slower");
868
869       g_menu_remove (G_MENU (m), g_menu_model_get_n_items (m) - 1);
870     }
871 }
872 static GtkWidget *
873 create_add_remove_buttons (GActionGroup *group,
874                            GMenuModel   *model,
875                            GtkWidget    *treeview)
876 {
877   GtkWidget *box;
878   GtkWidget *button;
879
880   box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
881
882   button = gtk_check_button_new_with_label ("Add Italic");
883   gtk_container_add (GTK_CONTAINER (box), button);
884
885   g_object_set_data  (G_OBJECT (button), "group", group);
886   g_object_set_data  (G_OBJECT (button), "model", model);
887
888   g_signal_connect (button, "toggled",
889                     G_CALLBACK (toggle_italic), treeview);
890
891   button = gtk_check_button_new_with_label ("Add Sumerian");
892   gtk_container_add (GTK_CONTAINER (box), button);
893
894   g_object_set_data  (G_OBJECT (button), "group", group);
895   g_object_set_data  (G_OBJECT (button), "model", model);
896
897   g_signal_connect (button, "toggled",
898                     G_CALLBACK (toggle_sumerian), NULL);
899
900   button = gtk_check_button_new_with_label ("Add Speed");
901   gtk_container_add (GTK_CONTAINER (box), button);
902
903   g_object_set_data  (G_OBJECT (button), "group", group);
904   g_object_set_data  (G_OBJECT (button), "model", model);
905
906   g_signal_connect (button, "toggled",
907                     G_CALLBACK (toggle_speed), treeview);
908   return box;
909 }
910
911 /* main {{{1 */
912
913 static void
914 button_clicked (GtkButton  *button,
915                 MenuHolder *holder)
916 {
917   GtkWidget *menu;
918
919   menu = menu_holder_get_menu (holder);
920   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 0, 0);
921 }
922
923 int
924 main (int argc, char *argv[])
925 {
926   GtkWidget *window;
927   GtkWidget *box;
928   GtkWidget *button;
929   GtkWidget *tv;
930   GtkWidget *buttons;
931   MenuHolder *holder;
932   GMenuModel *model;
933   GActionGroup *group;
934   GDBusConnection *bus;
935   GError *error = NULL;
936   gboolean do_export = FALSE;
937   gboolean do_import = FALSE;
938   GOptionEntry entries[] = {
939     { "export", 0, 0, G_OPTION_ARG_NONE, &do_export, "Export actions and menus over D-Bus", NULL },
940     { "import", 0, 0, G_OPTION_ARG_NONE, &do_import, "Use exported actions and menus", NULL },
941     { NULL, }
942   };
943
944   gtk_init_with_args (&argc, &argv, NULL, entries, NULL, NULL);
945
946   if (do_export && do_import)
947     {
948        g_error ("can't have it both ways\n");
949        exit (1);
950     }
951
952   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
953   box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
954   gtk_container_add (GTK_CONTAINER (window), box);
955
956   bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
957
958   if (do_import)
959     {
960       g_print ("Getting menus from the bus...\n");
961       model = (GMenuModel*)g_menu_proxy_get (bus, "org.gtk.TestMenus", "/path");
962       g_print ("Getting actions from the bus...\n");
963       group = (GActionGroup*)g_dbus_action_group_new_sync (bus, "org.gtk.TestMenus", "/path", 0, NULL, NULL);
964     }
965   else
966     {
967       group = get_group ();
968       model = get_model ();
969
970       tv = create_action_treeview (group);
971       gtk_container_add (GTK_CONTAINER (box), tv);
972       buttons = create_add_remove_buttons (group, model, tv);
973       gtk_container_add (GTK_CONTAINER (box), buttons);
974     }
975
976   if (do_export)
977     {
978       g_print ("Exporting menus on the bus...\n");
979       if (!g_menu_exporter_export (bus, "/path", model, &error))
980         {
981           g_warning ("Menu export failed: %s", error->message);
982           exit (1);
983         }
984       g_print ("Exporting actions on the bus...\n");
985       if (!g_action_group_exporter_export (bus, "/path", group, &error))
986         {
987           g_warning ("Action export failed: %s", error->message);
988           exit (1);
989         }
990       g_bus_own_name_on_connection (bus, "org.gtk.TestMenus",
991                                     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: */