Only show the mnemonic underline when pressing Alt

...and show them in menus when navigating the menu with the keyboard.
This is similar to what other platforms do, and reduces visual clutter.
There is a setting to control this. Most of the work on this patch was
done by Thomas Wood. See bug 588554.
This commit is contained in:
Matthias Clasen 2009-12-20 03:04:52 -05:00
parent af268d5b07
commit c59f76fda2
11 changed files with 353 additions and 8 deletions

View File

@ -58,6 +58,8 @@ typedef struct
gint wrap_width;
gint width_chars;
gint max_width_chars;
gboolean mnemonics_visible;
} GtkLabelPrivate;
/* Notes about the handling of links:
@ -264,6 +266,9 @@ static void gtk_label_buildable_custom_finished (GtkBuildable *builda
gpointer user_data);
static void connect_mnemonics_visible_notify (GtkLabel *label);
/* For selectable labels: */
static void gtk_label_move_cursor (GtkLabel *label,
GtkMovementStep step,
@ -1037,6 +1042,8 @@ gtk_label_init (GtkLabel *label)
label->mnemonic_widget = NULL;
label->mnemonic_window = NULL;
priv->mnemonics_visible = TRUE;
gtk_label_set_text (label, "");
}
@ -1488,6 +1495,8 @@ gtk_label_setup_mnemonic (GtkLabel *label,
if (label->mnemonic_keyval == GDK_VoidSymbol)
goto done;
connect_mnemonics_visible_notify (GTK_LABEL (widget));
toplevel = gtk_widget_get_toplevel (widget);
if (GTK_WIDGET_TOPLEVEL (toplevel))
{
@ -1564,6 +1573,65 @@ label_shortcut_setting_changed (GtkSettings *settings)
g_list_free (list);
}
static void
mnemonics_visible_apply (GtkWidget *widget,
gboolean mnemonics_visible)
{
GtkLabel *label;
GtkLabelPrivate *priv;
label = GTK_LABEL (widget);
if (!label->use_underline)
return;
priv = GTK_LABEL_GET_PRIVATE (label);
mnemonics_visible = mnemonics_visible != FALSE;
if (priv->mnemonics_visible != mnemonics_visible)
{
priv->mnemonics_visible = mnemonics_visible;
gtk_label_recalculate (label);
}
}
static void
label_mnemonics_visible_traverse_container (GtkWidget *widget,
gpointer data)
{
gboolean mnemonics_visible = GPOINTER_TO_INT (data);
_gtk_label_mnemonics_visible_apply_recursively (widget, mnemonics_visible);
}
void
_gtk_label_mnemonics_visible_apply_recursively (GtkWidget *widget,
gboolean mnemonics_visible)
{
if (GTK_IS_LABEL (widget))
mnemonics_visible_apply (widget, mnemonics_visible);
else if (GTK_IS_CONTAINER (widget))
gtk_container_forall (GTK_CONTAINER (widget),
label_mnemonics_visible_traverse_container,
GINT_TO_POINTER (mnemonics_visible));
}
static void
label_mnemonics_visible_changed (GtkWindow *window,
GParamSpec *pspec,
gpointer data)
{
gboolean mnemonics_visible;
g_object_get (window, "mnemonics-visible", &mnemonics_visible, NULL);
gtk_container_forall (GTK_CONTAINER (window),
label_mnemonics_visible_traverse_container,
GINT_TO_POINTER (mnemonics_visible));
}
static void
gtk_label_screen_changed (GtkWidget *widget,
GdkScreen *old_screen)
@ -2420,6 +2488,7 @@ static void
gtk_label_set_pattern_internal (GtkLabel *label,
const gchar *pattern)
{
GtkLabelPrivate *priv = GTK_LABEL_GET_PRIVATE (label);
PangoAttrList *attrs;
gboolean enable_mnemonics;
@ -2432,7 +2501,8 @@ gtk_label_set_pattern_internal (GtkLabel *label,
"gtk-enable-mnemonics", &enable_mnemonics,
NULL);
if (enable_mnemonics && pattern)
if (enable_mnemonics && priv->mnemonics_visible && pattern &&
GTK_WIDGET_IS_SENSITIVE (label))
attrs = gtk_label_pattern_to_attrs (label, pattern);
else
attrs = NULL;
@ -4178,6 +4248,38 @@ gtk_label_button_release (GtkWidget *widget,
return TRUE;
}
static void
connect_mnemonics_visible_notify (GtkLabel *label)
{
GtkLabelPrivate *priv = GTK_LABEL_GET_PRIVATE (label);
GtkWidget *toplevel;
gboolean connected;
toplevel = gtk_widget_get_toplevel (GTK_WIDGET (label));
if (!GTK_IS_WINDOW (toplevel))
return;
/* always set up this widgets initial value */
priv->mnemonics_visible =
gtk_window_get_mnemonics_visible (GTK_WINDOW (toplevel));
connected =
GPOINTER_TO_INT (g_object_get_data (G_OBJECT (toplevel),
"gtk-label-mnemonics-visible-connected"));
if (!connected)
{
g_signal_connect (toplevel,
"notify::mnemonics-visible",
G_CALLBACK (label_mnemonics_visible_changed),
label);
g_object_set_data (G_OBJECT (toplevel),
"gtk-label-mnemonics-visible-connected",
GINT_TO_POINTER (1));
}
}
static void
drag_begin_cb (GtkWidget *widget,
GdkDragContext *context,
@ -5707,7 +5809,7 @@ gtk_label_activate_current_link (GtkLabel *label)
if (window &&
window->default_widget != widget &&
!(widget == window->focus_widget &&
(!window->default_widget || !GTK_WIDGET_SENSITIVE (window->default_widget))))
(!window->default_widget || !GTK_WIDGET_IS_SENSITIVE (window->default_widget))))
gtk_window_activate_default (window);
}
}

