[cairo-commit] goocanvas/src goocanvasitemsimple.c, 1.8,
1.9 goocanvasitemview.c, 1.7, 1.8 goocanvasitemview.h, 1.7,
1.8 goocanvasview.c, 1.14, 1.15
Damon Chaplin
commit at pdx.freedesktop.org
Sat Apr 22 12:17:30 PDT 2006
Committed by: damon
Update of /cvs/cairo/goocanvas/src
In directory kemper:/tmp/cvs-serv26926/src
Modified Files:
goocanvasitemsimple.c goocanvasitemview.c goocanvasitemview.h
goocanvasview.c
Log Message:
2006-04-22 Damon Chaplin <damon at gnome.org>
* src/goocanvasview.c: added support for keyboard focus navigation.
(I still need to make it scroll to show the focused item if needed.)
* demo/demo-focus.c: new demo page to test keyboard focus navigation.
* src/goocanvasitemview.c: added "focus-in-event" & "focus-out-event"
signals.
* src/goocanvasitemsimple.c (goo_canvas_item_simple_get_path_bounds):
make sure we do min/max over all points of bounds.
* src/goocanvasview.c (goo_canvas_view_focus_out): emit
"focus_out_event", not "focus_in_event".
Index: goocanvasitemsimple.c
===================================================================
RCS file: /cvs/cairo/goocanvas/src/goocanvasitemsimple.c,v
retrieving revision 1.8
retrieving revision 1.9
diff -u -d -r1.8 -r1.9
--- goocanvasitemsimple.c 18 Apr 2006 23:40:44 -0000 1.8
+++ goocanvasitemsimple.c 22 Apr 2006 19:17:28 -0000 1.9
@@ -848,21 +848,33 @@
cairo_t *cr,
GooCanvasBounds *bounds)
{
- GooCanvasBounds tmp_bounds;
+ GooCanvasBounds tmp_bounds, tmp_bounds2;
/* Calculate the filled extents. */
goo_canvas_item_simple_set_fill_options (item, cr);
- cairo_fill_extents (cr, &bounds->x1, &bounds->y1, &bounds->x2, &bounds->y2);
+ cairo_fill_extents (cr, &tmp_bounds.x1, &tmp_bounds.y1,
+ &tmp_bounds.x2, &tmp_bounds.y2);
/* Check the stroke. */
goo_canvas_item_simple_set_stroke_options (item, cr);
- cairo_stroke_extents (cr, &tmp_bounds.x1, &tmp_bounds.y1,
- &tmp_bounds.x2, &tmp_bounds.y2);
+ cairo_stroke_extents (cr, &tmp_bounds2.x1, &tmp_bounds2.y1,
+ &tmp_bounds2.x2, &tmp_bounds2.y2);
- bounds->x1 = MIN (bounds->x1, tmp_bounds.x1);
- bounds->y1 = MIN (bounds->y1, tmp_bounds.y1);
- bounds->x2 = MAX (bounds->x2, tmp_bounds.x2);
- bounds->y2 = MAX (bounds->y2, tmp_bounds.y2);
+ bounds->x1 = MIN (tmp_bounds.x1, tmp_bounds.x2);
+ bounds->x1 = MIN (bounds->x1, tmp_bounds2.x1);
+ bounds->x1 = MIN (bounds->x1, tmp_bounds2.x2);
+
+ bounds->x2 = MAX (tmp_bounds.x1, tmp_bounds.x2);
+ bounds->x2 = MAX (bounds->x2, tmp_bounds2.x1);
+ bounds->x2 = MAX (bounds->x2, tmp_bounds2.x2);
+
+ bounds->y1 = MIN (tmp_bounds.y1, tmp_bounds.y2);
+ bounds->y1 = MIN (bounds->y1, tmp_bounds2.y1);
+ bounds->y1 = MIN (bounds->y1, tmp_bounds2.y2);
+
+ bounds->y2 = MAX (tmp_bounds.y1, tmp_bounds.y2);
+ bounds->y2 = MAX (bounds->y2, tmp_bounds2.y1);
+ bounds->y2 = MAX (bounds->y2, tmp_bounds2.y2);
goo_canvas_item_simple_user_bounds_to_device (item, cr, bounds);
}
Index: goocanvasitemview.c
===================================================================
RCS file: /cvs/cairo/goocanvas/src/goocanvasitemview.c,v
retrieving revision 1.7
retrieving revision 1.8
diff -u -d -r1.7 -r1.8
--- goocanvasitemview.c 18 Apr 2006 15:43:07 -0000 1.7
+++ goocanvasitemview.c 22 Apr 2006 19:17:28 -0000 1.8
@@ -30,6 +30,8 @@
BUTTON_RELEASE_EVENT,
/* Keyboard events. */
+ FOCUS_IN_EVENT,
+ FOCUS_OUT_EVENT,
KEY_PRESS_EVENT,
KEY_RELEASE_EVENT,
@@ -204,6 +206,52 @@
/* Keyboard events. */
/**
+ * GooCanvasItemView::focus-in-event
+ * @view: the item view that received the signal.
+ * @target_view: the target of the event.
+ * @event: the event data.
+ *
+ * Emitted when the item receives the keyboard focus.
+ *
+ * Returns: %TRUE to stop the signal emission, or %FALSE to let it
+ * continue.
+ */
+ canvas_item_view_signals[FOCUS_IN_EVENT] =
+ g_signal_new ("focus_in_event",
+ iface_type,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GooCanvasItemViewIface,
+ focus_in_event),
+ NULL, NULL,
+ goo_canvas_marshal_BOOLEAN__OBJECT_BOXED,
+ G_TYPE_BOOLEAN, 2,
+ GOO_TYPE_CANVAS_ITEM_VIEW,
+ GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+ /**
+ * GooCanvasItemView::focus-out-event
+ * @view: the item view that received the signal.
+ * @target_view: the target of the event.
+ * @event: the event data.
+ *
+ * Emitted when the item loses the keyboard focus.
+ *
+ * Returns: %TRUE to stop the signal emission, or %FALSE to let it
+ * continue.
+ */
+ canvas_item_view_signals[FOCUS_OUT_EVENT] =
+ g_signal_new ("focus_out_event",
+ iface_type,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GooCanvasItemViewIface,
+ focus_out_event),
+ NULL, NULL,
+ goo_canvas_marshal_BOOLEAN__OBJECT_BOXED,
+ G_TYPE_BOOLEAN, 2,
+ GOO_TYPE_CANVAS_ITEM_VIEW,
+ GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+ /**
* GooCanvasItemView::key-press-event
* @view: the item view that received the signal.
* @target_view: the target of the event.
@@ -528,6 +576,9 @@
*
* This entails checking the item's own visibility setting, as well as those
* of its ancestors.
+ *
+ * Note that this the item may be scrolled off the screen and so may not
+ * be actually visible to the user.
*
* Returns: %TRUE if the item is visible.
**/
Index: goocanvasitemview.h
===================================================================
RCS file: /cvs/cairo/goocanvas/src/goocanvasitemview.h,v
retrieving revision 1.7
retrieving revision 1.8
diff -u -d -r1.7 -r1.8
--- goocanvasitemview.h 18 Apr 2006 15:43:07 -0000 1.7
+++ goocanvasitemview.h 22 Apr 2006 19:17:28 -0000 1.8
@@ -91,6 +91,12 @@
gboolean (* button_release_event) (GooCanvasItemView *view,
GooCanvasItemView *target,
GdkEventButton *event);
+ gboolean (* focus_in_event) (GooCanvasItemView *view,
+ GooCanvasItemView *target,
+ GdkEventFocus *event);
+ gboolean (* focus_out_event) (GooCanvasItemView *view,
+ GooCanvasItemView *target,
+ GdkEventFocus *event);
gboolean (* key_press_event) (GooCanvasItemView *view,
GooCanvasItemView *target,
GdkEventKey *event);
Index: goocanvasview.c
===================================================================
RCS file: /cvs/cairo/goocanvas/src/goocanvasview.c,v
retrieving revision 1.14
retrieving revision 1.15
diff -u -d -r1.14 -r1.15
--- goocanvasview.c 18 Apr 2006 15:43:07 -0000 1.14
+++ goocanvasview.c 22 Apr 2006 19:17:28 -0000 1.15
@@ -121,6 +121,7 @@
*
*/
#include <config.h>
+#include <math.h>
#include <gtk/gtk.h>
#include "goocanvasatk.h"
#include "goocanvasview.h"
@@ -156,6 +157,8 @@
GdkEventButton *event);
static gboolean goo_canvas_view_motion (GtkWidget *widget,
GdkEventMotion *event);
+static gboolean goo_canvas_view_focus (GtkWidget *widget,
+ GtkDirectionType direction);
static gboolean goo_canvas_view_key_press (GtkWidget *widget,
GdkEventKey *event);
static gboolean goo_canvas_view_key_release (GtkWidget *widget,
@@ -196,6 +199,7 @@
widget_class->button_press_event = goo_canvas_view_button_press;
widget_class->button_release_event = goo_canvas_view_button_release;
widget_class->motion_notify_event = goo_canvas_view_motion;
+ widget_class->focus = goo_canvas_view_focus;
widget_class->key_press_event = goo_canvas_view_key_press;
widget_class->key_release_event = goo_canvas_view_key_release;
widget_class->enter_notify_event = goo_canvas_view_crossing;
@@ -1154,8 +1158,6 @@
if (!view->root_view)
return FALSE;
- /*sleep (1);*/
-
if (event->window != view->canvas_window)
return FALSE;
@@ -1600,7 +1602,7 @@
if (view->focused_item_view)
return propagate_event (view, view->focused_item_view,
- "focus_in_event", (GdkEvent*) event);
+ "focus_out_event", (GdkEvent*) event);
else
return FALSE;
}
@@ -1971,6 +1973,17 @@
}
+static void
+goo_canvas_view_convert_from_window_pixels (GooCanvasView *canvas_view,
+ gdouble *x,
+ gdouble *y)
+{
+ *x += canvas_view->hadjustment->value;
+ *y += canvas_view->vadjustment->value;
+ goo_canvas_view_convert_from_pixels (canvas_view, x, y);
+}
+
+
/**
* goo_canvas_view_convert_to_item_space:
* @canvas_view: a #GooCanvasView.
@@ -2063,3 +2076,399 @@
}
+/*
+ * Keyboard focus navigation.
+ */
+
+typedef struct _GooCanvasViewFocusData GooCanvasViewFocusData;
+struct _GooCanvasViewFocusData
+{
+ /* The current focused item view, or NULL. */
+ GooCanvasItemView *focused_item_view;
+
+ /* The bounds of the current focused item view. We try to find the next
+ closest one in the desired direction. */
+ GooCanvasBounds focused_bounds;
+ gdouble focused_center_x, focused_center_y;
+
+ /* The direction to move the focus in. */
+ GtkDirectionType direction;
+
+ /* The text direction of the widget. */
+ GtkTextDirection text_direction;
+
+ /* The best view found so far, and the offsets for it. */
+ GooCanvasItemView *best_item_view;
+ gdouble best_x_offset, best_y_offset, best_score;
+
+ /* The offsets for the item being tested. */
+ GooCanvasBounds *current_bounds;
+ gdouble current_x_offset, current_y_offset, current_score;
+};
+
+
+/* This tries to figure out the bounds of the currently focused canvas item
+ view or widget. */
+static void
+goo_canvas_view_get_current_focus_bounds (GooCanvasView *view,
+ GooCanvasViewFocusData *data)
+{
+ GooCanvasBounds *bounds;
+ GtkWidget *toplevel, *focus_widget;
+ GtkAllocation *allocation;
+ gint focus_widget_x, focus_widget_y;
+
+ /* If an item view is currently focused, we just need its bounds. */
+ if (data->focused_item_view)
+ {
+ bounds = goo_canvas_item_view_get_bounds (data->focused_item_view);
+ data->focused_bounds = *bounds;
+ return;
+ }
+
+ /* Otherwise try to get the currently focused widget in the window. */
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view));
+ bounds = &data->focused_bounds;
+ if (toplevel && GTK_IS_WINDOW (toplevel)
+ && GTK_WINDOW (toplevel)->focus_widget)
+ {
+ focus_widget = GTK_WINDOW (toplevel)->focus_widget;
+
+ /* Translate the allocation to be relative to the GooCanvasView.
+ Skip ancestor widgets as the coords won't help. */
+ if (!gtk_widget_is_ancestor (GTK_WIDGET (view), focus_widget)
+ && gtk_widget_translate_coordinates (focus_widget, GTK_WIDGET (view),
+ 0, 0, &focus_widget_x,
+ &focus_widget_y))
+ {
+ /* Translate into device units. */
+ bounds->x1 = focus_widget_x;
+ bounds->y1 = focus_widget_y;
+ bounds->x2 = focus_widget_x + focus_widget->allocation.width;
+ bounds->y2 = focus_widget_y + focus_widget->allocation.height;
+
+ goo_canvas_view_convert_from_window_pixels (view, &bounds->x1,
+ &bounds->y1);
+ goo_canvas_view_convert_from_window_pixels (view, &bounds->x2,
+ &bounds->y2);
+ return;
+ }
+ }
+
+ /* As a last resort, we guess a starting position based on the direction. */
+ allocation = >K_WIDGET (view)->allocation;
+ switch (data->direction)
+ {
+ case GTK_DIR_DOWN:
+ case GTK_DIR_RIGHT:
+ /* Start from top-left. */
+ bounds->x1 = 0.0;
+ bounds->y1 = 0.0;
+ break;
+
+ case GTK_DIR_UP:
+ /* Start from bottom-left. */
+ bounds->x1 = 0.0;
+ bounds->y1 = allocation->height;
+ break;
+
+ case GTK_DIR_LEFT:
+ /* Start from top-right. */
+ bounds->x1 = allocation->width;
+ bounds->y1 = 0.0;
+ break;
+
+ case GTK_DIR_TAB_FORWARD:
+ bounds->y1 = 0.0;
+ if (data->text_direction == GTK_TEXT_DIR_RTL)
+ /* Start from top-right. */
+ bounds->x1 = allocation->width;
+ else
+ /* Start from top-left. */
+ bounds->x1 = 0.0;
+ break;
+
+ case GTK_DIR_TAB_BACKWARD:
+ bounds->y1 = allocation->height;
+ if (data->text_direction == GTK_TEXT_DIR_RTL)
+ /* Start from bottom-left. */
+ bounds->x1 = 0.0;
+ else
+ /* Start from bottom-right. */
+ bounds->x1 = allocation->width;
+ break;
+ }
+
+ goo_canvas_view_convert_from_window_pixels (view, &bounds->x1, &bounds->y1);
+ bounds->x2 = bounds->x1;
+ bounds->y2 = bounds->y1;
+}
+
+
+/* Check if the given item view is a better candidate for the focus than
+ the current best one in the data struct. */
+static gboolean
+goo_canvas_view_focus_check_is_best (GooCanvasView *view,
+ GooCanvasItemView *item_view,
+ GooCanvasViewFocusData *data)
+{
+ gdouble center_x, center_y;
+ gdouble abs_x_offset, abs_y_offset;
+
+ data->current_score = 0.0;
+
+ data->current_bounds = goo_canvas_item_view_get_bounds (item_view);
+ center_x = (data->current_bounds->x1 + data->current_bounds->x2) / 2.0;
+ center_y = (data->current_bounds->y1 + data->current_bounds->y2) / 2.0;
+
+ /* Calculate the offsets of the center of this item view from the center
+ of the current focus item view or widget. */
+ data->current_x_offset = center_x - data->focused_center_x;
+ data->current_y_offset = center_y - data->focused_center_y;
+
+ abs_x_offset = fabs (data->current_x_offset);
+ abs_y_offset = fabs (data->current_y_offset);
+
+ switch (data->direction)
+ {
+ case GTK_DIR_UP:
+ /* If the y offset is > 0 we can discard this item view. */
+ if (data->current_y_offset >= 0)
+ return FALSE;
+
+ /* Compute a score (lower is best) and check if it is the best. */
+ data->current_score = abs_x_offset * 2 + abs_y_offset;
+ if (!data->best_item_view || data->current_score < data->best_score)
+ return TRUE;
+ break;
+
+ case GTK_DIR_DOWN:
+ /* If the y offset is < 0 we can discard this item view. */
+ if (data->current_y_offset <= 0)
+ return FALSE;
+
+ /* Compute a score (lower is best) and check if it is the best. */
+ data->current_score = abs_x_offset * 2 + abs_y_offset;
+ if (!data->best_item_view || data->current_score < data->best_score)
+ return TRUE;
+ break;
+
+ case GTK_DIR_LEFT:
+ /* If the x offset is > 0 we can discard this item view. */
+ if (data->current_x_offset >= 0)
+ return FALSE;
+
+ /* Compute a score (lower is best) and check if it is the best. */
+ data->current_score = abs_y_offset * 2 + abs_x_offset;
+ if (!data->best_item_view || data->current_score < data->best_score)
+ return TRUE;
+ break;
+
+ case GTK_DIR_RIGHT:
+ /* If the x offset is < 0 we can discard this item view. */
+ if (data->current_x_offset <= 0)
+ return FALSE;
+
+ /* Compute a score (lower is best) and check if it is the best. */
+ data->current_score = abs_y_offset * 2 + abs_x_offset;
+ if (!data->best_item_view || data->current_score < data->best_score)
+ return TRUE;
+ break;
+
+ case GTK_DIR_TAB_BACKWARD:
+ /* We need to handle this differently depending on text direction. */
+ if (data->text_direction == GTK_TEXT_DIR_RTL)
+ {
+ /* If the y offset is > 0, or it is 0 and the x offset > 0 we can
+ discard this item view. */
+ if (data->current_y_offset > 0
+ || (data->current_y_offset == 0 && data->current_x_offset < 0))
+ return FALSE;
+
+ /* If the y offset is > the current best y offset, this is best. */
+ if (!data->best_item_view || data->current_y_offset > data->best_y_offset)
+ return TRUE;
+
+ /* If the y offsets are the same, choose the largest x offset. */
+ if (data->current_y_offset == data->best_y_offset
+ && data->current_x_offset < data->best_x_offset)
+ return TRUE;
+ }
+ else
+ {
+ /* If the y offset is > 0, or it is 0 and the x offset > 0 we can
+ discard this item view. */
+ if (data->current_y_offset > 0
+ || (data->current_y_offset == 0 && data->current_x_offset > 0))
+ return FALSE;
+
+ /* If the y offset is > the current best y offset, this is best. */
+ if (!data->best_item_view || data->current_y_offset > data->best_y_offset)
+ return TRUE;
+
+ /* If the y offsets are the same, choose the largest x offset. */
+ if (data->current_y_offset == data->best_y_offset
+ && data->current_x_offset > data->best_x_offset)
+ return TRUE;
+ }
+ break;
+
+ case GTK_DIR_TAB_FORWARD:
+ /* We need to handle this differently depending on text direction. */
+ if (data->text_direction == GTK_TEXT_DIR_RTL)
+ {
+ /* If the y offset is < 0, or it is 0 and the x offset > 0 we can
+ discard this item view. */
+ if (data->current_y_offset < 0
+ || (data->current_y_offset == 0 && data->current_x_offset > 0))
+ return FALSE;
+
+ /* If the y offset is < the current best y offset, this is best. */
+ if (!data->best_item_view || data->current_y_offset < data->best_y_offset)
+ return TRUE;
+
+ /* If the y offsets are the same, choose the largest x offset. */
+ if (data->current_y_offset == data->best_y_offset
+ && data->current_x_offset > data->best_x_offset)
+ return TRUE;
+ }
+ else
+ {
+ /* If the y offset is < 0, or it is 0 and the x offset < 0 we can
+ discard this item view. */
+ if (data->current_y_offset < 0
+ || (data->current_y_offset == 0 && data->current_x_offset < 0))
+ return FALSE;
+
+ /* If the y offset is < the current best y offset, this is best. */
+ if (!data->best_item_view || data->current_y_offset < data->best_y_offset)
+ return TRUE;
+
+ /* If the y offsets are the same, choose the smallest x offset. */
+ if (data->current_y_offset == data->best_y_offset
+ && data->current_x_offset < data->best_x_offset)
+ return TRUE;
+ }
+ break;
+ }
+
+ return FALSE;
+}
+
+
+/* Recursively look for the next best item view in the desired direction. */
+static void
+goo_canvas_view_focus_recurse (GooCanvasView *view,
+ GooCanvasItemView *item_view,
+ GooCanvasViewFocusData *data)
+{
+ GooCanvasItem *item = goo_canvas_item_view_get_item (item_view);
+ GooCanvasItemView *child_view;
+ gboolean can_focus = FALSE, is_best;
+ gint n_children, i;
+
+ /* If the item is not a possible candidate, just return. */
+ is_best = goo_canvas_view_focus_check_is_best (view, item_view, data);
+
+ if (is_best && goo_canvas_item_view_is_visible (item_view, view->scale))
+ {
+ /* Check if the item view can take the focus. */
+ g_object_get (item_view, "can-focus", &can_focus, NULL);
+
+ /* If the item view can take the focus itself, and it isn't the current
+ focus item, save its score and return. (If it is a container it takes
+ precedence over its children). */
+ if (can_focus && item_view != data->focused_item_view)
+ {
+ data->best_item_view = item_view;
+ data->best_x_offset = data->current_x_offset;
+ data->best_y_offset = data->current_y_offset;
+ data->best_score = data->current_score;
+ return;
+ }
+ }
+
+ /* If the item view is a container view, check the children recursively. */
+ n_children = goo_canvas_item_view_get_n_children (item_view);
+ if (n_children)
+ {
+ /* Check if we can skip the entire group. */
+ switch (data->direction)
+ {
+ case GTK_DIR_UP:
+ /* If the group is below the bottom of the current focused item
+ view we can skip it. */
+ if (data->current_bounds->y1 > data->focused_bounds.y2)
+ return;
+ break;
+ case GTK_DIR_DOWN:
+ /* If the group is above the top of the current focused item
+ view we can skip it. */
+ if (data->current_bounds->y2 < data->focused_bounds.y1)
+ return;
+ break;
+ case GTK_DIR_LEFT:
+ /* If the group is to the right of the current focused item
+ view we can skip it. */
+ if (data->current_bounds->x1 > data->focused_bounds.x2)
+ return;
+ break;
+ case GTK_DIR_RIGHT:
+ /* If the group is to the left of the current focused item
+ view we can skip it. */
+ if (data->current_bounds->x2 < data->focused_bounds.x1)
+ return;
+ break;
+ default:
+ break;
+ }
+
+ for (i = 0; i < n_children; i++)
+ {
+ child_view = goo_canvas_item_view_get_child (item_view, i);
+ goo_canvas_view_focus_recurse (view, child_view, data);
+ }
+ }
+}
+
+
+static gboolean
+goo_canvas_view_focus (GtkWidget *widget,
+ GtkDirectionType direction)
+{
+ GooCanvasView *view;
+ GooCanvasViewFocusData data;
+
+ g_return_val_if_fail (GOO_IS_CANVAS_VIEW (widget), FALSE);
+
+ view = GOO_CANVAS_VIEW (widget);
+
+ /* If keyboard navigation has been turned off for the canvas, return FALSE.*/
+ if (!GTK_WIDGET_CAN_FOCUS (view))
+ return FALSE;
+
+ data.direction = direction;
+ data.text_direction = gtk_widget_get_direction (widget);
+ data.best_item_view = NULL;
+ data.focused_item_view = NULL;
+
+ if (GTK_WIDGET_HAS_FOCUS (view))
+ data.focused_item_view = view->focused_item_view;
+
+ /* Get the bounds of the currently focused item or widget. */
+ goo_canvas_view_get_current_focus_bounds (view, &data);
+ data.focused_center_x = (data.focused_bounds.x1 + data.focused_bounds.x2) / 2.0;
+ data.focused_center_y = (data.focused_bounds.y1 + data.focused_bounds.y2) / 2.0;
+
+ /* Recursively look for the next best item view in the desired direction. */
+ goo_canvas_view_focus_recurse (view, view->root_view, &data);
+
+ /* If we found an item view to focus, grab the focus and return TRUE. */
+ if (data.best_item_view)
+ {
+ goo_canvas_view_grab_focus (view, data.best_item_view);
+ return TRUE;
+ }
+
+ return FALSE;
+}
More information about the cairo-commit
mailing list