summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog51
-rw-r--r--gtk/gtk.symbols2
-rw-r--r--gtk/gtkcellrendereraccel.c2
-rw-r--r--gtk/gtkcombobox.c39
-rw-r--r--gtk/gtkentry.c73
-rw-r--r--gtk/gtkiconview.c16
-rw-r--r--gtk/gtkimcontextsimple.c39
-rw-r--r--gtk/gtklabel.c35
-rw-r--r--gtk/gtkmenu.c4
-rw-r--r--gtk/gtkmenushell.c29
-rw-r--r--gtk/gtknotebook.c40
-rw-r--r--gtk/gtkradiobutton.c17
-rw-r--r--gtk/gtkrange.c36
-rw-r--r--gtk/gtksettings.c63
-rw-r--r--gtk/gtkspinbutton.c5
-rw-r--r--gtk/gtktextview.c141
-rw-r--r--gtk/gtktreeview.c129
-rw-r--r--gtk/gtkwidget.c143
-rw-r--r--gtk/gtkwidget.h5
19 files changed, 740 insertions, 129 deletions
diff --git a/ChangeLog b/ChangeLog
index 0a95d7fdf7..129b653200 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,54 @@
+2006-11-16 Michael Natterer <mitch@imendio.com>
+
+ Add new infrastructure for notifications of failed keyboard
+ navigation and navigation with restricted set of keys.
+
+ The patch handles configurable beeping, navigating the GUI with
+ cursor keys only (as in phone environments), and configurable
+ wrap-around. Fixes bugs #322640, #70986, #318827, #334726, #334742
+ and #309291.
+
+ * gtk/gtksettings.c: added properties gtk-keynav-cursor-only,
+ gtk-keynav-wrap-around and gtk-error-bell.
+
+ * gtk/gtkwidget.[ch]: added new signal "keynav-failed" and public
+ API to emit it. Added New function gtk_widget_error_bell() which
+ looks at the gtk-error-bell setting and calls gdk_window_beep()
+ accordingly.
+
+ * gtk/gtk.symbols: add the new widget symbols.
+
+ * gtk/gtkcellrendereraccel.c
+ * gtk/gtkimcontextsimple.c
+ * gtk/gtkmenu.c
+ * gtk/gtknotebook.c: use gtk_widget_error_bell() or look at the
+ gtk-error-bell setting instead of calling gdk_display_beep()
+ unconditionally.
+
+ * gtk/gtkcombobox.c
+ * gtk/gtkentry.c
+ * gtk/gtkiconview.c
+ * gtk/gtklabel.c
+ * gtk/gtkmenushell.c
+ * gtk/gtkspinbutton.c
+ * gtk/gtktextview.c
+ * gtk/gtktreeview.c: call gtk_widget_error_bell() on failed keynav.
+
+ * gtk/gtkentry.c
+ * gtk/gtklabel.c
+ * gtk/gtkrange.c
+ * gtk/gtktextview.c: consult gtk_widget_keynav_failed() on failed
+ cursor navigation and leave the widget if it returns FALSE.
+
+ * gtk/gtkmenushell.c
+ * gtk/gtknotebook.c: only wrap around if gtk-keynav-wrap-around
+ is TRUE.
+
+ * gtk/gtkradiobutton.c: ask gtk_widget_keynav_failed() to decide
+ whether to to wrap-around, and don't select active items on cursor
+ navigation if gtk-keynav-cursor-only is TRUE. Should look at
+ gtk-keynav-wrap-around too, will look into that.
+
2006-11-16 Emmanuele Bassi <ebassi@gnome.org>
* gtk/gtkrecentmanager.c:
diff --git a/gtk/gtk.symbols b/gtk/gtk.symbols
index 61343e4a08..939370a65d 100644
--- a/gtk/gtk.symbols
+++ b/gtk/gtk.symbols
@@ -4470,6 +4470,7 @@ gtk_widget_create_pango_layout
gtk_widget_destroy
gtk_widget_destroyed
gtk_widget_ensure_style
+gtk_widget_error_bell
gtk_widget_event
gtk_widget_freeze_child_notify
gtk_widget_get_accessible
@@ -4511,6 +4512,7 @@ gtk_widget_hide_on_delete
gtk_widget_intersect
gtk_widget_is_ancestor
gtk_widget_is_focus
+gtk_widget_keynav_failed
gtk_widget_list_accel_closures
gtk_widget_list_mnemonic_labels
gtk_widget_map
diff --git a/gtk/gtkcellrendereraccel.c b/gtk/gtkcellrendereraccel.c
index 31b70c3e7e..9157fc2b60 100644
--- a/gtk/gtkcellrendereraccel.c
+++ b/gtk/gtkcellrendereraccel.c
@@ -456,7 +456,7 @@ grab_key_callback (GtkWidget *widget,
{
if (!gtk_accelerator_valid (accel_key, accel_mods))
{
- gdk_display_beep (display);
+ gtk_widget_error_bell (widget);
return TRUE;
}
diff --git a/gtk/gtkcombobox.c b/gtk/gtkcombobox.c
index 1cf287fe0f..c1d2bdd946 100644
--- a/gtk/gtkcombobox.c
+++ b/gtk/gtkcombobox.c
@@ -4978,7 +4978,10 @@ gtk_combo_box_real_move_active (GtkComboBox *combo_box,
gboolean found;
if (!combo_box->priv->model)
- return;
+ {
+ gtk_widget_error_bell (GTK_WIDGET (combo_box));
+ return;
+ }
active_iter = gtk_combo_box_get_active_iter (combo_box, &iter);
@@ -5024,28 +5027,28 @@ gtk_combo_box_real_move_active (GtkComboBox *combo_box,
return;
}
- if (found)
+ if (found && active_iter)
{
- if (active_iter)
- {
- GtkTreePath *old_path;
- GtkTreePath *new_path;
+ GtkTreePath *old_path;
+ GtkTreePath *new_path;
- old_path = gtk_tree_model_get_path (combo_box->priv->model, &iter);
- new_path = gtk_tree_model_get_path (combo_box->priv->model, &new_iter);
+ old_path = gtk_tree_model_get_path (combo_box->priv->model, &iter);
+ new_path = gtk_tree_model_get_path (combo_box->priv->model, &new_iter);
- if (gtk_tree_path_compare (old_path, new_path) == 0)
- found = FALSE;
+ if (gtk_tree_path_compare (old_path, new_path) == 0)
+ found = FALSE;
- gtk_tree_path_free (old_path);
- gtk_tree_path_free (new_path);
- }
+ gtk_tree_path_free (old_path);
+ gtk_tree_path_free (new_path);
+ }
- if (found)
- {
- gtk_combo_box_set_active_iter (combo_box, &new_iter);
- return;
- }
+ if (found)
+ {
+ gtk_combo_box_set_active_iter (combo_box, &new_iter);
+ }
+ else
+ {
+ gtk_widget_error_bell (GTK_WIDGET (combo_box));
}
}
diff --git a/gtk/gtkentry.c b/gtk/gtkentry.c
index 701c4347a4..30f96f1072 100644
--- a/gtk/gtkentry.c
+++ b/gtk/gtkentry.c
@@ -1709,12 +1709,18 @@ gtk_entry_button_press (GtkWidget *widget,
return TRUE;
}
- else if (event->button == 2 && event->type == GDK_BUTTON_PRESS && entry->editable)
+ else if (event->button == 2 && event->type == GDK_BUTTON_PRESS)
{
- priv->insert_pos = tmp_pos;
- gtk_entry_paste (entry, GDK_SELECTION_PRIMARY);
-
- return TRUE;
+ if (entry->editable)
+ {
+ priv->insert_pos = tmp_pos;
+ gtk_entry_paste (entry, GDK_SELECTION_PRIMARY);
+ return TRUE;
+ }
+ else
+ {
+ gtk_widget_error_bell (widget);
+ }
}
else if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
{
@@ -1976,6 +1982,9 @@ gtk_entry_key_press (GtkWidget *widget,
*/
return TRUE;
+ if (!entry->editable && event->length)
+ gtk_widget_error_bell (widget);
+
return FALSE;
}
@@ -2329,7 +2338,7 @@ gtk_entry_real_insert_text (GtkEditable *editable,
n_chars = g_utf8_strlen (new_text, new_text_length);
if (entry->text_max_length > 0 && n_chars + entry->text_length > entry->text_max_length)
{
- gdk_display_beep (gtk_widget_get_display (GTK_WIDGET (entry)));
+ gtk_widget_error_bell (GTK_WIDGET (entry));
n_chars = entry->text_max_length - entry->text_length;
new_text_length = g_utf8_offset_to_pointer (new_text, n_chars) - new_text;
}
@@ -2535,7 +2544,6 @@ gtk_entry_move_cursor (GtkEntry *entry,
new_pos = current_x < bound_x ? entry->current_pos : entry->selection_bound;
else
new_pos = current_x > bound_x ? entry->current_pos : entry->selection_bound;
-
break;
}
case GTK_MOVEMENT_LOGICAL_POSITIONS:
@@ -2566,6 +2574,27 @@ gtk_entry_move_cursor (GtkEntry *entry,
break;
case GTK_MOVEMENT_VISUAL_POSITIONS:
new_pos = gtk_entry_move_visually (entry, new_pos, count);
+ if (entry->current_pos == new_pos)
+ {
+ if (!extend_selection)
+ {
+ if (!gtk_widget_keynav_failed (GTK_WIDGET (entry),
+ count > 0 ?
+ GTK_DIR_RIGHT : GTK_DIR_LEFT))
+ {
+ GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (entry));
+
+ if (toplevel)
+ gtk_widget_child_focus (toplevel,
+ count > 0 ?
+ GTK_DIR_RIGHT : GTK_DIR_LEFT);
+ }
+ }
+ else
+ {
+ gtk_widget_error_bell (GTK_WIDGET (entry));
+ }
+ }
break;
case GTK_MOVEMENT_WORDS:
while (count > 0)
@@ -2578,11 +2607,15 @@ gtk_entry_move_cursor (GtkEntry *entry,
new_pos = gtk_entry_move_backward_word (entry, new_pos, FALSE);
count++;
}
+ if (entry->current_pos == new_pos)
+ gtk_widget_error_bell (GTK_WIDGET (entry));
break;
case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
case GTK_MOVEMENT_PARAGRAPH_ENDS:
case GTK_MOVEMENT_BUFFER_ENDS:
new_pos = count < 0 ? 0 : entry->text_length;
+ if (entry->current_pos == new_pos)
+ gtk_widget_error_bell (GTK_WIDGET (entry));
break;
case GTK_MOVEMENT_DISPLAY_LINES:
case GTK_MOVEMENT_PARAGRAPHS:
@@ -2624,11 +2657,15 @@ gtk_entry_delete_from_cursor (GtkEntry *entry,
GtkEditable *editable = GTK_EDITABLE (entry);
gint start_pos = entry->current_pos;
gint end_pos = entry->current_pos;
+ gint old_n_bytes = entry->n_bytes;
_gtk_entry_reset_im_context (entry);
if (!entry->editable)
- return;
+ {
+ gtk_widget_error_bell (GTK_WIDGET (entry));
+ return;
+ }
if (entry->selection_bound != entry->current_pos)
{
@@ -2685,7 +2722,10 @@ gtk_entry_delete_from_cursor (GtkEntry *entry,
gtk_entry_delete_whitespace (entry);
break;
}
-
+
+ if (entry->n_bytes == old_n_bytes)
+ gtk_widget_error_bell (GTK_WIDGET (entry));
+
gtk_entry_pend_cursor_blink (entry);
}
@@ -2698,7 +2738,10 @@ gtk_entry_backspace (GtkEntry *entry)
_gtk_entry_reset_im_context (entry);
if (!entry->editable || !entry->text)
- return;
+ {
+ gtk_widget_error_bell (GTK_WIDGET (entry));
+ return;
+ }
if (entry->selection_bound != entry->current_pos)
{
@@ -2751,6 +2794,10 @@ gtk_entry_backspace (GtkEntry *entry)
g_free (log_attrs);
}
+ else
+ {
+ gtk_widget_error_bell (GTK_WIDGET (entry));
+ }
gtk_entry_pend_cursor_blink (entry);
}
@@ -2784,6 +2831,10 @@ gtk_entry_cut_clipboard (GtkEntry *entry)
if (gtk_editable_get_selection_bounds (editable, &start, &end))
gtk_editable_delete_text (editable, start, end);
}
+ else
+ {
+ gtk_widget_error_bell (GTK_WIDGET (entry));
+ }
}
static void
@@ -2791,6 +2842,8 @@ gtk_entry_paste_clipboard (GtkEntry *entry)
{
if (entry->editable)
gtk_entry_paste (entry, GDK_NONE);
+ else
+ gtk_widget_error_bell (GTK_WIDGET (entry));
}
static void
diff --git a/gtk/gtkiconview.c b/gtk/gtkiconview.c
index 9e066cf938..1ffcddb236 100644
--- a/gtk/gtkiconview.c
+++ b/gtk/gtkiconview.c
@@ -3796,7 +3796,10 @@ gtk_icon_view_move_cursor_up_down (GtkIconView *icon_view,
}
if (!item)
- return;
+ {
+ gtk_widget_error_bell (GTK_WIDGET (icon_view));
+ return;
+ }
if (icon_view->priv->ctrl_pressed ||
!icon_view->priv->shift_pressed ||
@@ -3847,6 +3850,9 @@ gtk_icon_view_move_cursor_page_up_down (GtkIconView *icon_view,
icon_view->priv->cursor_item,
count);
+ if (item == icon_view->priv->cursor_item)
+ gtk_widget_error_bell (GTK_WIDGET (icon_view));
+
if (!item)
return;
@@ -3915,7 +3921,10 @@ gtk_icon_view_move_cursor_left_right (GtkIconView *icon_view,
}
if (!item)
- return;
+ {
+ gtk_widget_error_bell (GTK_WIDGET (icon_view));
+ return;
+ }
if (icon_view->priv->ctrl_pressed ||
!icon_view->priv->shift_pressed ||
@@ -3958,6 +3967,9 @@ gtk_icon_view_move_cursor_start_end (GtkIconView *icon_view,
item = list ? list->data : NULL;
+ if (item == icon_view->priv->cursor_item)
+ gtk_widget_error_bell (GTK_WIDGET (icon_view));
+
if (!item)
return;
diff --git a/gtk/gtkimcontextsimple.c b/gtk/gtkimcontextsimple.c
index e6267e514e..ea4182bd0a 100644
--- a/gtk/gtkimcontextsimple.c
+++ b/gtk/gtkimcontextsimple.c
@@ -23,6 +23,8 @@
#include <gdk/gdkkeysyms.h>
#include "gtkaccelgroup.h"
#include "gtkimcontextsimple.h"
+#include "gtksettings.h"
+#include "gtkwidget.h"
#include "gtkintl.h"
#include "gtkalias.h"
@@ -1175,6 +1177,31 @@ check_hex (GtkIMContextSimple *context_simple,
return TRUE;
}
+static void
+beep_window (GdkWindow *window)
+{
+ GtkWidget *widget;
+
+ gdk_window_get_user_data (window, &widget);
+
+ if (GTK_IS_WIDGET (widget))
+ {
+ gtk_widget_error_bell (widget);
+ }
+ else
+ {
+ GdkScreen *screen = gdk_drawable_get_screen (GDK_DRAWABLE (window));
+ gboolean beep;
+
+ g_object_get (gtk_settings_get_for_screen (screen),
+ "gtk-error-bell", &beep,
+ NULL);
+
+ if (beep)
+ gdk_window_beep (window);
+ }
+}
+
static gboolean
no_sequence_matches (GtkIMContextSimple *context_simple,
gint n_compose,
@@ -1212,7 +1239,7 @@ no_sequence_matches (GtkIMContextSimple *context_simple,
context_simple->compose_buffer[0] = 0;
if (n_compose > 1) /* Invalid sequence */
{
- gdk_display_beep (gdk_drawable_get_display (event->window));
+ beep_window (event->window);
return TRUE;
}
@@ -1317,7 +1344,7 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
else
{
/* invalid hex sequence */
- gdk_display_beep (gdk_drawable_get_display (event->window));
+ beep_window (event->window);
context_simple->tentative_match = 0;
context_simple->in_hex_sequence = FALSE;
@@ -1403,7 +1430,7 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
{
/* invalid hex sequence */
if (n_compose > 0)
- gdk_display_beep (gdk_drawable_get_display (event->window));
+ beep_window (event->window);
context_simple->tentative_match = 0;
context_simple->in_hex_sequence = FALSE;
@@ -1438,7 +1465,7 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
else if (!is_hex_end)
{
/* non-hex character in hex sequence */
- gdk_display_beep (gdk_drawable_get_display (event->window));
+ beep_window (event->window);
return TRUE;
}
@@ -1465,7 +1492,7 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
else
{
/* invalid hex sequence */
- gdk_display_beep (gdk_drawable_get_display (event->window));
+ beep_window (event->window);
context_simple->tentative_match = 0;
context_simple->in_hex_sequence = FALSE;
@@ -1473,7 +1500,7 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
}
}
else if (!check_hex (context_simple, n_compose))
- gdk_display_beep (gdk_drawable_get_display (event->window));
+ beep_window (event->window);
g_signal_emit_by_name (context_simple, "preedit_changed");
diff --git a/gtk/gtklabel.c b/gtk/gtklabel.c
index 409bc18e53..cfe21cef71 100644
--- a/gtk/gtklabel.c
+++ b/gtk/gtklabel.c
@@ -891,8 +891,8 @@ gtk_label_mnemonic_activate (GtkWidget *widget,
/* barf if there was nothing to activate */
g_warning ("Couldn't find a target for a mnemonic activation.");
- gdk_display_beep (gtk_widget_get_display (widget));
-
+ gtk_widget_error_bell (widget);
+
return FALSE;
}
@@ -3871,12 +3871,13 @@ gtk_label_move_cursor (GtkLabel *label,
gint count,
gboolean extend_selection)
{
+ gint old_pos;
gint new_pos;
if (label->select_info == NULL)
return;
-
- new_pos = label->select_info->selection_end;
+
+ old_pos = new_pos = label->select_info->selection_end;
if (label->select_info->selection_end != label->select_info->selection_anchor &&
!extend_selection)
@@ -3901,7 +3902,6 @@ gtk_label_move_cursor (GtkLabel *label,
new_pos = end_is_left ? label->select_info->selection_end : label->select_info->selection_anchor;
else
new_pos = !end_is_left ? label->select_info->selection_end : label->select_info->selection_anchor;
-
break;
}
case GTK_MOVEMENT_LOGICAL_POSITIONS:
@@ -3933,6 +3933,27 @@ gtk_label_move_cursor (GtkLabel *label,
break;
case GTK_MOVEMENT_VISUAL_POSITIONS:
new_pos = gtk_label_move_visually (label, new_pos, count);
+ if (new_pos == old_pos)
+ {
+ if (!extend_selection)
+ {
+ if (!gtk_widget_keynav_failed (GTK_WIDGET (label),
+ count > 0 ?
+ GTK_DIR_RIGHT : GTK_DIR_LEFT))
+ {
+ GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (label));
+
+ if (toplevel)
+ gtk_widget_child_focus (toplevel,
+ count > 0 ?
+ GTK_DIR_RIGHT : GTK_DIR_LEFT);
+ }
+ }
+ else
+ {
+ gtk_widget_error_bell (GTK_WIDGET (label));
+ }
+ }
break;
case GTK_MOVEMENT_WORDS:
while (count > 0)
@@ -3945,12 +3966,16 @@ gtk_label_move_cursor (GtkLabel *label,
new_pos = gtk_label_move_backward_word (label, new_pos);
count++;
}
+ if (new_pos == old_pos)
+ gtk_widget_error_bell (GTK_WIDGET (label));
break;
case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
case GTK_MOVEMENT_PARAGRAPH_ENDS:
case GTK_MOVEMENT_BUFFER_ENDS:
/* FIXME: Can do better here */
new_pos = count < 0 ? 0 : strlen (label->text);
+ if (new_pos == old_pos)
+ gtk_widget_error_bell (GTK_WIDGET (label));
break;
case GTK_MOVEMENT_DISPLAY_LINES:
case GTK_MOVEMENT_PARAGRAPHS:
diff --git a/gtk/gtkmenu.c b/gtk/gtkmenu.c
index b44e756e99..be89918fce 100644
--- a/gtk/gtkmenu.c
+++ b/gtk/gtkmenu.c
@@ -2756,7 +2756,7 @@ gtk_menu_key_press (GtkWidget *widget,
* (basically, those items are accelerator-locked).
*/
/* g_print("item has no path or is locked, menu prefix: %s\n", menu->accel_path); */
- gdk_display_beep (display);
+ gtk_widget_error_bell (widget);
}
else
{
@@ -2785,7 +2785,7 @@ gtk_menu_key_press (GtkWidget *widget,
* locked already
*/
/* g_print("failed to change\n"); */
- gdk_display_beep (display);
+ gtk_widget_error_bell (widget);
}
}
}
diff --git a/gtk/gtkmenushell.c b/gtk/gtkmenushell.c
index cb43ce1927..153620af39 100644
--- a/gtk/gtkmenushell.c
+++ b/gtk/gtkmenushell.c
@@ -1044,17 +1044,27 @@ gtk_menu_shell_move_selected (GtkMenuShell *menu_shell,
GList *node = g_list_find (menu_shell->children,
menu_shell->active_menu_item);
GList *start_node = node;
-
+ gboolean wrap_around;
+
+ g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu_shell)),
+ "gtk-keynav-wrap-around", &wrap_around,
+ NULL);
+
if (distance > 0)
{
node = node->next;
while (node != start_node &&
(!node || !_gtk_menu_item_is_selectable (node->data)))
{
- if (!node)
- node = menu_shell->children;
- else
+ if (node)
node = node->next;
+ else if (wrap_around)
+ node = menu_shell->children;
+ else
+ {
+ gtk_widget_error_bell (GTK_WIDGET (menu_shell));
+ break;
+ }
}
}
else
@@ -1063,10 +1073,15 @@ gtk_menu_shell_move_selected (GtkMenuShell *menu_shell,
while (node != start_node &&
(!node || !_gtk_menu_item_is_selectable (node->data)))
{
- if (!node)
- node = g_list_last (menu_shell->children);
- else
+ if (node)
node = node->prev;
+ else if (wrap_around)
+ node = g_list_last (menu_shell->children);
+ else
+ {
+ gtk_widget_error_bell (GTK_WIDGET (menu_shell));
+ break;
+ }
}
}
diff --git a/gtk/gtknotebook.c b/gtk/gtknotebook.c
index cd52f6fb28..19a584c618 100644
--- a/gtk/gtknotebook.c
+++ b/gtk/gtknotebook.c
@@ -1066,14 +1066,33 @@ gtk_notebook_change_current_page (GtkNotebook *notebook,
while (offset != 0)
{
- current = gtk_notebook_search_page (notebook, current, offset < 0 ? STEP_PREV : STEP_NEXT, TRUE);
+ current = gtk_notebook_search_page (notebook, current,
+ offset < 0 ? STEP_PREV : STEP_NEXT,
+ TRUE);
+
+ if (!current)
+ {
+ gboolean wrap_around;
+
+ g_object_get (gtk_widget_get_settings (GTK_WIDGET (notebook)),
+ "gtk-keynav-wrap-around", &wrap_around,
+ NULL);
+
+ if (wrap_around)
+ current = gtk_notebook_search_page (notebook, NULL,
+ offset < 0 ? STEP_PREV : STEP_NEXT,
+ TRUE);
+ else
+ break;
+ }
+
offset += offset < 0 ? 1 : -1;
}
if (current)
gtk_notebook_switch_page (notebook, current->data, -1);
else
- gdk_display_beep (gtk_widget_get_display (GTK_WIDGET (notebook)));
+ gtk_widget_error_bell (GTK_WIDGET (notebook));
}
static GtkDirectionType
@@ -3589,11 +3608,24 @@ focus_tabs_move (GtkNotebook *notebook,
new_page = gtk_notebook_search_page (notebook, notebook->focus_tab,
search_direction, TRUE);
+ if (!new_page)
+ {
+ gboolean wrap_around;
+
+ g_object_get (gtk_widget_get_settings (GTK_WIDGET (notebook)),
+ "gtk-keynav-wrap-around", &wrap_around,
+ NULL);
+
+ if (wrap_around)
+ new_page = gtk_notebook_search_page (notebook, NULL,
+ search_direction, TRUE);
+ }
+
if (new_page)
gtk_notebook_switch_focus_tab (notebook, new_page);
else
- gdk_display_beep (gtk_widget_get_display (GTK_WIDGET (notebook)));
-
+ gtk_widget_error_bell (GTK_WIDGET (notebook));
+
return TRUE;
}
diff --git a/gtk/gtkradiobutton.c b/gtk/gtkradiobutton.c
index d1d7f4fe93..91bedc3f81 100644
--- a/gtk/gtkradiobutton.c
+++ b/gtk/gtkradiobutton.c
@@ -491,6 +491,12 @@ gtk_radio_button_focus (GtkWidget *widget,
if (!new_focus)
{
+ if (!gtk_widget_keynav_failed (widget, direction))
+ {
+ g_slist_free (focus_list);
+ return FALSE;
+ }
+
tmp_list = focus_list;
while (tmp_list)
@@ -511,8 +517,17 @@ gtk_radio_button_focus (GtkWidget *widget,
if (new_focus)
{
+ GtkSettings *settings = gtk_widget_get_settings (widget);
+ gboolean cursor_only;
+
+ g_object_get (settings,
+ "gtk-keynav-cursor-only", &cursor_only,
+ NULL);
+
gtk_widget_grab_focus (new_focus);
- gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (new_focus), TRUE);
+
+ if (!cursor_only)
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (new_focus), TRUE);
}
return TRUE;
diff --git a/gtk/gtkrange.c b/gtk/gtkrange.c
index 216f9049c8..c6c6e56dda 100644
--- a/gtk/gtkrange.c
+++ b/gtk/gtkrange.c
@@ -2457,6 +2457,42 @@ static void
gtk_range_move_slider (GtkRange *range,
GtkScrollType scroll)
{
+ gboolean cursor_only;
+
+ g_object_get (gtk_widget_get_settings (GTK_WIDGET (range)),
+ "gtk-keynav-cursor-only", &cursor_only,
+ NULL);
+
+ if (cursor_only)
+ {
+ GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (range));
+
+ if (range->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ if (scroll == GTK_SCROLL_STEP_UP ||
+ scroll == GTK_SCROLL_STEP_DOWN)
+ {
+ if (toplevel)
+ gtk_widget_child_focus (toplevel,
+ scroll == GTK_SCROLL_STEP_UP ?
+ GTK_DIR_UP : GTK_DIR_DOWN);
+ return;
+ }
+ }
+ else
+ {
+ if (scroll == GTK_SCROLL_STEP_LEFT ||
+ scroll == GTK_SCROLL_STEP_RIGHT)
+ {
+ if (toplevel)
+ gtk_widget_child_focus (toplevel,
+ scroll == GTK_SCROLL_STEP_LEFT ?
+ GTK_DIR_LEFT : GTK_DIR_RIGHT);
+ return;
+ }
+ }
+ }
+
gtk_range_scroll (range, scroll);
/* Policy DELAYED makes sense with key events,
diff --git a/gtk/gtksettings.c b/gtk/gtksettings.c
index 0054a2fcb4..cd3eda5900 100644
--- a/gtk/gtksettings.c
+++ b/gtk/gtksettings.c
@@ -93,6 +93,9 @@ enum {
PROP_COLOR_SCHEME,
PROP_ENABLE_ANIMATIONS,
PROP_TOUCHSCREEN_MODE,
+ PROP_KEYNAV_CURSOR_ONLY,
+ PROP_KEYNAV_WRAP_AROUND,
+ PROP_ERROR_BELL,
PROP_COLOR_HASH
};
@@ -507,7 +510,7 @@ gtk_settings_class_init (GtkSettingsClass *class)
/**
* GtkSettings:gtk-touchscreen-mode:
*
- * When TRUE, there are no motion notify events delivered on this screen,
+ * When %TRUE, there are no motion notify events delivered on this screen,
* and widgets can't use the pointer hovering them for any essential
* functionality.
*
@@ -524,6 +527,64 @@ gtk_settings_class_init (GtkSettingsClass *class)
g_assert (result == PROP_TOUCHSCREEN_MODE);
/**
+ * GtkSettings:gtk-keynav-cursor-only:
+ *
+ * When %TRUE, keyboard navigation should be able to reach all widgets
+ * by using the cursor keys only. Tab, Shift etc. keys can't be expected
+ * to be present on the used input device.
+ *
+ * Since: 2.12
+ */
+ result = settings_install_property_parser (class,
+ g_param_spec_boolean ("gtk-keynav-cursor-only",
+ P_("Keynav Cursor Only"),
+ P_("When TRUE, there are only cursor keys available to navigate widgets"),
+ FALSE,
+ GTK_PARAM_READWRITE),
+ NULL);
+
+ g_assert (result == PROP_KEYNAV_CURSOR_ONLY);
+
+ /**
+ * GtkSettings:gtk-keynav-wrap-around:
+ *
+ * When %TRUE, some widgets will wrap around when doing keyboard
+ * navigation, such as menus, menubars and notebooks.
+ *
+ * Since: 2.12
+ */
+ result = settings_install_property_parser (class,
+ g_param_spec_boolean ("gtk-keynav-wrap-around",
+ P_("Keynav Wrap Around"),
+ P_("Whether to wrap around when keyboard-navigating widgets"),
+ TRUE,
+ GTK_PARAM_READWRITE),
+ NULL);
+
+ g_assert (result == PROP_KEYNAV_WRAP_AROUND);
+
+ /**
+ * GtkSettings:gtk-error-bell:
+ *
+ * When %TRUE, keyboard navigation and other input-related errors
+ * will cause a beep. Since the error bell is implemented using
+ * gdk_window_beep(), the windowing system may offer ways to
+ * configure the error bell in many ways, such as flashing the
+ * window or similar visual effects.
+ *
+ * Since: 2.12
+ */
+ result = settings_install_property_parser (class,
+ g_param_spec_boolean ("gtk-error-bell",
+ P_("Error Bell"),
+ P_("When TRUE, keyboard navigation and other errors will cause a beep"),
+ TRUE,
+ GTK_PARAM_READWRITE),
+ NULL);
+
+ g_assert (result == PROP_ERROR_BELL);
+
+ /**
* GtkSettings:color-hash:
*
* Holds a hash table representation of the gtk-color-scheme setting,
diff --git a/gtk/gtkspinbutton.c b/gtk/gtkspinbutton.c
index 7c76829cb6..bcd360bbc4 100644
--- a/gtk/gtkspinbutton.c
+++ b/gtk/gtkspinbutton.c
@@ -1243,6 +1243,8 @@ static void
gtk_spin_button_real_change_value (GtkSpinButton *spin,
GtkScrollType scroll)
{
+ gdouble old_value = spin->adjustment->value;
+
/* We don't test whether the entry is editable, since
* this key binding conceptually corresponds to changing
* the value with the buttons using the mouse, which
@@ -1320,6 +1322,9 @@ gtk_spin_button_real_change_value (GtkSpinButton *spin,
}
gtk_spin_button_update (spin);
+
+ if (spin->adjustment->value == old_value)
+ gtk_widget_error_bell (GTK_WIDGET (spin));
}
static gint
diff --git a/gtk/gtktextview.c b/gtk/gtktextview.c
index 69eb696222..d3e5b23711 100644
--- a/gtk/gtktextview.c
+++ b/gtk/gtktextview.c
@@ -260,10 +260,10 @@ static void gtk_text_view_move_viewport (GtkTextView *text_view,
GtkScrollStep step,
gint count);
static void gtk_text_view_set_anchor (GtkTextView *text_view);
-static void gtk_text_view_scroll_pages (GtkTextView *text_view,
+static gboolean gtk_text_view_scroll_pages (GtkTextView *text_view,
gint count,
gboolean extend_selection);
-static void gtk_text_view_scroll_hpages (GtkTextView *text_view,
+static gboolean gtk_text_view_scroll_hpages(GtkTextView *text_view,
gint count,
gboolean extend_selection);
static void gtk_text_view_insert_at_cursor (GtkTextView *text_view,
@@ -3922,6 +3922,9 @@ gtk_text_view_key_press_event (GtkWidget *widget, GdkEventKey *event)
gtk_text_view_reset_blink_time (text_view);
gtk_text_view_pend_cursor_blink (text_view);
+ if (!retval && event->length)
+ gtk_widget_error_bell (widget);
+
return retval;
}
@@ -4688,8 +4691,8 @@ gtk_text_view_move_cursor_internal (GtkTextView *text_view,
{
GtkTextIter insert;
GtkTextIter newplace;
-
gint cursor_x_pos = 0;
+ GtkDirectionType leave_direction = -1;
if (!text_view->cursor_visible)
{
@@ -4733,14 +4736,18 @@ gtk_text_view_move_cursor_internal (GtkTextView *text_view,
if (step == GTK_MOVEMENT_PAGES)
{
- gtk_text_view_scroll_pages (text_view, count, extend_selection);
+ if (!gtk_text_view_scroll_pages (text_view, count, extend_selection))
+ gtk_widget_error_bell (GTK_WIDGET (text_view));
+
gtk_text_view_check_cursor_blink (text_view);
gtk_text_view_pend_cursor_blink (text_view);
return;
}
else if (step == GTK_MOVEMENT_HORIZONTAL_PAGES)
{
- gtk_text_view_scroll_hpages (text_view, count, extend_selection);
+ if (!gtk_text_view_scroll_hpages (text_view, count, extend_selection))
+ gtk_widget_error_bell (GTK_WIDGET (text_view));
+
gtk_text_view_check_cursor_blink (text_view);
gtk_text_view_pend_cursor_blink (text_view);
return;
@@ -4777,19 +4784,23 @@ gtk_text_view_move_cursor_internal (GtkTextView *text_view,
case GTK_MOVEMENT_DISPLAY_LINES:
if (count < 0)
- {
- if (gtk_text_view_move_iter_by_lines (text_view, &newplace, count))
- gtk_text_layout_move_iter_to_x (text_view->layout, &newplace, cursor_x_pos);
- else
- gtk_text_iter_set_line_offset (&newplace, 0);
- }
+ {
+ leave_direction = GTK_DIR_UP;
+
+ if (gtk_text_view_move_iter_by_lines (text_view, &newplace, count))
+ gtk_text_layout_move_iter_to_x (text_view->layout, &newplace, cursor_x_pos);
+ else
+ gtk_text_iter_set_line_offset (&newplace, 0);
+ }
if (count > 0)
- {
- if (gtk_text_view_move_iter_by_lines (text_view, &newplace, count))
- gtk_text_layout_move_iter_to_x (text_view->layout, &newplace, cursor_x_pos);
- else
- gtk_text_iter_forward_to_line_end (&newplace);
- }
+ {
+ leave_direction = GTK_DIR_DOWN;
+
+ if (gtk_text_view_move_iter_by_lines (text_view, &newplace, count))
+ gtk_text_layout_move_iter_to_x (text_view->layout, &newplace, cursor_x_pos);
+ else
+ gtk_text_iter_forward_to_line_end (&newplace);
+ }
break;
case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
@@ -4860,6 +4871,18 @@ gtk_text_view_move_cursor_internal (GtkTextView *text_view,
if (step == GTK_MOVEMENT_DISPLAY_LINES)
gtk_text_view_set_virtual_cursor_pos (text_view, cursor_x_pos, -1);
}
+ else if (leave_direction != -1)
+ {
+ if (!gtk_widget_keynav_failed (GTK_WIDGET (text_view),
+ leave_direction))
+ {
+ gtk_text_view_move_focus (text_view, leave_direction);
+ }
+ }
+ else
+ {
+ gtk_widget_error_bell (GTK_WIDGET (text_view));
+ }
gtk_text_view_check_cursor_blink (text_view);
gtk_text_view_pend_cursor_blink (text_view);
@@ -4943,7 +4966,7 @@ gtk_text_view_set_anchor (GtkTextView *text_view)
gtk_text_buffer_create_mark (get_buffer (text_view), "anchor", &insert, TRUE);
}
-static void
+static gboolean
gtk_text_view_scroll_pages (GtkTextView *text_view,
gint count,
gboolean extend_selection)
@@ -4952,11 +4975,12 @@ gtk_text_view_scroll_pages (GtkTextView *text_view,
gdouble oldval;
GtkAdjustment *adj;
gint cursor_x_pos, cursor_y_pos;
+ GtkTextIter old_insert;
GtkTextIter new_insert;
GtkTextIter anchor;
gint y0, y1;
- g_return_if_fail (text_view->vadjustment != NULL);
+ g_return_val_if_fail (text_view->vadjustment != NULL, FALSE);
adj = text_view->vadjustment;
@@ -4970,9 +4994,13 @@ gtk_text_view_scroll_pages (GtkTextView *text_view,
gtk_text_view_scroll_mark_onscreen (text_view,
gtk_text_buffer_get_mark (get_buffer (text_view),
"insert"));
-
-/* Validate the region that will be brought into view by the cursor motion
+
+ /* Validate the region that will be brought into view by the cursor motion
*/
+ gtk_text_buffer_get_iter_at_mark (get_buffer (text_view),
+ &old_insert,
+ gtk_text_buffer_get_mark (get_buffer (text_view), "insert"));
+
if (count < 0)
{
gtk_text_view_get_first_para_iter (text_view, &anchor);
@@ -4989,6 +5017,8 @@ gtk_text_view_scroll_pages (GtkTextView *text_view,
gtk_text_layout_validate_yrange (text_view->layout, &anchor, y0, y1);
/* FIXME do we need to update the adjustment ranges here? */
+ new_insert = old_insert;
+
if (count < 0 && adj->value <= (adj->lower + 1e-12))
{
/* already at top, just be sure we are at offset 0 */
@@ -5005,9 +5035,9 @@ gtk_text_view_scroll_pages (GtkTextView *text_view,
{
gtk_text_view_get_virtual_cursor_pos (text_view, &cursor_x_pos, &cursor_y_pos);
- newval = adj->value;
oldval = adj->value;
-
+ newval = adj->value;
+
newval += count * adj->page_increment;
set_adjustment_clamped (adj, newval);
@@ -5027,9 +5057,11 @@ gtk_text_view_scroll_pages (GtkTextView *text_view,
gtk_text_view_scroll_mark_onscreen (text_view,
gtk_text_buffer_get_mark (get_buffer (text_view),
"insert"));
+
+ return !gtk_text_iter_equal (&old_insert, &new_insert);
}
-static void
+static gboolean
gtk_text_view_scroll_hpages (GtkTextView *text_view,
gint count,
gboolean extend_selection)
@@ -5038,10 +5070,11 @@ gtk_text_view_scroll_hpages (GtkTextView *text_view,
gdouble oldval;
GtkAdjustment *adj;
gint cursor_x_pos, cursor_y_pos;
+ GtkTextIter old_insert;
GtkTextIter new_insert;
gint y, height;
- g_return_if_fail (text_view->hadjustment != NULL);
+ g_return_val_if_fail (text_view->hadjustment != NULL, FALSE);
adj = text_view->hadjustment;
@@ -5055,16 +5088,18 @@ gtk_text_view_scroll_hpages (GtkTextView *text_view,
gtk_text_view_scroll_mark_onscreen (text_view,
gtk_text_buffer_get_mark (get_buffer (text_view),
"insert"));
-
+
/* Validate the line that we're moving within.
*/
gtk_text_buffer_get_iter_at_mark (get_buffer (text_view),
- &new_insert,
+ &old_insert,
gtk_text_buffer_get_mark (get_buffer (text_view), "insert"));
gtk_text_layout_get_line_yrange (text_view->layout, &new_insert, &y, &height);
gtk_text_layout_validate_yrange (text_view->layout, &new_insert, y, y + height);
/* FIXME do we need to update the adjustment ranges here? */
-
+
+ new_insert = old_insert;
+
if (count < 0 && adj->value <= (adj->lower + 1e-12))
{
/* already at far left, just be sure we are at offset 0 */
@@ -5082,9 +5117,9 @@ gtk_text_view_scroll_hpages (GtkTextView *text_view,
{
gtk_text_view_get_virtual_cursor_pos (text_view, &cursor_x_pos, &cursor_y_pos);
- newval = adj->value;
oldval = adj->value;
-
+ newval = adj->value;
+
newval += count * adj->page_increment;
set_adjustment_clamped (adj, newval);
@@ -5110,6 +5145,8 @@ gtk_text_view_scroll_hpages (GtkTextView *text_view,
gtk_text_view_scroll_mark_onscreen (text_view,
gtk_text_buffer_get_mark (get_buffer (text_view),
"insert"));
+
+ return !gtk_text_iter_equal (&old_insert, &new_insert);
}
static gboolean
@@ -5143,8 +5180,11 @@ static void
gtk_text_view_insert_at_cursor (GtkTextView *text_view,
const gchar *str)
{
- gtk_text_buffer_insert_interactive_at_cursor (get_buffer (text_view), str, -1,
- text_view->editable);
+ if (!gtk_text_buffer_insert_interactive_at_cursor (get_buffer (text_view), str, -1,
+ text_view->editable))
+ {
+ gtk_widget_error_bell (GTK_WIDGET (text_view));
+ }
}
static void
@@ -5261,6 +5301,10 @@ gtk_text_view_delete_from_cursor (GtkTextView *text_view,
" ", 1,
text_view->editable);
}
+ else
+ {
+ gtk_widget_error_bell (GTK_WIDGET (text_view));
+ }
gtk_text_buffer_end_user_action (get_buffer (text_view));
gtk_text_view_set_virtual_cursor_pos (text_view, -1, -1);
@@ -5269,6 +5313,10 @@ gtk_text_view_delete_from_cursor (GtkTextView *text_view,
gtk_text_view_scroll_mark_onscreen (text_view,
gtk_text_buffer_get_mark (get_buffer (text_view), "insert"));
}
+ else
+ {
+ gtk_widget_error_bell (GTK_WIDGET (text_view));
+ }
}
static void
@@ -5295,6 +5343,10 @@ gtk_text_view_backspace (GtkTextView *text_view)
gtk_text_view_scroll_mark_onscreen (text_view,
gtk_text_buffer_get_insert (get_buffer (text_view)));
}
+ else
+ {
+ gtk_widget_error_bell (GTK_WIDGET (text_view));
+ }
}
static void
@@ -6337,9 +6389,13 @@ insert_text_data (GtkTextView *text_view,
if (str)
{
- gtk_text_buffer_insert_interactive (get_buffer (text_view),
- drop_point, (gchar *) str, -1,
- text_view->editable);
+ if (!gtk_text_buffer_insert_interactive (get_buffer (text_view),
+ drop_point, (gchar *) str, -1,
+ text_view->editable))
+ {
+ gtk_widget_error_bell (GTK_WIDGET (text_view));
+ }
+
g_free (str);
}
}
@@ -6804,8 +6860,11 @@ gtk_text_view_commit_text (GtkTextView *text_view,
if (!strcmp (str, "\n"))
{
- gtk_text_buffer_insert_interactive_at_cursor (get_buffer (text_view), "\n", 1,
- text_view->editable);
+ if (!gtk_text_buffer_insert_interactive_at_cursor (get_buffer (text_view), "\n", 1,
+ text_view->editable))
+ {
+ gtk_widget_error_bell (GTK_WIDGET (text_view));
+ }
}
else
{
@@ -6820,8 +6879,12 @@ gtk_text_view_commit_text (GtkTextView *text_view,
if (!gtk_text_iter_ends_line (&insert))
gtk_text_view_delete_from_cursor (text_view, GTK_DELETE_CHARS, 1);
}
- gtk_text_buffer_insert_interactive_at_cursor (get_buffer (text_view), str, -1,
- text_view->editable);
+
+ if (!gtk_text_buffer_insert_interactive_at_cursor (get_buffer (text_view), str, -1,
+ text_view->editable))
+ {
+ gtk_widget_error_bell (GTK_WIDGET (text_view));
+ }
}
gtk_text_buffer_end_user_action (get_buffer (text_view));
diff --git a/gtk/gtktreeview.c b/gtk/gtktreeview.c
index 2f6a56e405..e8190d1669 100644
--- a/gtk/gtktreeview.c
+++ b/gtk/gtktreeview.c
@@ -413,7 +413,7 @@ static gboolean gtk_tree_view_search_scroll_event (GtkWidget *entry
static gboolean gtk_tree_view_search_key_press_event (GtkWidget *entry,
GdkEventKey *event,
GtkTreeView *tree_view);
-static void gtk_tree_view_search_move (GtkWidget *window,
+static gboolean gtk_tree_view_search_move (GtkWidget *window,
GtkTreeView *tree_view,
gboolean up);
static gboolean gtk_tree_view_search_equal_func (GtkTreeModel *model,
@@ -5115,11 +5115,16 @@ gtk_tree_view_key_press (GtkWidget *widget,
GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN (focus_column->data);
if (!column->resizable)
- return TRUE;
+ {
+ gtk_widget_error_bell (widget);
+ return TRUE;
+ }
if (event->keyval == (rtl ? GDK_Right : GDK_Left)
|| event->keyval == (rtl ? GDK_KP_Right : GDK_KP_Left))
{
+ gint old_width = column->resized_width;
+
column->resized_width = MAX (column->resized_width,
column->width);
column->resized_width -= 2;
@@ -5138,11 +5143,17 @@ gtk_tree_view_key_press (GtkWidget *widget,
column->max_width);
column->use_resized_width = TRUE;
- gtk_widget_queue_resize (widget);
+
+ if (column->resized_width != old_width)
+ gtk_widget_queue_resize (widget);
+ else
+ gtk_widget_error_bell (widget);
}
else if (event->keyval == (rtl ? GDK_Left : GDK_Right)
|| event->keyval == (rtl ? GDK_KP_Left : GDK_KP_Right))
{
+ gint old_width = column->resized_width;
+
column->resized_width = MAX (column->resized_width,
column->width);
column->resized_width += 2;
@@ -5152,7 +5163,11 @@ gtk_tree_view_key_press (GtkWidget *widget,
column->max_width);
column->use_resized_width = TRUE;
- gtk_widget_queue_resize (widget);
+
+ if (column->resized_width != old_width)
+ gtk_widget_queue_resize (widget);
+ else
+ gtk_widget_error_bell (widget);
}
return TRUE;
@@ -5174,6 +5189,8 @@ gtk_tree_view_key_press (GtkWidget *widget,
col = gtk_tree_view_get_drop_column (tree_view, column, DROP_LEFT);
if (col != (GtkTreeViewColumn *)0x1)
gtk_tree_view_move_column_after (tree_view, column, col);
+ else
+ gtk_widget_error_bell (widget);
}
else if (event->keyval == (rtl ? GDK_Left : GDK_Right)
|| event->keyval == (rtl ? GDK_KP_Left : GDK_KP_Right))
@@ -5182,6 +5199,8 @@ gtk_tree_view_key_press (GtkWidget *widget,
col = gtk_tree_view_get_drop_column (tree_view, column, DROP_RIGHT);
if (col != (GtkTreeViewColumn *)0x1)
gtk_tree_view_move_column_after (tree_view, column, col);
+ else
+ gtk_widget_error_bell (widget);
}
else if (event->keyval == GDK_Home || event->keyval == GDK_KP_Home)
{
@@ -5189,6 +5208,8 @@ gtk_tree_view_key_press (GtkWidget *widget,
col = gtk_tree_view_get_drop_column (tree_view, column, DROP_HOME);
if (col != (GtkTreeViewColumn *)0x1)
gtk_tree_view_move_column_after (tree_view, column, col);
+ else
+ gtk_widget_error_bell (widget);
}
else if (event->keyval == GDK_End || event->keyval == GDK_KP_End)
{
@@ -5196,6 +5217,8 @@ gtk_tree_view_key_press (GtkWidget *widget,
col = gtk_tree_view_get_drop_column (tree_view, column, DROP_END);
if (col != (GtkTreeViewColumn *)0x1)
gtk_tree_view_move_column_after (tree_view, column, col);
+ else
+ gtk_widget_error_bell (widget);
}
return TRUE;
@@ -5206,8 +5229,7 @@ gtk_tree_view_key_press (GtkWidget *widget,
|| event->keyval == GDK_Right || event->keyval == GDK_KP_Right))
{
if ((event->keyval == (rtl ? GDK_Right : GDK_Left)
- || event->keyval == (rtl ? GDK_KP_Right : GDK_KP_Left))
- && focus_column->prev)
+ || event->keyval == (rtl ? GDK_KP_Right : GDK_KP_Left)))
{
GList *tmp;
@@ -5216,7 +5238,10 @@ gtk_tree_view_key_press (GtkWidget *widget,
break;
if (!tmp)
- return FALSE;
+ {
+ gtk_widget_error_bell (widget);
+ return TRUE;
+ }
tree_view->priv->focus_column = GTK_TREE_VIEW_COLUMN (tmp->data);
gtk_widget_grab_focus (tree_view->priv->focus_column->button);
@@ -5227,8 +5252,7 @@ gtk_tree_view_key_press (GtkWidget *widget,
tree_view->priv->hadjustment->upper - tree_view->priv->hadjustment->page_size));
}
else if ((event->keyval == (rtl ? GDK_Left : GDK_Right)
- || event->keyval == (rtl ? GDK_KP_Left : GDK_KP_Right))
- && focus_column->next)
+ || event->keyval == (rtl ? GDK_KP_Left : GDK_KP_Right)))
{
GList *tmp;
@@ -5237,7 +5261,10 @@ gtk_tree_view_key_press (GtkWidget *widget,
break;
if (!tmp)
- return FALSE;
+ {
+ gtk_widget_error_bell (widget);
+ return TRUE;
+ }
tree_view->priv->focus_column = GTK_TREE_VIEW_COLUMN (tmp->data);
@@ -9558,6 +9585,7 @@ gtk_tree_view_move_cursor_up_down (GtkTreeView *tree_view,
else
{
gtk_tree_view_clamp_node_visible (tree_view, cursor_tree, cursor_node);
+ gtk_widget_error_bell (GTK_WIDGET (tree_view));
}
gtk_widget_grab_focus (GTK_WIDGET (tree_view));
@@ -9569,6 +9597,7 @@ gtk_tree_view_move_cursor_page_up_down (GtkTreeView *tree_view,
{
GtkRBTree *cursor_tree = NULL;
GtkRBNode *cursor_node = NULL;
+ GtkTreePath *old_cursor_path = NULL;
GtkTreePath *cursor_path = NULL;
gint y;
gint window_y;
@@ -9578,20 +9607,21 @@ gtk_tree_view_move_cursor_page_up_down (GtkTreeView *tree_view,
return;
if (gtk_tree_row_reference_valid (tree_view->priv->cursor))
- cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
+ old_cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
else
/* This is sorta weird. Focus in should give us a cursor */
return;
gtk_widget_style_get (GTK_WIDGET (tree_view), "vertical-separator", &vertical_separator, NULL);
- _gtk_tree_view_find_node (tree_view, cursor_path,
+ _gtk_tree_view_find_node (tree_view, old_cursor_path,
&cursor_tree, &cursor_node);
- gtk_tree_path_free (cursor_path);
-
if (cursor_tree == NULL)
- /* FIXME: we lost the cursor. Should we try to get one? */
- return;
+ {
+ /* FIXME: we lost the cursor. Should we try to get one? */
+ gtk_tree_path_free (old_cursor_path);
+ return;
+ }
g_return_if_fail (cursor_node != NULL);
y = _gtk_rbtree_node_find_offset (cursor_tree, cursor_node);
@@ -9606,11 +9636,16 @@ gtk_tree_view_move_cursor_page_up_down (GtkTreeView *tree_view,
cursor_path = _gtk_tree_view_find_path (tree_view, cursor_tree, cursor_node);
g_return_if_fail (cursor_path != NULL);
gtk_tree_view_real_set_cursor (tree_view, cursor_path, TRUE, FALSE);
- gtk_tree_path_free (cursor_path);
y -= window_y;
gtk_tree_view_scroll_to_point (tree_view, -1, y);
_gtk_tree_view_queue_draw_node (tree_view, cursor_tree, cursor_node, NULL);
+
+ if (!gtk_tree_path_compare (old_cursor_path, cursor_path))
+ gtk_widget_error_bell (GTK_WIDGET (tree_view));
+
+ gtk_tree_path_free (old_cursor_path);
+ gtk_tree_path_free (cursor_path);
}
static void
@@ -9703,6 +9738,11 @@ gtk_tree_view_move_cursor_left_right (GtkTreeView *tree_view,
NULL);
g_signal_emit (tree_view, tree_view_signals[CURSOR_CHANGED], 0);
}
+ else
+ {
+ gtk_widget_error_bell (GTK_WIDGET (tree_view));
+ }
+
gtk_tree_view_clamp_column_visible (tree_view, tree_view->priv->focus_column);
}
@@ -9713,23 +9753,25 @@ gtk_tree_view_move_cursor_start_end (GtkTreeView *tree_view,
GtkRBTree *cursor_tree;
GtkRBNode *cursor_node;
GtkTreePath *path;
+ GtkTreePath *old_path;
if (! GTK_WIDGET_HAS_FOCUS (tree_view))
return;
g_return_if_fail (tree_view->priv->tree != NULL);
+ gtk_tree_view_get_cursor (tree_view, &old_path, NULL);
+
+ cursor_tree = tree_view->priv->tree;
+ cursor_node = cursor_tree->root;
+
if (count == -1)
{
- cursor_tree = tree_view->priv->tree;
- cursor_node = cursor_tree->root;
while (cursor_node && cursor_node->left != cursor_tree->nil)
cursor_node = cursor_node->left;
}
else
{
- cursor_tree = tree_view->priv->tree;
- cursor_node = cursor_tree->root;
do
{
while (cursor_node && cursor_node->right != cursor_tree->nil)
@@ -9744,7 +9786,17 @@ gtk_tree_view_move_cursor_start_end (GtkTreeView *tree_view,
}
path = _gtk_tree_view_find_path (tree_view, cursor_tree, cursor_node);
- gtk_tree_view_real_set_cursor (tree_view, path, TRUE, TRUE);
+
+ if (gtk_tree_path_compare (old_path, path))
+ {
+ gtk_tree_view_real_set_cursor (tree_view, path, TRUE, TRUE);
+ }
+ else
+ {
+ gtk_widget_error_bell (GTK_WIDGET (tree_view));
+ }
+
+ gtk_tree_path_free (old_path);
gtk_tree_path_free (path);
}
@@ -13888,28 +13940,36 @@ gtk_tree_view_search_key_press_event (GtkWidget *widget,
/* select previous matching iter */
if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
{
- gtk_tree_view_search_move (widget, tree_view, TRUE);
+ if (!gtk_tree_view_search_move (widget, tree_view, TRUE))
+ gtk_widget_error_bell (widget);
+
retval = TRUE;
}
if (((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == (GDK_CONTROL_MASK | GDK_SHIFT_MASK))
&& (event->keyval == GDK_g || event->keyval == GDK_G))
{
- gtk_tree_view_search_move (widget, tree_view, TRUE);
+ if (!gtk_tree_view_search_move (widget, tree_view, TRUE))
+ gtk_widget_error_bell (widget);
+
retval = TRUE;
}
/* select next matching iter */
if (event->keyval == GDK_Down || event->keyval == GDK_KP_Down)
{
- gtk_tree_view_search_move (widget, tree_view, FALSE);
+ if (!gtk_tree_view_search_move (widget, tree_view, FALSE))
+ gtk_widget_error_bell (widget);
+
retval = TRUE;
}
if (((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == GDK_CONTROL_MASK)
&& (event->keyval == GDK_g || event->keyval == GDK_G))
{
- gtk_tree_view_search_move (widget, tree_view, FALSE);
+ if (!gtk_tree_view_search_move (widget, tree_view, FALSE))
+ gtk_widget_error_bell (widget);
+
retval = TRUE;
}
@@ -13927,7 +13987,10 @@ gtk_tree_view_search_key_press_event (GtkWidget *widget,
return retval;
}
-static void
+/* this function returns FALSE if there is a search string but
+ * nothing was found, and TRUE otherwise.
+ */
+static gboolean
gtk_tree_view_search_move (GtkWidget *window,
GtkTreeView *tree_view,
gboolean up)
@@ -13942,15 +14005,17 @@ gtk_tree_view_search_move (GtkWidget *window,
text = gtk_entry_get_text (GTK_ENTRY (tree_view->priv->search_entry));
- g_return_if_fail (text != NULL);
+ g_return_val_if_fail (text != NULL, FALSE);
+
+ len = strlen (text);
if (up && tree_view->priv->selected_iter == 1)
- return;
+ return strlen (text) < 1;
len = strlen (text);
if (len < 1)
- return;
+ return TRUE;
model = gtk_tree_view_get_model (tree_view);
selection = gtk_tree_view_get_selection (tree_view);
@@ -13958,7 +14023,7 @@ gtk_tree_view_search_move (GtkWidget *window,
/* search */
gtk_tree_selection_unselect_all (selection);
if (!gtk_tree_model_get_iter_first (model, &iter))
- return;
+ return TRUE;
ret = gtk_tree_view_search_iter (model, selection, &iter, text,
&count, up?((tree_view->priv->selected_iter) - 1):((tree_view->priv->selected_iter + 1)));
@@ -13967,6 +14032,7 @@ gtk_tree_view_search_move (GtkWidget *window,
{
/* found */
tree_view->priv->selected_iter += up?(-1):(1);
+ return TRUE;
}
else
{
@@ -13976,6 +14042,7 @@ gtk_tree_view_search_move (GtkWidget *window,
gtk_tree_view_search_iter (model, selection,
&iter, text,
&count, tree_view->priv->selected_iter);
+ return FALSE;
}
}
diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c
index 392ebf5185..ae16cb8fd6 100644
--- a/gtk/gtkwidget.c
+++ b/gtk/gtkwidget.c
@@ -121,6 +121,7 @@ enum {
CAN_ACTIVATE_ACCEL,
GRAB_BROKEN,
COMPOSITED_CHANGED,
+ KEYNAV_FAILED,
LAST_SIGNAL
};
@@ -204,6 +205,8 @@ static gboolean gtk_widget_real_focus_out_event (GtkWidget *widget,
GdkEventFocus *event);
static gboolean gtk_widget_real_focus (GtkWidget *widget,
GtkDirectionType direction);
+static gboolean gtk_widget_real_keynav_failed (GtkWidget *widget,
+ GtkDirectionType direction);
static PangoContext* gtk_widget_peek_pango_context (GtkWidget *widget);
static void gtk_widget_update_pango_context (GtkWidget *widget);
static void gtk_widget_propagate_state (GtkWidget *widget,
@@ -807,6 +810,29 @@ gtk_widget_class_init (GtkWidgetClass *klass)
_gtk_marshal_VOID__VOID,
G_TYPE_NONE, 0);
+ /**
+ * GtkWidget::keynav-failed:
+ * @widget: the object which received the signal.
+ * @direction: the direction of movement
+ *
+ * See gtk_widget_keynav_failed() for details.
+ *
+ * Returns: %TRUE if stopping keyboard navigation is fine, %FALSE
+ * if the emitting widget should try to handle the keyboard
+ * navigation attempt in its parent container(s).
+ *
+ * Since: 2.12
+ **/
+ widget_signals[KEYNAV_FAILED] =
+ _gtk_binding_signal_new (I_("keynav-failed"),
+ G_TYPE_FROM_CLASS (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ G_CALLBACK (gtk_widget_real_keynav_failed),
+ _gtk_boolean_handled_accumulator, NULL,
+ _gtk_marshal_BOOLEAN__ENUM,
+ G_TYPE_BOOLEAN, 1,
+ GTK_TYPE_DIRECTION_TYPE);
+
/**
* GtkWidget::delete-event:
* @widget: the object which received the signal.
@@ -3642,7 +3668,7 @@ gtk_widget_real_mnemonic_activate (GtkWidget *widget,
{
g_warning ("widget `%s' isn't suitable for mnemonic activation",
G_OBJECT_TYPE_NAME (widget));
- gdk_display_beep (gtk_widget_get_display (widget));
+ gtk_widget_error_bell (widget);
}
return TRUE;
}
@@ -4326,6 +4352,35 @@ gtk_widget_real_focus (GtkWidget *widget,
return FALSE;
}
+static gboolean
+gtk_widget_real_keynav_failed (GtkWidget *widget,
+ GtkDirectionType direction)
+{
+ gboolean cursor_only;
+
+ switch (direction)
+ {
+ case GTK_DIR_TAB_FORWARD:
+ case GTK_DIR_TAB_BACKWARD:
+ return FALSE;
+
+ case GTK_DIR_UP:
+ case GTK_DIR_DOWN:
+ case GTK_DIR_LEFT:
+ case GTK_DIR_RIGHT:
+ g_object_get (gtk_widget_get_settings (widget),
+ "gtk-keynav-cursor-only", &cursor_only,
+ NULL);
+ if (cursor_only)
+ return FALSE;
+ break;
+ }
+
+ gtk_widget_error_bell (widget);
+
+ return TRUE;
+}
+
/**
* gtk_widget_is_focus:
* @widget: a #GtkWidget
@@ -5900,6 +5955,92 @@ gtk_widget_child_focus (GtkWidget *widget,
}
/**
+ * gtk_widget_keynav_failed:
+ * @widget: a #GtkWidget
+ * @direction: direction of focus movement
+ *
+ * This function should be called whenever keyboard navigation within
+ * a single widget hits a boundary. The function emits the
+ * "keynav-changed" signal on the widget and its return value should
+ * be interpreted in a way similar to the return value of
+ * gtk_widget_child_focus():
+ *
+ * When %TRUE is returned, stay in the widget, the failed keyboard
+ * navigation is Ok and/or there is nowhere we can/should move the
+ * focus to.
+ *
+ * When %FALSE is returned, the caller should continue with keyboard
+ * navigation outside the widget, e.g. by calling
+ * gtk_widget_child_focus() on the widget's toplevel.
+ *
+ * The default implementation for the "keynav-failed" signal is to
+ * return %TRUE for %GTK_DIR_TAB_FORWARD and
+ * %GTK_DIR_TAB_BACKWARD. For the other values of #GtkDirectionType,
+ * it looks at the "gtk-keynav-cursor-only" settings property and
+ * returns %FALSE if the setting is %TRUE. This way the entire GUI
+ * becomes cursor-navigatable on input devices such as mobile phones
+ * which only have cursor keys but no tab key.
+ *
+ * Whenever the default implementation returns %TRUE, it also calls
+ * gtk_widget_error_bell() to notify the user of the failed keyboard
+ * navigation.
+ *
+ * A use case for providing an own implementation of keynav-failed (by
+ * either connecting to it or by overriding it) would be a row of
+ * #GtkEntry widgets where the user should be able to navigate the
+ * entire row with the cursor keys, as e.g. known from GUIs that
+ * require entering license keys.
+ *
+ * Return value: %TRUE if stopping keyboard navigation is fine, %FALSE
+ * if the emitting widget should try to handle the keyboard
+ * navigation attempt in its parent container(s).
+ *
+ * Since: 2.12
+ **/
+gboolean
+gtk_widget_keynav_failed (GtkWidget *widget,
+ GtkDirectionType direction)
+{
+ gboolean return_val;
+
+ g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
+
+ g_signal_emit (widget, widget_signals[KEYNAV_FAILED], 0,
+ direction, &return_val);
+
+ return return_val;
+}
+
+/**
+ * gtk_widget_error_bell:
+ * @widget: a #GtkWidget
+ *
+ * Notifies the user about an input-related error on this widget. If
+ * the gtk-error-bell settings property is %TRUE, it calls
+ * gdk_window_beep(), otherwise it does nothing.
+ *
+ * Note that the effect of gdk_window_beep() can be configured in many
+ * ways, depending on the windowing backend and the desktop environment
+ * or window manager that is used.
+ *
+ * Since: 2.12
+ **/
+void
+gtk_widget_error_bell (GtkWidget *widget)
+{
+ gboolean beep;
+
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ g_object_get (gtk_widget_get_settings (widget),
+ "gtk-error-bell", &beep,
+ NULL);
+
+ if (beep && widget->window)
+ gdk_window_beep (widget->window);
+}
+
+/**
* gtk_widget_set_uposition:
* @widget: a #GtkWidget
* @x: x position; -1 to unset x; -2 to leave x unchanged
diff --git a/gtk/gtkwidget.h b/gtk/gtkwidget.h
index 7cfc3df5cd..cb5841fbd3 100644
--- a/gtk/gtkwidget.h
+++ b/gtk/gtkwidget.h
@@ -408,7 +408,7 @@ struct _GtkWidgetClass
GdkEventGrabBroken *event);
void (* composited_changed) (GtkWidget *widget);
-
+
/* Padding for future expansion */
void (*_gtk_reserved4) (void);
void (*_gtk_reserved5) (void);
@@ -563,6 +563,9 @@ GdkWindow *gtk_widget_get_parent_window (GtkWidget *widget);
gboolean gtk_widget_child_focus (GtkWidget *widget,
GtkDirectionType direction);
+gboolean gtk_widget_keynav_failed (GtkWidget *widget,
+ GtkDirectionType direction);
+void gtk_widget_error_bell (GtkWidget *widget);
void gtk_widget_set_size_request (GtkWidget *widget,
gint width,