Scrolled window keyboard navigation
- From: Owen Taylor <otaylor redhat com>
- To: gtk-devel-list gnome org
- Subject: Scrolled window keyboard navigation
- Date: Thu, 7 Feb 2002 23:21:36 -0500 (EST)
The following patch is an attempt to implement the keynav
suggestions for GtkScrolledWin in:
http://bugzilla.gnome.org/show_bug.cgi?id=63480
And
http://developer.gnome.org/projects/gap/keynav/gtk_containers.html
It adds the following keybindings:
Arrow keys: scroll scrolled window
PageUp/PageDown: page up and down
Control-PageUp/PageDown page left and right
Home/End all the way left, all the way right
Control-Home/End all the way up, all the way down
[ Note, Control-Home/End Home/End are reversed from the
keynav table. I think this is more consistent with the
way that text widgets works ]
Control-[Shift]-Tab focus out of the scrolled window
This bindings take effect if the key press is not handled by the
focus widget or any widget between the focus widget and and the
scrolled window.
The patch also makes the scrolled window itself a possible focus
location, but only if no widget inside the scrolled window can
focus.
Concerns:
* The patch currently doesn't draw any focus indication if the
focus window itself is focused. I don't want to reserve the
space for a focus rectangle around the scrolled window since
the scrolled window will be focused so rarely. (Never for most
scrolled windows.) My idea currently is that a good focus
indication might be to draw focus indicator rectangles within
the thumbs of the two scrollbars. This would require some
scrollbar/scrolled-window private API addition.
* If the scrolled window contains multiple focusable widgets in
it, then scrolling the scrolled window wwith these bindings
can result in the focus widget being scrolled out of the
visible area. (This is poor UI design, of course.) The
scrolled window example in testgtk demonstrates this issue
well.
* Since the arrow keys are bound at the scrolled window level,
they don't get up to the toplevel, so directional focusing
with arrow keys within the scrolled window won't work. This
includes things like focusing between the tabs of a notebook,
or (more important here) the headers of a GtkTreeView. Fixing
this to make these arrows not be overriden by the scrolled
window would require using something other than the standard
focus() method to drive motion by arrow keys in these
contexts.
Control-arrows can be used for directional focusing, but I
think it might be better to reverse things and use
control-arrows to scroll the scrolled window and leave the
arrows working normally for widgets within the widget.
(It's also possible that the scrolled windows should
directionally focus out of the entire scrolled window in
analogy to Control-Tab, but that's probably less useful than
the other meanings here.)
Regards,
Owen
Index: ChangeLog
===================================================================
RCS file: /cvs/gnome/gtk+/ChangeLog,v
retrieving revision 1.2964
diff -u -p -r1.2964 ChangeLog
--- ChangeLog 2002/02/08 02:38:41 1.2964
+++ ChangeLog 2002/02/08 03:44:12
@@ -1,3 +1,30 @@
+Thu Feb 7 21:28:48 2002 Owen Taylor <otaylor redhat com>
+
+ * gtk/gtkscrolledwindow.c (gtk_scrolled_window_focus): Focus
+ the widget itself if no child can be focused.
+
+ * gtk/gtkscrolledwindow.[ch]: Add keybindings to scroll the
+ window when focus is on a child, and to focus out of the
+ entire scrolled window.
+
Index: gtk/gtkscrolledwindow.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkscrolledwindow.c,v
retrieving revision 1.54
diff -u -p -r1.54 gtkscrolledwindow.c
--- gtk/gtkscrolledwindow.c 2001/07/19 19:49:01 1.54
+++ gtk/gtkscrolledwindow.c 2002/02/08 03:44:12
@@ -24,6 +24,9 @@
* GTK+ at ftp://ftp.gtk.org/pub/gtk/.
*/
+#include <gdk/gdkkeysyms.h>
+#include "gtkbindings.h"
+#include "gtkmarshalers.h"
#include "gtkscrolledwindow.h"
#include "gtksignal.h"
#include "gtkintl.h"
@@ -79,6 +82,14 @@ enum {
PROP_LAST
};
+/* Signals */
+enum
+{
+ SCROLL_CHILD,
+ MOVE_FOCUS_OUT,
+ LAST_SIGNAL
+};
+
static void gtk_scrolled_window_class_init (GtkScrolledWindowClass *klass);
static void gtk_scrolled_window_init (GtkScrolledWindow *scrolled_window);
static void gtk_scrolled_window_destroy (GtkObject *object);
@@ -100,6 +111,8 @@ static void gtk_scrolled_window_size_all
GtkAllocation *allocation);
static gint gtk_scrolled_window_scroll_event (GtkWidget *widget,
GdkEventScroll *event);
+static gint gtk_scrolled_window_focus (GtkWidget *widget,
+ GtkDirectionType direction);
static void gtk_scrolled_window_add (GtkContainer *container,
GtkWidget *widget);
static void gtk_scrolled_window_remove (GtkContainer *container,
@@ -108,6 +121,12 @@ static void gtk_scrolled_window_forall
gboolean include_internals,
GtkCallback callback,
gpointer callback_data);
+static void gtk_scrolled_window_scroll_child (GtkScrolledWindow *scrolled_window,
+ GtkScrollType scroll,
+ gboolean horizontal);
+static void gtk_scrolled_window_move_focus_out (GtkScrolledWindow *scrolled_window,
+ GtkDirectionType direction_type);
+
static void gtk_scrolled_window_relative_allocation(GtkWidget *widget,
GtkAllocation *allocation);
static void gtk_scrolled_window_adjustment_changed (GtkAdjustment *adjustment,
@@ -115,6 +134,7 @@ static void gtk_scrolled_window_adjustme
static GtkContainerClass *parent_class = NULL;
+static guint signals[LAST_SIGNAL] = {0};
GtkType
gtk_scrolled_window_get_type (void)
@@ -142,12 +162,48 @@ gtk_scrolled_window_get_type (void)
}
static void
+add_scroll_binding (GtkBindingSet *binding_set,
+ guint keyval,
+ GdkModifierType mask,
+ GtkScrollType scroll,
+ gboolean horizontal)
+{
+ guint keypad_keyval = keyval - GDK_Left + GDK_KP_Left;
+
+ gtk_binding_entry_add_signal (binding_set, keyval, mask,
+ "scroll_child", 2,
+ GTK_TYPE_SCROLL_TYPE, scroll,
+ G_TYPE_BOOLEAN, horizontal);
+ gtk_binding_entry_add_signal (binding_set, keypad_keyval, mask,
+ "scroll_child", 2,
+ GTK_TYPE_SCROLL_TYPE, scroll,
+ G_TYPE_BOOLEAN, horizontal);
+}
+
+static void
+add_tab_bindings (GtkBindingSet *binding_set,
+ GdkModifierType modifiers,
+ GtkDirectionType direction)
+{
+ gtk_binding_entry_add_signal (binding_set, GDK_Tab, modifiers,
+ "move_focus_out", 1,
+ GTK_TYPE_DIRECTION_TYPE, direction);
+ gtk_binding_entry_add_signal (binding_set, GDK_KP_Tab, modifiers,
+ "move_focus_out", 1,
+ GTK_TYPE_DIRECTION_TYPE, direction);
+ gtk_binding_entry_add_signal (binding_set, GDK_ISO_Left_Tab, modifiers,
+ "move_focus_out", 1,
+ GTK_TYPE_DIRECTION_TYPE, direction);
+}
+
+static void
gtk_scrolled_window_class_init (GtkScrolledWindowClass *class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
GtkObjectClass *object_class;
GtkWidgetClass *widget_class;
GtkContainerClass *container_class;
+ GtkBindingSet *binding_set;
object_class = (GtkObjectClass*) class;
widget_class = (GtkWidgetClass*) class;
@@ -164,6 +220,7 @@ gtk_scrolled_window_class_init (GtkScrol
widget_class->size_request = gtk_scrolled_window_size_request;
widget_class->size_allocate = gtk_scrolled_window_size_allocate;
widget_class->scroll_event = gtk_scrolled_window_scroll_event;
+ widget_class->focus = gtk_scrolled_window_focus;
container_class->add = gtk_scrolled_window_add;
container_class->remove = gtk_scrolled_window_remove;
@@ -171,6 +228,9 @@ gtk_scrolled_window_class_init (GtkScrol
class->scrollbar_spacing = DEFAULT_SCROLLBAR_SPACING;
+ class->scroll_child = gtk_scrolled_window_scroll_child;
+ class->move_focus_out = gtk_scrolled_window_move_focus_out;
+
g_object_class_install_property (gobject_class,
PROP_HADJUSTMENT,
g_param_spec_object ("hadjustment",
@@ -218,12 +278,57 @@ gtk_scrolled_window_class_init (GtkScrol
GTK_TYPE_SHADOW_TYPE,
GTK_SHADOW_NONE,
G_PARAM_READABLE | G_PARAM_WRITABLE));
+
+ signals[SCROLL_CHILD] =
+ g_signal_new ("scroll_child",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GtkScrolledWindowClass, scroll_child),
+ NULL, NULL,
+ _gtk_marshal_VOID__ENUM_BOOLEAN,
+ G_TYPE_NONE, 2,
+ GTK_TYPE_SCROLL_TYPE,
+ G_TYPE_BOOLEAN);
+ signals[MOVE_FOCUS_OUT] =
+ g_signal_new ("move_focus_out",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GtkScrolledWindowClass, move_focus_out),
+ NULL, NULL,
+ _gtk_marshal_VOID__ENUM,
+ G_TYPE_NONE, 1,
+ GTK_TYPE_DIRECTION_TYPE);
+
+ binding_set = gtk_binding_set_by_class (class);
+
+ add_scroll_binding (binding_set, GDK_Left, 0, GTK_SCROLL_STEP_BACKWARD, TRUE);
+ add_scroll_binding (binding_set, GDK_Right, 0, GTK_SCROLL_STEP_FORWARD, TRUE);
+ add_scroll_binding (binding_set, GDK_Up, 0, GTK_SCROLL_STEP_BACKWARD, FALSE);
+ add_scroll_binding (binding_set, GDK_Down, 0, GTK_SCROLL_STEP_FORWARD, FALSE);
+
+ add_scroll_binding (binding_set, GDK_Page_Up, GDK_CONTROL_MASK, GTK_SCROLL_PAGE_BACKWARD, TRUE);
+ add_scroll_binding (binding_set, GDK_Page_Down, GDK_CONTROL_MASK, GTK_SCROLL_PAGE_FORWARD, TRUE);
+ add_scroll_binding (binding_set, GDK_Page_Up, 0, GTK_SCROLL_PAGE_BACKWARD, FALSE);
+ add_scroll_binding (binding_set, GDK_Page_Down, 0, GTK_SCROLL_PAGE_FORWARD, FALSE);
+
+ add_scroll_binding (binding_set, GDK_Page_Up, GDK_CONTROL_MASK, GTK_SCROLL_PAGE_BACKWARD, TRUE);
+ add_scroll_binding (binding_set, GDK_Page_Down, GDK_CONTROL_MASK, GTK_SCROLL_PAGE_FORWARD, TRUE);
+ add_scroll_binding (binding_set, GDK_Page_Up, 0, GTK_SCROLL_PAGE_BACKWARD, FALSE);
+ add_scroll_binding (binding_set, GDK_Page_Down, 0, GTK_SCROLL_PAGE_FORWARD, FALSE);
+
+ add_scroll_binding (binding_set, GDK_Home, 0, GTK_SCROLL_START, TRUE);
+ add_scroll_binding (binding_set, GDK_End, 0, GTK_SCROLL_END, TRUE);
+ add_scroll_binding (binding_set, GDK_Home, GDK_CONTROL_MASK, GTK_SCROLL_START, FALSE);
+ add_scroll_binding (binding_set, GDK_End, GDK_CONTROL_MASK, GTK_SCROLL_END, FALSE);
+
+ add_tab_bindings (binding_set, GDK_CONTROL_MASK, GTK_DIR_TAB_FORWARD);
+ add_tab_bindings (binding_set, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GTK_DIR_TAB_BACKWARD);
}
static void
gtk_scrolled_window_init (GtkScrolledWindow *scrolled_window)
{
- GTK_WIDGET_SET_FLAGS (scrolled_window, GTK_NO_WINDOW);
+ GTK_WIDGET_SET_FLAGS (scrolled_window, GTK_NO_WINDOW | GTK_CAN_FOCUS);
gtk_container_set_resize_mode (GTK_CONTAINER (scrolled_window), GTK_RESIZE_QUEUE);
@@ -233,6 +338,7 @@ gtk_scrolled_window_init (GtkScrolledWin
scrolled_window->vscrollbar_policy = GTK_POLICY_ALWAYS;
scrolled_window->hscrollbar_visible = FALSE;
scrolled_window->vscrollbar_visible = FALSE;
+ scrolled_window->focus_out = FALSE;
scrolled_window->window_placement = GTK_CORNER_TOP_LEFT;
}
@@ -683,6 +789,127 @@ gtk_scrolled_window_forall (GtkContainer
}
static void
+gtk_scrolled_window_scroll_child (GtkScrolledWindow *scrolled_window,
+ GtkScrollType scroll,
+ gboolean horizontal)
+{
+ GtkAdjustment *adjustment = NULL;
+
+ switch (scroll)
+ {
+ case GTK_SCROLL_STEP_UP:
+ scroll = GTK_SCROLL_STEP_BACKWARD;
+ horizontal = FALSE;
+ break;
+ case GTK_SCROLL_STEP_DOWN:
+ scroll = GTK_SCROLL_STEP_FORWARD;
+ horizontal = FALSE;
+ break;
+ case GTK_SCROLL_STEP_LEFT:
+ scroll = GTK_SCROLL_STEP_BACKWARD;
+ horizontal = TRUE;
+ break;
+ case GTK_SCROLL_STEP_RIGHT:
+ scroll = GTK_SCROLL_STEP_FORWARD;
+ horizontal = TRUE;
+ break;
+ case GTK_SCROLL_PAGE_UP:
+ scroll = GTK_SCROLL_PAGE_BACKWARD;
+ horizontal = FALSE;
+ break;
+ case GTK_SCROLL_PAGE_DOWN:
+ scroll = GTK_SCROLL_PAGE_FORWARD;
+ horizontal = FALSE;
+ break;
+ case GTK_SCROLL_PAGE_LEFT:
+ scroll = GTK_SCROLL_STEP_BACKWARD;
+ horizontal = TRUE;
+ break;
+ case GTK_SCROLL_PAGE_RIGHT:
+ scroll = GTK_SCROLL_STEP_FORWARD;
+ horizontal = TRUE;
+ break;
+ case GTK_SCROLL_STEP_BACKWARD:
+ case GTK_SCROLL_STEP_FORWARD:
+ case GTK_SCROLL_PAGE_BACKWARD:
+ case GTK_SCROLL_PAGE_FORWARD:
+ case GTK_SCROLL_START:
+ case GTK_SCROLL_END:
+ break;
+ default:
+ g_warning ("Invalid scroll type %d for GtkSpinButton::change-value", scroll);
+ return;
+ }
+
+ if (horizontal)
+ {
+ if (scrolled_window->hscrollbar)
+ adjustment = gtk_range_get_adjustment (GTK_RANGE (scrolled_window->hscrollbar));
+ }
+ else
+ {
+ if (scrolled_window->vscrollbar)
+ adjustment = gtk_range_get_adjustment (GTK_RANGE (scrolled_window->vscrollbar));
+ }
+
+ if (adjustment)
+ {
+ gdouble value = adjustment->value;
+
+ switch (scroll)
+ {
+ case GTK_SCROLL_STEP_FORWARD:
+ value += adjustment->step_increment;
+ break;
+ case GTK_SCROLL_STEP_BACKWARD:
+ value -= adjustment->step_increment;
+ break;
+ case GTK_SCROLL_PAGE_FORWARD:
+ value += adjustment->page_increment;
+ break;
+ case GTK_SCROLL_PAGE_BACKWARD:
+ value -= adjustment->page_increment;
+ break;
+ case GTK_SCROLL_START:
+ value = adjustment->lower;
+ break;
+ case GTK_SCROLL_END:
+ value = adjustment->upper;
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ value = CLAMP (value, adjustment->lower, adjustment->upper - adjustment->page_size);
+
+ gtk_adjustment_set_value (adjustment, value);
+ }
+}
+
+static void
+gtk_scrolled_window_move_focus_out (GtkScrolledWindow *scrolled_window,
+ GtkDirectionType direction_type)
+{
+ GtkWidget *toplevel;
+
+ /* Focus out of the scrolled window entirely. We do this by setting
+ * a flag, then propagating the focus motion to the notebook.
+ */
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (scrolled_window));
+ if (!GTK_WIDGET_TOPLEVEL (toplevel))
+ return;
+
+ g_object_ref (scrolled_window);
+
+ scrolled_window->focus_out = TRUE;
+ g_signal_emit_by_name (G_OBJECT (toplevel), "move_focus", direction_type);
+ scrolled_window->focus_out = FALSE;
+
+ g_object_unref (scrolled_window);
+}
+
+static void
gtk_scrolled_window_size_request (GtkWidget *widget,
GtkRequisition *requisition)
{
@@ -1000,6 +1227,31 @@ gtk_scrolled_window_scroll_event (GtkWid
}
return FALSE;
+}
+
+static gint
+gtk_scrolled_window_focus (GtkWidget *widget,
+ GtkDirectionType direction)
+{
+ GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW (widget);
+
+ if (scrolled_window->focus_out)
+ {
+ scrolled_window->focus_out = FALSE; /* Clear this to catch the wrap-around case */
+ return FALSE;
+ }
+
+ if (gtk_widget_is_focus (widget))
+ return FALSE;
+
+ if (GTK_BIN (widget)->child)
+ {
+ if (gtk_widget_child_focus (GTK_BIN (widget)->child, direction))
+ return TRUE;
+ }
+
+ gtk_widget_grab_focus (widget);
+ return TRUE;
}
static void
Index: gtk/gtkscrolledwindow.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkscrolledwindow.h,v
retrieving revision 1.18
diff -u -p -r1.18 gtkscrolledwindow.h
--- gtk/gtkscrolledwindow.h 2001/09/08 06:24:46 1.18
+++ gtk/gtkscrolledwindow.h 2002/02/08 03:44:12
@@ -63,6 +63,7 @@ struct _GtkScrolledWindow
guint hscrollbar_visible : 1;
guint vscrollbar_visible : 1;
guint window_placement : 2;
+ guint focus_out : 1; /* Flag used by ::move-focus-out implementation */
guint16 shadow_type;
};
@@ -72,6 +73,20 @@ struct _GtkScrolledWindowClass
GtkBinClass parent_class;
gint scrollbar_spacing;
+
+ /* Action signals for keybindings. Do not connect to these signals
+ */
+
+ /* Unfortunately, GtkScrollType is deficient in that there is
+ * no horizontal/vertical variants for GTK_SCROLL_START/END,
+ * so we have to add an additional boolean flag.
+ */
+ void (*scroll_child) (GtkScrolledWindow *scrolled_window,
+ GtkScrollType scroll,
+ gboolean horizontal);
+
+ void (* move_focus_out) (GtkScrolledWindow *scrolled_window,
+ GtkDirectionType direction);
};
Index: gtk/gtkmarshalers.list
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkmarshalers.list,v
retrieving revision 1.45
diff -u -p -r1.45 gtkmarshalers.list
--- gtk/gtkmarshalers.list 2002/01/17 22:38:18 1.45
+++ gtk/gtkmarshalers.list 2002/02/08 03:44:12
@@ -52,6 +52,7 @@ VOID:BOXED,UINT
VOID:BOXED,UINT,FLAGS
VOID:BOXED,UINT,UINT
VOID:ENUM
+VOID:ENUM,BOOLEAN
VOID:ENUM,ENUM
VOID:ENUM,FLOAT
VOID:ENUM,FLOAT,BOOLEAN
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]