listview: Add move keybindings

My god, these are a lot.

And my god, these are complicated to get right.
This commit is contained in:
Benjamin Otte 2019-10-16 21:31:50 +02:00 committed by Matthias Clasen
parent 2b9481ecdc
commit 042e13a932

View File

@ -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);