X-Git-Url: https://vcs.maemo.org/git/?a=blobdiff_plain;f=src%2Fhildon-app-menu.c;h=a444c7acd944301401bb4ad9791fd494e1e193dc;hb=28eb04ae758aad26f4f059ecac94f4c3ee5806a7;hp=2a0c0914d005a374aadcaa0f59729054b16112f1;hpb=12e34c6d5aa2884639ae9133ef6c4135e811b154;p=hildon diff --git a/src/hildon-app-menu.c b/src/hildon-app-menu.c index 2a0c091..a444c7a 100644 --- a/src/hildon-app-menu.c +++ b/src/hildon-app-menu.c @@ -27,68 +27,79 @@ * entries (#GtkButton) organized in two columns. Entries are added * left to right and top to bottom. * - * Besides that, the #HildonAppMenu can contain filter buttons - * (#GtkToggleButton or #GtkRadioButton), which can be grouped. + * Besides that, the #HildonAppMenu can contain a group of filter buttons + * (#GtkToggleButton or #GtkRadioButton). + * + * To use a #HildonAppMenu, add it to a #HildonStackableWindow with + * with hildon_stackable_window_set_main_menu(). The menu will appear + * when the user presses the window title bar. + * + * Alternatively, you can show it by hand using gtk_widget_show(). + * + * The menu will be automatically hidden when one of its buttons is + * clicked. Use g_signal_connect_after() when connecting callbacks to + * buttons to make sure that they're called after the menu + * disappears. Alternatively, you can add the button to the menu + * before connecting any callback. * * * Creating a HildonAppMenu * + * HildonStackableWindow *win; * HildonAppMenu *menu; * GtkWidget *button; * GtkWidget *filter; - * GtkWidget *filtergroup; * + * win = HILDON_STACKABLE_WINDOW (hildon_stackable_window_new ()); * menu = HILDON_APP_MENU (hildon_app_menu_new ()); * * // Create a button and add it to the menu * button = gtk_button_new_with_label ("Menu command one"); - * g_signal_connect (button, "clicked", G_CALLBACK (button_one_clicked), userdata); + * g_signal_connect_after (button, "clicked", G_CALLBACK (button_one_clicked), userdata); * hildon_app_menu_append (menu, GTK_BUTTON (button)); + * * // Another button * button = gtk_button_new_with_label ("Menu command two"); - * g_signal_connect (button, "clicked", G_CALLBACK (button_two_clicked), userdata); + * g_signal_connect_after (button, "clicked", G_CALLBACK (button_two_clicked), userdata); * hildon_app_menu_append (menu, GTK_BUTTON (button)); * * // Create a filter and add it to the menu - * filter = gtk_toggle_button_new_with_label ("Filter one"); - * g_signal_connect (filter, "clicked", G_CALLBACK (filter_one_clicked), userdata); - * hildon_app_menu_add_filter (menu, GTK_BUTTON (filter), NULL); - * - * // Create another filter and add it to a new filter group - * filter = gtk_radio_button_new_with_label (NULL, "Filter two"); + * filter = gtk_radio_button_new_with_label (NULL, "Filter one"); * gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (filter), FALSE); - * g_signal_connect (filter, "clicked", G_CALLBACK (filter_two_clicked), userdata); - * filtergroup = hildon_app_menu_add_filter (menu, GTK_BUTTON (filter), NULL); - * // Add a new filter to the same filter group - * filter = gtk_radio_button_new_with_label_from_widget (GTK_RADIO_BUTTON (filter), "Filter three"); + * g_signal_connect_after (filter, "clicked", G_CALLBACK (filter_one_clicked), userdata); + * hildon_app_menu_add_filter (menu, GTK_BUTTON (filter)); + * + * // Add a new filter + * filter = gtk_radio_button_new_with_label_from_widget (GTK_RADIO_BUTTON (filter), "Filter two"); * gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (filter), FALSE); - * g_signal_connect (filter, "clicked", G_CALLBACK (filter_three_clicked), userdata); - * hildon_app_menu_add_filter (menu, GTK_BUTTON (filter), filtergroup); + * g_signal_connect_after (filter, "clicked", G_CALLBACK (filter_two_clicked), userdata); + * hildon_app_menu_add_filter (menu, GTK_BUTTON (filter)); * - * // Pop the menu up - * hildon_app_menu_popup (menu); + * // Add the menu to the window + * hildon_stackable_window_set_main_menu (win, menu); * * * */ -#include "hildon-app-menu.h" -#include "hildon-app-menu-private.h" +#include +#include +#include -static void -hildon_app_menu_popdown (HildonAppMenu *menu); +#include "hildon-app-menu.h" +#include "hildon-app-menu-private.h" static GdkWindow * -grab_transfer_window_get (HildonAppMenu *menu); +grab_transfer_window_get (GtkWidget *widget); G_DEFINE_TYPE (HildonAppMenu, hildon_app_menu, GTK_TYPE_WINDOW); /** * hildon_app_menu_new: * - * Creates a new HildonAppMenu. + * Creates a new #HildonAppMenu. * - * Return value: A @HildonAppMenu. + * Return value: A #HildonAppMenu. **/ GtkWidget * hildon_app_menu_new (void) @@ -98,11 +109,11 @@ hildon_app_menu_new (void) } /** - * hildon_app_menu_append - * @menu : A @HildonAppMenu - * @item : A @GtkButton to add to the HildonAppMenu + * hildon_app_menu_append: + * @menu : A #HildonAppMenu + * @item : A #GtkButton to add to the #HildonAppMenu * - * Adds the @item to the @HildonAppMenu + * Adds the @item to @menu. */ void hildon_app_menu_append (HildonAppMenu *menu, @@ -124,155 +135,91 @@ hildon_app_menu_append (HildonAppMenu *menu, /* GtkTable already calls gtk_table_resize() if necessary */ gtk_table_attach_defaults (priv->table, GTK_WIDGET (item), col, col + 1, row, row + 1); - /* Close the menu when the button is pressed */ - g_signal_connect_swapped (item, "clicked", G_CALLBACK (hildon_app_menu_popdown), menu); + /* Close the menu when the button is clicked */ + g_signal_connect_swapped (item, "clicked", G_CALLBACK (gtk_widget_hide), menu); gtk_widget_show (GTK_WIDGET (item)); } /** * hildon_app_menu_add_filter - * @menu : A @HildonAppMenu - * @filter : A @GtkButton to add to the HildonAppMenu - * @group : An existing filter group, or %NULL to create a new one - * - * Adds the @filter to the @HildonAppMenu, to the group specified by @group + * @menu : A #HildonAppMenu + * @filter : A #GtkButton to add to the #HildonAppMenu. * - * Return value: The filter group where the filter has been added + * Adds the @filter to @menu. */ -GtkWidget * +void hildon_app_menu_add_filter (HildonAppMenu *menu, - GtkButton *filter, - GtkWidget *group) + GtkButton *filter) { HildonAppMenuPrivate *priv; - g_return_val_if_fail (HILDON_IS_APP_MENU (menu), NULL); - g_return_val_if_fail (GTK_IS_BUTTON (filter), NULL); - g_return_val_if_fail (!group || GTK_IS_BOX (group), NULL); + g_return_if_fail (HILDON_IS_APP_MENU (menu)); + g_return_if_fail (GTK_IS_BUTTON (filter)); priv = HILDON_APP_MENU_GET_PRIVATE(menu); - /* Create a new group if needed */ - if (!group) { - group = gtk_hbox_new (TRUE, 0); - gtk_box_pack_start (priv->filters_hbox, group, TRUE, TRUE, 0); - gtk_widget_show (group); - } - /* Pack the filter in the group and set its size */ - gtk_box_pack_start (GTK_BOX (group), GTK_WIDGET (filter), TRUE, TRUE, 0); - gtk_size_group_add_widget (priv->sizegroup, GTK_WIDGET (filter)); + gtk_box_pack_start (GTK_BOX (priv->filters_hbox), GTK_WIDGET (filter), TRUE, TRUE, 0); - /* Close the menu when the button is pressed */ - g_signal_connect_swapped (filter, "clicked", G_CALLBACK (hildon_app_menu_popdown), menu); + /* Close the menu when the button is clicked */ + g_signal_connect_swapped (filter, "clicked", G_CALLBACK (gtk_widget_hide), menu); gtk_widget_show (GTK_WIDGET (filter)); - - return group; } -/** - * hildon_app_menu_get_group_from_filter - * @menu : A @HildonAppMenu - * @filter : A @GtkButton previously added to the menu - * - * Gets the filter group from a @filter previously added to a @HildonAppMenu - * - * Return value: The group where the @filter is in, or %NULL - */ -GtkWidget * -hildon_app_menu_get_group_from_filter (HildonAppMenu *menu, - GtkButton *filter) +static void +hildon_app_menu_map (GtkWidget *widget) { - HildonAppMenuPrivate *priv; - GList *grouplist; - GtkWidget *result = NULL; - - g_return_val_if_fail (HILDON_IS_APP_MENU (menu), NULL); - g_return_val_if_fail (GTK_IS_BUTTON (filter), NULL); - - priv = HILDON_APP_MENU_GET_PRIVATE(menu); - - /* Get the list of filter groups */ - grouplist = gtk_container_get_children (GTK_CONTAINER (priv->filters_hbox)); - - for (; grouplist != NULL && !result; grouplist = grouplist->next) { + HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(widget); - GtkBox *group = GTK_BOX (grouplist->data); - GList *items = gtk_container_get_children (GTK_CONTAINER (group)); + GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->map (widget); - /* Look for the filter inside each filter group */ - for (; items != NULL && !result; items = items->next) { - if (filter == items->data) { - result = GTK_WIDGET (group); + /* Grab pointer and keyboard */ + if (priv->transfer_window == NULL) { + gboolean has_grab = FALSE; + + priv->transfer_window = grab_transfer_window_get (widget); + + if (gdk_pointer_grab (priv->transfer_window, TRUE, + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | + GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | + GDK_POINTER_MOTION_MASK, NULL, NULL, + GDK_CURRENT_TIME) == GDK_GRAB_SUCCESS) { + if (gdk_keyboard_grab (priv->transfer_window, TRUE, + GDK_CURRENT_TIME) == GDK_GRAB_SUCCESS) { + has_grab = TRUE; + } else { + gdk_display_pointer_ungrab (gtk_widget_get_display (widget), + GDK_CURRENT_TIME); } } - g_list_free (items); - - } - g_list_free (grouplist); - - if (!result) - g_critical("Filter not found in hildon app menu!"); - - return result; -} - -/** - * hildon_app_menu_popup - * @menu : A @HildonAppMenu - * - * Displays the @HildonAppMenu on top of the screen - */ -void -hildon_app_menu_popup (HildonAppMenu *menu) -{ - g_return_if_fail (HILDON_IS_APP_MENU (menu)); - int x, xpos; - GtkRequisition req; - HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(menu); - GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (menu)); - /* Grab pointer and keyboard */ - if (priv->transfer_window == NULL) { - priv->transfer_window = grab_transfer_window_get (menu); - gdk_pointer_grab ( - priv->transfer_window, TRUE, - GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | - GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | - GDK_POINTER_MOTION_MASK, NULL, NULL, GDK_CURRENT_TIME); - gdk_keyboard_grab (priv->transfer_window, TRUE, GDK_CURRENT_TIME); - gtk_grab_add (GTK_WIDGET (menu)); + if (has_grab) { + gtk_grab_add (widget); + } else { + gdk_window_destroy (priv->transfer_window); + priv->transfer_window = NULL; + } } - - /* Position the menu in the top center of the screen */ - gtk_window_get_default_size (GTK_WINDOW (menu), &x, NULL); - gtk_widget_size_request (GTK_WIDGET (menu), &req); - xpos = (gdk_screen_get_width (screen) - MAX(x, req.width)) / 2; - gtk_window_move (GTK_WINDOW (menu), xpos, 0); - - gtk_widget_show (GTK_WIDGET (menu)); } static void -hildon_app_menu_popdown (HildonAppMenu *menu) +hildon_app_menu_unmap (GtkWidget *widget) { - g_return_if_fail (HILDON_IS_APP_MENU (menu)); - HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(menu); + HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(widget); + /* Remove the grab */ if (priv->transfer_window != NULL) { - /* Remove the grab */ - gdk_display_pointer_ungrab (gtk_widget_get_display (GTK_WIDGET (menu)), + gdk_display_pointer_ungrab (gtk_widget_get_display (widget), GDK_CURRENT_TIME); - gtk_grab_remove (GTK_WIDGET (menu)); + gtk_grab_remove (widget); - /* Destroy the transfer window */ gdk_window_destroy (priv->transfer_window); priv->transfer_window = NULL; } - gtk_widget_hide (GTK_WIDGET (menu)); + GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->unmap (widget); } static gboolean @@ -281,22 +228,53 @@ hildon_app_menu_button_press (GtkWidget *widget, { int x, y; HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(widget); - gdk_window_get_position(widget->window, &x, &y); - if (event->window != priv->transfer_window || - event->x < x || event->x > x + widget->allocation.width || - event->y < y || event->y > y + widget->allocation.height) { - hildon_app_menu_popdown (HILDON_APP_MENU (widget)); - return TRUE; - } else if (GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->button_press_event) { + + gdk_window_get_position (widget->window, &x, &y); + + /* Whether the button has been pressed outside the widget */ + priv->pressed_outside = (event->x_root < x || event->x_root > x + widget->allocation.width || + event->y_root < y || event->y_root > y + widget->allocation.height); + + if (GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->button_press_event) { return GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->button_press_event (widget, event); } else { return FALSE; } } +static gboolean +hildon_app_menu_button_release (GtkWidget *widget, + GdkEventButton *event) +{ + HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(widget); + + if (priv->pressed_outside) { + int x, y; + gboolean released_outside; + + gdk_window_get_position (widget->window, &x, &y); + + /* Whether the button has been released outside the widget */ + released_outside = (event->x_root < x || event->x_root > x + widget->allocation.width || + event->y_root < y || event->y_root > y + widget->allocation.height); + + if (released_outside) { + gtk_widget_hide (widget); + } + + priv->pressed_outside = FALSE; /* Always reset pressed_outside to FALSE */ + } + + if (GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->button_release_event) { + return GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->button_release_event (widget, event); + } else { + return FALSE; + } +} + /* Grab transfer window (based on the one from GtkMenu) */ static GdkWindow * -grab_transfer_window_get (HildonAppMenu *menu) +grab_transfer_window_get (GtkWidget *widget) { GdkWindow *window; GdkWindowAttr attributes; @@ -313,9 +291,9 @@ grab_transfer_window_get (HildonAppMenu *menu) attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_NOREDIR; - window = gdk_window_new (gtk_widget_get_root_window (GTK_WIDGET (menu)), + window = gdk_window_new (gtk_widget_get_root_window (widget), &attributes, attributes_mask); - gdk_window_set_user_data (window, menu); + gdk_window_set_user_data (window, widget); gdk_window_show (window); @@ -325,8 +303,19 @@ grab_transfer_window_get (HildonAppMenu *menu) static void hildon_app_menu_realize (GtkWidget *widget) { + GdkDisplay *display; + Atom atom; + const gchar *notification_type = "_HILDON_WM_WINDOW_TYPE_APP_MENU"; + GTK_WIDGET_CLASS (hildon_app_menu_parent_class)->realize (widget); - gdk_window_set_override_redirect(widget->window, TRUE); + + gdk_window_set_decorations (widget->window, GDK_DECOR_BORDER); + + display = gdk_drawable_get_display (widget->window); + atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE"); + XChangeProperty (GDK_WINDOW_XDISPLAY (widget->window), GDK_WINDOW_XID (widget->window), + atom, XA_STRING, 8, PropModeReplace, (guchar *) notification_type, + strlen (notification_type)); } static void @@ -335,24 +324,24 @@ hildon_app_menu_init (HildonAppMenu *menu) GtkWidget *alignment; GdkScreen *screen; int width; - guint filter_group_spacing, horizontal_spacing, vertical_spacing, - external_border; + guint horizontal_spacing, vertical_spacing, + inner_border, external_border; HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(menu); gtk_widget_style_get (GTK_WIDGET (menu), - "filter-group-spacing", &filter_group_spacing, "horizontal-spacing", &horizontal_spacing, "vertical-spacing", &vertical_spacing, + "inner-border", &inner_border, "external-border", &external_border, NULL); /* Initialize private variables */ - priv->filters_hbox = GTK_BOX (gtk_hbox_new (FALSE, filter_group_spacing)); - priv->vbox = GTK_BOX (gtk_vbox_new (FALSE, 10)); + priv->filters_hbox = GTK_BOX (gtk_hbox_new (TRUE, 0)); + priv->vbox = GTK_BOX (gtk_vbox_new (FALSE, vertical_spacing)); priv->table = GTK_TABLE (gtk_table_new (1, 2, TRUE)); - priv->sizegroup = GTK_SIZE_GROUP (gtk_size_group_new (GTK_SIZE_GROUP_BOTH)); priv->nitems = 0; priv->transfer_window = NULL; + priv->pressed_outside = FALSE; /* Set spacing between table elements */ gtk_table_set_row_spacings (priv->table, vertical_spacing); @@ -372,9 +361,12 @@ hildon_app_menu_init (HildonAppMenu *menu) width = gdk_screen_get_width (screen) - external_border * 2; gtk_window_set_default_size (GTK_WINDOW (menu), width, -1); - gtk_widget_show_all (GTK_WIDGET (priv->vbox)); + /* Set inner border */ + gtk_container_set_border_width (GTK_CONTAINER (menu), inner_border); - gtk_window_set_type_hint (GTK_WINDOW (menu), GDK_WINDOW_TYPE_HINT_POPUP_MENU); + gtk_window_set_modal (GTK_WINDOW (menu), TRUE); + + gtk_widget_show_all (GTK_WIDGET (priv->vbox)); } static void @@ -382,7 +374,6 @@ hildon_app_menu_finalize (GObject *object) { HildonAppMenuPrivate *priv = HILDON_APP_MENU_GET_PRIVATE(object); - g_object_unref (priv->sizegroup); if (priv->transfer_window) gdk_window_destroy (priv->transfer_window); @@ -397,36 +388,39 @@ hildon_app_menu_class_init (HildonAppMenuClass *klass) GtkWidgetClass *widget_class = (GtkWidgetClass *)klass; gobject_class->finalize = hildon_app_menu_finalize; + widget_class->map = hildon_app_menu_map; + widget_class->unmap = hildon_app_menu_unmap; widget_class->realize = hildon_app_menu_realize; widget_class->button_press_event = hildon_app_menu_button_press; + widget_class->button_release_event = hildon_app_menu_button_release; g_type_class_add_private (klass, sizeof (HildonAppMenuPrivate)); gtk_widget_class_install_style_property ( widget_class, g_param_spec_uint ( - "filter-group-spacing", - "Space between filter groups", - "Space in pixels between the filter groups", - 0, G_MAXUINT, 10, + "horizontal-spacing", + "Horizontal spacing on menu items", + "Horizontal spacing between each menu item. Does not apply to filter buttons.", + 0, G_MAXUINT, 16, G_PARAM_READABLE)); gtk_widget_class_install_style_property ( widget_class, g_param_spec_uint ( - "horizontal-spacing", - "Horizontal spacing on menu items", - "Horizontal spacing between each menu item (but not filters)", - 0, G_MAXUINT, 10, + "vertical-spacing", + "Vertical spacing on menu items", + "Vertical spacing between each menu item. Does not apply to filter buttons.", + 0, G_MAXUINT, 16, G_PARAM_READABLE)); gtk_widget_class_install_style_property ( widget_class, g_param_spec_uint ( - "vertical-spacing", - "Vertical spacing on menu items", - "Vertical spacing between each menu item (but not filters)", - 0, G_MAXUINT, 10, + "inner-border", + "Border between menu edges and buttons", + "Border between menu edges and buttons", + 0, G_MAXUINT, 16, G_PARAM_READABLE)); gtk_widget_class_install_style_property (