From d9f158678df31d2ac80484c059768f1f0d4d643a Mon Sep 17 00:00:00 2001 From: William Jon McCann Date: Sun, 10 Feb 2013 07:52:25 -0500 Subject: [PATCH] flow-box: add basic keynav --- egg-flow-box.c | 658 +++++++++++++++++++++++++++++++++++++++++++++++- egg-flow-box.h | 5 + test-flow-box.c | 3 + 3 files changed, 658 insertions(+), 8 deletions(-) diff --git a/egg-flow-box.c b/egg-flow-box.c index ff30c494..abcf0dd9 100644 --- a/egg-flow-box.c +++ b/egg-flow-box.c @@ -42,6 +42,33 @@ #include #include "egg-flow-box.h" +/* This already exists in gtk as _gtk_marshal_VOID__ENUM_INT, inline it here for now + to avoid separate marshallers file */ +static void +_egg_marshal_VOID__ENUM_INT (GClosure * closure, + GValue * return_value, + guint n_param_values, + const GValue * param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__ENUM_INT) (gpointer data1, gint arg_1, gint arg_2, gpointer data2); + register GMarshalFunc_VOID__ENUM_INT callback; + register GCClosure * cc; + register gpointer data1; + register gpointer data2; + cc = (GCClosure *) closure; + g_return_if_fail (n_param_values == 3); + if (G_CCLOSURE_SWAP_DATA (closure)) { + data1 = closure->data; + data2 = param_values->data[0].v_pointer; + } else { + data1 = param_values->data[0].v_pointer; + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__ENUM_INT) (marshal_data ? marshal_data : cc->callback); + callback (data1, g_value_get_enum (param_values + 1), g_value_get_int (param_values + 2), data2); +} #define P_(msgid) (msgid) @@ -50,6 +77,9 @@ enum { CHILD_ACTIVATED, SELECTED_CHILDREN_CHANGED, + ACTIVATE_CURSOR_CHILD, + TOGGLE_CURSOR_CHILD, + MOVE_CURSOR, LAST_SIGNAL }; @@ -76,6 +106,7 @@ struct _EggFlowBoxPrivate { guint homogeneous : 1; guint activate_on_single_click : 1; GtkSelectionMode selection_mode; + GtkAdjustment *adjustment; guint row_spacing; guint column_spacing; @@ -83,9 +114,12 @@ struct _EggFlowBoxPrivate { gboolean active_child_active; EggFlowBoxChildInfo *active_child; EggFlowBoxChildInfo *prelight_child; + EggFlowBoxChildInfo *cursor_child; + EggFlowBoxChildInfo *selected_child; guint16 min_children_per_line; guint16 max_children_per_line; + guint16 cur_children_per_line; GSequence *children; GHashTable *child_hash; @@ -140,6 +174,21 @@ egg_flow_box_lookup_info (EggFlowBox *flow_box, GtkWidget* child) return g_hash_table_lookup (priv->child_hash, child); } +void +egg_flow_box_set_adjustment (EggFlowBox *box, + GtkAdjustment *adjustment) +{ + EggFlowBoxPrivate *priv = box->priv; + + g_return_if_fail (box != NULL); + + g_object_ref (adjustment); + g_clear_object (&priv->adjustment); + priv->adjustment = adjustment; + gtk_container_set_focus_vadjustment (GTK_CONTAINER (box), + adjustment); +} + /** * egg_flow_box_get_homogeneous: * @box: a #EggFlowBox @@ -185,6 +234,14 @@ egg_flow_box_set_homogeneous (EggFlowBox *box, } } +/* Children are visible if they are shown by the app (visible) + and not filtered out (child_visible) by the box */ +static gboolean +child_is_visible (GtkWidget *child) +{ + return gtk_widget_get_visible (child) && gtk_widget_get_child_visible (child); +} + static gint get_visible_children (EggFlowBox *box) { @@ -202,7 +259,7 @@ get_visible_children (EggFlowBox *box) child_info = g_sequence_get (iter); child = child_info->widget; - if (!gtk_widget_get_visible (child)) + if (!child_is_visible (child)) continue; i++; @@ -236,7 +293,7 @@ get_average_item_size (EggFlowBox *box, child_info = g_sequence_get (iter); child = child_info->widget; - if (!gtk_widget_get_visible (child)) + if (!child_is_visible (child)) continue; if (orientation == GTK_ORIENTATION_HORIZONTAL) @@ -281,7 +338,7 @@ get_largest_size_for_opposing_orientation (EggFlowBox *box, child_info = g_sequence_get (iter); child = child_info->widget; - if (!gtk_widget_get_visible (child)) + if (!child_is_visible (child)) continue; if (orientation == GTK_ORIENTATION_HORIZONTAL) @@ -335,7 +392,7 @@ get_largest_size_for_line_in_opposing_orientation (EggFlowBox *box, child_info = g_sequence_get (iter); child = child_info->widget; - if (!gtk_widget_get_visible (child)) + if (!child_is_visible (child)) continue; /* Distribute the extra pixels to the first children in the line @@ -402,7 +459,7 @@ gather_aligned_item_requests (EggFlowBox *box, child_info = g_sequence_get (iter); child = child_info->widget; - if (!gtk_widget_get_visible (child)) + if (!child_is_visible (child)) continue; if (orientation == GTK_ORIENTATION_HORIZONTAL) @@ -715,6 +772,8 @@ egg_flow_box_real_size_allocate (GtkWidget *widget, * go on to distribute expand space if needed. */ + priv->cur_children_per_line = line_length; + /* FIXME: This portion needs to consider which columns * and rows asked for expand space and distribute those * accordingly for the case of ALIGNED allocation. @@ -777,7 +836,7 @@ egg_flow_box_real_size_allocate (GtkWidget *widget, child_info = g_sequence_get (iter); child = child_info->widget; - if (!gtk_widget_get_visible (child)) + if (!child_is_visible (child)) { child_info->area.x = child_allocation.x; child_info->area.y = child_allocation.y; @@ -933,7 +992,7 @@ egg_flow_box_real_remove (GtkContainer *container, g_return_if_fail (child != NULL); - was_visible = gtk_widget_get_visible (child); + was_visible = child_is_visible (child); child_info = egg_flow_box_lookup_info (box, child); if (child_info == NULL) @@ -948,6 +1007,8 @@ egg_flow_box_real_remove (GtkContainer *container, priv->prelight_child = NULL; if (child_info == priv->active_child) priv->active_child = NULL; + if (child_info == priv->selected_child) + priv->selected_child = NULL; gtk_widget_unparent (child); g_hash_table_remove (priv->child_hash, child); @@ -1033,7 +1094,7 @@ get_largest_aligned_line_length (EggFlowBox *box, child_info = g_sequence_get (iter); child = child_info->widget; - if (!gtk_widget_get_visible (child)) + if (!child_is_visible (child)) continue; if (orientation == GTK_ORIENTATION_HORIZONTAL) @@ -2159,6 +2220,46 @@ egg_flow_box_unselect_all_internal (EggFlowBox *box) } +static void +egg_flow_box_unselect_child_info (EggFlowBox *box, + EggFlowBoxChildInfo *child_info) +{ + if (!child_info->selected) + return; + + if (box->priv->selection_mode == GTK_SELECTION_NONE) + return; + else if (box->priv->selection_mode != GTK_SELECTION_MULTIPLE) + egg_flow_box_unselect_all_internal (box); + else + child_info->selected = TRUE; + + g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0); + + egg_flow_box_queue_draw_child (box, child_info); +} + +static void +egg_flow_box_update_cursor (EggFlowBox *box, + EggFlowBoxChildInfo *child_info) +{ + EggFlowBoxPrivate *priv = box->priv; + + priv->cursor_child = child_info; + gtk_widget_grab_focus (GTK_WIDGET (box)); + gtk_widget_queue_draw (GTK_WIDGET (box)); + + if (child_info != NULL && priv->adjustment != NULL) + { + GtkAllocation allocation; + + gtk_widget_get_allocation (GTK_WIDGET (box), &allocation); + gtk_adjustment_clamp_page (priv->adjustment, + priv->cursor_child->area.y + allocation.y, + priv->cursor_child->area.y + allocation.y + priv->cursor_child->area.height); + } +} + static void egg_flow_box_select_child_info (EggFlowBox *box, EggFlowBoxChildInfo *child_info) @@ -2172,10 +2273,13 @@ egg_flow_box_select_child_info (EggFlowBox *box, egg_flow_box_unselect_all_internal (box); child_info->selected = TRUE; + box->priv->selected_child = child_info; g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0); egg_flow_box_queue_draw_child (box, child_info); + + egg_flow_box_update_cursor (box, child_info); } static void @@ -2218,6 +2322,285 @@ egg_flow_box_real_button_release_event (GtkWidget *widget, return FALSE; } +static EggFlowBoxChildInfo * +egg_flow_box_get_first_visible (EggFlowBox *box) +{ + EggFlowBoxPrivate *priv = box->priv; + EggFlowBoxChildInfo *child_info; + GSequenceIter *iter; + + for (iter = g_sequence_get_begin_iter (priv->children); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + child_info = g_sequence_get (iter); + if (child_is_visible (child_info->widget)) + return child_info; + } + + return NULL; +} + +static EggFlowBoxChildInfo * +egg_flow_box_get_last_visible (EggFlowBox *box) +{ + EggFlowBoxPrivate *priv = box->priv; + EggFlowBoxChildInfo *child_info; + GSequenceIter *iter; + + iter = g_sequence_get_end_iter (priv->children); + while (!g_sequence_iter_is_begin (iter)) + { + iter = g_sequence_iter_prev (iter); + child_info = g_sequence_get (iter); + if (child_is_visible (child_info->widget)) + return child_info; + } + + return NULL; +} + +static GSequenceIter * +egg_flow_box_get_previous_visible (EggFlowBox *box, + GSequenceIter *iter) +{ + EggFlowBoxChildInfo *child_info; + + if (g_sequence_iter_is_begin (iter)) + return NULL; + + do + { + iter = g_sequence_iter_prev (iter); + child_info = g_sequence_get (iter); + if (child_is_visible (child_info->widget)) + return iter; + } + while (!g_sequence_iter_is_begin (iter)); + + return NULL; +} + +static GSequenceIter * +egg_flow_box_get_next_visible (EggFlowBox *box, + GSequenceIter *iter) +{ + EggFlowBoxChildInfo *child_info; + + if (g_sequence_iter_is_end (iter)) + return iter; + + do + { + iter = g_sequence_iter_next (iter); + if (!g_sequence_iter_is_end (iter)) + { + child_info = g_sequence_get (iter); + if (child_is_visible (child_info->widget)) + return iter; + } + } + while (!g_sequence_iter_is_end (iter)); + + return iter; +} + +static GSequenceIter * +egg_flow_box_get_above_visible (EggFlowBox *box, + GSequenceIter *iter) +{ + EggFlowBoxChildInfo *child_info; + GSequenceIter *ret = NULL; + gint i; + + if (g_sequence_iter_is_begin (iter)) + return NULL; + + i = 0; + do + { + iter = g_sequence_iter_prev (iter); + child_info = g_sequence_get (iter); + if (child_is_visible (child_info->widget)) + i++; + } + while (!g_sequence_iter_is_begin (iter) + && i < box->priv->cur_children_per_line); + + if (i == box->priv->cur_children_per_line) + ret = iter; + + return ret; +} + +static GSequenceIter * +egg_flow_box_get_below_visible (EggFlowBox *box, + GSequenceIter *iter) +{ + EggFlowBoxChildInfo *child_info; + GSequenceIter *ret = NULL; + gint i; + + if (g_sequence_iter_is_end (iter)) + return iter; + + i = 0; + do + { + iter = g_sequence_iter_next (iter); + if (!g_sequence_iter_is_end (iter)) + { + child_info = g_sequence_get (iter); + if (child_is_visible (child_info->widget)) + i++; + } + } + while (!g_sequence_iter_is_end (iter) + && i < box->priv->cur_children_per_line); + + if (i == box->priv->cur_children_per_line) + ret = iter; + + return ret; +} + +static gboolean +egg_flow_box_real_focus (GtkWidget *widget, + GtkDirectionType direction) +{ + EggFlowBox *box = EGG_FLOW_BOX (widget); + EggFlowBoxPrivate *priv = box->priv; + gboolean had_focus = FALSE; + GtkWidget *recurse_into; + EggFlowBoxChildInfo *current_focus_child; + EggFlowBoxChildInfo *next_focus_child; + gboolean modify_selection_pressed; + GdkModifierType state = 0; + + recurse_into = NULL; + + g_object_get (GTK_WIDGET (box), "has-focus", &had_focus, NULL); + current_focus_child = NULL; + next_focus_child = NULL; + + if (had_focus) + { + /* If on row, going right, enter into possible container */ + if (direction == GTK_DIR_RIGHT || direction == GTK_DIR_TAB_FORWARD) + { + if (priv->cursor_child != NULL) + recurse_into = priv->cursor_child->widget; + } + current_focus_child = priv->cursor_child; + } + else if (gtk_container_get_focus_child ((GtkContainer *) box) != NULL) + { + /* There is a focus child, always navigate inside it first */ + recurse_into = gtk_container_get_focus_child ((GtkContainer *) box); + current_focus_child = egg_flow_box_lookup_info (box, recurse_into); + + /* If exiting child container to the left, select row or out */ + if (direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD) + next_focus_child = current_focus_child; + } + else + { + /* If coming from the left, enter into possible container */ + if (direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD) + { + if (priv->selected_child != NULL) + recurse_into = priv->selected_child->widget; + } + } + + if (recurse_into != NULL) + { + if (gtk_widget_child_focus (recurse_into, direction)) + return TRUE; + } + + if (next_focus_child == NULL) + { + if (current_focus_child != NULL) + { + GSequenceIter *i; + + if (direction == GTK_DIR_LEFT) + { + i = egg_flow_box_get_previous_visible (box, current_focus_child->iter); + if (i != NULL) + next_focus_child = g_sequence_get (i); + } + else if (direction == GTK_DIR_RIGHT) + { + i = egg_flow_box_get_next_visible (box, current_focus_child->iter); + if (i != NULL && !g_sequence_iter_is_end (i)) + next_focus_child = g_sequence_get (i); + } + else if (direction == GTK_DIR_UP) + { + i = egg_flow_box_get_above_visible (box, current_focus_child->iter); + if (i != NULL && !g_sequence_iter_is_end (i)) + next_focus_child = g_sequence_get (i); + } + else if (direction == GTK_DIR_DOWN) + { + i = egg_flow_box_get_below_visible (box, current_focus_child->iter); + if (i != NULL && !g_sequence_iter_is_end (i)) + next_focus_child = g_sequence_get (i); + } + } + else + { + switch (direction) + { + case GTK_DIR_UP: + case GTK_DIR_TAB_BACKWARD: + next_focus_child = priv->selected_child; + if (next_focus_child == NULL) + next_focus_child = egg_flow_box_get_last_visible (box); + break; + default: + next_focus_child = priv->selected_child; + if (next_focus_child == NULL) + next_focus_child = + egg_flow_box_get_first_visible (box); + break; + } + } + } + + if (next_focus_child == NULL) + { + if (direction == GTK_DIR_UP || direction == GTK_DIR_DOWN + || direction == GTK_DIR_LEFT || direction == GTK_DIR_RIGHT) + { + if (gtk_widget_keynav_failed (GTK_WIDGET (box), direction)) + return TRUE; + } + + return FALSE; + } + + modify_selection_pressed = FALSE; + if (gtk_get_current_event_state (&state)) + { + GdkModifierType modify_mod_mask; + + modify_mod_mask = gtk_widget_get_modifier_mask (GTK_WIDGET (box), + GDK_MODIFIER_INTENT_MODIFY_SELECTION); + if ((state & modify_mod_mask) == modify_mod_mask) + modify_selection_pressed = TRUE; + } + + if (modify_selection_pressed) + egg_flow_box_update_cursor (box, next_focus_child); + else + egg_flow_box_select_child_info (box, next_focus_child); + + return TRUE; +} + typedef struct { EggFlowBoxChildInfo *child; GtkStateFlags state; @@ -2242,6 +2625,204 @@ child_flags_find_or_add (ChildFlags *array, return &array[*array_length - 1]; } +static void +egg_flow_box_add_move_binding (GtkBindingSet *binding_set, + guint keyval, + GdkModifierType modmask, + GtkMovementStep step, + gint count) +{ + gtk_binding_entry_add_signal (binding_set, + keyval, + modmask, + "move-cursor", + (guint) 2, + GTK_TYPE_MOVEMENT_STEP, + step, + G_TYPE_INT, + count, + NULL); + + if ((modmask & GDK_CONTROL_MASK) == GDK_CONTROL_MASK) + return; + + gtk_binding_entry_add_signal (binding_set, + keyval, + GDK_CONTROL_MASK, + "move-cursor", + (guint) 2, + GTK_TYPE_MOVEMENT_STEP, + step, + G_TYPE_INT, + count, + NULL); +} + +static void +egg_flow_box_real_activate_cursor_child (EggFlowBox *box) +{ + EggFlowBoxPrivate *priv = box->priv; + + egg_flow_box_select_and_activate (box, priv->cursor_child); +} + +static void +egg_flow_box_real_toggle_cursor_child (EggFlowBox *box) +{ + EggFlowBoxPrivate *priv = box->priv; + + if (priv->cursor_child == NULL) + return; + + if (priv->selection_mode == GTK_SELECTION_SINGLE && + priv->cursor_child->selected) + egg_flow_box_unselect_child_info (box, priv->cursor_child); + else + egg_flow_box_select_and_activate (box, priv->cursor_child); +} + +static void +egg_flow_box_real_move_cursor (EggFlowBox *box, + GtkMovementStep step, + gint count) +{ + EggFlowBoxPrivate *priv = box->priv; + GdkModifierType state; + gboolean modify_selection_pressed; + EggFlowBoxChildInfo *child; + GdkModifierType modify_mod_mask; + EggFlowBoxChildInfo *prev; + EggFlowBoxChildInfo *next; + gint page_size; + GSequenceIter *iter; + gint start_y; + gint end_y; + + modify_selection_pressed = FALSE; + + if (gtk_get_current_event_state (&state)) + { + modify_mod_mask = gtk_widget_get_modifier_mask (GTK_WIDGET (box), + GDK_MODIFIER_INTENT_MODIFY_SELECTION); + if ((state & modify_mod_mask) == modify_mod_mask) + modify_selection_pressed = TRUE; + } + + child = NULL; + switch (step) + { + case GTK_MOVEMENT_BUFFER_ENDS: + if (count < 0) + child = egg_flow_box_get_first_visible (box); + else + child = egg_flow_box_get_last_visible (box); + break; + case GTK_MOVEMENT_DISPLAY_LINES: + if (priv->cursor_child != NULL) + { + iter = priv->cursor_child->iter; + + while (count < 0 && iter != NULL) + { + iter = egg_flow_box_get_previous_visible (box, iter); + count = count + 1; + } + while (count > 0 && iter != NULL) + { + iter = egg_flow_box_get_next_visible (box, iter); + count = count - 1; + } + + if (iter != NULL && !g_sequence_iter_is_end (iter)) + child = g_sequence_get (iter); + } + break; + case GTK_MOVEMENT_PAGES: + page_size = 100; + if (priv->adjustment != NULL) + page_size = gtk_adjustment_get_page_increment (priv->adjustment); + + if (priv->cursor_child != NULL) + { + start_y = priv->cursor_child->area.y; + end_y = start_y; + iter = priv->cursor_child->iter; + + child = priv->cursor_child; + if (count < 0) + { + gint i = 0; + + /* Up */ + while (iter != NULL && !g_sequence_iter_is_begin (iter)) + { + iter = egg_flow_box_get_previous_visible (box, iter); + if (iter == NULL) + break; + + prev = g_sequence_get (iter); + + /* go up an even number of rows */ + if (i % priv->cur_children_per_line == 0 + && prev->area.y < start_y - page_size) + break; + + child = prev; + i++; + } + } + else + { + gint i = 0; + + /* Down */ + while (iter != NULL && !g_sequence_iter_is_end (iter)) + { + iter = egg_flow_box_get_next_visible (box, iter); + if (g_sequence_iter_is_end (iter)) + break; + + next = g_sequence_get (iter); + + if (i % priv->cur_children_per_line == 0 + && next->area.y > start_y + page_size) + break; + + child = next; + i++; + } + } + end_y = child->area.y; + } + break; + default: + return; + } + + if (child == NULL || child == priv->cursor_child) + { + GtkDirectionType direction = count < 0 ? GTK_DIR_UP : GTK_DIR_DOWN; + + if (!gtk_widget_keynav_failed (GTK_WIDGET (box), direction)) + { + GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (box)); + + if (toplevel) + gtk_widget_child_focus (toplevel, + direction == GTK_DIR_UP ? + GTK_DIR_TAB_BACKWARD : + GTK_DIR_TAB_FORWARD); + + } + + return; + } + + egg_flow_box_update_cursor (box, child); + if (!modify_selection_pressed) + egg_flow_box_select_child_info (box, child); +} + static gboolean egg_flow_box_real_draw (GtkWidget *widget, cairo_t *cr) @@ -2343,6 +2924,7 @@ egg_flow_box_finalize (GObject *obj) g_sequence_free (priv->children); g_hash_table_unref (priv->child_hash); + g_clear_object (&priv->adjustment); G_OBJECT_CLASS (egg_flow_box_parent_class)->finalize (obj); } @@ -2353,6 +2935,7 @@ egg_flow_box_class_init (EggFlowBoxClass *class) GObjectClass *object_class = G_OBJECT_CLASS (class); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class); + GtkBindingSet *binding_set; object_class->finalize = egg_flow_box_finalize; object_class->get_property = egg_flow_box_get_property; @@ -2363,6 +2946,7 @@ egg_flow_box_class_init (EggFlowBoxClass *class) widget_class->motion_notify_event = egg_flow_box_real_motion_notify_event; widget_class->size_allocate = egg_flow_box_real_size_allocate; widget_class->realize = egg_flow_box_real_realize; + widget_class->focus = egg_flow_box_real_focus; widget_class->draw = egg_flow_box_real_draw; widget_class->button_press_event = egg_flow_box_real_button_press_event; widget_class->button_release_event = egg_flow_box_real_button_release_event; @@ -2378,6 +2962,10 @@ egg_flow_box_class_init (EggFlowBoxClass *class) container_class->child_type = egg_flow_box_real_child_type; gtk_container_class_handle_border_width (container_class); + class->activate_cursor_child = egg_flow_box_real_activate_cursor_child; + class->toggle_cursor_child = egg_flow_box_real_toggle_cursor_child; + class->move_cursor = egg_flow_box_real_move_cursor; + g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation"); g_object_class_install_property (object_class, @@ -2521,6 +3109,59 @@ egg_flow_box_class_init (EggFlowBoxClass *class) NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + signals[ACTIVATE_CURSOR_CHILD] = g_signal_new ("activate-cursor-child", + EGG_TYPE_FLOW_BOX, + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (EggFlowBoxClass, activate_cursor_child), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals[TOGGLE_CURSOR_CHILD] = g_signal_new ("toggle-cursor-child", + EGG_TYPE_FLOW_BOX, + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (EggFlowBoxClass, toggle_cursor_child), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals[MOVE_CURSOR] = g_signal_new ("move-cursor", + EGG_TYPE_FLOW_BOX, + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (EggFlowBoxClass, move_cursor), + NULL, NULL, + _egg_marshal_VOID__ENUM_INT, + G_TYPE_NONE, 2, + GTK_TYPE_MOVEMENT_STEP, G_TYPE_INT); + + widget_class->activate_signal = signals[ACTIVATE_CURSOR_CHILD]; + + binding_set = gtk_binding_set_by_class (class); + egg_flow_box_add_move_binding (binding_set, GDK_KEY_Home, 0, + GTK_MOVEMENT_BUFFER_ENDS, -1); + egg_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Home, 0, + GTK_MOVEMENT_BUFFER_ENDS, -1); + egg_flow_box_add_move_binding (binding_set, GDK_KEY_End, 0, + GTK_MOVEMENT_BUFFER_ENDS, 1); + egg_flow_box_add_move_binding (binding_set, GDK_KEY_KP_End, 0, + GTK_MOVEMENT_BUFFER_ENDS, 1); + egg_flow_box_add_move_binding (binding_set, GDK_KEY_Up, GDK_CONTROL_MASK, + GTK_MOVEMENT_DISPLAY_LINES, -1); + egg_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Up, GDK_CONTROL_MASK, + GTK_MOVEMENT_DISPLAY_LINES, -1); + egg_flow_box_add_move_binding (binding_set, GDK_KEY_Down, GDK_CONTROL_MASK, + GTK_MOVEMENT_DISPLAY_LINES, 1); + egg_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Down, GDK_CONTROL_MASK, + GTK_MOVEMENT_DISPLAY_LINES, 1); + egg_flow_box_add_move_binding (binding_set, GDK_KEY_Page_Up, 0, + GTK_MOVEMENT_PAGES, -1); + egg_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Page_Up, 0, + GTK_MOVEMENT_PAGES, -1); + egg_flow_box_add_move_binding (binding_set, GDK_KEY_Page_Down, 0, + GTK_MOVEMENT_PAGES, 1); + egg_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Page_Down, 0, + GTK_MOVEMENT_PAGES, 1); + + gtk_binding_entry_add_signal (binding_set, GDK_KEY_space, GDK_CONTROL_MASK, + "toggle-cursor-child", 0, NULL); g_type_class_add_private (class, sizeof (EggFlowBoxPrivate)); } @@ -2543,6 +3184,7 @@ egg_flow_box_init (EggFlowBox *box) priv->child_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL); priv->activate_on_single_click = TRUE; + gtk_widget_set_can_focus (GTK_WIDGET (box), TRUE); gtk_widget_set_has_window (GTK_WIDGET (box), TRUE); } diff --git a/egg-flow-box.h b/egg-flow-box.h index c99905ef..9748522d 100644 --- a/egg-flow-box.h +++ b/egg-flow-box.h @@ -63,6 +63,9 @@ struct _EggFlowBoxClass void (* child_activated) (EggFlowBox *self, GtkWidget *child); void (* selected_children_changed) (EggFlowBox *self); + void (*activate_cursor_child) (EggFlowBox *self); + void (*toggle_cursor_child) (EggFlowBox *self); + void (*move_cursor) (EggFlowBox *self, GtkMovementStep step, gint count); }; GType egg_flow_box_get_type (void) G_GNUC_CONST; @@ -107,6 +110,8 @@ void egg_flow_box_select_child (EggFlowBox GtkSelectionMode egg_flow_box_get_selection_mode (EggFlowBox *box); void egg_flow_box_set_selection_mode (EggFlowBox *box, GtkSelectionMode mode); +void egg_flow_box_set_adjustment (EggFlowBox *box, + GtkAdjustment *adjustment); G_END_DECLS diff --git a/test-flow-box.c b/test-flow-box.c index 88565e7c..ef06eaaf 100644 --- a/test-flow-box.c +++ b/test-flow-box.c @@ -338,6 +338,9 @@ create_window (void) gtk_widget_show (flowbox); gtk_container_add (GTK_CONTAINER (swindow), flowbox); + egg_flow_box_set_adjustment (EGG_FLOW_BOX (flowbox), + gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (swindow))); + g_signal_connect (flowbox, "child-activated", G_CALLBACK (on_child_activated), NULL); g_signal_connect (flowbox, "selected-children-changed", G_CALLBACK (on_selected_children_changed), NULL);