diff options
-rw-r--r-- | ChangeLog | 51 | ||||
-rw-r--r-- | gtk/gtk.symbols | 2 | ||||
-rw-r--r-- | gtk/gtkcellrendereraccel.c | 2 | ||||
-rw-r--r-- | gtk/gtkcombobox.c | 39 | ||||
-rw-r--r-- | gtk/gtkentry.c | 73 | ||||
-rw-r--r-- | gtk/gtkiconview.c | 16 | ||||
-rw-r--r-- | gtk/gtkimcontextsimple.c | 39 | ||||
-rw-r--r-- | gtk/gtklabel.c | 35 | ||||
-rw-r--r-- | gtk/gtkmenu.c | 4 | ||||
-rw-r--r-- | gtk/gtkmenushell.c | 29 | ||||
-rw-r--r-- | gtk/gtknotebook.c | 40 | ||||
-rw-r--r-- | gtk/gtkradiobutton.c | 17 | ||||
-rw-r--r-- | gtk/gtkrange.c | 36 | ||||
-rw-r--r-- | gtk/gtksettings.c | 63 | ||||
-rw-r--r-- | gtk/gtkspinbutton.c | 5 | ||||
-rw-r--r-- | gtk/gtktextview.c | 141 | ||||
-rw-r--r-- | gtk/gtktreeview.c | 129 | ||||
-rw-r--r-- | gtk/gtkwidget.c | 143 | ||||
-rw-r--r-- | gtk/gtkwidget.h | 5 |
19 files changed, 740 insertions, 129 deletions
@@ -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, |