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