How Having trouble adding a sort function for multiple columns when using the GTK 4 Columnview list model. C coding, a Linux/GNOME enviroment

93 views Asked by At

I'm testing the GTK4 columnview list model for displaying multiple columns. Coding in C. I have that code working (see code below). I am running into trouble adding the standard columnview sort function, using the C snippet referenced in the GTK 4.0 documentation as a guide

C snippet is shown here: https://docs.gtk.org/gtk4/method.ColumnView.get_sorter.html

Working code, pre-sort attempt, is below. The app displays the columns, no errors to std out, working as expected.

// Use for testing columnview

#include <gtk/gtk.h>

// Setup custom type - each item has two strings which will be displayed in two columns

#define TESTCOL_TYPE_ITEM (testcol_item_get_type())
G_DECLARE_FINAL_TYPE (TestColItem, testcol_item, TESTCOL, ITEM, GObject)

struct _TestColItem
{
    GObject parent_instance;
    const char *str1;
    const char *str2;
};

struct _TestColItemClass
{
    GObjectClass parent_class;
};

G_DEFINE_TYPE (TestColItem, testcol_item, G_TYPE_OBJECT)

static void testcol_item_init(TestColItem *item)
{
}

static void testcol_item_class_init(TestColItemClass *class)
{
}
enter image description here
// Set label for factory widget
static void setup_cb(GtkSignalListItemFactory *factory,GObject  *listitem)
{
    GtkWidget *label =gtk_label_new(NULL);
    gtk_list_item_set_child(GTK_LIST_ITEM(listitem),label);
}

// Set text from the column1 passed item for label
static void bind_col1_cb(GtkSignalListItemFactory *factory, GtkListItem *listitem)
{
    GtkWidget *label = gtk_list_item_get_child(listitem);
    TestColItem *item = gtk_list_item_get_item(GTK_LIST_ITEM(listitem));
    const char *string = item->str1;
    g_print("value is %s\n",string); // Show on std out
    gtk_label_set_text(GTK_LABEL (label), string);
}
// Set text from the column2 passed item for label
static void bind_col2_cb(GtkSignalListItemFactory *factory, GtkListItem *listitem)
{
    GtkWidget *label = gtk_list_item_get_child(listitem);
    TestColItem *item = gtk_list_item_get_item(GTK_LIST_ITEM(listitem));
    const char *string = item->str2;
    g_print("value is %s\n",string); // Show on std out
    gtk_label_set_text(GTK_LABEL (label), string);
}

// Create the custom object
static TestColItem * testcol_item_new(const char *str1, const char *str2)
{
    TestColItem  *item = g_object_new(TESTCOL_TYPE_ITEM, NULL);
    item->str1 = g_strdup(str1);
    item->str2 = g_strdup(str2);
    return item;
}   

// Activate
static void activate (GtkApplication* app, gpointer user_data)
{
    // Create and populate custom list model
    GListStore *store = g_list_store_new(G_TYPE_OBJECT); 
    GListModel *model = G_LIST_MODEL(store);

    g_list_store_append(store,testcol_item_new("abc1","ghi1"));
    g_list_store_append(store,testcol_item_new("def2","aaa2"));
    g_list_store_append(store,testcol_item_new("aaa3","jkl3"));

    // Set selection for custom model
    GtkSingleSelection *selection = gtk_single_selection_new(G_LIST_MODEL(model));
    gtk_single_selection_set_autoselect(selection,TRUE);

    // Set Windows in child scrolled window
    GtkWidget *window = gtk_application_window_new (app);
    gtk_window_set_title (GTK_WINDOW (window), "Test");
    gtk_window_set_default_size (GTK_WINDOW (window), 200, 200);
    GtkWidget *scrolled_window = gtk_scrolled_window_new (); 
    gtk_window_set_child (GTK_WINDOW(window), scrolled_window);

    // Add column view.  Put in scrolled window.  
    GtkWidget *cv = gtk_column_view_new(GTK_SELECTION_MODEL (selection));
    gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrolled_window),cv);
    gtk_column_view_set_show_column_separators (GTK_COLUMN_VIEW (cv),TRUE);

    // Factory setup and bind for column view
    GtkListItemFactory *factory = gtk_signal_list_item_factory_new();
    g_signal_connect(factory, "setup", G_CALLBACK(setup_cb),NULL);
    g_signal_connect(factory, "bind", G_CALLBACK(bind_col1_cb),NULL);
    GtkColumnViewColumn *column = gtk_column_view_column_new("Col1", factory);
    gtk_column_view_append_column (GTK_COLUMN_VIEW (cv), column);

    // Factory setup and bind for column view
    factory = gtk_signal_list_item_factory_new();
    g_signal_connect(factory, "setup", G_CALLBACK(setup_cb),NULL);
    g_signal_connect(factory, "bind", G_CALLBACK(bind_col2_cb),NULL);
    column = gtk_column_view_column_new("Col2", factory);
    gtk_column_view_append_column (GTK_COLUMN_VIEW (cv), column);


    // Show window
    gtk_window_present (GTK_WINDOW (window));
}