View File

@ -197,6 +197,11 @@ guint gtk_label_parse_uline (GtkLabel *label,
#endif /* GTK_DISABLE_DEPRECATED */
/* private */
void _gtk_label_mnemonics_visible_apply_recursively (GtkWidget *widget,
gboolean mnemonics_visible);
G_END_DECLS
#endif /* __GTK_LABEL_H__ */

View File

@ -63,6 +63,8 @@
#include "gtktooltip.h"
#include "gtkdebug.h"
#include "gtkalias.h"
#include "gtkmenu.h"
#include "gdk/gdkkeysyms.h"
#include "gdk/gdkprivate.h" /* for GDK_WINDOW_DESTROYED */
@ -1613,6 +1615,27 @@ gtk_main_do_event (GdkEvent *event)
if (gtk_invoke_key_snoopers (grab_widget, event))
break;
}
/* catch alt press to enable auto-mnemonics */
if (event->key.keyval == GDK_Alt_L || event->key.keyval == GDK_Alt_R)
{
gboolean auto_mnemonics;
g_object_get (gtk_widget_get_settings (grab_widget),
"gtk-auto-mnemonics", &auto_mnemonics, NULL);
if (auto_mnemonics)
{
gboolean mnemonics_visible;
GtkWidget *window;
mnemonics_visible = (event->type == GDK_KEY_PRESS);
window = gtk_widget_get_toplevel (grab_widget);
if (GTK_IS_WINDOW (window))
gtk_window_set_mnemonics_visible (GTK_WINDOW (window), mnemonics_visible);
}
}
/* else fall through */
case GDK_MOTION_NOTIFY:
case GDK_BUTTON_RELEASE:

View File

@ -1616,6 +1616,18 @@ gtk_menu_popup (GtkMenu *menu,
if (xgrab_shell == widget)
popup_grab_on_window (widget->window, activate_time, grab_keyboard); /* Should always succeed */
gtk_grab_add (GTK_WIDGET (menu));
if (parent_menu_shell)
{
gboolean keyboard_mode;
keyboard_mode = _gtk_menu_shell_get_keyboard_mode (GTK_MENU_SHELL (parent_menu_shell));
_gtk_menu_shell_set_keyboard_mode (menu_shell, keyboard_mode);
}
else if (menu_shell->button == 0) /* a keynav-activated context menu */
_gtk_menu_shell_set_keyboard_mode (menu_shell, TRUE);
_gtk_menu_shell_update_mnemonics (menu_shell);
}
void

View File

