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