// Init application
int main (int argc, char **argv)
{
    GtkApplication *app;
    int status;

    app = gtk_application_new ("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
    g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
    status = g_application_run (G_APPLICATION (app), argc, argv);
    g_object_unref (app);

    return status;
}

PROBLEM

I tried adding sort capability using the C snippet above as a guide. When working we see a sort icon in the column header that, when clicked, activates a sort. Many code test variations, this is one:

First I commented out the gtk_column_view_append_column and then added:

    // First comment out append and then add
    GtkSorter *sorter = gtk_column_view_get_sorter ((GtkColumnView *) cv); // get the sorter for col
    gtk_column_view_column_set_sorter(column, sorter); // set the sorter on column
    gtk_column_view_append_column (GTK_COLUMN_VIEW (cv), column); // put col into colview
    GtkSortListModel *smodel = gtk_sort_list_model_new ((GListModel *) model, sorter);
    selection = gtk_single_selection_new(G_LIST_MODEL(smodel));
    gtk_column_view_set_model ((GtkColumnView *)cv, (GtkSelectionModel *)selection);

Std out showed.

(out:22870): GLib-GIO-CRITICAL **: 21:39:59.759: g_list_model_get_n_items: assertion 'G_IS_LIST_MODEL (list)' failed

(out:22870): GLib-GIO-CRITICAL **: 21:39:59.759: g_list_model_get_n_items: assertion 'G_IS_LIST_MODEL (list)' failed

(out:22870): GLib-GIO-CRITICAL **: 21:39:59.759: g_list_model_get_n_items: assertion 'G_IS_LIST_MODEL (list)' failed

(out:22870): GLib-GIO-CRITICAL **: 21:39:59.759: g_list_model_get_n_items: assertion 'G_IS_LIST_MODEL (list)' failed

(out:22870): GLib-GIO-CRITICAL **: 21:39:59.759: g_list_model_get_n_items: assertion 'G_IS_LIST_MODEL (list)' failed

(out:22870): GLib-GIO-CRITICAL **: 21:39:59.759: g_list_model_get_n_items: assertion 'G_IS_LIST_MODEL (list)' failed

(out:22870): GLib-GIO-CRITICAL **: 21:39:59.797: g_list_model_get_n_items: assertion 'G_IS_LIST_MODEL (list)' failed

(out:22870): GLib-GIO-CRITICAL **: 21:39:59.807: g_list_model_get_n_items: assertion 'G_IS_LIST_MODEL (list)' failed

Clearly I'm not understanding this. Continuing to experiment, but would appreciate any suggestions on how to add C code to my working example that introduces the sort capability on the two columns.

2

There are 2 answers

1
Holger On BEST ANSWER

As the error message shows, str1 and str2 have yet to be implemented as properties. I have added this accordingly in the listing. Now it works.


// Use for testing columnview

#include <gtk/gtk.h>

// Setup custom type - each item has two strings which will be displayed in two columns

#define TESTCOL_TYPE_ITEM (testcol_item_get_type())
G_DECLARE_FINAL_TYPE (TestColItem, testcol_item, TESTCOL, ITEM, GObject)

struct _TestColItem
{
    GObject parent_instance;
     char *str1;
     char *str2;
};

struct _TestColItemClass
{
    GObjectClass parent_class;
};

G_DEFINE_TYPE (TestColItem, testcol_item, G_TYPE_OBJECT)

enum {
    PROP_STR1 = 1,
    PROP_STR2 = 2,
    N_PROPERTIES
};

static void
testcol_item_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
    TestColItem *self = (TestColItem *)object;

    switch (prop_id) {
    case PROP_STR1:
        g_value_set_string(value, self->str1);
        break;
    case PROP_STR2:
       g_value_set_string(value, self->str2);
       break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
    }
}

