]> Pileus Git - ~andy/gtk/blob - tests/testgmenu.c
testgmenu: Use activate for toggle 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   b = g_strcmp0 (a->target, g_variant_get_string (state, NULL)) == 0;
148
149   gtk_check_menu_item_set_active (w, b);
150 }
151
152 /* Menuitem callbacks {{{2 */
153
154 static void
155 item_activated (GtkWidget *w,
156                 gpointer   data)
157 {
158   ActionData *a;
159
160   a = g_object_get_data (G_OBJECT (w), "action");
161   g_action_group_activate_action (a->group, a->name, NULL);
162 }
163
164 static void
165 radio_item_toggled (GtkCheckMenuItem *w,
166                     gpointer          data)
167 {
168   ActionData *a;
169   GVariant *v;
170
171   a = g_object_get_data (G_OBJECT (w), "action");
172   if (gtk_check_menu_item_get_active (w))
173     {
174       g_action_group_change_action_state (a->group, a->name,
175                                           g_variant_new_string (a->target));
176     }
177   else
178     {
179       v = g_action_group_get_action_state (a->group, a->name);
180       if (g_strcmp0 (g_variant_get_string (v, NULL), a->target) == 0)
181         gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w), TRUE);
182       g_variant_unref (v);
183     }
184 }
185
186 /* GtkMenu construction {{{2 */
187
188 static GtkWidget *
189 create_menuitem_from_model (GMenuModel   *model,
190                             gint          item,
191                             GActionGroup *group)
192 {
193   GtkWidget *w;
194   gchar *label;
195   gchar *action;
196   gchar *target;
197   gchar *s;
198   ActionData *a;
199   const GVariantType *type;
200   GVariant *v;
201
202   g_menu_model_get_item_attribute (model, item, G_MENU_ATTRIBUTE_LABEL, "s", &label);
203
204   action = NULL;
205   g_menu_model_get_item_attribute (model, item, G_MENU_ATTRIBUTE_ACTION, "s", &action);
206
207   if (action != NULL)
208     type = g_action_group_get_action_state_type (group, action);
209   else
210     type = NULL;
211
212   if (type == NULL)
213     w = gtk_menu_item_new_with_mnemonic (label);
214   else if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN))
215     w = gtk_check_menu_item_new_with_label (label);
216   else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING))
217     {
218       w = gtk_check_menu_item_new_with_label (label);
219       gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (w), TRUE);
220     }
221   else
222     g_assert_not_reached ();
223
224   if (action != NULL)
225     {
226       a = g_new0 (ActionData, 1);
227       a->group = g_object_ref (group);
228       a->name = g_strdup (action);
229       g_object_set_data_full (G_OBJECT (w), "action", a, action_data_free);
230
231       if (!g_action_group_get_action_enabled (group, action))
232         gtk_widget_set_sensitive (w, FALSE);
233
234       s = g_strconcat ("action-enabled-changed::", action, NULL);
235       a->enabled_changed_id = g_signal_connect (group, s,
236                                                 G_CALLBACK (enabled_changed), w);
237       g_free (s);
238
239       if (type == NULL)
240         a->activate_handler = g_signal_connect (w, "activate", G_CALLBACK (item_activated), NULL);
241       else if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN))
242         {
243           a->activate_handler = g_signal_connect (w, "activate", G_CALLBACK (item_activated), NULL);
244           s = g_strconcat ("action-state-changed::", action, NULL);
245           a->state_changed_id = g_signal_connect (group, s,
246                                                   G_CALLBACK (toggle_state_changed), w);
247           g_free (s);
248           v = g_action_group_get_action_state (group, action);
249           gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w),
250                                           g_variant_get_boolean (v));
251           g_variant_unref (v);
252         }
253       else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING))
254         {
255           g_signal_connect (w, "toggled", G_CALLBACK (radio_item_toggled), NULL);
256           s = g_strconcat ("action-state-changed::", action, NULL);
257           a->state_changed_id = g_signal_connect (group, s,
258                                                   G_CALLBACK (radio_state_changed), w);
259           g_free (s);
260           g_menu_model_get_item_attribute (model, item, G_MENU_ATTRIBUTE_TARGET, "s", &target);
261           a->target = g_strdup (target);
262           v = g_action_group_get_action_state (group, action);
263           gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w),
264                                           g_strcmp0 (g_variant_get_string (v, NULL), target) == 0);
265           g_variant_unref (v);
266           g_free (target);
267         }
268       else
269         g_assert_not_reached ();
270     }
271
272   g_free (label);
273   g_free (action);
274
275   return w;
276 }
277
278 static GtkWidget *create_menu_from_model (GMenuModel   *model,
279                                           GActionGroup *group);
280
281 static void
282 append_items_from_model (GtkWidget    *menu,
283                          GMenuModel   *model,
284                          GActionGroup *group,
285                          gboolean     *need_separator)
286 {
287   gint n;
288   gint i;
289   GtkWidget *w;
290   GtkWidget *menuitem;
291   GtkWidget *submenu;
292   GMenuModel *m;
293
294   n = g_menu_model_get_n_items (model);
295
296   if (*need_separator && n > 0)
297     {
298       w = gtk_separator_menu_item_new ();
299       gtk_widget_show (w);
300       gtk_menu_shell_append (GTK_MENU_SHELL (menu), w);
301
302       *need_separator = FALSE;
303     }
304
305   for (i = 0; i < n; i++)
306     {
307       if ((m = g_menu_model_get_item_link (model, i, G_MENU_LINK_SECTION)))
308         {
309           append_items_from_model (menu, m, group, need_separator);
310           g_object_unref (m);
311           continue;
312         }
313
314       menuitem = create_menuitem_from_model (model, i, group);
315
316       if ((m = g_menu_model_get_item_link (model, i, G_MENU_LINK_SUBMENU)))
317         {
318           submenu = create_menu_from_model (m, group);
319           gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem), submenu);
320           g_object_unref (m);
321         }
322
323       gtk_widget_show (menuitem);
324       gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
325
326       *need_separator = TRUE;
327     }
328 }
329
330 static GtkWidget *
331 create_menu_from_model (GMenuModel   *model,
332                         GActionGroup *group)
333 {
334   GtkWidget *w;
335   gboolean need_separator;
336
337   w = gtk_menu_new ();
338   need_separator = FALSE;
339   append_items_from_model (w, model, group, &need_separator);
340
341   return w;
342 }
343
344 /* }}}2 */
345
346 MenuHolder *
347 menu_holder_new (GMenuModel   *model,
348                  GActionGroup *group)
349 {
350   MenuHolder *holder;
351
352   holder = g_new (MenuHolder, 1);
353   holder->model = g_object_ref (model);
354   holder->group = g_object_ref (group);
355   holder->menu = create_menu_from_model (model, group);
356   holder->items_changed = FALSE;
357
358   connect_to_items_changed (model, G_CALLBACK (items_changed), holder);
359
360   return holder;
361 }
362
363 GtkWidget *
364 menu_holder_get_menu (MenuHolder *holder)
365 {
366   if (holder->items_changed)
367     {
368       holder->items_changed = FALSE;
369       gtk_widget_destroy (holder->menu);
370       holder->menu = create_menu_from_model (holder->model, holder->group);
371     }
372
373   return holder->menu;
374 }
375
376 /* The example menu {{{1 */
377
378 static const gchar menu_markup[] =
379   "<menu id='edit-menu'>\n"
380   "  <section>\n"
381   "    <item action='undo'>\n"
382   "      <attribute name='label' translatable='yes' context='Stock label'>'_Undo'</attribute>\n"
383   "    </item>\n"
384   "    <item label='Redo' action='redo'/>\n"
385   "  </section>\n"
386   "  <section></section>\n"
387   "  <section label='Copy &amp; Paste'>\n"
388   "    <item label='Cut' action='cut'/>\n"
389   "    <item label='Copy' action='copy'/>\n"
390   "    <item label='Paste' action='paste'/>\n"
391   "  </section>\n"
392   "  <section>\n"
393   "    <item label='Bold' action='bold'/>\n"
394   "    <submenu label='Language'>\n"
395   "      <item label='Latin' action='lang' target='latin'/>\n"
396   "      <item label='Greek' action='lang' target='greek'/>\n"
397   "      <item label='Urdu'  action='lang' target='urdu'/>\n"
398   "    </submenu>\n"
399   "  </section>\n"
400   "</menu>\n";
401
402 static void
403 start_element (GMarkupParseContext *context,
404                const gchar         *element_name,
405                const gchar        **attribute_names,
406                const gchar        **attribute_values,
407                gpointer             user_data,
408                GError             **error)
409 {
410   if (strcmp (element_name, "menu") == 0)
411     g_menu_markup_parser_start_menu (context, "gtk30", NULL);
412 }
413
414 static void
415 end_element (GMarkupParseContext *context,
416              const gchar         *element_name,
417              gpointer             user_data,
418              GError             **error)
419 {
420   GMenu **menu = user_data;
421
422   if (strcmp (element_name, "menu") == 0)
423     *menu = g_menu_markup_parser_end_menu (context);
424 }
425
426 static const GMarkupParser parser = {
427    start_element, end_element, NULL, NULL, NULL
428 };
429
430 static GMenuModel *
431 get_model (void)
432 {
433   GMarkupParseContext *context;
434   GMenu *menu = NULL;
435   GError *error = NULL;
436
437   context = g_markup_parse_context_new (&parser, 0, &menu, NULL);
438   if (!g_markup_parse_context_parse (context, menu_markup, -1, &error))
439     {
440        g_warning ("menu parsing failed: %s\n", error->message);
441        exit (1);
442     }
443   g_markup_parse_context_free (context);
444   g_assert (menu);
445
446    return G_MENU_MODEL (menu);
447 }
448
449  /* The example actions {{{1 */
450
451 static void
452 activate_action (GSimpleAction *action, GVariant *parameter, gpointer user_data)
453 {
454   g_print ("Action %s activated\n", g_action_get_name (G_ACTION (action)));
455 }
456
457 static void
458 activate_toggle (GSimpleAction *action, GVariant *parameter, gpointer user_data)
459 {
460   GVariant *old_state, *new_state;
461
462   old_state = g_action_get_state (G_ACTION (action));
463   new_state = g_variant_new_boolean (!g_variant_get_boolean (old_state));
464
465   g_print ("Toggle action %s activated, state changes from %d to %d\n",
466            g_action_get_name (G_ACTION (action)),
467            g_variant_get_boolean (old_state),
468            g_variant_get_boolean (new_state));
469
470   g_simple_action_set_state (action, new_state);
471   g_variant_unref (old_state);
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",  activate_toggle, NULL, "true",    NULL },
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, "activate", G_CALLBACK (activate_toggle), 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 static gboolean
925 on_delete_event (GtkWidget   *widget,
926                  GdkEvent    *event,
927                  gpointer     user_data)
928 {
929   gtk_main_quit ();
930   return TRUE;
931 }
932
933 int
934 main (int argc, char *argv[])
935 {
936   GtkWidget *window;
937   GtkWidget *box;
938   GtkWidget *button;
939   GtkWidget *tv;
940   GtkWidget *buttons;
941   MenuHolder *holder;
942   GMenuModel *model;
943   GActionGroup *group;
944   GDBusConnection *bus;
945   GError *error = NULL;
946   gboolean do_export = FALSE;
947   gboolean do_import = FALSE;
948   GOptionEntry entries[] = {
949     { "export", 0, 0, G_OPTION_ARG_NONE, &do_export, "Export actions and menus over D-Bus", NULL },
950     { "import", 0, 0, G_OPTION_ARG_NONE, &do_import, "Use exported actions and menus", NULL },
951     { NULL, }
952   };
953
954   gtk_init_with_args (&argc, &argv, NULL, entries, NULL, NULL);
955
956   if (do_export && do_import)
957     {
958        g_error ("can't have it both ways\n");
959        exit (1);
960     }
961
962   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
963   g_signal_connect (window, "delete-event", G_CALLBACK(on_delete_event), NULL);
964   box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
965   gtk_container_add (GTK_CONTAINER (window), box);
966
967   bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
968
969   if (do_import)
970     {
971       g_print ("Getting menus from the bus...\n");
972       model = (GMenuModel*)g_menu_proxy_get (bus, BUS_NAME, OBJ_PATH);
973       g_print ("Getting actions from the bus...\n");
974       group = (GActionGroup*)g_dbus_action_group_new_sync (bus, BUS_NAME, OBJ_PATH, 0, NULL, NULL);
975     }
976   else
977     {
978       group = get_group ();
979       model = get_model ();
980
981       tv = create_action_treeview (group);
982       gtk_container_add (GTK_CONTAINER (box), tv);
983       buttons = create_add_remove_buttons (group, model, tv);
984       gtk_container_add (GTK_CONTAINER (box), buttons);
985     }
986
987   if (do_export)
988     {
989       g_print ("Exporting menus on the bus...\n");
990       if (!g_menu_exporter_export (bus, OBJ_PATH, model, &error))
991         {
992           g_warning ("Menu export failed: %s", error->message);
993           exit (1);
994         }
995       g_print ("Exporting actions on the bus...\n");
996       if (!g_action_group_exporter_export (bus, OBJ_PATH, group, &error))
997         {
998           g_warning ("Action export failed: %s", error->message);
999           exit (1);
1000         }
1001       g_bus_own_name_on_connection (bus, BUS_NAME, 0, NULL, NULL, NULL, NULL);
1002     }
1003   else
1004     {
1005       holder = menu_holder_new (model, group);
1006       button = gtk_button_new_with_label ("Click here");
1007       g_signal_connect (button, "clicked",
1008                         G_CALLBACK (button_clicked), holder);
1009       gtk_container_add (GTK_CONTAINER (box), button);
1010     }
1011
1012   gtk_widget_show_all (window);
1013
1014   gtk_main ();
1015
1016   return 0;
1017 }
1018
1019 /* Epilogue {{{1 */
1020 /* vim:set foldmethod=marker: */