@ -625,6 +625,7 @@ window_key_press_handler (GtkWidget *widget,
{
GtkMenuShell *menu_shell = GTK_MENU_SHELL (menubars->data);
_gtk_menu_shell_set_keyboard_mode (menu_shell, TRUE);
_gtk_menu_shell_activate (menu_shell);
gtk_menu_shell_select_first (menu_shell, FALSE);

View File

@ -1374,6 +1374,9 @@ static gboolean
gtk_menu_item_mnemonic_activate (GtkWidget *widget,
gboolean group_cycling)
{
if (GTK_IS_MENU_SHELL (widget->parent))
_gtk_menu_shell_set_keyboard_mode (GTK_MENU_SHELL (widget->parent), TRUE);
if (group_cycling &&
widget->parent &&
GTK_IS_MENU_SHELL (widget->parent) &&

View File

@ -30,6 +30,7 @@
#include "gdk/gdkkeysyms.h"
#include "gtkbindings.h"
#include "gtkkeyhash.h"
#include "gtklabel.h"
#include "gtkmain.h"
#include "gtkmarshalers.h"
#include "gtkmenu.h"
@ -773,6 +774,76 @@ gtk_menu_shell_button_release (GtkWidget *widget,
return TRUE;
}
void
_gtk_menu_shell_set_keyboard_mode (GtkMenuShell *menu_shell,
gboolean keyboard_mode)
{
menu_shell->keyboard_mode = keyboard_mode;
}
gboolean
_gtk_menu_shell_get_keyboard_mode (GtkMenuShell *menu_shell)
{
return menu_shell->keyboard_mode;
}
void
_gtk_menu_shell_update_mnemonics (GtkMenuShell *menu_shell)
{
GtkMenuShell *target;
gboolean auto_mnemonics;
gboolean found;
gboolean mnemonics_visible;
g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu_shell)),
"gtk-auto-mnemonics", &auto_mnemonics, NULL);
if (!auto_mnemonics)
return;
target = menu_shell;
found = FALSE;
while (target)
{
/* The idea with keyboard mode is that once you start using
* the keyboard to navigate the menus, we show mnemonics
* until the menu navigation is over. To that end, we spread
* the keyboard mode upwards in the menu hierarchy here.
* Also see gtk_menu_popup, where we inherit it downwards.
*/
if (menu_shell->keyboard_mode)
target->keyboard_mode = TRUE;
/* While navigating menus, the first parent menu with an active
* item is the one where mnemonics are effective, as can be seen
* in gtk_menu_shell_key_press below.
* We also show mnemonics in context menus. The grab condition is
* necessary to ensure we remove underlines from menu bars when
* dismissing menus.
*/
mnemonics_visible = target->keyboard_mode &&
((target->active_menu_item && !found) ||
(target == menu_shell &&
!target->parent_menu_shell &&
gtk_widget_has_grab (target)));
/* While menus are up, only show underlines inside the menubar,
* not in the entire window.
*/
if (GTK_IS_MENU_BAR (target))
_gtk_label_mnemonics_visible_apply_recursively (GTK_WIDGET (target),
mnemonics_visible);
else
gtk_window_set_mnemonics_visible (GTK_WINDOW (gtk_widget_get_toplevel (target)),
mnemonics_visible);
if (target->active_menu_item)
found = TRUE;
target = GTK_MENU_SHELL (target->parent_menu_shell);
}
}
static gint
gtk_menu_shell_key_press (GtkWidget *widget,
GdkEventKey *event)
@ -780,6 +851,8 @@ gtk_menu_shell_key_press (GtkWidget *widget,
GtkMenuShell *menu_shell = GTK_MENU_SHELL (widget);
gboolean enable_mnemonics;
menu_shell->keyboard_mode = TRUE;
if (!menu_shell->active_menu_item && menu_shell->parent_menu_shell)
return gtk_widget_event (menu_shell->parent_menu_shell, (GdkEvent *)event);
@ -997,6 +1070,10 @@ gtk_real_menu_shell_deactivate (GtkMenuShell *menu_shell)
gdk_display_pointer_ungrab (display, GDK_CURRENT_TIME);
gdk_display_keyboard_ungrab (display, GDK_CURRENT_TIME);
}
menu_shell->keyboard_mode = FALSE;
_gtk_menu_shell_update_mnemonics (menu_shell);
}
}
@ -1079,6 +1156,8 @@ gtk_menu_shell_real_select_item (GtkMenuShell *menu_shell,
GTK_MENU_SHELL_GET_CLASS (menu_shell)->submenu_placement);
gtk_menu_item_select (GTK_MENU_ITEM (menu_shell->active_menu_item));
_gtk_menu_shell_update_mnemonics (menu_shell);
/* This allows the bizarre radio buttons-with-submenus-display-history
* behavior
*/
@ -1095,6 +1174,7 @@ gtk_menu_shell_deselect (GtkMenuShell *menu_shell)
{
gtk_menu_item_deselect (GTK_MENU_ITEM (menu_shell->active_menu_item));
menu_shell->active_menu_item = NULL;
_gtk_menu_shell_update_mnemonics (menu_shell);
}
}
@ -1340,6 +1420,7 @@ gtk_real_menu_shell_move_current (GtkMenuShell *menu_shell,
* menu.
*/
_gtk_menu_item_popdown_submenu (menu_shell->active_menu_item);
_gtk_menu_shell_update_mnemonics (menu_shell);
}
else if (parent_menu_shell)
{
@ -1347,6 +1428,7 @@ gtk_real_menu_shell_move_current (GtkMenuShell *menu_shell,
{
/* close menu when returning from submenu. */
_gtk_menu_item_popdown_submenu (GTK_MENU (menu_shell)->parent_menu_item);
_gtk_menu_shell_update_mnemonics (parent_menu_shell);
break;
}

View File

@ -65,6 +65,7 @@ struct _GtkMenuShell
guint GSEAL (ignore_leave) : 1; /* unused */
guint GSEAL (menu_flag) : 1; /* unused */
guint GSEAL (ignore_enter) : 1;
guint GSEAL (keyboard_mode) : 1;
};
struct _GtkMenuShellClass
@ -130,6 +131,11 @@ gboolean gtk_menu_shell_get_take_focus (GtkMenuShell *menu_shell);
void gtk_menu_shell_set_take_focus (GtkMenuShell *menu_shell,
gboolean take_focus);
void _gtk_menu_shell_update_mnemonics (GtkMenuShell *menu_shell);
void _gtk_menu_shell_set_keyboard_mode (GtkMenuShell *menu_shell,
gboolean keyboard_mode);
gboolean _gtk_menu_shell_get_keyboard_mode (GtkMenuShell *menu_shell);
G_END_DECLS
#endif /* __GTK_MENU_SHELL_H__ */

View File

@ -124,7 +124,8 @@ enum {
PROP_ENABLE_EVENT_SOUNDS,
PROP_ENABLE_TOOLTIPS,
PROP_TOOLBAR_STYLE,
PROP_TOOLBAR_ICON_SIZE
PROP_TOOLBAR_ICON_SIZE,
PROP_AUTO_MNEMONICS
};
@ -999,6 +1000,23 @@ gtk_settings_class_init (GtkSettingsClass *class)
GTK_PARAM_READWRITE),
gtk_rc_property_parse_enum);
g_assert (result == PROP_TOOLBAR_ICON_SIZE);
/**
* GtkSettings:gtk-auto-mnemonics:
*
* Whether mnemonics should be automatically shown and hidden when the user
* presses the mnemonic activator.
*
* Since: 2.20
*/
result = settings_install_property_parser (class,
g_param_spec_boolean ("gtk-auto-mnemonics",
P_("Auto Mnemonics"),
P_("Whether mnemonics should be automatically shown and hidden when the user presses the mnemonic activator."),
FALSE,
GTK_PARAM_READWRITE),
NULL);
g_assert (result == PROP_AUTO_MNEMONICS);
}
static void