static void
testcol_item_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
{
    TestColItem *self = (TestColItem *)object;

    switch (prop_id) {
    case PROP_STR1:
        g_free(self->str1);
        self->str1 = g_value_dup_string(value);
        break;
    case PROP_STR2:
        g_free(self->str2);
        self->str2 = g_value_dup_string(value);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
    }
}

static void testcol_item_init(TestColItem *item)
{
    item->str1 = NULL;
    item->str2 = NULL;
}


static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };

static void testcol_item_class_init(TestColItemClass *class)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS(class);

    gobject_class->get_property = testcol_item_get_property;
    gobject_class->set_property = testcol_item_set_property;

    obj_properties[PROP_STR1] = g_param_spec_string("str1",
                                                    "Str1",
                                                    "first column",
                                                    NULL,
                                                    G_PARAM_READWRITE);

    obj_properties[PROP_STR2] = g_param_spec_string("str2",
                                                    "Str2",
                                                    "second column",
                                                    NULL,
                                                    G_PARAM_READWRITE);

    g_object_class_install_properties(gobject_class, N_PROPERTIES, obj_properties);
}


// Set label for factory widget
static void setup_cb(GtkSignalListItemFactory *factory,GObject  *listitem)
{
    GtkWidget *label =gtk_label_new(NULL);
    gtk_list_item_set_child(GTK_LIST_ITEM(listitem),label);
}

// Set text from the column1 passed item for label
static void bind_col1_cb(GtkSignalListItemFactory *factory, GtkListItem *listitem)
{
    GtkWidget *label = gtk_list_item_get_child(listitem);
    TestColItem *item = gtk_list_item_get_item(GTK_LIST_ITEM(listitem));
    const char *string = item->str1;
    g_print("value is %s\n",string); // Show on std out
    gtk_label_set_text(GTK_LABEL (label), string);
}
// Set text from the column2 passed item for label
static void bind_col2_cb(GtkSignalListItemFactory *factory, GtkListItem *listitem)
{
    GtkWidget *label = gtk_list_item_get_child(listitem);
    TestColItem *item = gtk_list_item_get_item(GTK_LIST_ITEM(listitem));
    const char *string = item->str2;
    g_print("value is %s\n",string); // Show on std out
    gtk_label_set_text(GTK_LABEL (label), string);
}

// Create the custom object
static TestColItem * testcol_item_new(const char *str1, const char *str2)
{
    TestColItem  *item = g_object_new(TESTCOL_TYPE_ITEM, NULL);
    item->str1 = g_strdup(str1);
    item->str2 = g_strdup(str2);
    return item;
}   

