From 495411e16ddbf9ce211f09724902b9821c10ab6d Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Wed, 17 May 2023 16:27:06 -0400 Subject: [PATCH] testsections: A testbed for sections Add a simple test client that lets us compare and explore the section handling in listview and gridview. --- tests/meson.build | 1 + tests/testsections.c | 389 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 390 insertions(+) create mode 100644 tests/testsections.c diff --git a/tests/meson.build b/tests/meson.build index 3294b47d08..3552c21990 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -1,5 +1,6 @@ gtk_tests = [ # testname, optional extra sources + ['testsections'], ['testfilelauncher'], ['input'], ['testpopup'], diff --git a/tests/testsections.c b/tests/testsections.c new file mode 100644 index 0000000000..8ca03d5dcc --- /dev/null +++ b/tests/testsections.c @@ -0,0 +1,389 @@ +#include + +static void +setup_item (GtkSignalListItemFactory *self, + GObject *object) +{ + GtkListItem *list_item = GTK_LIST_ITEM (object); + GtkWidget *child = gtk_label_new (""); + + gtk_label_set_xalign (GTK_LABEL (child), 0); + gtk_list_item_set_child (list_item, child); +} + +static void +bind_item (GtkSignalListItemFactory *self, + GObject *object) +{ + GtkListItem *list_item = GTK_LIST_ITEM (object); + GObject *item = gtk_list_item_get_item (list_item); + GtkWidget *child = gtk_list_item_get_child (list_item); + + gtk_label_set_label (GTK_LABEL (child), + gtk_string_object_get_string (GTK_STRING_OBJECT (item))); +} + +static char * +reverse_word (const char *word) +{ + GString *s = g_string_new (""); + const char *p; + gunichar c; + gboolean capitalize; + + capitalize = g_unichar_isupper (g_utf8_get_char (word)); + + p = word + strlen (word); + while ((p = g_utf8_find_prev_char (word, p)) != NULL) + { + c = g_utf8_get_char (p); + + if (s->len == 0 && capitalize) + c = g_unichar_toupper (c); + else + c = g_unichar_tolower (c); + + g_string_append_unichar (s, c); + } + + return g_string_free (s, FALSE); +} + +static void +bind_item_reverse (GtkSignalListItemFactory *self, + GObject *object) +{ + GtkListItem *list_item = GTK_LIST_ITEM (object); + GObject *item = gtk_list_item_get_item (list_item); + GtkWidget *child = gtk_list_item_get_child (list_item); + char *word; + + word = reverse_word (gtk_string_object_get_string (GTK_STRING_OBJECT (item))); + gtk_label_set_label (GTK_LABEL (child), word); + g_free (word); +} + +static void +setup_header (GtkSignalListItemFactory *self, + GObject *object) +{ + GtkListHeader *header = GTK_LIST_HEADER (object); + GtkWidget *child = gtk_label_new (""); + + gtk_label_set_xalign (GTK_LABEL (child), 0); + gtk_list_header_set_child (header, child); +} + +static char * +get_first (GObject *this) +{ + const char *s = gtk_string_object_get_string (GTK_STRING_OBJECT (this)); + char buffer[6] = { 0, }; + + g_unichar_to_utf8 (g_unichar_toupper (g_utf8_get_char (s)), buffer); + + return g_strdup (buffer); +} + +static void +bind_header (GtkSignalListItemFactory *self, + GObject *object) +{ + GtkListHeader *header = GTK_LIST_HEADER (object); + GObject *item = gtk_list_header_get_item (header); + GtkWidget *child = gtk_list_header_get_child (header); + PangoAttrList *attrs; + char *string; + + string = get_first (item); + + gtk_label_set_label (GTK_LABEL (child), string); + attrs = pango_attr_list_new (); + pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_X_LARGE)); + pango_attr_list_insert (attrs, pango_attr_weight_new (PANGO_WEIGHT_BOLD)); + gtk_label_set_attributes (GTK_LABEL (child), attrs); + pango_attr_list_unref (attrs); + g_free (string); +} + +static const char *strings[] = { + "Alpha", "Andromeda", "Anaphylaxis", "Anaheim", "Beer", "Branch", "Botulism", "Banana", + "Bee", "Crane", "Caldera", "Copper", "Crowd", "Dora", "Dolphin", "Dam", "Ding", + NULL, +}; + +gboolean done_reading = FALSE; + +static gboolean +dump_sections (gpointer data) +{ + GtkSectionModel *model = data; + + if (!done_reading) + return G_SOURCE_CONTINUE; + + for (unsigned int i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (model)); i++) + { + unsigned int s, e; + gtk_section_model_get_section (model, i, &s, &e); + g_print ("(%u %u)\n", s, e - 1); + i = e; + } + + return G_SOURCE_REMOVE; +} + + +static void +read_lines_cb (GObject *object, + GAsyncResult *result, + gpointer data) +{ + GBufferedInputStream *stream = G_BUFFERED_INPUT_STREAM (object); + GtkStringList *stringlist = data; + GError *error = NULL; + gsize size; + GPtrArray *lines; + gssize n_filled; + const char *buffer, *newline; + + n_filled = g_buffered_input_stream_fill_finish (stream, result, &error); + if (n_filled < 0) + { + g_print ("Could not read data: %s\n", error->message); + g_clear_error (&error); + g_object_unref (stringlist); + return; + } + + buffer = g_buffered_input_stream_peek_buffer (stream, &size); + + if (n_filled == 0) + { + if (size) + gtk_string_list_take (stringlist, g_utf8_make_valid (buffer, size)); + g_object_unref (stringlist); + done_reading = TRUE; + return; + } + + lines = NULL; + while ((newline = memchr (buffer, '\n', size))) + { + if (newline > buffer) + { + if (lines == NULL) + lines = g_ptr_array_new_with_free_func (g_free); + g_ptr_array_add (lines, g_utf8_make_valid (buffer, newline - buffer)); + } + if (g_input_stream_skip (G_INPUT_STREAM (stream), newline - buffer + 1, NULL, &error) < 0) + { + g_clear_error (&error); + break; + } + buffer = g_buffered_input_stream_peek_buffer (stream, &size); + } + if (lines == NULL) + { + g_buffered_input_stream_set_buffer_size (stream, g_buffered_input_stream_get_buffer_size (stream) + 4096); + } + else + { + g_ptr_array_add (lines, NULL); + gtk_string_list_splice (stringlist, g_list_model_get_n_items (G_LIST_MODEL (stringlist)), 0, (const char **) lines->pdata); + g_ptr_array_free (lines, TRUE); + } + + g_buffered_input_stream_fill_async (stream, -1, G_PRIORITY_HIGH_IDLE, NULL, read_lines_cb, data); +} + +static void +file_is_open_cb (GObject *file, + GAsyncResult *result, + gpointer data) +{ + GError *error = NULL; + GFileInputStream *file_stream; + GBufferedInputStream *stream; + + file_stream = g_file_read_finish (G_FILE (file), result, &error); + if (file_stream == NULL) + { + g_print ("Could not open file: %s\n", error->message); + g_error_free (error); + g_object_unref (data); + return; + } + + stream = G_BUFFERED_INPUT_STREAM (g_buffered_input_stream_new (G_INPUT_STREAM (file_stream))); + g_buffered_input_stream_fill_async (stream, -1, G_PRIORITY_HIGH_IDLE, NULL, read_lines_cb, data); + g_object_unref (stream); +} + +static void +load_file (GtkStringList *list, + GFile *file) +{ + gtk_string_list_splice (list, 0, g_list_model_get_n_items (G_LIST_MODEL (list)), NULL); + g_file_read_async (file, G_PRIORITY_HIGH_IDLE, NULL, file_is_open_cb, g_object_ref (list)); +} + +static void +toggle_cb (GtkCheckButton *check, GtkWidget *list) +{ + GtkListItemFactory *header_factory = NULL; + + if (gtk_check_button_get_active (check)) + { + header_factory = gtk_signal_list_item_factory_new (); + g_signal_connect (header_factory, "setup", G_CALLBACK (setup_header), NULL); + g_signal_connect (header_factory, "bind", G_CALLBACK (bind_header), NULL); + } + + g_object_set (list, "header-factory", header_factory, NULL); + + g_clear_object (&header_factory); +} + +static void +value_changed_cb (GtkAdjustment *adj, gpointer data) +{ + g_print ("horizontal adjustment changed to %f\n", gtk_adjustment_get_value (adj)); +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + GtkWidget *sw; + GtkWidget *lv; + GtkWidget *gv; + GtkWidget *cv; + GtkWidget *header; + GtkWidget *toggle; + GtkWidget *switcher; + GtkWidget *stack; + GtkListItemFactory *factory; + GtkExpression *expression; + GtkSortListModel *sortmodel; + GtkSelectionModel *selection; + GtkStringList *stringlist; + GtkAdjustment *adj; + GtkColumnViewColumn *column; + + stringlist = gtk_string_list_new (NULL); + + if (argc > 1) + { + GFile *file = g_file_new_for_commandline_arg (argv[1]); + load_file (stringlist, file); + g_object_unref (file); + } + else + { + for (int i = 0; strings[i]; i++) + gtk_string_list_append (stringlist, strings[i]); + done_reading = TRUE; + } + + gtk_init (); + + window = gtk_window_new (); + gtk_window_set_default_size (GTK_WINDOW (window), 800, 600); + + header = gtk_header_bar_new (); + gtk_window_set_titlebar (GTK_WINDOW (window), header); + + toggle = gtk_check_button_new (); + gtk_widget_set_valign (toggle, GTK_ALIGN_CENTER); + gtk_header_bar_pack_start (GTK_HEADER_BAR (header), toggle); + + stack = gtk_stack_new (); + gtk_window_set_child (GTK_WINDOW (window), stack); + + switcher = gtk_stack_switcher_new (); + gtk_header_bar_set_title_widget (GTK_HEADER_BAR (header), switcher); + + gtk_stack_switcher_set_stack (GTK_STACK_SWITCHER (switcher), GTK_STACK (stack)); + + expression = gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string"); + sortmodel = gtk_sort_list_model_new (G_LIST_MODEL (stringlist), + GTK_SORTER (gtk_string_sorter_new (expression))); + expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL, 0, NULL, (GCallback) get_first, NULL, NULL); + gtk_sort_list_model_set_section_sorter (sortmodel, GTK_SORTER (gtk_string_sorter_new (expression))); + selection = GTK_SELECTION_MODEL (gtk_no_selection_new (G_LIST_MODEL (sortmodel))); + + /* list */ + + sw = gtk_scrolled_window_new (); + gtk_stack_add_titled (GTK_STACK (stack), sw, "list", "List"); + + factory = gtk_signal_list_item_factory_new (); + g_signal_connect (factory, "setup", G_CALLBACK (setup_item), NULL); + g_signal_connect (factory, "bind", G_CALLBACK (bind_item), NULL); + + lv = gtk_list_view_new (g_object_ref (selection), factory); + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), lv); + + g_signal_connect (toggle, "toggled", G_CALLBACK (toggle_cb), lv); + + /* grid */ + + sw = gtk_scrolled_window_new (); + gtk_stack_add_titled (GTK_STACK (stack), sw, "grid", "Grid"); + + factory = gtk_signal_list_item_factory_new (); + g_signal_connect (factory, "setup", G_CALLBACK (setup_item), NULL); + g_signal_connect (factory, "bind", G_CALLBACK (bind_item), NULL); + + gv = gtk_grid_view_new (g_object_ref (selection), factory); + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), gv); + + g_signal_connect (toggle, "toggled", G_CALLBACK (toggle_cb), gv); + + gtk_grid_view_set_min_columns (GTK_GRID_VIEW (gv), 5); + + adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (sw)); + g_signal_connect (adj, "value-changed", G_CALLBACK (value_changed_cb), NULL); + + /* columns */ + + sw = gtk_scrolled_window_new (); + gtk_stack_add_titled (GTK_STACK (stack), sw, "columns", "Columns"); + + cv = gtk_column_view_new (g_object_ref (selection)); + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), cv); + + factory = gtk_signal_list_item_factory_new (); + g_signal_connect (factory, "setup", G_CALLBACK (setup_item), NULL); + g_signal_connect (factory, "bind", G_CALLBACK (bind_item), NULL); + + column = gtk_column_view_column_new ("Word", factory); + gtk_column_view_append_column (GTK_COLUMN_VIEW (cv), column); + gtk_column_view_column_set_expand (column, TRUE); + gtk_column_view_column_set_resizable (column, TRUE); + g_object_unref (column); + + factory = gtk_signal_list_item_factory_new (); + g_signal_connect (factory, "setup", G_CALLBACK (setup_item), NULL); + g_signal_connect (factory, "bind", G_CALLBACK (bind_item_reverse), NULL); + + column = gtk_column_view_column_new ("Reverse", factory); + gtk_column_view_append_column (GTK_COLUMN_VIEW (cv), column); + gtk_column_view_column_set_expand (column, TRUE); + gtk_column_view_column_set_resizable (column, TRUE); + g_object_unref (column); + + g_signal_connect (toggle, "toggled", G_CALLBACK (toggle_cb), cv); + + gtk_window_present (GTK_WINDOW (window)); + + g_timeout_add (500, dump_sections, selection); + + while (g_list_model_get_n_items (gtk_window_get_toplevels ()) > 0) + g_main_context_iteration (NULL, FALSE); + + g_object_unref (selection); + + return 0; +}