2008-09-25 Alberto Garcia <agarcia@igalia.com>
[hildon] / src / hildon-app-menu.c
1 /*
2  * This file is a part of hildon
3  *
4  * Copyright (C) 2008 Nokia Corporation, all rights reserved.
5  *
6  * Contact: Karl Lattimer <karl.lattimer@nokia.com>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU Lesser Public License as published by
10  * the Free Software Foundation; version 2 of the license.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Lesser Public License for more details.
16  *
17  */
18
19 /**
20  * SECTION:hildon-app-menu
21  * @short_description: Widget representing the application menu in the Hildon framework.
22  *
23  * The #HildonAppMenu is a GTK widget which represents an application
24  * menu in the Hildon framework.
25  *
26  * This menu opens from the top of the screen and contains a number of
27  * entries (#GtkButton) organized in two columns. Entries are added
28  * left to right and top to bottom.
29  *
30  * Besides that, the #HildonAppMenu can contain a group of filter buttons
31  * (#GtkToggleButton or #GtkRadioButton).
32  *
33  * To use a #HildonAppMenu, add it to a #HildonStackableWindow with
34  * with hildon_stackable_window_set_main_menu(). The menu will appear
35  * when the user presses the window title bar.
36  *
37  * Alternatively, you can show it by hand using gtk_widget_show().
38  *
39  * The menu will be automatically hidden when one of its buttons is
40  * clicked. Use g_signal_connect_after() when connecting callbacks to
41  * buttons to make sure that they're called after the menu
42  * disappears. Alternatively, you can add the button to the menu
43  * before connecting any callback.
44  *
45  * Although implemented with a #GtkWindow, #HildonAppMenu behaves like
46  * a normal ref-counted widget, so g_object_ref(), g_object_unref(),
47  * g_object_ref_sink() and friends will behave just like with any
48  * other non-toplevel widget.
49  *
50  * <example>
51  * <title>Creating a HildonAppMenu</title>
52  * <programlisting>
53  * HildonStackableWindow *win;
54  * HildonAppMenu *menu;
55  * GtkWidget *button;
56  * GtkWidget *filter;
57  * <!-- -->
58  * win = HILDON_STACKABLE_WINDOW (hildon_stackable_window_new ());
59  * menu = HILDON_APP_MENU (hildon_app_menu_new ());
60  * <!-- -->
61  * // Create a button and add it to the menu
62  * button = gtk_button_new_with_label ("Menu command one");
63  * g_signal_connect_after (button, "clicked", G_CALLBACK (button_one_clicked), userdata);
64  * hildon_app_menu_append (menu, GTK_BUTTON (button));
65  * <!-- -->
66  * // Another button
67  * button = gtk_button_new_with_label ("Menu command two");
68  * g_signal_connect_after (button, "clicked", G_CALLBACK (button_two_clicked), userdata);
69  * hildon_app_menu_append (menu, GTK_BUTTON (button));
70  * <!-- -->
71  * // Create a filter and add it to the menu
72  * filter = gtk_radio_button_new_with_label (NULL, "Filter one");
73  * gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (filter), FALSE);
74  * g_signal_connect_after (filter, "clicked", G_CALLBACK (filter_one_clicked), userdata);
75  * hildon_app_menu_add_filter (menu, GTK_BUTTON (filter));
76  * <!-- -->
77  * // Add a new filter
78  * filter = gtk_radio_button_new_with_label_from_widget (GTK_RADIO_BUTTON (filter), "Filter two");
79  * gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (filter), FALSE);
80  * g_signal_connect_after (filter, "clicked", G_CALLBACK (filter_two_clicked), userdata);
81  * hildon_app_menu_add_filter (menu, GTK_BUTTON (filter));
82  * <!-- -->
83  * // Add the menu to the window
84  * hildon_stackable_window_set_main_menu (win, menu);
85  * </programlisting>
86  * </example>
87  *
88  */
89
90 #include                                        <string.h>
91 #include                                        <X11/Xatom.h>
92 #include                                        <gdk/gdkx.h>
93
94 #include                                        "hildon-app-menu.h"
95 #include                                        "hildon-app-menu-private.h"
96
97 enum {
98     PROP_COLUMNS = 1
99 };
100
101 static GdkWindow *
102 grab_transfer_window_get                        (GtkWidget *widget);
103
104 static void
105 hildon_app_menu_construct_child                 (HildonAppMenu *menu);
106
107 static void
108 button_visibility_changed                       (GtkWidget     *item,
109                                                  GParamSpec    *arg1,
110                                                  HildonAppMenu *menu);
111
112 G_DEFINE_TYPE (HildonAppMenu, hildon_app_menu, GTK_TYPE_WINDOW);
113
114 /**
115  * hildon_app_menu_new:
116  *
117  * Creates a new #HildonAppMenu.
118  *
119  * Return value: A #HildonAppMenu.
120  **/
121 GtkWidget *
122 hildon_app_menu_new                             (void)
123 {
124     GtkWidget *menu = g_object_new (HILDON_TYPE_APP_MENU, NULL);
125     return menu;
126 }
127
128 /**
129  * hildon_app_menu_insert:
130  * @menu : A #HildonAppMenu
131  * @item : A #GtkButton to add to the #HildonAppMenu
132  * @position : The position in the item list where @item is added (from 0 to n-1).
133  *
134  * Adds @item to @menu at the position indicated by @position.
135  */
136 void
137 hildon_app_menu_insert                          (HildonAppMenu *menu,
138                                                  GtkButton     *item,
139                                                  gint           position)
140 {
141     HildonAppMenuPrivate *priv;
142
143     g_return_if_fail (HILDON_IS_APP_MENU (menu));
144     g_return_if_fail (GTK_IS_BUTTON (item));
145
146     priv = HILDON_APP_MENU_GET_PRIVATE(menu);
147
148     /* Add the item to the menu */
149     gtk_widget_show (GTK_WIDGET (item));
150     priv->buttons = g_list_insert (priv->buttons, item, position);
151     hildon_app_menu_construct_child (menu);
152
153     /* Close the menu when the button is clicked */
154     g_signal_connect_swapped (item, "clicked", G_CALLBACK (gtk_widget_hide), menu);
155     g_signal_connect (item, "notify::visible", G_CALLBACK (button_visibility_changed), menu);
156 }
157
158 /**
159  * hildon_app_menu_append:
160  * @menu : A #HildonAppMenu
161  * @item : A #GtkButton to add to the #HildonAppMenu
162  *
163  * Adds @item to the end of the menu's item list.
164  */
165 void
166 hildon_app_menu_append                          (HildonAppMenu *menu,
167                                                  GtkButton     *item)
168 {
169     hildon_app_menu_insert (menu, item, -1);
170 }
171
172 /**
173  * hildon_app_menu_prepend:
174  * @menu : A #HildonAppMenu
175  * @item : A #GtkButton to add to the #HildonAppMenu
176  *
177  * Adds @item to the beginning of the menu's item list.
178  */
179 void
180 hildon_app_menu_prepend                         (HildonAppMenu *menu,
181                                                  GtkButton     *item)
182 {
183     hildon_app_menu_insert (menu, item, 0);
184 }
185
186 /**
187  * hildon_app_menu_reorder_child:
188  * @menu : A #HildonAppMenu
189  * @item : A #GtkButton to move
190  * @position : The new position to place @item (from 0 to n-1).
191  *
192  * Moves a #GtkButton to a new position within #HildonAppMenu.
193  */
194 void
195 hildon_app_menu_reorder_child                   (HildonAppMenu *menu,
196                                                  GtkButton     *item,
197                                                  gint           position)
198 {
199     HildonAppMenuPrivate *priv;
200
201     g_return_if_fail (HILDON_IS_APP_MENU (menu));
202     g_return_if_fail (GTK_IS_BUTTON (item));
203     g_return_if_fail (position >= 0);
204
205     priv = HILDON_APP_MENU_GET_PRIVATE (menu);
206
207     g_return_if_fail (g_list_find (priv->buttons, item));
208
209     /* Move the item */
210     priv->buttons = g_list_remove (priv->buttons, item);
211     priv->buttons = g_list_insert (priv->buttons, item, position);
212
213     hildon_app_menu_construct_child (menu);
214 }
215
216 /**
217  * hildon_app_menu_add_filter:
218  * @menu : A #HildonAppMenu
219  * @filter : A #GtkButton to add to the #HildonAppMenu.
220  *
221  * Adds the @filter to @menu.
222  */
223 void
224 hildon_app_menu_add_filter                      (HildonAppMenu *menu,
225                                                  GtkButton *filter)
226 {
227     HildonAppMenuPrivate *priv;
228
229     g_return_if_fail (HILDON_IS_APP_MENU (menu));
230     g_return_if_fail (GTK_IS_BUTTON (filter));
231
232     priv = HILDON_APP_MENU_GET_PRIVATE(menu);
233
234     /* Add the filter to the menu */
235     gtk_widget_show (GTK_WIDGET (filter));
236     priv->filters = g_list_append (priv->filters, filter);
237     hildon_app_menu_construct_child (menu);
238
239     /* Close the menu when the button is clicked */
240     g_signal_connect_swapped (filter, "clicked", G_CALLBACK (gtk_widget_hide), menu);
241     g_signal_connect (filter, "notify::visible", G_CALLBACK (button_visibility_changed), menu);
242 }
243
244 static void
245 hildon_app_menu_set_columns                     (HildonAppMenu *menu,
246                                                  guint          columns)
247 {
248     HildonAppMenuPrivate *priv;
249
250     g_warning ("This property will be removed in the future. See documentation for details");
251
252     g_return_if_fail (HILDON_IS_APP_MENU (menu));
253     g_return_if_fail (columns > 0);
254
255     priv = HILDON_APP_MENU_GET_PRIVATE (menu);
256
257     if (columns != priv->columns) {
258         priv->columns = columns;
259         hildon_app_menu_construct_child (menu);
260     }
261 }
262
263 static void
264 hildon_app_menu_set_property                    (GObject      *object,
265                                                  guint         prop_id,
266                                                  const GValue *value,
267                                                  GParamSpec   *pspec)
268 {
269     HildonAppMenu *menu = HILDON_APP_MENU (object);
270
271     switch (prop_id)
272     {
273     case PROP_COLUMNS:
274         hildon_app_menu_set_columns (menu, g_value_get_uint (value));
275         break;
276     default:
277         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
278         break;
279     }
280 }
281
282 static void
283 button_visibility_changed                       (GtkWidget     *item,
284                                                  GParamSpec    *arg1,
285                                                  HildonAppMenu *menu)
286 {
287     hildon_app_menu_construct_child (menu);
288 }
289
290 static void
291 hildon_app_menu_map                             (GtkWidget *widget)
292 {
293     HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(widget);
294
295     GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->map (widget);
296
297     /* Grab pointer and keyboard */
298     if (priv->transfer_window == NULL) {
299         gboolean has_grab = FALSE;
300
301         priv->transfer_window = grab_transfer_window_get (widget);
302
303         if (gdk_pointer_grab (priv->transfer_window, TRUE,
304                               GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
305                               GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
306                               GDK_POINTER_MOTION_MASK, NULL, NULL,
307                               GDK_CURRENT_TIME) == GDK_GRAB_SUCCESS) {
308             if (gdk_keyboard_grab (priv->transfer_window, TRUE,
309                                    GDK_CURRENT_TIME) == GDK_GRAB_SUCCESS) {
310                 has_grab = TRUE;
311             } else {
312                 gdk_display_pointer_ungrab (gtk_widget_get_display (widget),
313                                             GDK_CURRENT_TIME);
314             }
315         }
316
317         if (has_grab) {
318             gtk_grab_add (widget);
319         } else {
320             gdk_window_destroy (priv->transfer_window);
321             priv->transfer_window = NULL;
322         }
323     }
324 }
325
326 static void
327 hildon_app_menu_unmap                           (GtkWidget *widget)
328 {
329     HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(widget);
330
331     /* Remove the grab */
332     if (priv->transfer_window != NULL) {
333         gdk_display_pointer_ungrab (gtk_widget_get_display (widget),
334                                     GDK_CURRENT_TIME);
335         gtk_grab_remove (widget);
336
337         gdk_window_destroy (priv->transfer_window);
338         priv->transfer_window = NULL;
339     }
340
341     GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->unmap (widget);
342 }
343
344 static gboolean
345 hildon_app_menu_button_press                    (GtkWidget *widget,
346                                                  GdkEventButton *event)
347 {
348     int x, y;
349     HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(widget);
350
351     gdk_window_get_position (widget->window, &x, &y);
352
353     /* Whether the button has been pressed outside the widget */
354     priv->pressed_outside = (event->x_root < x || event->x_root > x + widget->allocation.width ||
355                              event->y_root < y || event->y_root > y + widget->allocation.height);
356
357     if (GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->button_press_event) {
358         return GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->button_press_event (widget, event);
359     } else {
360         return FALSE;
361     }
362 }
363
364 static gboolean
365 hildon_app_menu_button_release                  (GtkWidget *widget,
366                                                  GdkEventButton *event)
367 {
368     HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(widget);
369
370     if (priv->pressed_outside) {
371         int x, y;
372         gboolean released_outside;
373
374         gdk_window_get_position (widget->window, &x, &y);
375
376         /* Whether the button has been released outside the widget */
377         released_outside = (event->x_root < x || event->x_root > x + widget->allocation.width ||
378                             event->y_root < y || event->y_root > y + widget->allocation.height);
379
380         if (released_outside) {
381             gtk_widget_hide (widget);
382         }
383
384         priv->pressed_outside = FALSE; /* Always reset pressed_outside to FALSE */
385     }
386
387     if (GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->button_release_event) {
388         return GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->button_release_event (widget, event);
389     } else {
390         return FALSE;
391     }
392 }
393
394 /* Grab transfer window (based on the one from GtkMenu) */
395 static GdkWindow *
396 grab_transfer_window_get                        (GtkWidget *widget)
397 {
398     GdkWindow *window;
399     GdkWindowAttr attributes;
400     gint attributes_mask;
401
402     attributes.x = 0;
403     attributes.y = 0;
404     attributes.width = 10;
405     attributes.height = 10;
406     attributes.window_type = GDK_WINDOW_TEMP;
407     attributes.wclass = GDK_INPUT_ONLY;
408     attributes.override_redirect = TRUE;
409     attributes.event_mask = 0;
410
411     attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_NOREDIR;
412
413     window = gdk_window_new (gtk_widget_get_root_window (widget),
414                                  &attributes, attributes_mask);
415     gdk_window_set_user_data (window, widget);
416
417     gdk_window_show (window);
418
419     return window;
420 }
421
422 static void
423 hildon_app_menu_realize                         (GtkWidget *widget)
424 {
425     GdkDisplay *display;
426     Atom atom;
427     const gchar *notification_type = "_HILDON_WM_WINDOW_TYPE_APP_MENU";
428
429     GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->realize (widget);
430
431     gdk_window_set_decorations (widget->window, GDK_DECOR_BORDER);
432
433     display = gdk_drawable_get_display (widget->window);
434     atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE");
435     XChangeProperty (GDK_WINDOW_XDISPLAY (widget->window), GDK_WINDOW_XID (widget->window),
436                      atom, XA_STRING, 8, PropModeReplace, (guchar *) notification_type,
437                      strlen (notification_type));
438 }
439
440 static void
441 hildon_app_menu_apply_style                     (GtkWidget *widget)
442 {
443     GdkScreen *screen;
444     gint width;
445     guint horizontal_spacing, vertical_spacing, inner_border, external_border;
446     HildonAppMenuPrivate *priv;
447
448     priv = HILDON_APP_MENU_GET_PRIVATE (widget);
449
450     gtk_widget_style_get (widget,
451                           "horizontal-spacing", &horizontal_spacing,
452                           "vertical-spacing", &vertical_spacing,
453                           "inner-border", &inner_border,
454                           "external-border", &external_border,
455                           NULL);
456
457     /* Set spacings */
458     gtk_table_set_row_spacings (priv->table, vertical_spacing);
459     gtk_table_set_col_spacings (priv->table, horizontal_spacing);
460     gtk_box_set_spacing (priv->vbox, vertical_spacing);
461
462     /* Set inner border */
463     gtk_container_set_border_width (GTK_CONTAINER (widget), inner_border);
464
465     /* Set default size */
466     screen = gtk_widget_get_screen (widget);
467     width = gdk_screen_get_width (screen) - external_border * 2;
468     gtk_window_set_default_size (GTK_WINDOW (widget), width, -1);
469 }
470
471 static void
472 hildon_app_menu_style_set                       (GtkWidget *widget,
473                                                  GtkStyle  *previous_style)
474 {
475     if (GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->style_set)
476         GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->style_set (widget, previous_style);
477
478     hildon_app_menu_apply_style (widget);
479 }
480
481 static void
482 hildon_app_menu_construct_child                 (HildonAppMenu *menu)
483 {
484     GtkWidget *alignment;
485     HildonAppMenuPrivate *priv;
486     gint col, row;
487     GList *iter;
488
489     priv = HILDON_APP_MENU_GET_PRIVATE(menu);
490
491     /* Remove all buttons from their parents */
492     for (iter = priv->buttons; iter != NULL; iter = iter->next) {
493         GtkWidget *item = GTK_WIDGET (iter->data);
494         GtkWidget *parent = gtk_widget_get_parent (item);
495         if (parent) {
496             g_object_ref (item);
497             gtk_container_remove (GTK_CONTAINER (parent), item);
498         }
499     }
500
501     for (iter = priv->filters; iter != NULL; iter = iter->next) {
502         GtkWidget *item = GTK_WIDGET (iter->data);
503         GtkWidget *parent = gtk_widget_get_parent (item);
504         if (parent) {
505             g_object_ref (item);
506             gtk_container_remove (GTK_CONTAINER (parent), item);
507         }
508     }
509
510     /* Create the contents of the menu again */
511     if (priv->vbox) {
512         gtk_widget_destroy (GTK_WIDGET (priv->vbox));
513     }
514
515     /* Resize the menu to its minimum size */
516     gtk_window_resize (GTK_WINDOW (menu), 1, 1);
517
518     /* Create boxes and tables */
519     priv->filters_hbox = GTK_BOX (gtk_hbox_new (TRUE, 0));
520     priv->vbox = GTK_BOX (gtk_vbox_new (FALSE, 0));
521     priv->table = GTK_TABLE (gtk_table_new (1, priv->columns, TRUE));
522
523     /* Align the filters to the center */
524     alignment = gtk_alignment_new (0.5, 0.5, 0, 0);
525     gtk_container_add (GTK_CONTAINER (alignment), GTK_WIDGET (priv->filters_hbox));
526
527     /* Pack everything */
528     gtk_container_add (GTK_CONTAINER (menu), GTK_WIDGET (priv->vbox));
529     gtk_box_pack_start (priv->vbox, alignment, TRUE, TRUE, 0);
530     gtk_box_pack_start (priv->vbox, GTK_WIDGET (priv->table), TRUE, TRUE, 0);
531
532     /* Apply style properties */
533     hildon_app_menu_apply_style (GTK_WIDGET (menu));
534
535     /* Add buttons */
536     col = row = 0;
537     for (iter = priv->buttons; iter != NULL; iter = iter->next) {
538         GtkWidget *item = GTK_WIDGET (iter->data);
539         if (GTK_WIDGET_VISIBLE (item)) {
540             gtk_table_attach_defaults (priv->table, item, col, col + 1, row, row + 1);
541             if (++col == priv->columns) {
542                 col = 0;
543                 row++;
544             }
545         }
546     }
547
548     for (iter = priv->filters; iter != NULL; iter = iter->next) {
549         GtkWidget *filter = GTK_WIDGET (iter->data);
550         if (GTK_WIDGET_VISIBLE (filter)) {
551             gtk_box_pack_start (GTK_BOX (priv->filters_hbox), filter, TRUE, TRUE, 0);
552         }
553     }
554
555     gtk_widget_show_all (GTK_WIDGET (priv->vbox));
556
557     if (GTK_WIDGET_VISIBLE (GTK_WIDGET (menu))) {
558         gtk_window_reshow_with_initial_size (GTK_WINDOW (menu));
559     }
560 }
561
562 static void
563 hildon_app_menu_init                            (HildonAppMenu *menu)
564 {
565     HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(menu);
566
567     /* Initialize private variables */
568     priv->filters_hbox = NULL;
569     priv->vbox = NULL;
570     priv->table = NULL;
571     priv->transfer_window = NULL;
572     priv->pressed_outside = FALSE;
573     priv->buttons = NULL;
574     priv->filters = NULL;
575     priv->columns = 2;
576
577     hildon_app_menu_construct_child (menu);
578
579     gtk_window_set_modal (GTK_WINDOW (menu), TRUE);
580
581     /* This should be treated like a normal, ref-counted widget */
582     g_object_force_floating (G_OBJECT (menu));
583     GTK_WINDOW (menu)->has_user_ref_count = FALSE;
584 }
585
586 static void
587 hildon_app_menu_finalize                        (GObject *object)
588 {
589     HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(object);
590
591     if (priv->transfer_window)
592         gdk_window_destroy (priv->transfer_window);
593
594     g_list_free (priv->buttons);
595     g_list_free (priv->filters);
596
597     g_signal_handlers_destroy (object);
598     G_OBJECT_CLASS (hildon_app_menu_parent_class)->finalize (object);
599 }
600
601 static void
602 hildon_app_menu_class_init                      (HildonAppMenuClass *klass)
603 {
604     GObjectClass *gobject_class = (GObjectClass *)klass;
605     GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
606
607     gobject_class->finalize = hildon_app_menu_finalize;
608     gobject_class->set_property = hildon_app_menu_set_property;
609     widget_class->map = hildon_app_menu_map;
610     widget_class->unmap = hildon_app_menu_unmap;
611     widget_class->realize = hildon_app_menu_realize;
612     widget_class->button_press_event = hildon_app_menu_button_press;
613     widget_class->button_release_event = hildon_app_menu_button_release;
614     widget_class->style_set = hildon_app_menu_style_set;
615
616     g_type_class_add_private (klass, sizeof (HildonAppMenuPrivate));
617
618     g_object_class_install_property (
619         gobject_class,
620         PROP_COLUMNS,
621         g_param_spec_uint (
622             "columns",
623             "Columns",
624             "Number of columns used to display menu items. "
625             "IMPORTANT: this is a temporary property. Don't use unless really needed. "
626             "The number of columns will be managed automatically in the future, "
627             "and this property will be removed.",
628             1, G_MAXUINT, 2,
629             G_PARAM_WRITABLE));
630
631     gtk_widget_class_install_style_property (
632         widget_class,
633         g_param_spec_uint (
634             "horizontal-spacing",
635             "Horizontal spacing on menu items",
636             "Horizontal spacing between each menu item. Does not apply to filter buttons.",
637             0, G_MAXUINT, 16,
638             G_PARAM_READABLE));
639
640     gtk_widget_class_install_style_property (
641         widget_class,
642         g_param_spec_uint (
643             "vertical-spacing",
644             "Vertical spacing on menu items",
645             "Vertical spacing between each menu item. Does not apply to filter buttons.",
646             0, G_MAXUINT, 16,
647             G_PARAM_READABLE));
648
649     gtk_widget_class_install_style_property (
650         widget_class,
651         g_param_spec_uint (
652             "inner-border",
653             "Border between menu edges and buttons",
654             "Border between menu edges and buttons",
655             0, G_MAXUINT, 16,
656             G_PARAM_READABLE));
657
658     gtk_widget_class_install_style_property (
659         widget_class,
660         g_param_spec_uint (
661             "external-border",
662             "Border between menu and screen edges",
663             "Border between the right and left edges of the menu and the screen edges",
664             0, G_MAXUINT, 40,
665             G_PARAM_READABLE));
666 }