// Activate
static void activate (GtkApplication* app, gpointer user_data)
{
    // Create and populate custom list model
    GListStore *store = g_list_store_new(G_TYPE_OBJECT); 
    g_list_store_append(store,testcol_item_new("abc1","ghi1"));
    g_list_store_append(store,testcol_item_new("def2","aaa2"));
    g_list_store_append(store,testcol_item_new("aaa3","jkl3"));

    // Set Windows in child scrolled window
    GtkWidget *window = gtk_application_window_new (app);
    gtk_window_set_title (GTK_WINDOW (window), "Test");
    gtk_window_set_default_size (GTK_WINDOW (window), 200, 200);
    GtkWidget *scrolled_window = gtk_scrolled_window_new (); 
    gtk_window_set_child (GTK_WINDOW(window), scrolled_window);

    // Add column view.  Put in scrolled window.  
    GtkWidget *cv = gtk_column_view_new(NULL); // Null for now want to add sorter
    gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrolled_window),cv);
    gtk_column_view_set_show_column_separators (GTK_COLUMN_VIEW (cv),TRUE);
     
    // Setup sort model then overlay with with columnview
    GtkSorter *sorter = g_object_ref (gtk_column_view_get_sorter (GTK_COLUMN_VIEW (cv)));
    GtkSortListModel *model = gtk_sort_list_model_new (G_LIST_MODEL (store), sorter);
    GtkSingleSelection *selection = gtk_single_selection_new (G_LIST_MODEL (model));
    gtk_single_selection_set_autoselect (selection, TRUE);
    gtk_column_view_set_model (GTK_COLUMN_VIEW (cv), GTK_SELECTION_MODEL (selection));

    // Factory setup and bind for column 1 
    GtkListItemFactory *factory = gtk_signal_list_item_factory_new();
    g_signal_connect(factory, "setup", G_CALLBACK(setup_cb),NULL);
    g_signal_connect(factory, "bind", G_CALLBACK(bind_col1_cb),NULL);
    GtkColumnViewColumn *column = gtk_column_view_column_new("Col1", factory);
    sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (TESTCOL_TYPE_ITEM,NULL,"str1")));
    gtk_column_view_column_set_sorter (column, GTK_SORTER (sorter));
    gtk_column_view_append_column (GTK_COLUMN_VIEW (cv), column);

    // Factory setup and bind for column 2
    factory = gtk_signal_list_item_factory_new();
    g_signal_connect(factory, "setup", G_CALLBACK(setup_cb),NULL);
    g_signal_connect(factory, "bind", G_CALLBACK(bind_col2_cb),NULL);
    column = gtk_column_view_column_new("Col2", factory);
    sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (TESTCOL_TYPE_ITEM, NULL, "str2")));
    gtk_column_view_column_set_sorter (column, GTK_SORTER (sorter));
    gtk_column_view_append_column (GTK_COLUMN_VIEW (cv), column);

    // Show window
    gtk_window_present (GTK_WINDOW (window));
}

