summaryrefslogtreecommitdiff
path: root/gtk/gtklistbase.c
diff options
context:
space:
mode:
Diffstat (limited to 'gtk/gtklistbase.c')
-rw-r--r--gtk/gtklistbase.c257
1 files changed, 237 insertions, 20 deletions
diff --git a/gtk/gtklistbase.c b/gtk/gtklistbase.c
index cf0e4e2912..5daf6e83f8 100644
--- a/gtk/gtklistbase.c
+++ b/gtk/gtklistbase.c
@@ -72,6 +72,8 @@ struct _GtkListBasePrivate
guint autoscroll_id;
double autoscroll_delta_x;
double autoscroll_delta_y;
+
+ GtkFilter *search_filter;
};
enum
@@ -545,6 +547,8 @@ gtk_list_base_focus (GtkWidget *widget,
}
}
+static void gtk_list_base_clear_search_filter (GtkListBase *self);
+
static void
gtk_list_base_dispose (GObject *object)
{
@@ -573,6 +577,8 @@ gtk_list_base_dispose (GObject *object)
g_clear_object (&priv->model);
+ gtk_list_base_clear_search_filter (self);
+
G_OBJECT_CLASS (gtk_list_base_parent_class)->dispose (object);
}
@@ -786,26 +792,18 @@ gtk_list_base_update_focus_tracker (GtkListBase *self)
}
}
-static void
-gtk_list_base_scroll_to_item (GtkWidget *widget,
- const char *action_name,
- GVariant *parameter)
+static gboolean
+gtk_list_base_scroll_to_position (GtkListBase *self,
+ guint pos)
{
- GtkListBase *self = GTK_LIST_BASE (widget);
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
int start, end;
double align_along, align_across;
GtkPackType side_along, side_across;
- guint pos;
-
- if (!g_variant_check_format_string (parameter, "u", FALSE))
- return;
-
- g_variant_get (parameter, "u", &pos);
/* figure out primary orientation and if position is valid */
if (!gtk_list_base_get_allocation_along (GTK_LIST_BASE (self), pos, &start, &end))
- return;
+ return FALSE;
end += start;
gtk_list_base_compute_scroll_align (self,
@@ -816,7 +814,7 @@ gtk_list_base_scroll_to_item (GtkWidget *widget,
/* now do the same thing with the other orientation */
if (!gtk_list_base_get_allocation_across (GTK_LIST_BASE (self), pos, &start, &end))
- return;
+ return FALSE;
end += start;
gtk_list_base_compute_scroll_align (self,
@@ -830,13 +828,32 @@ gtk_list_base_scroll_to_item (GtkWidget *widget,
align_across, side_across,
align_along, side_along);
- /* HACK HACK HACK
- *
- * GTK has no way to track the focused child. But we now that when a listitem
- * gets focus, it calls this action. So we update our focus tracker from here
- * because it's the closest we can get to accurate tracking.
- */
- gtk_list_base_update_focus_tracker (self);
+ return TRUE;
+}
+
+static void
+gtk_list_base_scroll_to_item (GtkWidget *widget,
+ const char *action_name,
+ GVariant *parameter)
+{
+ GtkListBase *self = GTK_LIST_BASE (widget);
+ guint pos;
+
+ if (!g_variant_check_format_string (parameter, "u", FALSE))
+ return;
+
+ g_variant_get (parameter, "u", &pos);
+
+ if (gtk_list_base_scroll_to_position (self, pos))
+ {
+ /* HACK HACK HACK
+ *
+ * GTK has no way to track the focused child. But we know that when a listitem
+ * gets focus, it calls this action. So we update our focus tracker from here
+ * because it's the closest we can get to accurate tracking.
+ */
+ gtk_list_base_update_focus_tracker (self);
+ }
}
static void
@@ -885,6 +902,17 @@ gtk_list_base_unselect_all (GtkWidget *widget,
gtk_selection_model_unselect_all (selection_model);
}
+static void
+gtk_list_base_next_match_action (GtkWidget *widget,
+ const char *action_name,
+ GVariant *parameter)
+{
+ gboolean forward;
+
+ g_variant_get (parameter, "(b)", &forward);
+ gtk_list_base_select_next_match (GTK_LIST_BASE (widget), forward);
+}
+
static gboolean
gtk_list_base_move_cursor_to_start (GtkWidget *widget,
GVariant *args,
@@ -1199,6 +1227,11 @@ gtk_list_base_class_init (GtkListBaseClass *klass)
NULL,
gtk_list_base_unselect_all);
+ gtk_widget_class_install_action (widget_class,
+ "list.next-match",
+ "(b)",
+ gtk_list_base_next_match_action);
+
gtk_list_base_add_move_binding (widget_class, GDK_KEY_Up, GTK_ORIENTATION_VERTICAL, -1);
gtk_list_base_add_move_binding (widget_class, GDK_KEY_KP_Up, GTK_ORIENTATION_VERTICAL, -1);
gtk_list_base_add_move_binding (widget_class, GDK_KEY_Down, GTK_ORIENTATION_VERTICAL, 1);
@@ -1221,6 +1254,9 @@ gtk_list_base_class_init (GtkListBaseClass *klass)
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_slash, GDK_CONTROL_MASK, "list.select-all", NULL);
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_A, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "list.unselect-all", NULL);
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_backslash, GDK_CONTROL_MASK, "list.unselect-all", NULL);
+
+ gtk_widget_class_add_binding_action (widget_class, GDK_KEY_g, GDK_CONTROL_MASK, "list.next-match", "(b)", TRUE);
+ gtk_widget_class_add_binding_action (widget_class, GDK_KEY_g, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "list.next-match", "(b)", FALSE);
}
static void gtk_list_base_update_rubberband_selection (GtkListBase *self);
@@ -1906,3 +1942,184 @@ gtk_list_base_set_model (GtkListBase *self,
return TRUE;
}
+
+static guint
+find_first_selected (GtkSelectionModel *model)
+{
+ guint i, start, n_items;
+ gboolean selected;
+
+ n_items = 0;
+ for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (model)); i += n_items)
+ {
+ gtk_selection_model_query_range (model, i, &start, &n_items, &selected);
+ if (selected)
+ return i;
+ }
+
+ return GTK_INVALID_LIST_POSITION;
+}
+
+static gboolean
+match_item (GListModel *model,
+ GtkFilter *filter,
+ guint position)
+{
+ gpointer item;
+ gboolean result;
+
+ item = g_list_model_get_item (model, position);
+ result = gtk_filter_match (filter, item);
+ g_object_unref (item);
+
+ return result;
+}
+
+static guint
+find_next_match (GListModel *model,
+ GtkFilter *filter,
+ guint start,
+ gboolean forward)
+{
+ guint i;
+
+ if (start == GTK_INVALID_LIST_POSITION)
+ start = 0;
+
+ if (forward)
+ for (i = start; i < g_list_model_get_n_items (model); i++)
+ {
+ if (match_item (model, filter, i))
+ return i;
+ }
+ else
+ for (i = start; ; i--)
+ {
+ if (match_item (model, filter, i))
+ return i;
+ if (i == 0)
+ break;
+ }
+
+ return GTK_INVALID_LIST_POSITION;
+}
+
+static void
+gtk_list_base_search_filter_changed_cb (GtkFilter *filter,
+ GtkFilterChange change,
+ GtkListBase *self)
+{
+ GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
+ GtkSelectionModel *model = gtk_list_item_manager_get_model (priv->item_manager);
+
+ if (model == NULL)
+ return;
+
+ if (gtk_filter_get_strictness (priv->search_filter) == GTK_FILTER_MATCH_NONE)
+ gtk_selection_model_unselect_all (model);
+ else
+ {
+ guint position;
+
+ switch (change)
+ {
+ case GTK_FILTER_CHANGE_DIFFERENT:
+ case GTK_FILTER_CHANGE_LESS_STRICT:
+ position = find_next_match (G_LIST_MODEL (model), priv->search_filter, 0, TRUE);
+ break;
+
+ case GTK_FILTER_CHANGE_MORE_STRICT:
+ position = find_first_selected (model);
+ if (position == GTK_INVALID_LIST_POSITION)
+ position = 0;
+ position = find_next_match (G_LIST_MODEL (model), priv->search_filter, position, TRUE);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ if (position == GTK_INVALID_LIST_POSITION)
+ gtk_selection_model_unselect_all (model);
+ else
+ gtk_list_base_grab_focus_on_item (self, position, TRUE, FALSE, FALSE);
+ }
+}
+
+static void
+gtk_list_base_clear_search_filter (GtkListBase *self)
+{
+ GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
+
+ if (priv->search_filter == NULL)
+ return;
+
+ g_signal_handlers_disconnect_by_func (priv->search_filter,
+ gtk_list_base_search_filter_changed_cb,
+ self);
+
+ g_clear_object (&priv->search_filter);
+}
+
+void
+gtk_list_base_set_search_filter (GtkListBase *self,
+ GtkFilter *filter)
+{
+ GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
+
+ if (priv->search_filter == filter)
+ return;
+
+ gtk_list_base_clear_search_filter (self);
+
+ if (filter)
+ {
+ priv->search_filter = g_object_ref (filter);
+ g_signal_connect (priv->search_filter, "changed",
+ G_CALLBACK (gtk_list_base_search_filter_changed_cb), self);
+ gtk_list_base_search_filter_changed_cb (priv->search_filter, GTK_FILTER_CHANGE_DIFFERENT, self);
+ }
+
+ gtk_widget_action_set_enabled (GTK_WIDGET (self), "list.next-match", filter != NULL);
+}
+
+GtkFilter *
+gtk_list_base_get_search_filter (GtkListBase *self)
+{
+ GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
+
+ return priv->search_filter;
+}
+
+gboolean
+gtk_list_base_select_next_match (GtkListBase *self,
+ gboolean forward)
+{
+ GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
+ GtkSelectionModel *model = gtk_list_item_manager_get_model (priv->item_manager);
+ guint position;
+
+ if (!priv->search_filter)
+ return FALSE;
+
+ position = find_first_selected (model);
+ if (position == GTK_INVALID_LIST_POSITION)
+ return FALSE;
+
+ if (forward)
+ position = position + 1;
+ else if (position > 0)
+ position = position - 1;
+ else
+ return FALSE;
+
+ position = find_next_match (G_LIST_MODEL (model), priv->search_filter, position, forward);
+ if (position == GTK_INVALID_LIST_POSITION)
+ {
+ gtk_widget_error_bell (GTK_WIDGET (self));
+ return FALSE;
+ }
+
+ gtk_list_base_grab_focus_on_item (self, position, TRUE, FALSE, FALSE);
+ return TRUE;
+}