2 * This file is a part of hildon
4 * Copyright (C) 2008 Nokia Corporation, all rights reserved.
6 * Contact: Rodrigo Novo <rodrigo.novo@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: Application menu for Hildon applications.
23 * #HildonAppMenu is an application menu for applications in the Hildon
26 * This menu opens from the top of the screen and contains a number of
27 * entries (#GtkButton) organized in one or two columns, depending on
28 * the size of the screen (the number of columns changes automatically
29 * if the screen is resized). Entries are added left to right and top
32 * Besides that, #HildonAppMenu can contain a group of filter buttons
33 * (#GtkToggleButton or #GtkRadioButton). Filters are meant to change
34 * the way data is presented in the application, rather than change
35 * the layout of the menu itself. For example, a file manager can have
36 * filters to decide the order used to display a list of files (name,
39 * To use a #HildonAppMenu, add it to a #HildonWindow using
40 * hildon_window_set_app_menu(). The menu will appear when the user
41 * presses the window title bar. Alternatively, you can show it by
42 * hand using hildon_app_menu_popup().
44 * The menu will be automatically hidden when one of its buttons is
45 * clicked. Use g_signal_connect_after() when connecting callbacks to
46 * buttons to make sure that they're called after the menu
47 * disappears. Alternatively, you can add the button to the menu
48 * before connecting any callback.
50 * Although implemented with a #GtkWindow, #HildonAppMenu behaves like
51 * a normal ref-counted widget, so g_object_ref(), g_object_unref(),
52 * g_object_ref_sink() and friends will behave just like with any
53 * other non-toplevel widget.
56 * <title>Creating a HildonAppMenu</title>
59 * HildonAppMenu *menu;
63 * win = hildon_stackable_window_new ();
64 * menu = HILDON_APP_MENU (hildon_app_menu_new ());
66 * // Create a button and add it to the menu
67 * button = gtk_button_new_with_label ("Menu command one");
68 * g_signal_connect_after (button, "clicked", G_CALLBACK (button_one_clicked), userdata);
69 * hildon_app_menu_append (menu, GTK_BUTTON (button));
72 * button = gtk_button_new_with_label ("Menu command two");
73 * g_signal_connect_after (button, "clicked", G_CALLBACK (button_two_clicked), userdata);
74 * hildon_app_menu_append (menu, GTK_BUTTON (button));
76 * // Create a filter and add it to the menu
77 * filter = gtk_radio_button_new_with_label (NULL, "Filter one");
78 * gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (filter), FALSE);
79 * g_signal_connect_after (filter, "clicked", G_CALLBACK (filter_one_clicked), userdata);
80 * hildon_app_menu_add_filter (menu, GTK_BUTTON (filter));
83 * filter = gtk_radio_button_new_with_label_from_widget (GTK_RADIO_BUTTON (filter), "Filter two");
84 * gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (filter), FALSE);
85 * g_signal_connect_after (filter, "clicked", G_CALLBACK (filter_two_clicked), userdata);
86 * hildon_app_menu_add_filter (menu, GTK_BUTTON (filter));
88 * // Show all menu items
89 * gtk_widget_show_all (GTK_WIDGET (menu));
91 * // Add the menu to the window
92 * hildon_window_set_app_menu (HILDON_WINDOW (win), menu);
99 #include <X11/Xatom.h>
100 #include <gdk/gdkx.h>
102 #include "hildon-gtk.h"
103 #include "hildon-app-menu.h"
104 #include "hildon-app-menu-private.h"
105 #include "hildon-window.h"
106 #include "hildon-banner.h"
107 #include "hildon-animation-actor.h"
110 grab_transfer_window_get (GtkWidget *widget);
113 hildon_app_menu_repack_items (HildonAppMenu *menu,
117 hildon_app_menu_repack_filters (HildonAppMenu *menu);
120 can_activate_accel (GtkWidget *widget,
125 item_visibility_changed (GtkWidget *item,
127 HildonAppMenu *menu);
130 filter_visibility_changed (GtkWidget *item,
132 HildonAppMenu *menu);
135 menu_item_button_event (GtkButton *item,
136 GdkEventButton *event,
140 remove_item_from_list (GList **list,
144 hildon_app_menu_apply_style (GtkWidget *widget);
146 G_DEFINE_TYPE (HildonAppMenu, hildon_app_menu, GTK_TYPE_WINDOW);
149 * hildon_app_menu_new:
151 * Creates a new #HildonAppMenu.
153 * Return value: A #HildonAppMenu.
158 hildon_app_menu_new (void)
160 GtkWidget *menu = g_object_new (HILDON_TYPE_APP_MENU, NULL);
165 * hildon_app_menu_insert:
166 * @menu : A #HildonAppMenu
167 * @item : A #GtkButton to add to the #HildonAppMenu
168 * @position : The position in the item list where @item is added (from 0 to n-1).
170 * Adds @item to @menu at the position indicated by @position.
175 hildon_app_menu_insert (HildonAppMenu *menu,
179 HildonAppMenuPrivate *priv;
181 g_return_if_fail (HILDON_IS_APP_MENU (menu));
182 g_return_if_fail (GTK_IS_BUTTON (item));
184 priv = HILDON_APP_MENU_GET_PRIVATE(menu);
186 /* Force widget size */
187 hildon_gtk_widget_set_theme_size (GTK_WIDGET (item),
188 HILDON_SIZE_FINGER_HEIGHT | HILDON_SIZE_AUTO_WIDTH);
190 /* Add the item to the menu */
191 g_object_ref_sink (item);
192 priv->buttons = g_list_insert (priv->buttons, item, position);
193 if (GTK_WIDGET_VISIBLE (item))
194 hildon_app_menu_repack_items (menu, position);
196 /* Enable accelerators */
197 g_signal_connect (item, "can-activate-accel", G_CALLBACK (can_activate_accel), NULL);
199 /* Close the menu when the button is clicked */
200 g_signal_connect_swapped (item, "clicked", G_CALLBACK (gtk_widget_hide), menu);
201 g_signal_connect (item, "notify::visible", G_CALLBACK (item_visibility_changed), menu);
203 /* Keep track of the latest menu item to receive a button-press event */
204 g_signal_connect (item, "button-press-event", G_CALLBACK (menu_item_button_event), menu);
205 g_signal_connect (item, "button-release-event", G_CALLBACK (menu_item_button_event), menu);
207 /* Remove item from list when it is destroyed */
208 g_object_weak_ref (G_OBJECT (item), (GWeakNotify) remove_item_from_list, &(priv->buttons));
212 * hildon_app_menu_append:
213 * @menu : A #HildonAppMenu
214 * @item : A #GtkButton to add to the #HildonAppMenu
216 * Adds @item to the end of the menu's item list.
221 hildon_app_menu_append (HildonAppMenu *menu,
224 hildon_app_menu_insert (menu, item, -1);
228 * hildon_app_menu_prepend:
229 * @menu : A #HildonAppMenu
230 * @item : A #GtkButton to add to the #HildonAppMenu
232 * Adds @item to the beginning of the menu's item list.
237 hildon_app_menu_prepend (HildonAppMenu *menu,
240 hildon_app_menu_insert (menu, item, 0);
244 * hildon_app_menu_reorder_child:
245 * @menu : A #HildonAppMenu
246 * @item : A #GtkButton to move
247 * @position : The new position to place @item (from 0 to n-1).
249 * Moves a #GtkButton to a new position within #HildonAppMenu.
254 hildon_app_menu_reorder_child (HildonAppMenu *menu,
258 HildonAppMenuPrivate *priv;
261 g_return_if_fail (HILDON_IS_APP_MENU (menu));
262 g_return_if_fail (GTK_IS_BUTTON (item));
263 g_return_if_fail (position >= 0);
265 priv = HILDON_APP_MENU_GET_PRIVATE (menu);
266 old_position = g_list_index (priv->buttons, item);
268 g_return_if_fail (old_position >= 0);
271 priv->buttons = g_list_remove (priv->buttons, item);
272 priv->buttons = g_list_insert (priv->buttons, item, position);
274 hildon_app_menu_repack_items (menu, MIN (old_position, position));
278 * hildon_app_menu_add_filter:
279 * @menu : A #HildonAppMenu
280 * @filter : A #GtkButton to add to the #HildonAppMenu.
282 * Adds the @filter to @menu.
287 hildon_app_menu_add_filter (HildonAppMenu *menu,
290 HildonAppMenuPrivate *priv;
292 g_return_if_fail (HILDON_IS_APP_MENU (menu));
293 g_return_if_fail (GTK_IS_BUTTON (filter));
295 priv = HILDON_APP_MENU_GET_PRIVATE(menu);
297 /* Force widget size */
298 hildon_gtk_widget_set_theme_size (GTK_WIDGET (filter),
299 HILDON_SIZE_FINGER_HEIGHT | HILDON_SIZE_AUTO_WIDTH);
301 /* Add the filter to the menu */
302 g_object_ref_sink (filter);
303 priv->filters = g_list_append (priv->filters, filter);
304 if (GTK_WIDGET_VISIBLE (filter))
305 hildon_app_menu_repack_filters (menu);
307 /* Enable accelerators */
308 g_signal_connect (filter, "can-activate-accel", G_CALLBACK (can_activate_accel), NULL);
310 /* Close the menu when the button is clicked */
311 g_signal_connect_swapped (filter, "clicked", G_CALLBACK (gtk_widget_hide), menu);
312 g_signal_connect (filter, "notify::visible", G_CALLBACK (filter_visibility_changed), menu);
314 /* Keep track of the latest menu item to receive a button-press event */
315 g_signal_connect (filter, "button-press-event", G_CALLBACK (menu_item_button_event), menu);
316 g_signal_connect (filter, "button-release-event", G_CALLBACK (menu_item_button_event), menu);
318 /* Remove filter from list when it is destroyed */
319 g_object_weak_ref (G_OBJECT (filter), (GWeakNotify) remove_item_from_list, &(priv->filters));
323 hildon_app_menu_set_columns (HildonAppMenu *menu,
326 HildonAppMenuPrivate *priv;
328 g_return_if_fail (HILDON_IS_APP_MENU (menu));
329 g_return_if_fail (columns > 0);
331 priv = HILDON_APP_MENU_GET_PRIVATE (menu);
333 if (columns != priv->columns) {
334 priv->columns = columns;
335 hildon_app_menu_repack_items (menu, 0);
340 parent_window_topmost_notify (HildonWindow *parent_win,
344 if (!hildon_window_get_is_topmost (parent_win))
345 gtk_widget_hide (menu);
349 parent_window_unmapped (HildonWindow *parent_win,
352 gtk_widget_hide (menu);
356 hildon_app_menu_set_parent_window (HildonAppMenu *self,
357 GtkWindow *parent_window)
359 HildonAppMenuPrivate *priv;
361 g_return_if_fail (HILDON_IS_APP_MENU (self));
362 g_return_if_fail (parent_window == NULL || GTK_IS_WINDOW (parent_window));
364 priv = HILDON_APP_MENU_GET_PRIVATE(self);
366 /* Disconnect old handlers, if any */
367 if (priv->parent_window) {
368 g_signal_handlers_disconnect_by_func (priv->parent_window, parent_window_topmost_notify, self);
369 g_signal_handlers_disconnect_by_func (priv->parent_window, parent_window_unmapped, self);
372 /* Connect a new handler */
374 g_signal_connect (parent_window, "notify::is-topmost", G_CALLBACK (parent_window_topmost_notify), self);
375 g_signal_connect (parent_window, "unmap", G_CALLBACK (parent_window_unmapped), self);
378 priv->parent_window = parent_window;
380 if (parent_window == NULL && GTK_WIDGET_VISIBLE (self))
381 gtk_widget_hide (GTK_WIDGET (self));
384 gpointer G_GNUC_INTERNAL
385 hildon_app_menu_get_parent_window (HildonAppMenu *self)
387 HildonAppMenuPrivate *priv;
389 g_return_val_if_fail (HILDON_IS_APP_MENU (self), NULL);
391 priv = HILDON_APP_MENU_GET_PRIVATE (self);
393 return priv->parent_window;
397 screen_size_changed (GdkScreen *screen,
400 hildon_app_menu_apply_style (GTK_WIDGET (menu));
402 if (gdk_screen_get_width (screen) > gdk_screen_get_height (screen)) {
403 hildon_app_menu_set_columns (menu, 2);
405 hildon_app_menu_set_columns (menu, 1);
410 can_activate_accel (GtkWidget *widget,
414 return GTK_WIDGET_VISIBLE (widget);
418 item_visibility_changed (GtkWidget *item,
422 HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE (menu);
424 if (! priv->inhibit_repack)
425 hildon_app_menu_repack_items (menu, g_list_index (priv->buttons, item));
429 filter_visibility_changed (GtkWidget *item,
433 HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE (menu);
435 if (! priv->inhibit_repack)
436 hildon_app_menu_repack_filters (menu);
440 menu_item_button_event (GtkButton *item,
441 GdkEventButton *event,
444 HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE (menu);
446 if (event->type == GDK_BUTTON_PRESS) {
447 priv->last_pressed_button = item;
448 } else if (event->type == GDK_BUTTON_RELEASE) {
449 /* A pressed button might not receive the button-release event due
450 * to the grab that HildonAppMenu has, so we have to simulate that
451 * event. See NB#108337 */
452 if (priv->last_pressed_button && priv->last_pressed_button != item) {
453 gtk_button_released (priv->last_pressed_button);
455 priv->last_pressed_button = NULL;
461 remove_item_from_list (GList **list,
464 *list = g_list_remove (*list, item);
468 hildon_app_menu_show_all (GtkWidget *widget)
470 HildonAppMenu *menu = HILDON_APP_MENU (widget);
471 HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE (widget);
473 priv->inhibit_repack = TRUE;
475 /* Show children, but not self. */
476 g_list_foreach (priv->buttons, (GFunc) gtk_widget_show_all, NULL);
477 g_list_foreach (priv->filters, (GFunc) gtk_widget_show_all, NULL);
479 priv->inhibit_repack = FALSE;
481 hildon_app_menu_repack_items (menu, 0);
482 hildon_app_menu_repack_filters (menu);
487 hildon_app_menu_hide_all (GtkWidget *widget)
489 HildonAppMenu *menu = HILDON_APP_MENU (widget);
490 HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE (widget);
492 priv->inhibit_repack = TRUE;
494 /* Hide children, but not self. */
495 g_list_foreach (priv->buttons, (GFunc) gtk_widget_hide_all, NULL);
496 g_list_foreach (priv->filters, (GFunc) gtk_widget_hide_all, NULL);
498 priv->inhibit_repack = FALSE;
500 hildon_app_menu_repack_items (menu, 0);
501 hildon_app_menu_repack_filters (menu);
505 * There's a race condition that can freeze the UI if a dialog appears
506 * between a HildonAppMenu and its parent window, see NB#100468
509 hildon_app_menu_find_intruder (gpointer data)
511 GtkWidget *widget = GTK_WIDGET (data);
512 HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE (widget);
514 priv->find_intruder_idle_id = 0;
516 /* If there's a window between the menu and its parent window, hide the menu */
517 if (priv->parent_window) {
518 gboolean intruder_found = FALSE;
519 GdkScreen *screen = gtk_widget_get_screen (widget);
520 GList *stack = gdk_screen_get_window_stack (screen);
521 GList *parent_pos = g_list_find (stack, GTK_WIDGET (priv->parent_window)->window);
522 GList *toplevels = gtk_window_list_toplevels ();
525 for (i = toplevels; i != NULL && !intruder_found; i = i->next) {
526 if (i->data != widget && i->data != priv->parent_window) {
527 if (g_list_find (parent_pos, GTK_WIDGET (i->data)->window)) {
528 /* HildonBanners are not closed automatically when
529 * a new window appears, so we must close them by
530 * hand to make the AppMenu work as expected.
531 * Yes, this is a hack. See NB#111027 */
532 if (HILDON_IS_BANNER (i->data)) {
533 gtk_widget_hide (i->data);
534 } else if (!HILDON_IS_ANIMATION_ACTOR (i->data)) {
535 intruder_found = TRUE;
541 g_list_foreach (stack, (GFunc) g_object_unref, NULL);
543 g_list_free (toplevels);
546 gtk_widget_hide (widget);
553 hildon_app_menu_map (GtkWidget *widget)
555 HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(widget);
557 if (priv->transfer_window == NULL)
558 priv->transfer_window = grab_transfer_window_get (widget);
560 GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->map (widget);
562 /* Grab pointer and keyboard */
563 if (priv->transfer_window != NULL) {
564 gboolean has_grab = FALSE;
566 if (gdk_pointer_grab (priv->transfer_window, TRUE,
567 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
568 GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
569 GDK_POINTER_MOTION_MASK, NULL, NULL,
570 GDK_CURRENT_TIME) == GDK_GRAB_SUCCESS) {
571 if (gdk_keyboard_grab (priv->transfer_window, TRUE,
572 GDK_CURRENT_TIME) == GDK_GRAB_SUCCESS) {
575 gdk_display_pointer_ungrab (gtk_widget_get_display (widget),
581 gtk_grab_add (widget);
583 gdk_window_destroy (priv->transfer_window);
584 priv->transfer_window = NULL;
588 /* Make the menu temporary when it's mapped, so it's closed if a
589 * new window appears */
590 gtk_window_set_is_temporary (GTK_WINDOW (widget), TRUE);
592 if (priv->find_intruder_idle_id == 0)
593 priv->find_intruder_idle_id = gdk_threads_add_idle (hildon_app_menu_find_intruder, widget);
597 hildon_app_menu_unmap (GtkWidget *widget)
599 HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(widget);
601 /* Remove the grab */
602 if (priv->transfer_window != NULL) {
603 gdk_display_pointer_ungrab (gtk_widget_get_display (widget),
605 gtk_grab_remove (widget);
607 gdk_window_destroy (priv->transfer_window);
608 priv->transfer_window = NULL;
611 GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->unmap (widget);
613 gtk_window_set_is_temporary (GTK_WINDOW (widget), FALSE);
617 hildon_app_menu_grab_notify (GtkWidget *widget,
618 gboolean was_grabbed)
620 if (GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->grab_notify)
621 GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->grab_notify (widget, was_grabbed);
623 if (!was_grabbed && GTK_WIDGET_VISIBLE (widget))
624 gtk_widget_hide (widget);
628 hildon_app_menu_hide_idle (gpointer widget)
630 HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE (widget);
631 gtk_widget_hide (GTK_WIDGET (widget));
632 priv->hide_idle_id = 0;
636 /* Send keyboard accelerators to the parent window, if necessary.
637 * This code is heavily based on gtk_menu_key_press ()
640 hildon_app_menu_key_press (GtkWidget *widget,
643 GtkWindow *parent_window;
644 HildonAppMenuPrivate *priv;
646 g_return_val_if_fail (HILDON_IS_APP_MENU (widget), FALSE);
647 g_return_val_if_fail (event != NULL, FALSE);
649 if (GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->key_press_event (widget, event))
652 priv = HILDON_APP_MENU_GET_PRIVATE (widget);
653 parent_window = priv->parent_window;
656 guint accel_key, accel_mods;
657 GdkModifierType consumed_modifiers;
659 GSList *accel_groups;
662 display = gtk_widget_get_display (widget);
664 /* Figure out what modifiers went into determining the key symbol */
665 gdk_keymap_translate_keyboard_state (gdk_keymap_get_for_display (display),
666 event->hardware_keycode, event->state, event->group,
667 NULL, NULL, NULL, &consumed_modifiers);
669 accel_key = gdk_keyval_to_lower (event->keyval);
670 accel_mods = event->state & gtk_accelerator_get_default_mod_mask () & ~consumed_modifiers;
672 /* If lowercasing affects the keysym, then we need to include SHIFT in the modifiers,
673 * We re-upper case when we match against the keyval, but display and save in caseless form.
675 if (accel_key != event->keyval)
676 accel_mods |= GDK_SHIFT_MASK;
678 accel_groups = gtk_accel_groups_from_object (G_OBJECT (parent_window));
680 for (list = accel_groups; list; list = list->next) {
681 GtkAccelGroup *accel_group = list->data;
683 if (gtk_accel_group_query (accel_group, accel_key, accel_mods, NULL)) {
684 gtk_window_activate_key (parent_window, event);
685 priv->hide_idle_id = gdk_threads_add_idle (hildon_app_menu_hide_idle, widget);
695 hildon_app_menu_button_press (GtkWidget *widget,
696 GdkEventButton *event)
699 HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(widget);
701 gdk_window_get_position (widget->window, &x, &y);
703 /* Whether the button has been pressed outside the widget */
704 priv->pressed_outside = (event->x_root < x || event->x_root > x + widget->allocation.width ||
705 event->y_root < y || event->y_root > y + widget->allocation.height);
707 if (GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->button_press_event) {
708 return GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->button_press_event (widget, event);
715 hildon_app_menu_button_release (GtkWidget *widget,
716 GdkEventButton *event)
718 HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(widget);
720 if (priv->pressed_outside) {
722 gboolean released_outside;
724 gdk_window_get_position (widget->window, &x, &y);
726 /* Whether the button has been released outside the widget */
727 released_outside = (event->x_root < x || event->x_root > x + widget->allocation.width ||
728 event->y_root < y || event->y_root > y + widget->allocation.height);
730 if (released_outside) {
731 gtk_widget_hide (widget);
734 priv->pressed_outside = FALSE; /* Always reset pressed_outside to FALSE */
735 } else if (priv->last_pressed_button) {
736 menu_item_button_event (NULL, event, widget);
739 if (GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->button_release_event) {
740 return GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->button_release_event (widget, event);
747 hildon_app_menu_delete_event_handler (GtkWidget *widget,
750 /* Hide the menu if it receives a delete-event, but don't destroy it */
751 gtk_widget_hide (widget);
755 /* Grab transfer window (based on the one from GtkMenu) */
757 grab_transfer_window_get (GtkWidget *widget)
760 GdkWindowAttr attributes;
761 gint attributes_mask;
765 attributes.width = 10;
766 attributes.height = 10;
767 attributes.window_type = GDK_WINDOW_TEMP;
768 attributes.wclass = GDK_INPUT_ONLY;
769 attributes.override_redirect = TRUE;
770 attributes.event_mask = 0;
772 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_NOREDIR;
774 window = gdk_window_new (gtk_widget_get_root_window (widget),
775 &attributes, attributes_mask);
776 gdk_window_set_user_data (window, widget);
778 gdk_window_show (window);
784 hildon_app_menu_size_request (GtkWidget *widget,
785 GtkRequisition *requisition)
787 HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE (widget);
789 GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->size_request (widget, requisition);
791 requisition->width = priv->width_request;
795 hildon_app_menu_realize (GtkWidget *widget)
797 Atom property, window_type;
799 GdkDisplay *gdkdisplay;
802 GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->realize (widget);
804 gdk_window_set_decorations (widget->window, GDK_DECOR_BORDER);
806 gdkdisplay = gdk_drawable_get_display (widget->window);
807 xdisplay = GDK_WINDOW_XDISPLAY (widget->window);
809 property = gdk_x11_get_xatom_by_name_for_display (gdkdisplay, "_NET_WM_WINDOW_TYPE");
810 window_type = XInternAtom (xdisplay, "_HILDON_WM_WINDOW_TYPE_APP_MENU", False);
811 XChangeProperty (xdisplay, GDK_WINDOW_XID (widget->window), property,
812 XA_ATOM, 32, PropModeReplace, (guchar *) &window_type, 1);
814 /* Detect any screen changes */
815 screen = gtk_widget_get_screen (widget);
816 g_signal_connect (screen, "size-changed", G_CALLBACK (screen_size_changed), widget);
818 /* Force menu to set the initial layout */
819 screen_size_changed (screen, HILDON_APP_MENU (widget));
823 hildon_app_menu_unrealize (GtkWidget *widget)
825 GdkScreen *screen = gtk_widget_get_screen (widget);
826 /* Disconnect "size-changed" signal handler */
827 g_signal_handlers_disconnect_by_func (screen, G_CALLBACK (screen_size_changed), widget);
829 GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->unrealize (widget);
833 hildon_app_menu_apply_style (GtkWidget *widget)
836 gint filter_group_width;
837 guint horizontal_spacing, vertical_spacing, filter_vertical_spacing;
838 guint inner_border, external_border;
839 HildonAppMenuPrivate *priv;
841 priv = HILDON_APP_MENU_GET_PRIVATE (widget);
843 gtk_widget_style_get (widget,
844 "horizontal-spacing", &horizontal_spacing,
845 "vertical-spacing", &vertical_spacing,
846 "filter-group-width", &filter_group_width,
847 "filter-vertical-spacing", &filter_vertical_spacing,
848 "inner-border", &inner_border,
849 "external-border", &external_border,
853 gtk_table_set_row_spacings (priv->table, vertical_spacing);
854 gtk_table_set_col_spacings (priv->table, horizontal_spacing);
855 gtk_box_set_spacing (priv->vbox, filter_vertical_spacing);
857 /* Set inner border */
858 gtk_container_set_border_width (GTK_CONTAINER (widget), inner_border);
860 /* Set width of the group of filter buttons */
861 gtk_widget_set_size_request (GTK_WIDGET (priv->filters_hbox), filter_group_width, -1);
863 /* Compute width request */
864 screen = gtk_widget_get_screen (widget);
865 if (gdk_screen_get_width (screen) < gdk_screen_get_height (screen)) {
868 priv->width_request = gdk_screen_get_width (screen) - external_border * 2;
869 gtk_window_move (GTK_WINDOW (widget), external_border, 0);
870 gtk_widget_queue_resize (widget);
874 hildon_app_menu_style_set (GtkWidget *widget,
875 GtkStyle *previous_style)
877 if (GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->style_set)
878 GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->style_set (widget, previous_style);
880 hildon_app_menu_apply_style (widget);
884 hildon_app_menu_repack_filters (HildonAppMenu *menu)
886 HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(menu);
889 for (iter = priv->filters; iter != NULL; iter = iter->next) {
890 GtkWidget *filter = GTK_WIDGET (iter->data);
891 GtkWidget *parent = gtk_widget_get_parent (filter);
893 g_object_ref (filter);
894 gtk_container_remove (GTK_CONTAINER (parent), filter);
898 for (iter = priv->filters; iter != NULL; iter = iter->next) {
899 GtkWidget *filter = GTK_WIDGET (iter->data);
900 if (GTK_WIDGET_VISIBLE (filter)) {
901 gtk_box_pack_start (GTK_BOX (priv->filters_hbox), filter, TRUE, TRUE, 0);
902 g_object_unref (filter);
903 /* GtkButton must be realized for accelerators to work */
904 gtk_widget_realize (filter);
910 * When items displayed in the menu change (e.g, a new item is added,
911 * an item is hidden or the list is reordered), the layout must be
912 * updated. To do this we repack all items starting from a given one.
915 hildon_app_menu_repack_items (HildonAppMenu *menu,
918 HildonAppMenuPrivate *priv;
919 gint row, col, nvisible, i;
922 priv = HILDON_APP_MENU_GET_PRIVATE(menu);
925 for (iter = priv->buttons; iter != NULL; iter = iter->next) {
926 /* Count number of visible items */
927 if (GTK_WIDGET_VISIBLE (iter->data))
929 /* Remove buttons from their parent */
930 if (start_from != -1 && i >= start_from) {
931 GtkWidget *item = GTK_WIDGET (iter->data);
932 GtkWidget *parent = gtk_widget_get_parent (item);
935 gtk_container_remove (GTK_CONTAINER (parent), item);
941 /* If items have been removed, recalculate the size of the menu */
942 if (start_from != -1)
943 gtk_window_resize (GTK_WINDOW (menu), 1, 1);
945 /* Set the final size now to avoid unnecessary resizes later */
947 gtk_table_resize (priv->table, ((nvisible - 1) / priv->columns) + 1, priv->columns);
951 for (iter = priv->buttons; iter != NULL; iter = iter->next) {
952 GtkWidget *item = GTK_WIDGET (iter->data);
953 if (GTK_WIDGET_VISIBLE (item)) {
954 /* Don't add an item to the table if it's already there */
955 if (gtk_widget_get_parent (item) == NULL) {
956 gtk_table_attach_defaults (priv->table, item, col, col + 1, row, row + 1);
957 g_object_unref (item);
958 /* GtkButton must be realized for accelerators to work */
959 gtk_widget_realize (item);
961 if (++col == priv->columns) {
970 * hildon_app_menu_popup:
971 * @menu: a #HildonAppMenu
972 * @parent_window: a #GtkWindow
974 * Displays a menu on top of a window and makes it available for
980 hildon_app_menu_popup (HildonAppMenu *menu,
981 GtkWindow *parent_window)
983 HildonAppMenuPrivate *priv;
984 gboolean show_menu = FALSE;
987 g_return_if_fail (HILDON_IS_APP_MENU (menu));
988 g_return_if_fail (GTK_IS_WINDOW (parent_window));
990 priv = HILDON_APP_MENU_GET_PRIVATE (menu);
992 /* Don't show menu if it doesn't contain visible items */
993 for (i = priv->buttons; i && !show_menu; i = i->next)
994 show_menu = GTK_WIDGET_VISIBLE (i->data);
996 for (i = priv->filters; i && !show_menu; i = i->next)
997 show_menu = GTK_WIDGET_VISIBLE (i->data);
1000 hildon_app_menu_set_parent_window (menu, parent_window);
1001 gtk_widget_show (GTK_WIDGET (menu));
1007 * hildon_app_menu_get_items:
1008 * @menu: a #HildonAppMenu
1010 * Returns a list of all items (regular items, not filters) contained
1013 * Returns: a newly-allocated list containing the items in @menu
1018 hildon_app_menu_get_items (HildonAppMenu *menu)
1020 HildonAppMenuPrivate *priv;
1022 g_return_val_if_fail (HILDON_IS_APP_MENU (menu), NULL);
1024 priv = HILDON_APP_MENU_GET_PRIVATE (menu);
1026 return g_list_copy (priv->buttons);
1030 * hildon_app_menu_get_filters:
1031 * @menu: a #HildonAppMenu
1033 * Returns a list of all filters contained in @menu.
1035 * Returns: a newly-allocated list containing the filters in @menu
1040 hildon_app_menu_get_filters (HildonAppMenu *menu)
1042 HildonAppMenuPrivate *priv;
1044 g_return_val_if_fail (HILDON_IS_APP_MENU (menu), NULL);
1046 priv = HILDON_APP_MENU_GET_PRIVATE (menu);
1048 return g_list_copy (priv->filters);
1052 hildon_app_menu_init (HildonAppMenu *menu)
1054 GtkWidget *alignment;
1055 HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(menu);
1057 /* Initialize private variables */
1058 priv->parent_window = NULL;
1059 priv->transfer_window = NULL;
1060 priv->pressed_outside = FALSE;
1061 priv->inhibit_repack = FALSE;
1062 priv->last_pressed_button = NULL;
1063 priv->buttons = NULL;
1064 priv->filters = NULL;
1066 priv->width_request = -1;
1067 priv->find_intruder_idle_id = 0;
1068 priv->hide_idle_id = 0;
1070 /* Create boxes and tables */
1071 priv->filters_hbox = GTK_BOX (gtk_hbox_new (TRUE, 0));
1072 priv->vbox = GTK_BOX (gtk_vbox_new (FALSE, 0));
1073 priv->table = GTK_TABLE (gtk_table_new (1, priv->columns, TRUE));
1075 /* Align the filters to the center */
1076 alignment = gtk_alignment_new (0.5, 0.5, 0, 0);
1077 gtk_container_add (GTK_CONTAINER (alignment), GTK_WIDGET (priv->filters_hbox));
1079 /* Pack everything */
1080 gtk_container_add (GTK_CONTAINER (menu), GTK_WIDGET (priv->vbox));
1081 gtk_box_pack_start (priv->vbox, alignment, TRUE, TRUE, 0);
1082 gtk_box_pack_start (priv->vbox, GTK_WIDGET (priv->table), TRUE, TRUE, 0);
1084 /* This should be treated like a normal, ref-counted widget */
1085 g_object_force_floating (G_OBJECT (menu));
1086 GTK_WINDOW (menu)->has_user_ref_count = FALSE;
1088 gtk_widget_show_all (GTK_WIDGET (priv->vbox));
1092 hildon_app_menu_finalize (GObject *object)
1094 HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(object);
1096 if (priv->find_intruder_idle_id) {
1097 g_source_remove (priv->find_intruder_idle_id);
1098 priv->find_intruder_idle_id = 0;
1101 if (priv->hide_idle_id) {
1102 g_source_remove (priv->hide_idle_id);
1103 priv->hide_idle_id = 0;
1106 if (priv->parent_window) {
1107 g_signal_handlers_disconnect_by_func (priv->parent_window, parent_window_topmost_notify, object);
1108 g_signal_handlers_disconnect_by_func (priv->parent_window, parent_window_unmapped, object);
1111 if (priv->transfer_window)
1112 gdk_window_destroy (priv->transfer_window);
1114 g_list_foreach (priv->buttons, (GFunc) g_object_unref, NULL);
1115 g_list_foreach (priv->filters, (GFunc) g_object_unref, NULL);
1117 g_list_free (priv->buttons);
1118 g_list_free (priv->filters);
1120 g_signal_handlers_destroy (object);
1121 G_OBJECT_CLASS (hildon_app_menu_parent_class)->finalize (object);
1125 hildon_app_menu_class_init (HildonAppMenuClass *klass)
1127 GObjectClass *gobject_class = (GObjectClass *)klass;
1128 GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
1130 gobject_class->finalize = hildon_app_menu_finalize;
1131 widget_class->show_all = hildon_app_menu_show_all;
1132 widget_class->hide_all = hildon_app_menu_hide_all;
1133 widget_class->map = hildon_app_menu_map;
1134 widget_class->unmap = hildon_app_menu_unmap;
1135 widget_class->realize = hildon_app_menu_realize;
1136 widget_class->unrealize = hildon_app_menu_unrealize;
1137 widget_class->grab_notify = hildon_app_menu_grab_notify;
1138 widget_class->key_press_event = hildon_app_menu_key_press;
1139 widget_class->button_press_event = hildon_app_menu_button_press;
1140 widget_class->button_release_event = hildon_app_menu_button_release;
1141 widget_class->style_set = hildon_app_menu_style_set;
1142 widget_class->delete_event = hildon_app_menu_delete_event_handler;
1143 widget_class->size_request = hildon_app_menu_size_request;
1145 g_type_class_add_private (klass, sizeof (HildonAppMenuPrivate));
1147 gtk_widget_class_install_style_property (
1150 "horizontal-spacing",
1151 "Horizontal spacing on menu items",
1152 "Horizontal spacing between each menu item. Does not apply to filter buttons.",
1156 gtk_widget_class_install_style_property (
1160 "Vertical spacing on menu items",
1161 "Vertical spacing between each menu item. Does not apply to filter buttons.",
1165 gtk_widget_class_install_style_property (
1168 "filter-group-width",
1169 "Width of the group of filter buttons",
1170 "Total width of the group of filter buttons, "
1171 "or -1 to use the natural size request.",
1175 gtk_widget_class_install_style_property (
1178 "filter-vertical-spacing",
1179 "Vertical spacing between filters and menu items",
1180 "Vertical spacing between filters and menu items",
1184 gtk_widget_class_install_style_property (
1188 "Border between menu edges and buttons",
1189 "Border between menu edges and buttons",
1193 gtk_widget_class_install_style_property (
1197 "Border between menu and screen edges (in horizontal mode)",
1198 "Border between the right and left edges of the menu and "
1199 "the screen edges (in horizontal mode)",