View File

@ -101,6 +101,8 @@ enum {
/* Writeonly properties */
PROP_STARTUP_ID,
PROP_MNEMONICS_VISIBLE,
LAST_ARG
};
@ -185,6 +187,9 @@ struct _GtkWindowPrivate
guint opacity_set : 1;
guint builder_visible : 1;
guint mnemonics_visible : 1;
guint mnemonics_visible_set : 1;
GdkWindowTypeHint type_hint;
gdouble opacity;
@ -230,6 +235,8 @@ static gint gtk_window_client_event (GtkWidget *widget,
static void gtk_window_check_resize (GtkContainer *container);
static gint gtk_window_focus (GtkWidget *widget,
GtkDirectionType direction);
static void gtk_window_grab_notify (GtkWidget *widget,
gboolean was_grabbed);
static void gtk_window_real_set_focus (GtkWindow *window,
GtkWidget *focus);
@ -456,8 +463,8 @@ gtk_window_class_init (GtkWindowClass *klass)
widget_class->focus_out_event = gtk_window_focus_out_event;
widget_class->client_event = gtk_window_client_event;
widget_class->focus = gtk_window_focus;
widget_class->expose_event = gtk_window_expose;
widget_class->grab_notify = gtk_window_grab_notify;
container_class->check_resize = gtk_window_check_resize;
@ -591,6 +598,13 @@ gtk_window_class_init (GtkWindowClass *klass)
P_("Icon for this window"),
GDK_TYPE_PIXBUF,
GTK_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_MNEMONICS_VISIBLE,
g_param_spec_boolean ("mnemonics-visible",
P_("Mnemonics Visible"),
P_("Whether mnemonics are currently visible in this window"),
TRUE,
GTK_PARAM_READWRITE));
/**
* GtkWindow:icon-name:
@ -929,6 +943,7 @@ gtk_window_init (GtkWindow *window)
priv->type_hint = GDK_WINDOW_TYPE_HINT_NORMAL;
priv->opacity = 1.0;
priv->startup_id = NULL;
priv->mnemonics_visible = TRUE;
colormap = _gtk_widget_peek_colormap ();
if (colormap)
@ -951,9 +966,12 @@ gtk_window_set_property (GObject *object,
GParamSpec *pspec)
{
GtkWindow *window;
GtkWindowPrivate *priv;
window = GTK_WINDOW (object);
priv = GTK_WINDOW_GET_PRIVATE (window);
switch (prop_id)
{
case PROP_TYPE:
@ -1050,6 +1068,9 @@ gtk_window_set_property (GObject *object,
case PROP_OPACITY:
gtk_window_set_opacity (window, g_value_get_double (value));
break;
case PROP_MNEMONICS_VISIBLE:
gtk_window_set_mnemonics_visible (window, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -1165,6 +1186,9 @@ gtk_window_get_property (GObject *object,
case PROP_OPACITY:
g_value_set_double (value, gtk_window_get_opacity (window));
break;
case PROP_MNEMONICS_VISIBLE:
g_value_set_boolean (value, priv->mnemonics_visible);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -4537,6 +4561,7 @@ gtk_window_map (GtkWidget *widget)
GtkWindow *window = GTK_WINDOW (widget);
GtkWindowPrivate *priv = GTK_WINDOW_GET_PRIVATE (window);
GdkWindow *toplevel;
gboolean auto_mnemonics;
GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
@ -4612,6 +4637,14 @@ gtk_window_map (GtkWidget *widget)
gdk_notify_startup_complete ();
}
}
/* if auto-mnemonics is enabled and mnemonics visible is not already set
* (as in the case of popup menus), then hide mnemonics initially
*/
g_object_get (gtk_widget_get_settings (widget), "gtk-auto-mnemonics",
&auto_mnemonics, NULL);
if (auto_mnemonics && !priv->mnemonics_visible_set)
gtk_window_set_mnemonics_visible (window, FALSE);
}
static gboolean
@ -5288,10 +5321,18 @@ gtk_window_focus_out_event (GtkWidget *widget,
GdkEventFocus *event)
{
GtkWindow *window = GTK_WINDOW (widget);
gboolean auto_mnemonics;
_gtk_window_set_has_toplevel_focus (window, FALSE);
_gtk_window_set_is_active (window, FALSE);
/* set the mnemonic-visible property to false */
g_object_get (gtk_widget_get_settings (widget),
"gtk-auto-mnemonics", &auto_mnemonics, NULL);
if (auto_mnemonics)
gtk_window_set_mnemonics_visible (window, FALSE);
return FALSE;
}
@ -8432,6 +8473,55 @@ gtk_window_get_window_type (GtkWindow *window)
return window->type;
}
gboolean
gtk_window_get_mnemonics_visible (GtkWindow *window)
{
GtkWindowPrivate *priv;
g_return_val_if_fail (GTK_IS_WINDOW (window), FALSE);
priv = GTK_WINDOW_GET_PRIVATE (window);
return priv->mnemonics_visible;
}
void
gtk_window_set_mnemonics_visible (GtkWindow *window,
gboolean setting)
{
GtkWindowPrivate *priv;
g_return_if_fail (GTK_IS_WINDOW (window));
priv = GTK_WINDOW_GET_PRIVATE (window);
setting = setting != FALSE;
if (priv->mnemonics_visible != setting)
{
priv->mnemonics_visible = setting;
g_object_notify (G_OBJECT (window), "mnemonics-visible");
}
priv->mnemonics_visible_set = TRUE;
}
static void
gtk_window_grab_notify (GtkWidget *widget,
gboolean was_grabbed)
{
gboolean auto_mnemonics;
if (was_grabbed)
return;
g_object_get (gtk_widget_get_settings (widget), "gtk-auto-mnemonics",
&auto_mnemonics, NULL);
if (auto_mnemonics)
gtk_window_set_mnemonics_visible (GTK_WINDOW (widget), FALSE);
}
#if defined (G_OS_WIN32) && !defined (_WIN64)
#undef gtk_window_set_icon_from_file

View File

@ -230,6 +230,9 @@ gboolean gtk_window_get_focus_on_map (GtkWindow *window);
void gtk_window_set_destroy_with_parent (GtkWindow *window,
gboolean setting);
gboolean gtk_window_get_destroy_with_parent (GtkWindow *window);
void gtk_window_set_mnemonics_visible (GtkWindow *window,
gboolean setting);
gboolean gtk_window_get_mnemonics_visible (GtkWindow *window);
void gtk_window_set_resizable (GtkWindow *window,
gboolean resizable);