2 * This file is a part of hildon
4 * Copyright (C) 2008 Nokia Corporation, all rights reserved.
6 * Contact: Karl Lattimer <karl.lattimer@nokia.com>
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.
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.
20 * SECTION:hildon-app-menu
21 * @short_description: Widget representing the application menu in the Hildon framework.
23 * The #HildonAppMenu is a GTK widget which represents an application
24 * menu in the Hildon framework.
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.
30 * Besides that, the #HildonAppMenu can contain a group of filter buttons
31 * (#GtkToggleButton or #GtkRadioButton).
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.
37 * Alternatively, you can show it by hand using gtk_widget_show().
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.
46 * <title>Creating a HildonAppMenu</title>
48 * HildonStackableWindow *win;
49 * HildonAppMenu *menu;
53 * win = HILDON_STACKABLE_WINDOW (hildon_stackable_window_new ());
54 * menu = HILDON_APP_MENU (hildon_app_menu_new ());
56 * // Create a button and add it to the menu
57 * button = gtk_button_new_with_label ("Menu command one");
58 * g_signal_connect_after (button, "clicked", G_CALLBACK (button_one_clicked), userdata);
59 * hildon_app_menu_append (menu, GTK_BUTTON (button));
62 * button = gtk_button_new_with_label ("Menu command two");
63 * g_signal_connect_after (button, "clicked", G_CALLBACK (button_two_clicked), userdata);
64 * hildon_app_menu_append (menu, GTK_BUTTON (button));
66 * // Create a filter and add it to the menu
67 * filter = gtk_radio_button_new_with_label (NULL, "Filter one");
68 * gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (filter), FALSE);
69 * g_signal_connect_after (filter, "clicked", G_CALLBACK (filter_one_clicked), userdata);
70 * hildon_app_menu_add_filter (menu, GTK_BUTTON (filter));
73 * filter = gtk_radio_button_new_with_label_from_widget (GTK_RADIO_BUTTON (filter), "Filter two");
74 * gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (filter), FALSE);
75 * g_signal_connect_after (filter, "clicked", G_CALLBACK (filter_two_clicked), userdata);
76 * hildon_app_menu_add_filter (menu, GTK_BUTTON (filter));
78 * // Add the menu to the window
79 * hildon_stackable_window_set_main_menu (win, menu);
86 #include <X11/Xatom.h>
89 #include "hildon-app-menu.h"
90 #include "hildon-app-menu-private.h"
97 grab_transfer_window_get (GtkWidget *widget);
100 hildon_app_menu_construct_child (HildonAppMenu *menu);
103 button_visibility_changed (GtkWidget *item,
105 HildonAppMenu *menu);
107 G_DEFINE_TYPE (HildonAppMenu, hildon_app_menu, GTK_TYPE_WINDOW);
110 * hildon_app_menu_new:
112 * Creates a new #HildonAppMenu.
114 * Return value: A #HildonAppMenu.
117 hildon_app_menu_new (void)
119 GtkWidget *menu = g_object_new (HILDON_TYPE_APP_MENU, NULL);
124 * hildon_app_menu_append:
125 * @menu : A #HildonAppMenu
126 * @item : A #GtkButton to add to the #HildonAppMenu
128 * Adds the @item to @menu.
131 hildon_app_menu_append (HildonAppMenu *menu,
134 HildonAppMenuPrivate *priv;
136 g_return_if_fail (HILDON_IS_APP_MENU (menu));
137 g_return_if_fail (GTK_IS_BUTTON (item));
139 priv = HILDON_APP_MENU_GET_PRIVATE(menu);
141 /* Add the item to the menu */
142 gtk_widget_show (GTK_WIDGET (item));
143 priv->buttons = g_list_append (priv->buttons, item);
144 hildon_app_menu_construct_child (menu);
146 /* Close the menu when the button is clicked */
147 g_signal_connect_swapped (item, "clicked", G_CALLBACK (gtk_widget_hide), menu);
148 g_signal_connect (item, "notify::visible", G_CALLBACK (button_visibility_changed), menu);
152 * hildon_app_menu_add_filter:
153 * @menu : A #HildonAppMenu
154 * @filter : A #GtkButton to add to the #HildonAppMenu.
156 * Adds the @filter to @menu.
159 hildon_app_menu_add_filter (HildonAppMenu *menu,
162 HildonAppMenuPrivate *priv;
164 g_return_if_fail (HILDON_IS_APP_MENU (menu));
165 g_return_if_fail (GTK_IS_BUTTON (filter));
167 priv = HILDON_APP_MENU_GET_PRIVATE(menu);
169 /* Add the filter to the menu */
170 gtk_widget_show (GTK_WIDGET (filter));
171 priv->filters = g_list_append (priv->filters, filter);
172 hildon_app_menu_construct_child (menu);
174 /* Close the menu when the button is clicked */
175 g_signal_connect_swapped (filter, "clicked", G_CALLBACK (gtk_widget_hide), menu);
176 g_signal_connect (filter, "notify::visible", G_CALLBACK (button_visibility_changed), menu);
180 hildon_app_menu_set_columns (HildonAppMenu *menu,
183 HildonAppMenuPrivate *priv;
185 g_warning ("This property will be removed in the future. See documentation for details");
187 g_return_if_fail (HILDON_IS_APP_MENU (menu));
188 g_return_if_fail (columns > 0);
190 priv = HILDON_APP_MENU_GET_PRIVATE (menu);
192 if (columns != priv->columns) {
193 priv->columns = columns;
194 hildon_app_menu_construct_child (menu);
199 hildon_app_menu_set_property (GObject *object,
204 HildonAppMenu *menu = HILDON_APP_MENU (object);
209 hildon_app_menu_set_columns (menu, g_value_get_uint (value));
212 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
218 button_visibility_changed (GtkWidget *item,
222 hildon_app_menu_construct_child (menu);
226 hildon_app_menu_map (GtkWidget *widget)
228 HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(widget);
230 GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->map (widget);
232 /* Grab pointer and keyboard */
233 if (priv->transfer_window == NULL) {
234 gboolean has_grab = FALSE;
236 priv->transfer_window = grab_transfer_window_get (widget);
238 if (gdk_pointer_grab (priv->transfer_window, TRUE,
239 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
240 GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
241 GDK_POINTER_MOTION_MASK, NULL, NULL,
242 GDK_CURRENT_TIME) == GDK_GRAB_SUCCESS) {
243 if (gdk_keyboard_grab (priv->transfer_window, TRUE,
244 GDK_CURRENT_TIME) == GDK_GRAB_SUCCESS) {
247 gdk_display_pointer_ungrab (gtk_widget_get_display (widget),
253 gtk_grab_add (widget);
255 gdk_window_destroy (priv->transfer_window);
256 priv->transfer_window = NULL;
262 hildon_app_menu_unmap (GtkWidget *widget)
264 HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(widget);
266 /* Remove the grab */
267 if (priv->transfer_window != NULL) {
268 gdk_display_pointer_ungrab (gtk_widget_get_display (widget),
270 gtk_grab_remove (widget);
272 gdk_window_destroy (priv->transfer_window);
273 priv->transfer_window = NULL;
276 GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->unmap (widget);
280 hildon_app_menu_button_press (GtkWidget *widget,
281 GdkEventButton *event)
284 HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(widget);
286 gdk_window_get_position (widget->window, &x, &y);
288 /* Whether the button has been pressed outside the widget */
289 priv->pressed_outside = (event->x_root < x || event->x_root > x + widget->allocation.width ||
290 event->y_root < y || event->y_root > y + widget->allocation.height);
292 if (GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->button_press_event) {
293 return GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->button_press_event (widget, event);
300 hildon_app_menu_button_release (GtkWidget *widget,
301 GdkEventButton *event)
303 HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(widget);
305 if (priv->pressed_outside) {
307 gboolean released_outside;
309 gdk_window_get_position (widget->window, &x, &y);
311 /* Whether the button has been released outside the widget */
312 released_outside = (event->x_root < x || event->x_root > x + widget->allocation.width ||
313 event->y_root < y || event->y_root > y + widget->allocation.height);
315 if (released_outside) {
316 gtk_widget_hide (widget);
319 priv->pressed_outside = FALSE; /* Always reset pressed_outside to FALSE */
322 if (GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->button_release_event) {
323 return GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->button_release_event (widget, event);
329 /* Grab transfer window (based on the one from GtkMenu) */
331 grab_transfer_window_get (GtkWidget *widget)
334 GdkWindowAttr attributes;
335 gint attributes_mask;
339 attributes.width = 10;
340 attributes.height = 10;
341 attributes.window_type = GDK_WINDOW_TEMP;
342 attributes.wclass = GDK_INPUT_ONLY;
343 attributes.override_redirect = TRUE;
344 attributes.event_mask = 0;
346 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_NOREDIR;
348 window = gdk_window_new (gtk_widget_get_root_window (widget),
349 &attributes, attributes_mask);
350 gdk_window_set_user_data (window, widget);
352 gdk_window_show (window);
358 hildon_app_menu_realize (GtkWidget *widget)
362 const gchar *notification_type = "_HILDON_WM_WINDOW_TYPE_APP_MENU";
364 GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->realize (widget);
366 gdk_window_set_decorations (widget->window, GDK_DECOR_BORDER);
368 display = gdk_drawable_get_display (widget->window);
369 atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE");
370 XChangeProperty (GDK_WINDOW_XDISPLAY (widget->window), GDK_WINDOW_XID (widget->window),
371 atom, XA_STRING, 8, PropModeReplace, (guchar *) notification_type,
372 strlen (notification_type));
376 hildon_app_menu_apply_style (GtkWidget *widget)
380 guint horizontal_spacing, vertical_spacing, inner_border, external_border;
381 HildonAppMenuPrivate *priv;
383 priv = HILDON_APP_MENU_GET_PRIVATE (widget);
385 gtk_widget_style_get (widget,
386 "horizontal-spacing", &horizontal_spacing,
387 "vertical-spacing", &vertical_spacing,
388 "inner-border", &inner_border,
389 "external-border", &external_border,
393 gtk_table_set_row_spacings (priv->table, vertical_spacing);
394 gtk_table_set_col_spacings (priv->table, horizontal_spacing);
395 gtk_box_set_spacing (priv->vbox, vertical_spacing);
397 /* Set inner border */
398 gtk_container_set_border_width (GTK_CONTAINER (widget), inner_border);
400 /* Set default size */
401 screen = gtk_widget_get_screen (widget);
402 width = gdk_screen_get_width (screen) - external_border * 2;
403 gtk_window_set_default_size (GTK_WINDOW (widget), width, -1);
407 hildon_app_menu_style_set (GtkWidget *widget,
408 GtkStyle *previous_style)
410 if (GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->style_set)
411 GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->style_set (widget, previous_style);
413 hildon_app_menu_apply_style (widget);
417 hildon_app_menu_construct_child (HildonAppMenu *menu)
419 GtkWidget *alignment;
420 HildonAppMenuPrivate *priv;
424 priv = HILDON_APP_MENU_GET_PRIVATE(menu);
426 /* Remove all buttons from their parents */
427 for (iter = priv->buttons; iter != NULL; iter = iter->next) {
428 GtkWidget *item = GTK_WIDGET (iter->data);
429 GtkWidget *parent = gtk_widget_get_parent (item);
432 gtk_container_remove (GTK_CONTAINER (parent), item);
436 for (iter = priv->filters; iter != NULL; iter = iter->next) {
437 GtkWidget *item = GTK_WIDGET (iter->data);
438 GtkWidget *parent = gtk_widget_get_parent (item);
441 gtk_container_remove (GTK_CONTAINER (parent), item);
445 /* Create the contents of the menu again */
447 gtk_widget_destroy (GTK_WIDGET (priv->vbox));
450 /* Resize the menu to its minimum size */
451 gtk_window_resize (GTK_WINDOW (menu), 1, 1);
453 /* Create boxes and tables */
454 priv->filters_hbox = GTK_BOX (gtk_hbox_new (TRUE, 0));
455 priv->vbox = GTK_BOX (gtk_vbox_new (FALSE, 0));
456 priv->table = GTK_TABLE (gtk_table_new (1, priv->columns, TRUE));
458 /* Align the filters to the center */
459 alignment = gtk_alignment_new (0.5, 0.5, 0, 0);
460 gtk_container_add (GTK_CONTAINER (alignment), GTK_WIDGET (priv->filters_hbox));
462 /* Pack everything */
463 gtk_container_add (GTK_CONTAINER (menu), GTK_WIDGET (priv->vbox));
464 gtk_box_pack_start (priv->vbox, alignment, TRUE, TRUE, 0);
465 gtk_box_pack_start (priv->vbox, GTK_WIDGET (priv->table), TRUE, TRUE, 0);
467 /* Apply style properties */
468 hildon_app_menu_apply_style (GTK_WIDGET (menu));
472 for (iter = priv->buttons; iter != NULL; iter = iter->next) {
473 GtkWidget *item = GTK_WIDGET (iter->data);
474 if (GTK_WIDGET_VISIBLE (item)) {
475 gtk_table_attach_defaults (priv->table, item, col, col + 1, row, row + 1);
476 if (++col == priv->columns) {
483 for (iter = priv->filters; iter != NULL; iter = iter->next) {
484 GtkWidget *filter = GTK_WIDGET (iter->data);
485 if (GTK_WIDGET_VISIBLE (filter)) {
486 gtk_box_pack_start (GTK_BOX (priv->filters_hbox), filter, TRUE, TRUE, 0);
490 gtk_widget_show_all (GTK_WIDGET (priv->vbox));
492 if (GTK_WIDGET_VISIBLE (GTK_WIDGET (menu))) {
493 gtk_window_reshow_with_initial_size (GTK_WINDOW (menu));
498 hildon_app_menu_init (HildonAppMenu *menu)
500 HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(menu);
502 /* Initialize private variables */
503 priv->filters_hbox = NULL;
506 priv->transfer_window = NULL;
507 priv->pressed_outside = FALSE;
508 priv->buttons = NULL;
509 priv->filters = NULL;
512 hildon_app_menu_construct_child (menu);
514 gtk_window_set_modal (GTK_WINDOW (menu), TRUE);
518 hildon_app_menu_finalize (GObject *object)
520 HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(object);
522 if (priv->transfer_window)
523 gdk_window_destroy (priv->transfer_window);
525 g_list_free (priv->buttons);
526 g_list_free (priv->filters);
528 g_signal_handlers_destroy (object);
529 G_OBJECT_CLASS (hildon_app_menu_parent_class)->finalize (object);
533 hildon_app_menu_class_init (HildonAppMenuClass *klass)
535 GObjectClass *gobject_class = (GObjectClass *)klass;
536 GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
538 gobject_class->finalize = hildon_app_menu_finalize;
539 gobject_class->set_property = hildon_app_menu_set_property;
540 widget_class->map = hildon_app_menu_map;
541 widget_class->unmap = hildon_app_menu_unmap;
542 widget_class->realize = hildon_app_menu_realize;
543 widget_class->button_press_event = hildon_app_menu_button_press;
544 widget_class->button_release_event = hildon_app_menu_button_release;
545 widget_class->style_set = hildon_app_menu_style_set;
547 g_type_class_add_private (klass, sizeof (HildonAppMenuPrivate));
549 g_object_class_install_property (
555 "Number of columns used to display menu items. "
556 "IMPORTANT: this is a temporary property. Don't use unless really needed. "
557 "The number of columns will be managed automatically in the future, "
558 "and this property will be removed.",
562 gtk_widget_class_install_style_property (
565 "horizontal-spacing",
566 "Horizontal spacing on menu items",
567 "Horizontal spacing between each menu item. Does not apply to filter buttons.",
571 gtk_widget_class_install_style_property (
575 "Vertical spacing on menu items",
576 "Vertical spacing between each menu item. Does not apply to filter buttons.",
580 gtk_widget_class_install_style_property (
584 "Border between menu edges and buttons",
585 "Border between menu edges and buttons",
589 gtk_widget_class_install_style_property (
593 "Border between menu and screen edges",
594 "Border between the right and left edges of the menu and the screen edges",