// Init application
int main (int argc, char **argv)
{
    GtkApplication *app;
    int status;

    app = gtk_application_new ("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
    g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
    status = g_application_run (G_APPLICATION (app), argc, argv);
    g_object_unref (app);

    return status;
}

enter image description here

1
dhugh On

Got a helpful suggestion over at discourse.gnome.org. Have to manually add the sorter (although sorting is built-in in a sense to columnview, the sorter has to be added manually). Also, you can define the column view with a null to allow you to create the column to associated with the sorter, which facilitates putting the sort under the columnview model.

Modified code is here. Still not sorting, but that looks like a problem with the expression evaluating the property, the basic sort function code is there

// Use for testing columnview

#include <gtk/gtk.h>

// Setup custom type - each item has two strings which will be displayed in two columns

#define TESTCOL_TYPE_ITEM (testcol_item_get_type())
G_DECLARE_FINAL_TYPE (TestColItem, testcol_item, TESTCOL, ITEM, GObject)

struct _TestColItem
{
    GObject parent_instance;
    const char *str1;
    const char *str2;
};

struct _TestColItemClass
{
    GObjectClass parent_class;
};

G_DEFINE_TYPE (TestColItem, testcol_item, G_TYPE_OBJECT)

static void testcol_item_init(TestColItem *item)
{
}

static void testcol_item_class_init(TestColItemClass *class)
{
}

// Set label for factory widget
static void setup_cb(GtkSignalListItemFactory *factory,GObject  *listitem)
{
    GtkWidget *label =gtk_label_new(NULL);
    gtk_list_item_set_child(GTK_LIST_ITEM(listitem),label);
}

// Set text from the column1 passed item for label
static void bind_col1_cb(GtkSignalListItemFactory *factory, GtkListItem *listitem)
{
    GtkWidget *label = gtk_list_item_get_child(listitem);
    TestColItem *item = gtk_list_item_get_item(GTK_LIST_ITEM(listitem));
    const char *string = item->str1;
    g_print("value is %s\n",string); // Show on std out
    gtk_label_set_text(GTK_LABEL (label), string);
}
// Set text from the column2 passed item for label
static void bind_col2_cb(GtkSignalListItemFactory *factory, GtkListItem *listitem)
{
    GtkWidget *label = gtk_list_item_get_child(listitem);
    TestColItem *item = gtk_list_item_get_item(GTK_LIST_ITEM(listitem));
    const char *string = item->str2;
    g_print("value is %s\n",string); // Show on std out
    gtk_label_set_text(GTK_LABEL (label), string);
}

// Create the custom object
static TestColItem * testcol_item_new(const char *str1, const char *str2)
{
    TestColItem  *item = g_object_new(TESTCOL_TYPE_ITEM, NULL);
    item->str1 = g_strdup(str1);
    item->str2 = g_strdup(str2);
    return item;
}   

// Activate
static void activate (GtkApplication* app, gpointer user_data)
{
    // Create and populate custom list model
    GListStore *store = g_list_store_new(G_TYPE_OBJECT); 
    g_list_store_append(store,testcol_item_new("abc1","ghi1"));
    g_list_store_append(store,testcol_item_new("def2","aaa2"));
    g_list_store_append(store,testcol_item_new("aaa3","jkl3"));

    // Set Windows in child scrolled window
    GtkWidget *window = gtk_application_window_new (app);
    gtk_window_set_title (GTK_WINDOW (window), "Test");
    gtk_window_set_default_size (GTK_WINDOW (window), 200, 200);
    GtkWidget *scrolled_window = gtk_scrolled_window_new (); 
    gtk_window_set_child (GTK_WINDOW(window), scrolled_window);

    // Add column view.  Put in scrolled window.  
    GtkWidget *cv = gtk_column_view_new(NULL); // Null for now want to add sorter
    gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrolled_window),cv);
    gtk_column_view_set_show_column_separators (GTK_COLUMN_VIEW (cv),TRUE);

    // Factory setup and bind for column 1 
    GtkListItemFactory *factory = gtk_signal_list_item_factory_new();
    g_signal_connect(factory, "setup", G_CALLBACK(setup_cb),NULL);
    g_signal_connect(factory, "bind", G_CALLBACK(bind_col1_cb),NULL);
    GtkColumnViewColumn *column = gtk_column_view_column_new("Col1", factory);
    GtkSorter *sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (TESTCOL_TYPE_ITEM, NULL, "str1")));
    gtk_column_view_column_set_sorter (column, GTK_SORTER (sorter));
    gtk_column_view_append_column (GTK_COLUMN_VIEW (cv), column);

    // Factory setup and bind for column 2
    factory = gtk_signal_list_item_factory_new();
    g_signal_connect(factory, "setup", G_CALLBACK(setup_cb),NULL);
    g_signal_connect(factory, "bind", G_CALLBACK(bind_col2_cb),NULL);
    column = gtk_column_view_column_new("Col2", factory);
    sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (TESTCOL_TYPE_ITEM, NULL, "str2")));
    gtk_column_view_column_set_sorter (column, GTK_SORTER (sorter));
    gtk_column_view_append_column (GTK_COLUMN_VIEW (cv), column);

    // Setup sort model then overlay with with columnview
    sorter = g_object_ref (gtk_column_view_get_sorter (GTK_COLUMN_VIEW (cv)));
    GtkSortListModel *model = gtk_sort_list_model_new (G_LIST_MODEL (store), sorter);
    GtkSingleSelection *selection = gtk_single_selection_new (G_LIST_MODEL (model));
    gtk_single_selection_set_autoselect (selection, TRUE);
    gtk_column_view_set_model (GTK_COLUMN_VIEW (cv), GTK_SELECTION_MODEL (selection));

    // Show window
    gtk_window_present (GTK_WINDOW (window));
}

// Init application
int main (int argc, char **argv)
{
    GtkApplication *app;
    int status;

    app = gtk_application_new ("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
    g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
    status = g_application_run (G_APPLICATION (app), argc, argv);
    g_object_unref (app);

    return status;
}