diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index 2b8c41de2f..e2eb610597 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -1071,6 +1071,264 @@ gtk_list_view_activate_item (GtkWidget *widget, g_signal_emit (widget, signals[ACTIVATE], 0, pos); } +static void +gtk_list_view_move_to (GtkListView *self, + guint pos, + gboolean select, + gboolean modify, + gboolean extend) +{ + ListRow *row; + + row = gtk_list_item_manager_get_nth (self->item_manager, pos, NULL); + if (row == NULL) + return; + + if (!row->parent.widget) + { + GtkListItemTracker *tracker = gtk_list_item_tracker_new (self->item_manager); + + /* We need a tracker here to create the widget. + * That needs to have happened or we can't grab it. + * And we can't use a different tracker, because they manage important rows, + * so we create a temporary one. */ + gtk_list_item_tracker_set_position (self->item_manager, tracker, pos, 0, 0); + + row = gtk_list_item_manager_get_nth (self->item_manager, pos, NULL); + g_assert (row->parent.widget); + + if (!gtk_widget_grab_focus (row->parent.widget)) + return; /* FIXME: What now? Can this even happen? */ + + gtk_list_item_tracker_free (self->item_manager, tracker); + } + else + { + if (!gtk_widget_grab_focus (row->parent.widget)) + return; /* FIXME: What now? Can this even happen? */ + } + + if (select) + gtk_list_view_select_item (self, pos, modify, extend); +} + +static gboolean +gtk_list_view_move_cursor (GtkWidget *widget, + GVariant *args, + gpointer unused) +{ + GtkListView *self = GTK_LIST_VIEW (widget); + int amount; + guint orientation; + guint pos, new_pos, n_items; + gboolean select, modify, extend; + + g_variant_get (args, "(ubbbi)", &orientation, &select, &modify, &extend, &amount); + + if (self->orientation != orientation) + return TRUE; + + if (orientation == GTK_ORIENTATION_HORIZONTAL && + gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) + amount = -amount; + + pos = gtk_list_item_tracker_get_position (self->item_manager, self->focus); + n_items = self->model ? g_list_model_get_n_items (self->model) : 0; + if (pos >= n_items) + return TRUE; + + new_pos = pos + amount; + /* This overflow check only works reliably for amount == 1 */ + if (new_pos >= n_items) + return TRUE; + + gtk_list_view_move_to (self, new_pos, select, modify, extend); + + return TRUE; +} + +static gboolean +gtk_list_view_move_cursor_to_start (GtkWidget *widget, + GVariant *args, + gpointer unused) +{ + GtkListView *self = GTK_LIST_VIEW (widget); + gboolean select, modify, extend; + + if (self->model == NULL || g_list_model_get_n_items (self->model) == 0) + return TRUE; + + g_variant_get (args, "(bbb)", &select, &modify, &extend); + + gtk_list_view_move_to (self, 0, select, modify, extend); + + return TRUE; +} + +static gboolean +gtk_list_view_move_cursor_to_end (GtkWidget *widget, + GVariant *args, + gpointer unused) +{ + GtkListView *self = GTK_LIST_VIEW (widget); + gboolean select, modify, extend; + guint n_items; + + if (self->model == NULL) + return TRUE; + + n_items = g_list_model_get_n_items (self->model); + if (n_items == 0) + return TRUE; + + g_variant_get (args, "(bbb)", &select, &modify, &extend); + + gtk_list_view_move_to (self, n_items - 1, select, modify, extend); + + return TRUE; +} + +static gboolean +gtk_list_view_move_cursor_page_up (GtkWidget *widget, + GVariant *args, + gpointer unused) +{ + GtkListView *self = GTK_LIST_VIEW (widget); + gboolean select, modify, extend; + guint start, pos, n_items; + ListRow *row; + int pixels, offset; + + start = gtk_list_item_tracker_get_position (self->item_manager, self->focus); + row = gtk_list_item_manager_get_nth (self->item_manager, start, NULL); + if (row == NULL) + return TRUE; + n_items = self->model ? g_list_model_get_n_items (self->model) : 0; + /* check that we can go at least one row up */ + if (n_items == 0 || start == 0) + return TRUE; + + if (self->orientation == GTK_ORIENTATION_HORIZONTAL) + pixels = gtk_widget_get_width (widget); + else + pixels = gtk_widget_get_height (widget); + pixels -= row->height; + + pos = gtk_list_view_get_position_at_y (self, + MAX (0, list_row_get_y (self, row) - pixels), + &offset, + NULL); + /* there'll always be rows between 0 and this row */ + g_assert (pos < n_items); + /* if row is too high, go one row less */ + if (offset > 0) + pos++; + /* but go at least 1 row */ + if (pos >= start) + pos = start - 1; + + g_variant_get (args, "(bbb)", &select, &modify, &extend); + + gtk_list_view_move_to (self, pos, select, modify, extend); + + return TRUE; +} + +static gboolean +gtk_list_view_move_cursor_page_down (GtkWidget *widget, + GVariant *args, + gpointer unused) +{ + GtkListView *self = GTK_LIST_VIEW (widget); + gboolean select, modify, extend; + guint start, pos, n_items; + ListRow *row; + int pixels, offset; + + start = gtk_list_item_tracker_get_position (self->item_manager, self->focus); + row = gtk_list_item_manager_get_nth (self->item_manager, start, NULL); + if (row == NULL) + return TRUE; + n_items = self->model ? g_list_model_get_n_items (self->model) : 0; + /* check that we can go at least one row down */ + if (n_items == 0 || start >= n_items - 1) + return TRUE; + + if (self->orientation == GTK_ORIENTATION_HORIZONTAL) + pixels = gtk_widget_get_width (widget); + else + pixels = gtk_widget_get_height (widget); + + pos = gtk_list_view_get_position_at_y (self, + list_row_get_y (self, row) + pixels, + &offset, + NULL); + if (pos >= n_items) + pos = n_items - 1; + /* if row is too high, go one row less */ + else if (pos > 0 && offset > 0) + pos--; + /* but go at least 1 row */ + if (pos <= start) + pos = start + 1; + + g_variant_get (args, "(bbb)", &select, &modify, &extend); + + gtk_list_view_move_to (self, pos, select, modify, extend); + + return TRUE; +} + +static void +gtk_list_view_add_custom_move_binding (GtkWidgetClass *widget_class, + guint keyval, + GtkShortcutFunc callback) +{ + gtk_widget_class_add_binding (widget_class, + keyval, + 0, + callback, + "(bbb)", TRUE, FALSE, FALSE); + gtk_widget_class_add_binding (widget_class, + keyval, + GDK_CONTROL_MASK, + callback, + "(bbb)", FALSE, FALSE, FALSE); + gtk_widget_class_add_binding (widget_class, + keyval, + GDK_SHIFT_MASK, + callback, + "(bbb)", TRUE, FALSE, TRUE); + gtk_widget_class_add_binding (widget_class, + keyval, + GDK_CONTROL_MASK | GDK_SHIFT_MASK, + callback, + "(bbb)", TRUE, TRUE, TRUE); +} + +static void +gtk_list_view_add_move_binding (GtkWidgetClass *widget_class, + guint keyval, + GtkOrientation orientation, + int amount) +{ + gtk_widget_class_add_binding (widget_class, + keyval, + GDK_CONTROL_MASK, + gtk_list_view_move_cursor, + "(ubbbi)", orientation, FALSE, FALSE, FALSE, amount); + gtk_widget_class_add_binding (widget_class, + keyval, + GDK_SHIFT_MASK, + gtk_list_view_move_cursor, + "(ubbbi)", orientation, TRUE, FALSE, TRUE, amount); + gtk_widget_class_add_binding (widget_class, + keyval, + GDK_CONTROL_MASK | GDK_SHIFT_MASK, + gtk_list_view_move_cursor, + "(ubbbi)", orientation, TRUE, TRUE, TRUE, amount); +} + static void gtk_list_view_class_init (GtkListViewClass *klass) { @@ -1247,6 +1505,24 @@ gtk_list_view_class_init (GtkListViewClass *klass) "u", gtk_list_view_scroll_to_item); + gtk_list_view_add_move_binding (widget_class, GDK_KEY_Up, GTK_ORIENTATION_VERTICAL, -1); + gtk_list_view_add_move_binding (widget_class, GDK_KEY_KP_Up, GTK_ORIENTATION_VERTICAL, -1); + gtk_list_view_add_move_binding (widget_class, GDK_KEY_Down, GTK_ORIENTATION_VERTICAL, 1); + gtk_list_view_add_move_binding (widget_class, GDK_KEY_KP_Down, GTK_ORIENTATION_VERTICAL, 1); + gtk_list_view_add_move_binding (widget_class, GDK_KEY_Left, GTK_ORIENTATION_HORIZONTAL, -1); + gtk_list_view_add_move_binding (widget_class, GDK_KEY_KP_Left, GTK_ORIENTATION_HORIZONTAL, -1); + gtk_list_view_add_move_binding (widget_class, GDK_KEY_Right, GTK_ORIENTATION_HORIZONTAL, 1); + gtk_list_view_add_move_binding (widget_class, GDK_KEY_KP_Right, GTK_ORIENTATION_HORIZONTAL, 1); + + gtk_list_view_add_custom_move_binding (widget_class, GDK_KEY_Home, gtk_list_view_move_cursor_to_start); + gtk_list_view_add_custom_move_binding (widget_class, GDK_KEY_KP_Home, gtk_list_view_move_cursor_to_start); + gtk_list_view_add_custom_move_binding (widget_class, GDK_KEY_End, gtk_list_view_move_cursor_to_end); + gtk_list_view_add_custom_move_binding (widget_class, GDK_KEY_KP_End, gtk_list_view_move_cursor_to_end); + gtk_list_view_add_custom_move_binding (widget_class, GDK_KEY_Page_Up, gtk_list_view_move_cursor_page_up); + gtk_list_view_add_custom_move_binding (widget_class, GDK_KEY_KP_Page_Up, gtk_list_view_move_cursor_page_up); + gtk_list_view_add_custom_move_binding (widget_class, GDK_KEY_Page_Down, gtk_list_view_move_cursor_page_down); + gtk_list_view_add_custom_move_binding (widget_class, GDK_KEY_KP_Page_Down, gtk_list_view_move_cursor_page_down); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_a, GDK_CONTROL_MASK, "list.select-all", NULL); gtk_widget_class_add_binding_action (widget_class, GDK_KEY_slash, GDK_CONTROL_MASK, "list.select-all", NULL);