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