--- gtk+-2.6.4/gtk/gtkmenu.c 2005-03-01 08:28:56.000000000 +0200 +++ gtk+-2.6.4/gtk/gtkmenu.c 2005-04-06 16:19:36.921925376 +0300 @@ -24,10 +24,16 @@ * GTK+ at ftp://ftp.gtk.org/pub/gtk/. */ +/* Modified for Nokia Oyj during 2002-2005. See CHANGES file for list + * of changes. + */ + #define GTK_MENU_INTERNALS #include #include /* memset */ +#include +#include #include "gdk/gdkkeysyms.h" #include "gtkalias.h" #include "gtkaccellabel.h" @@ -44,7 +50,11 @@ #include "gtkvscrollbar.h" #include "gtksettings.h" #include "gtkintl.h" +#include "gtkcombobox.h" +/* Hildon : We need this to figure out if menu should have + * corners etc. */ +#include "gtkmenubar.h" #define MENU_ITEM_CLASS(w) GTK_MENU_ITEM_GET_CLASS (w) @@ -55,16 +65,43 @@ * extends below the submenu */ +/* HILDON: + * Urgh, nasty thing to hard-code things like these :p + * One should really do some rewriting here... + */ + #define MENU_SCROLL_STEP1 8 #define MENU_SCROLL_STEP2 15 -#define MENU_SCROLL_ARROW_HEIGHT 16 -#define MENU_SCROLL_FAST_ZONE 8 +#define MENU_SCROLL_ARROW_HEIGHT 20 /* This used to be: 23; This hard-coding should be + * changed. Add arrow_height style property into + * commongtkrc and read it from there everywhere + * where a reference to MENU_SCROLL_ARROW_HEIGHT + * is made. + * If these changes are made, please modify also + * gtkcombobox.c. + */ +#define MENU_SCROLL_FAST_ZONE MENU_SCROLL_ARROW_HEIGHT /* Was originally 8 */ #define MENU_SCROLL_TIMEOUT1 50 #define MENU_SCROLL_TIMEOUT2 20 #define ATTACH_INFO_KEY "gtk-menu-child-attach-info-key" #define ATTACHED_MENUS "gtk-attached-menus" +/* HILDON: */ +#define HILDON_MENU_NAME_SHARP "menu_with_corners" + +/* needed to allow different themeing for first level menus */ +#define HILDON_MENU_NAME_ROUND_FIRST_LEVEL "menu_without_corners_first_level" +#define HILDON_MENU_NAME_ROUND "menu_without_corners" +#define HILDON_MENU_NAME_FORCE_SHARP "menu_force_with_corners" +#define HILDON_MENU_NAME_FORCE_ROUND "menu_force_without_corners" + +/* maximum sizes for menus when attached to comboboxes */ +#define HILDON_MENU_COMBO_MAX_WIDTH 406 +#define HILDON_MENU_COMBO_MIN_WIDTH 66 +#define HILDON_MENU_COMBO_MAX_HEIGHT 305 +#define HILDON_MENU_COMBO_MIN_HEIGHT 70 + typedef struct _GtkMenuAttachData GtkMenuAttachData; typedef struct _GtkMenuPrivate GtkMenuPrivate; @@ -92,6 +129,15 @@ gboolean have_layout; gint n_rows; gint n_columns; + + /* Arrow states */ + GtkStateType lower_arrow_state; + GtkStateType upper_arrow_state; + + /* For context menu behavior */ + gboolean context_menu; + int popup_pointer_x; + int popup_pointer_y; }; typedef struct @@ -108,6 +154,7 @@ enum { MOVE_SCROLL, + CLOSE_CURRENT, LAST_SIGNAL }; @@ -191,7 +238,8 @@ static void gtk_menu_handle_scrolling (GtkMenu *menu, gint event_x, gint event_y, - gboolean enter); + gboolean enter, + gboolean motion); static void gtk_menu_set_tearoff_hints (GtkMenu *menu, gint width); static void gtk_menu_style_set (GtkWidget *widget, @@ -232,6 +280,9 @@ guint signal_id); static void _gtk_menu_refresh_accel_paths (GtkMenu *menu, gboolean group_changed); +static gboolean gtk_menu_check_name (GtkWidget *widget); + +static void _gtk_menu_close_current (GtkMenu *menu); static GtkMenuShellClass *parent_class = NULL; static const gchar attach_data_key[] = "gtk-menu-attach-data"; @@ -496,7 +547,6 @@ widget_class->hide_all = gtk_menu_hide_all; widget_class->enter_notify_event = gtk_menu_enter_notify; widget_class->leave_notify_event = gtk_menu_leave_notify; - widget_class->motion_notify_event = gtk_menu_motion_notify; widget_class->style_set = gtk_menu_style_set; widget_class->focus = gtk_menu_focus; widget_class->can_activate_accel = gtk_menu_real_can_activate_accel; @@ -521,6 +571,15 @@ _gtk_marshal_VOID__ENUM, G_TYPE_NONE, 1, GTK_TYPE_SCROLL_TYPE); + + menu_signals[CLOSE_CURRENT] = + _gtk_binding_signal_new ("close_current", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_CALLBACK (_gtk_menu_close_current), + NULL, NULL, + _gtk_marshal_VOID__VOID, + G_TYPE_NONE, 0); g_object_class_install_property (gobject_class, PROP_TEAROFF_TITLE, @@ -606,6 +665,11 @@ G_PARAM_READWRITE)); binding_set = gtk_binding_set_by_class (class); + /* Hildon : We moved handling of escape-key here because we need it to + * work like closing a submenu, not closing all the menus. */ + gtk_binding_entry_add_signal (binding_set, + GDK_Escape, 0, + "close_current", 0); gtk_binding_entry_add_signal (binding_set, GDK_Up, 0, "move_current", 1, @@ -709,6 +773,25 @@ DEFAULT_POPDOWN_DELAY, G_PARAM_READWRITE)); + /* Hildon addition : border width was + replaced with horizontal-padding and + vertical-padding (which already is an style + property for GtkMenu). */ + gtk_widget_class_install_style_property (widget_class, + g_param_spec_int ("horizontal-padding", + P_("Horizontal Padding"), + P_("Extra space at the left and right edges of the menu"), + 0, + G_MAXINT, + 0, /* 1, */ + G_PARAM_READABLE)); + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_boolean ("double_arrows", + P_("Double Arrows"), + P_("When scrolling, always show both arrows."), + FALSE, + G_PARAM_READABLE)); } @@ -884,13 +967,14 @@ menu->toggle_size = 0; menu->toplevel = g_object_connect (g_object_new (GTK_TYPE_WINDOW, - "type", GTK_WINDOW_POPUP, - "child", menu, - NULL), + "type", GTK_WINDOW_POPUP, + "child", menu, + NULL), "signal::event", gtk_menu_window_event, menu, "signal::size_request", gtk_menu_window_size_request, menu, "signal::destroy", gtk_widget_destroyed, &menu->toplevel, NULL); + gtk_window_set_resizable (GTK_WINDOW (menu->toplevel), FALSE); gtk_window_set_mnemonic_modifier (GTK_WINDOW (menu->toplevel), 0); @@ -919,6 +1003,15 @@ menu->lower_arrow_visible = FALSE; menu->upper_arrow_prelight = FALSE; menu->lower_arrow_prelight = FALSE; + + /* */ + priv->upper_arrow_state = GTK_STATE_NORMAL; + priv->lower_arrow_state = GTK_STATE_NORMAL; + + priv->context_menu = FALSE; + priv->popup_pointer_x = -1; + priv->popup_pointer_y = -1; + /* have_layout = FALSE; } @@ -1220,7 +1313,8 @@ static gboolean popup_grab_on_window (GdkWindow *window, - guint32 activate_time) + guint32 activate_time, + gboolean grab_keyboard) { if ((gdk_pointer_grab (window, TRUE, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | @@ -1228,7 +1322,8 @@ GDK_POINTER_MOTION_MASK, NULL, NULL, activate_time) == 0)) { - if (gdk_keyboard_grab (window, TRUE, + if (!grab_keyboard || + gdk_keyboard_grab (window, TRUE, activate_time) == 0) return TRUE; else @@ -1282,6 +1377,7 @@ GtkWidget *parent; GdkEvent *current_event; GtkMenuShell *menu_shell; + gboolean grab_keyboard; GtkMenuPrivate *priv = gtk_menu_get_private (menu); g_return_if_fail (GTK_IS_MENU (menu)); @@ -1333,10 +1429,28 @@ * probably could just leave the grab on the other window, with a * little reorganization of the code in gtkmenu*). */ + + grab_keyboard = gtk_menu_shell_get_take_focus (menu_shell); + gtk_window_set_accept_focus (GTK_WINDOW (menu->toplevel), grab_keyboard); + if (xgrab_shell && xgrab_shell != widget) { - if (popup_grab_on_window (xgrab_shell->window, activate_time)) + if (popup_grab_on_window (xgrab_shell->window, activate_time, grab_keyboard)) GTK_MENU_SHELL (xgrab_shell)->have_xgrab = TRUE; + + /* HILDON: + * Check wheter parent is GtkMenuBar. If so, + * then we need sharp upper corners for this menu. + */ + if (gtk_menu_check_name (widget)) + { + if (GTK_IS_MENU_BAR (parent_menu_shell)) + gtk_widget_set_name (widget, HILDON_MENU_NAME_SHARP); + else if (GTK_IS_MENU (parent_menu_shell)) + gtk_widget_set_name( widget, HILDON_MENU_NAME_ROUND); + else + gtk_widget_set_name (widget, HILDON_MENU_NAME_ROUND_FIRST_LEVEL); + } } else { @@ -1344,8 +1458,14 @@ xgrab_shell = widget; transfer_window = menu_grab_transfer_window_get (menu); - if (popup_grab_on_window (transfer_window, activate_time)) + if (popup_grab_on_window (transfer_window, activate_time, grab_keyboard)) GTK_MENU_SHELL (xgrab_shell)->have_xgrab = TRUE; + + /* HILDON: + * We want this menu to have round corners (Used by default) + */ + if (gtk_menu_check_name (widget)) + gtk_widget_set_name (widget, HILDON_MENU_NAME_ROUND_FIRST_LEVEL); } if (!GTK_MENU_SHELL (xgrab_shell)->have_xgrab) @@ -1409,6 +1529,23 @@ /* Position the menu, possibly changing the size request */ + if (GTK_IS_COMBO_BOX (gtk_menu_get_attach_widget (menu))) + { + /* Hildon - limit the size if the menu is attached to a ComboBox */ + GtkRequisition req; + gint width, height; + + gtk_widget_set_size_request (widget, -1, -1); + gtk_widget_size_request (widget, &req); + + width = MAX (MIN (req.width, HILDON_MENU_COMBO_MAX_WIDTH), + HILDON_MENU_COMBO_MIN_WIDTH); + height = MAX (MIN (req.height, HILDON_MENU_COMBO_MAX_HEIGHT), + HILDON_MENU_COMBO_MIN_HEIGHT); + + gtk_widget_set_size_request (widget, width, height); + } + gtk_menu_position (menu); /* Compute the size of the toplevel and realize it so we @@ -1430,13 +1567,29 @@ gtk_menu_scroll_to (menu, menu->scroll_offset); + if (priv->context_menu) + { + /* Save position of the pointer during popup */ + /* currently not-multihead safe */ + GdkScreen *screen; + GdkDisplay *display; + + screen = gtk_widget_get_screen (widget); + display = gdk_screen_get_display (screen); + + gdk_display_get_pointer (display, NULL, + &priv->popup_pointer_x, + &priv->popup_pointer_y, + NULL); + } + /* Once everything is set up correctly, map the toplevel window on the screen. */ gtk_widget_show (menu->toplevel); if (xgrab_shell == widget) - popup_grab_on_window (widget->window, activate_time); /* Should always succeed */ + popup_grab_on_window (widget->window, activate_time, grab_keyboard); /* Should always succeed */ gtk_grab_add (GTK_WIDGET (menu)); } @@ -1996,6 +2149,7 @@ GtkWidget *child; GList *children; guint vertical_padding; + guint horizontal_padding; g_return_if_fail (GTK_IS_MENU (widget)); @@ -2025,9 +2179,10 @@ gtk_widget_style_get (GTK_WIDGET (menu), "vertical-padding", &vertical_padding, + "horizontal-padding", &horizontal_padding, NULL); - attributes.x = border_width + widget->style->xthickness; + attributes.x = border_width + widget->style->xthickness + horizontal_padding; attributes.y = border_width + widget->style->ythickness + vertical_padding; attributes.width = MAX (1, widget->allocation.width - attributes.x * 2); attributes.height = MAX (1, widget->allocation.height - attributes.y * 2); @@ -2040,11 +2195,14 @@ if (menu->lower_arrow_visible) attributes.height -= MENU_SCROLL_ARROW_HEIGHT; + attributes.window_type = GDK_WINDOW_CHILD; + menu->view_window = gdk_window_new (widget->window, &attributes, attributes_mask); gdk_window_set_user_data (menu->view_window, menu); attributes.x = 0; attributes.y = 0; + attributes.width = MAX (1, widget->requisition.width - (border_width + widget->style->xthickness + horizontal_padding) * 2); attributes.height = MAX (1, widget->requisition.height - (border_width + widget->style->ythickness + vertical_padding) * 2); menu->bin_window = gdk_window_new (menu->view_window, &attributes, attributes_mask); @@ -2164,6 +2322,10 @@ guint vertical_padding; GtkRequisition child_requisition; GtkMenuPrivate *priv; + guint horizontal_padding; + GdkScreen *screen; + GdkRectangle monitor; + gint monitor_num; g_return_if_fail (GTK_IS_MENU (widget)); g_return_if_fail (requisition != NULL); @@ -2182,6 +2344,16 @@ priv->heights = g_new0 (guint, gtk_menu_get_n_rows (menu)); priv->heights_length = gtk_menu_get_n_rows (menu); +/* Hildon addition to find out the monitor width */ + screen = gtk_widget_get_screen (widget); + if (widget->window != NULL) + monitor_num = gdk_screen_get_monitor_at_window (screen, widget->window); + else + monitor_num = 0; + if (monitor_num < 0) + monitor_num = 0; + gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor); + children = menu_shell->children; while (children) { @@ -2223,15 +2395,18 @@ requisition->width += max_toggle_size + max_accel_width; requisition->width *= gtk_menu_get_n_columns (menu); - requisition->width += (GTK_CONTAINER (menu)->border_width + - widget->style->xthickness) * 2; gtk_widget_style_get (GTK_WIDGET (menu), + "horizontal-padding", &horizontal_padding, "vertical-padding", &vertical_padding, NULL); + requisition->width += (GTK_CONTAINER (menu)->border_width + horizontal_padding + + widget->style->xthickness) * 2; requisition->height += (GTK_CONTAINER (menu)->border_width + vertical_padding + widget->style->ythickness) * 2; +/* Hildon addition to not make the menu too wide for the screen. */ + requisition->width = MIN (requisition->width, monitor.width); menu->toggle_size = max_toggle_size; /* Don't resize the tearoff if it is not active, because it won't redraw (it is only a background pixmap). @@ -2253,6 +2428,7 @@ GList *children; gint x, y; gint width, height; + guint horizontal_padding; guint vertical_padding; g_return_if_fail (GTK_IS_MENU (widget)); @@ -2266,10 +2442,11 @@ gtk_widget_get_child_requisition (GTK_WIDGET (menu), &child_requisition); gtk_widget_style_get (GTK_WIDGET (menu), + "horizontal-padding", &horizontal_padding, "vertical-padding", &vertical_padding, NULL); - x = GTK_CONTAINER (menu)->border_width + widget->style->xthickness; + x = GTK_CONTAINER (menu)->border_width + widget->style->xthickness + horizontal_padding; y = GTK_CONTAINER (menu)->border_width + widget->style->ythickness + vertical_padding; width = MAX (1, allocation->width - x * 2); @@ -2407,27 +2584,32 @@ GdkEventExpose *event) { GtkMenu *menu; - gint width, height; - gint border_x, border_y; - guint vertical_padding; g_return_if_fail (GTK_IS_MENU (widget)); menu = GTK_MENU (widget); - gtk_widget_style_get (GTK_WIDGET (menu), - "vertical-padding", &vertical_padding, - NULL); - - border_x = GTK_CONTAINER (widget)->border_width + widget->style->xthickness; - border_y = GTK_CONTAINER (widget)->border_width + widget->style->ythickness + vertical_padding; - gdk_drawable_get_size (widget->window, &width, &height); - if (event->window == widget->window) { + gint width, height; + gint border_x, border_y; + guint vertical_padding; + guint horizontal_padding; + GtkMenuPrivate *priv; gint arrow_space = MENU_SCROLL_ARROW_HEIGHT - 2 * widget->style->ythickness; gint arrow_size = 0.7 * arrow_space; + priv = gtk_menu_get_private (menu); + + gtk_widget_style_get (GTK_WIDGET (menu), + "vertical-padding", &vertical_padding, + "horizontal-padding", &horizontal_padding, + NULL); + + border_x = GTK_CONTAINER (widget)->border_width + widget->style->xthickness + horizontal_padding; + border_y = GTK_CONTAINER (widget)->border_width + widget->style->ythickness + vertical_padding; + gdk_drawable_get_size (widget->window, &width, &height); + gtk_paint_box (widget->style, widget->window, GTK_STATE_NORMAL, @@ -2436,21 +2618,9 @@ 0, 0, -1, -1); if (menu->upper_arrow_visible && !menu->tearoff_active) { - gtk_paint_box (widget->style, - widget->window, - menu->upper_arrow_prelight ? - GTK_STATE_PRELIGHT : GTK_STATE_NORMAL, - GTK_SHADOW_OUT, - NULL, widget, "menu", - border_x, - border_y, - width - 2 * border_x, - MENU_SCROLL_ARROW_HEIGHT); - gtk_paint_arrow (widget->style, widget->window, - menu->upper_arrow_prelight ? - GTK_STATE_PRELIGHT : GTK_STATE_NORMAL, + priv->upper_arrow_state, GTK_SHADOW_OUT, NULL, widget, "menu_scroll_arrow_up", GTK_ARROW_UP, @@ -2462,21 +2632,9 @@ if (menu->lower_arrow_visible && !menu->tearoff_active) { - gtk_paint_box (widget->style, - widget->window, - menu->lower_arrow_prelight ? - GTK_STATE_PRELIGHT : GTK_STATE_NORMAL, - GTK_SHADOW_OUT, - NULL, widget, "menu", - border_x, - height - border_y - MENU_SCROLL_ARROW_HEIGHT, - width - 2*border_x, - MENU_SCROLL_ARROW_HEIGHT); - gtk_paint_arrow (widget->style, widget->window, - menu->lower_arrow_prelight ? - GTK_STATE_PRELIGHT : GTK_STATE_NORMAL, + priv->lower_arrow_state, GTK_SHADOW_OUT, NULL, widget, "menu_scroll_arrow_down", GTK_ARROW_DOWN, @@ -2516,18 +2674,82 @@ GTK_WIDGET_CLASS (parent_class)->show (widget); } +static GtkWidget * +find_active_menu_item (GdkEventButton *event) +{ + GtkWidget *menu_item; + + menu_item = gtk_get_event_widget ((GdkEvent*) event); + while (menu_item && !GTK_IS_MENU_ITEM (menu_item)) + menu_item = menu_item->parent; + + return menu_item; +} + +static gboolean +pointer_in_menu_tree (GtkWidget *widget) +{ + GtkMenuShell *mshell; + int width, height, x, y; + + mshell = GTK_MENU_SHELL (widget); + + gdk_window_get_pointer (widget->window, &x, &y, NULL); + gdk_drawable_get_size (widget->window, &width, &height); + + if ((x <= width) && (x >= 0) && (y <= height) && (y >= 0)) + return TRUE; + + if ((mshell->parent_menu_shell != NULL) && + GTK_IS_MENU (mshell->parent_menu_shell)) + return pointer_in_menu_tree (mshell->parent_menu_shell); + + return FALSE; +} + +static int +distance_traveled (GtkWidget *widget) +{ + GtkMenuPrivate *priv; + GdkScreen *screen; + GdkDisplay *display; + int x, y, dx, dy; + + priv = gtk_menu_get_private (GTK_MENU (widget)); + + screen = gtk_widget_get_screen (widget); + display = gdk_screen_get_display (screen); + + gdk_display_get_pointer (display, NULL, &x, &y, NULL); + + dx = (priv->popup_pointer_x - x); + dy = (priv->popup_pointer_y - y); + + return abs ((int) sqrt ((double) (dx * dx + dy * dy))); +} + static gboolean gtk_menu_button_press (GtkWidget *widget, - GdkEventButton *event) + GdkEventButton *event) { - /* Don't pop down the menu for releases over scroll arrows - */ - if (GTK_IS_MENU (widget)) + GtkWidget *menu_item; + + menu_item = find_active_menu_item (event); + if (menu_item == NULL) { GtkMenu *menu = GTK_MENU (widget); - if (menu->upper_arrow_prelight || menu->lower_arrow_prelight) - return TRUE; + if (menu->upper_arrow_prelight || menu->lower_arrow_prelight) + { + gtk_menu_handle_scrolling (GTK_MENU (widget), event->x_root, event->y_root, TRUE, FALSE); + + return TRUE; + } + + /* Don't pass down to menu shell if a non-menuitem part + * of the menu was clicked. */ + if (pointer_in_menu_tree (widget)) + return TRUE; } return GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, event); @@ -2537,14 +2759,44 @@ gtk_menu_button_release (GtkWidget *widget, GdkEventButton *event) { - /* Don't pop down the menu for releases over scroll arrows - */ - if (GTK_IS_MENU (widget)) + GtkMenuPrivate *priv; + GtkWidget *menu_item; + + priv = gtk_menu_get_private (GTK_MENU (widget)); + + menu_item = find_active_menu_item (event); + if (menu_item == NULL) { GtkMenu *menu = GTK_MENU (widget); - if (menu->upper_arrow_prelight || menu->lower_arrow_prelight) - return TRUE; + if (menu->upper_arrow_prelight || menu->lower_arrow_prelight) + { + gtk_menu_handle_scrolling (GTK_MENU (widget), event->x_root, event->y_root, FALSE, FALSE); + + return TRUE; + } + + if (priv->context_menu && + (priv->popup_pointer_x >= 0) && + (priv->popup_pointer_y >= 0)) + { + int distance; + + distance = distance_traveled (widget); + + priv->popup_pointer_x = -1; + priv->popup_pointer_y = -1; + + /* Don't popdown if we traveled less than 20px since popup point, + * as per the specs. */ + if (distance < 20) + return TRUE; + } + + /* Don't pass down to menu shell if a non-menuitem part + * of the menu was clicked. */ + if (pointer_in_menu_tree (widget)) + return TRUE; } return GTK_WIDGET_CLASS (parent_class)->button_release_event (widget, event); @@ -2765,7 +3017,7 @@ gboolean need_enter; if (GTK_IS_MENU (widget)) - gtk_menu_handle_scrolling (GTK_MENU (widget), event->x_root, event->y_root, TRUE); + gtk_menu_handle_scrolling (GTK_MENU (widget), event->x_root, event->y_root, TRUE, TRUE); /* We received the event for one of two reasons: * @@ -2779,7 +3031,27 @@ menu_item = gtk_get_event_widget ((GdkEvent*) event); if (!menu_item || !GTK_IS_MENU_ITEM (menu_item) || !GTK_IS_MENU (menu_item->parent)) - return FALSE; + { + GtkMenuPrivate *priv; + + priv = gtk_menu_get_private (GTK_MENU (widget)); + + if (priv->context_menu) + { + /* Context menu mode. If we dragged out of the menu, + * close the menu, as by the specs. */ + if (!pointer_in_menu_tree (widget) && + (distance_traveled (widget) >= 20) && + (event->state & GDK_BUTTON1_MASK)) + { + gtk_menu_deactivate (GTK_MENU_SHELL (widget)); + + return TRUE; + } + } + + return FALSE; + } menu_shell = GTK_MENU_SHELL (menu_item->parent); menu = GTK_MENU (menu_shell); @@ -2795,6 +3067,11 @@ */ if (gtk_menu_navigating_submenu (menu, event->x_root, event->y_root)) return TRUE; +/* HILDON MOD. + * Close the submenus that are two levels down from the currently selected. + * This ensures that the focus is correct all the time.*/ + if (GTK_MENU_ITEM(menu_item)->submenu != NULL) + gtk_menu_shell_deselect (GTK_MENU_SHELL(&(GTK_MENU(GTK_MENU_ITEM(menu_item)->submenu)->menu_shell))); /* Make sure we pop down if we enter a non-selectable menu item, so we * don't show a submenu when the cursor is outside the stay-up triangle. @@ -2828,6 +3105,7 @@ send_event->crossing.y_root = event->y_root; send_event->crossing.x = event->x; send_event->crossing.y = event->y; + send_event->crossing.state = event->state; /* We send the event to 'widget', the currently active menu, * instead of 'menu', the menu that the pointer is in. This @@ -2852,17 +3130,24 @@ GtkWidget *widget; gint offset; gint view_width, view_height; + gboolean double_arrows; widget = GTK_WIDGET (menu); offset = menu->scroll_offset + step; + /* get double_arrows style property */ + gtk_widget_style_get (widget, + "double_arrows", &double_arrows, + NULL); + /* If we scroll upward and the non-visible top part * is smaller than the scroll arrow it would be * pretty stupid to show the arrow and taking more * screen space than just scrolling to the top. */ - if ((step < 0) && (offset < MENU_SCROLL_ARROW_HEIGHT)) - offset = 0; + if (!double_arrows) + if ((step < 0) && (offset < MENU_SCROLL_ARROW_HEIGHT)) + offset = 0; /* Don't scroll over the top if we weren't before: */ if ((menu->scroll_offset >= 0) && (offset < 0)) @@ -2874,6 +3159,12 @@ if (menu->scroll_offset > 0) view_height -= MENU_SCROLL_ARROW_HEIGHT; + /* When both arrows are always shown, reduce + * view height even more. + */ + if (double_arrows) + view_height -= MENU_SCROLL_ARROW_HEIGHT; + if ((menu->scroll_offset + view_height <= widget->requisition.height) && (offset + view_height > widget->requisition.height)) offset = widget->requisition.height - view_height; @@ -2922,18 +3213,21 @@ gtk_menu_handle_scrolling (GtkMenu *menu, gint x, gint y, - gboolean enter) + gboolean enter, + gboolean motion) { GtkMenuShell *menu_shell; + GtkMenuPrivate *priv; gint width, height; gint border; GdkRectangle rect; - gboolean in_arrow; gboolean scroll_fast = FALSE; guint vertical_padding; gint top_x, top_y; gint win_x, win_y; + priv = gtk_menu_get_private (menu); + menu_shell = GTK_MENU_SHELL (menu); gdk_drawable_get_size (GTK_WIDGET (menu)->window, &width, &height); @@ -2946,10 +3240,11 @@ GTK_WIDGET (menu)->style->ythickness + vertical_padding; gdk_window_get_position (menu->toplevel->window, &top_x, &top_y); + x -= top_x; + y -= top_y; + gdk_window_get_position (GTK_WIDGET (menu)->window, &win_x, &win_y); - win_x += top_x; - win_y += top_y; - + if (menu->upper_arrow_visible && !menu->tearoff_active) { rect.x = win_x; @@ -2957,35 +3252,49 @@ rect.width = width; rect.height = MENU_SCROLL_ARROW_HEIGHT + border; - in_arrow = FALSE; + menu->upper_arrow_prelight = FALSE; if ((x >= rect.x) && (x < rect.x + rect.width) && (y >= rect.y) && (y < rect.y + rect.height)) - { - in_arrow = TRUE; - scroll_fast = (y < rect.y + MENU_SCROLL_FAST_ZONE); - } - - if (enter && in_arrow && - (!menu->upper_arrow_prelight || menu->scroll_fast != scroll_fast)) - { - menu->upper_arrow_prelight = TRUE; - menu->scroll_fast = scroll_fast; - gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE); - - /* Deselect the active item so that any submenus are poped down */ - gtk_menu_shell_deselect (menu_shell); + menu->upper_arrow_prelight = TRUE; - gtk_menu_remove_scroll_timeout (menu); - menu->scroll_step = (scroll_fast) ? -MENU_SCROLL_STEP2 : -MENU_SCROLL_STEP1; - menu->timeout_id = g_timeout_add ((scroll_fast) ? MENU_SCROLL_TIMEOUT2 : MENU_SCROLL_TIMEOUT1, - gtk_menu_scroll_timeout, - menu); - } - else if (!enter && !in_arrow && menu->upper_arrow_prelight) + if (priv->upper_arrow_state != GTK_STATE_INSENSITIVE) { - gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE); - - gtk_menu_stop_scrolling (menu); + if (enter && menu->upper_arrow_prelight && + (menu->timeout_id == 0 || menu->scroll_fast != scroll_fast)) + { + menu->scroll_fast = scroll_fast; + + /* Deselect the active item so that any submenus are poped down */ + gtk_menu_shell_deselect (menu_shell); + + gtk_menu_remove_scroll_timeout (menu); + menu->scroll_step = (scroll_fast) ? -MENU_SCROLL_STEP2 : -MENU_SCROLL_STEP1; + + if (!motion) + { + /* Only do stuff on click. */ + GtkSettings *settings; + guint timeout; + + settings = gtk_settings_get_default (); + g_object_get (settings, "gtk-update-timeout", &timeout, NULL); + + menu->timeout_id = g_timeout_add (timeout / 2, gtk_menu_scroll_timeout, menu); + + priv->upper_arrow_state = GTK_STATE_ACTIVE; + } + + gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE); + } + else if (!enter) + { + gtk_menu_stop_scrolling (menu); + + priv->upper_arrow_state = menu->upper_arrow_prelight ? + GTK_STATE_PRELIGHT : GTK_STATE_NORMAL; + + gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE); + } } } @@ -2996,36 +3305,50 @@ rect.width = width; rect.height = MENU_SCROLL_ARROW_HEIGHT + border; - in_arrow = FALSE; + menu->lower_arrow_prelight = FALSE; if ((x >= rect.x) && (x < rect.x + rect.width) && (y >= rect.y) && (y < rect.y + rect.height)) - { - in_arrow = TRUE; - scroll_fast = (y > rect.y + rect.height - MENU_SCROLL_FAST_ZONE); - } + menu->lower_arrow_prelight = TRUE; - if (enter && in_arrow && - (!menu->lower_arrow_prelight || menu->scroll_fast != scroll_fast)) + if (priv->lower_arrow_state != GTK_STATE_INSENSITIVE) { - menu->lower_arrow_prelight = TRUE; - menu->scroll_fast = scroll_fast; - gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE); + if (enter && menu->lower_arrow_prelight && + (menu->timeout_id == 0 || menu->scroll_fast != scroll_fast)) + { + menu->scroll_fast = scroll_fast; - /* Deselect the active item so that any submenus are poped down */ - gtk_menu_shell_deselect (menu_shell); + /* Deselect the active item so that any submenus are poped down */ + gtk_menu_shell_deselect (menu_shell); - gtk_menu_remove_scroll_timeout (menu); - menu->scroll_step = (scroll_fast) ? MENU_SCROLL_STEP2 : MENU_SCROLL_STEP1; - menu->timeout_id = g_timeout_add ((scroll_fast) ? MENU_SCROLL_TIMEOUT2 : MENU_SCROLL_TIMEOUT1, - gtk_menu_scroll_timeout, - menu); - } - else if (!enter && !in_arrow && menu->lower_arrow_prelight) - { - gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE); - - gtk_menu_stop_scrolling (menu); - } + gtk_menu_remove_scroll_timeout (menu); + menu->scroll_step = (scroll_fast) ? MENU_SCROLL_STEP2 : MENU_SCROLL_STEP1; + + if (!motion) + { + /* Only do stuff on click. */ + GtkSettings *settings; + guint timeout; + + settings = gtk_settings_get_default (); + g_object_get (settings, "gtk-update-timeout", &timeout, NULL); + + menu->timeout_id = g_timeout_add (timeout / 2, gtk_menu_scroll_timeout, menu); + + priv->lower_arrow_state = GTK_STATE_ACTIVE; + } + + gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE); + } + else if (!enter) + { + gtk_menu_stop_scrolling (menu); + + priv->lower_arrow_state = menu->lower_arrow_prelight ? + GTK_STATE_PRELIGHT : GTK_STATE_NORMAL; + + gdk_window_invalidate_rect (GTK_WIDGET (menu)->window, &rect, FALSE); + } + } } } @@ -3041,7 +3364,7 @@ GtkMenuShell *menu_shell = GTK_MENU_SHELL (widget); if (!menu_shell->ignore_enter) - gtk_menu_handle_scrolling (GTK_MENU (widget), event->x_root, event->y_root, TRUE); + gtk_menu_handle_scrolling (GTK_MENU (widget), event->x_root, event->y_root, TRUE, TRUE); } if (menu_item && GTK_IS_MENU_ITEM (menu_item)) @@ -3106,7 +3429,7 @@ if (gtk_menu_navigating_submenu (menu, event->x_root, event->y_root)) return TRUE; - gtk_menu_handle_scrolling (menu, event->x_root, event->y_root, FALSE); + gtk_menu_handle_scrolling (menu, event->x_root, event->y_root, FALSE, TRUE); event_widget = gtk_get_event_widget ((GdkEvent*) event); @@ -3611,7 +3934,13 @@ requisition.width, requisition.height); } - menu->scroll_offset = scroll_offset; + /* Hildon hack for menu in comboboxes: + * in case the menu in attached to a ComboBox, the scroll_offset is + * calculated in the positioning function so we dont't overwrite it + * with the value calculated above (in this function) */ + if ( !GTK_IS_COMBO_BOX(gtk_menu_get_attach_widget(menu)) ) + menu->scroll_offset = scroll_offset; + } static void @@ -3628,9 +3957,6 @@ gtk_menu_stop_scrolling (GtkMenu *menu) { gtk_menu_remove_scroll_timeout (menu); - - menu->upper_arrow_prelight = FALSE; - menu->lower_arrow_prelight = FALSE; } static void @@ -3644,6 +3970,8 @@ gboolean last_visible; gint menu_height; guint vertical_padding; + guint horizontal_padding; + gboolean double_arrows; widget = GTK_WIDGET (menu); @@ -3663,19 +3991,93 @@ gtk_widget_style_get (GTK_WIDGET (menu), "vertical-padding", &vertical_padding, + "horizontal-padding", &horizontal_padding, + "double_arrows", &double_arrows, NULL); border_width = GTK_CONTAINER (menu)->border_width; - view_width -= (border_width + widget->style->xthickness) * 2; + view_width -= (border_width + widget->style->xthickness + horizontal_padding) * 2; view_height -= (border_width + widget->style->ythickness + vertical_padding) * 2; menu_height = widget->requisition.height - (border_width + widget->style->ythickness + vertical_padding) * 2; - x = border_width + widget->style->xthickness; + x = border_width + widget->style->xthickness + horizontal_padding; y = border_width + widget->style->ythickness + vertical_padding; + if (double_arrows && !menu->tearoff_active && (view_height < menu_height)) + { + GtkMenuPrivate *priv; + GtkStateType upper_arrow_previous_state, lower_arrow_previous_state; + + priv = gtk_menu_get_private (menu); + + upper_arrow_previous_state = priv->upper_arrow_state; + lower_arrow_previous_state = priv->lower_arrow_state; + + if (!menu->upper_arrow_visible || !menu->lower_arrow_visible) + gtk_widget_queue_draw (GTK_WIDGET (menu)); + + view_height -= 2*MENU_SCROLL_ARROW_HEIGHT; + y += MENU_SCROLL_ARROW_HEIGHT; + + menu->upper_arrow_visible = menu->lower_arrow_visible = TRUE; + if (priv->upper_arrow_state == GTK_STATE_INSENSITIVE) + { + priv->upper_arrow_state = menu->upper_arrow_prelight ? + GTK_STATE_PRELIGHT : GTK_STATE_NORMAL; + } + if (priv->lower_arrow_state == GTK_STATE_INSENSITIVE) + { + priv->lower_arrow_state = menu->lower_arrow_prelight ? + GTK_STATE_PRELIGHT : GTK_STATE_NORMAL; + } + + if (offset <= 0) + { + offset = 0; + priv->upper_arrow_state = GTK_STATE_INSENSITIVE; + } + if (offset >= menu_height - view_height) + { + offset = menu_height - view_height; + priv->lower_arrow_state = GTK_STATE_INSENSITIVE; + } + + if ((priv->upper_arrow_state != upper_arrow_previous_state) || + (priv->lower_arrow_state != lower_arrow_previous_state)) + gtk_widget_queue_draw (GTK_WIDGET (menu)); + + if (upper_arrow_previous_state != GTK_STATE_INSENSITIVE && + priv->upper_arrow_state == GTK_STATE_INSENSITIVE) + { + /* If we hid the upper arrow, possibly remove timeout */ + if (menu->scroll_step < 0) + { + gtk_menu_stop_scrolling (menu); + gtk_widget_queue_draw (GTK_WIDGET (menu)); + } + } + + if (lower_arrow_previous_state != GTK_STATE_INSENSITIVE && + priv->lower_arrow_state == GTK_STATE_INSENSITIVE) + { + /* If we hid the lower arrow, possibly remove timeout */ + if (menu->scroll_step > 0) + { + gtk_menu_stop_scrolling (menu); + gtk_widget_queue_draw (GTK_WIDGET (menu)); + } + } + } + else if (!menu->tearoff_active) { + if (offset <= 0) + offset = 0; + + if (offset >= menu_height - view_height) + offset = menu_height - view_height; + last_visible = menu->upper_arrow_visible; menu->upper_arrow_visible = offset > 0; @@ -3685,8 +4087,6 @@ if ( (last_visible != menu->upper_arrow_visible) && !menu->upper_arrow_visible) { - menu->upper_arrow_prelight = FALSE; - /* If we hid the upper arrow, possibly remove timeout */ if (menu->scroll_step < 0) { @@ -3704,8 +4104,6 @@ if ( (last_visible != menu->lower_arrow_visible) && !menu->lower_arrow_visible) { - menu->lower_arrow_prelight = FALSE; - /* If we hid the lower arrow, possibly remove timeout */ if (menu->scroll_step > 0) { @@ -3792,12 +4190,14 @@ &child_offset, &child_height, &last_child)) { guint vertical_padding; + gboolean double_arrows; y = menu->scroll_offset; gdk_drawable_get_size (GTK_WIDGET (menu)->window, &width, &height); gtk_widget_style_get (GTK_WIDGET (menu), "vertical-padding", &vertical_padding, + "double_arrows", &double_arrows, NULL); height -= 2*GTK_CONTAINER (menu)->border_width + 2*GTK_WIDGET (menu)->style->ythickness + 2*vertical_padding; @@ -3820,11 +4220,11 @@ if (child_offset + child_height > y + height - arrow_height) { arrow_height = 0; - if (!last_child && !menu->tearoff_active) + if ((!last_child && !menu->tearoff_active) || (double_arrows)) arrow_height += MENU_SCROLL_ARROW_HEIGHT; y = child_offset + child_height - height + arrow_height; - if ((y > 0) && !menu->tearoff_active) + if (((y > 0) && !menu->tearoff_active) || (double_arrows)) { /* Need upper arrow */ arrow_height += MENU_SCROLL_ARROW_HEIGHT; @@ -4374,3 +4774,60 @@ return list; } +/* Little help function for making some sanity tests on this menu. + * Checks that given widget really is a menu and that it has no name + * assigned to it yet. + * Names used to do hildon theming: + * HILDON_MENU_NAME_SHARP for menu with sharp upper corners + * HILDON_MENU_NAME_ROUND for menu with round corners + */ +static gboolean +gtk_menu_check_name (GtkWidget *widget) +{ + gboolean legal_name = FALSE; + gchar **tmp = NULL; + const gchar *name = NULL; + static gchar *menu_names[] = { "GtkMenu", + HILDON_MENU_NAME_SHARP, + HILDON_MENU_NAME_ROUND, + HILDON_MENU_NAME_ROUND_FIRST_LEVEL, + NULL }; + if (GTK_IS_MENU (widget) && + (name = gtk_widget_get_name (widget))) + { + if (!g_ascii_strcasecmp (name, HILDON_MENU_NAME_FORCE_SHARP) || !g_ascii_strcasecmp (name, HILDON_MENU_NAME_FORCE_ROUND)) + return FALSE; + for (tmp = menu_names; *tmp; tmp++) + if (!g_ascii_strcasecmp (name, *tmp )) + { + legal_name = TRUE; + break; + } + } + + return legal_name; +} + +/* A function called when esc-key is pressed. */ +static void +_gtk_menu_close_current (GtkMenu * menu) +{ + GtkMenuShell * shell = GTK_MENU_SHELL (menu); + + /* Check is a submenu of current menu item is visible. + * If it is, close that first. */ + if (shell->active_menu_item && (GTK_MENU_ITEM (shell->active_menu_item)->submenu) && GTK_WIDGET_VISIBLE (GTK_MENU_ITEM (shell->active_menu_item)->submenu)) + gtk_menu_popdown (GTK_MENU (GTK_MENU_ITEM (shell->active_menu_item)->submenu)); + else + gtk_menu_popdown (menu); + +} + +/* Hildon function to make context menus behave according to spec */ +void +_gtk_menu_enable_context_menu_behavior (GtkMenu *menu) +{ + GtkMenuPrivate *priv = gtk_menu_get_private (menu); + + priv->context_menu = TRUE; +}