Search code examples
clistviewgtk4

ListView editable, with GTK4, C and VFL


The new possibilities of GTK4 regarding TreeView are not easy to implement. So it would be nice to have an introductory example of this.

After a few hours and with the help of valuable advice from this forum, I managed to create a small test example for the implementation of ListView. Maybe it will be interesting for others as well.


Solution

  • Important Notice:

    The sole purpose of this program is to test some approaches to using ListView with GTK4, C, and VFL. It may contain errors and does not claim to be considered "best practice".

      /*
       * The sole purpose of this program is to test some approaches to using 
       * ListView with GTK4, C, and VFL.It may contain errors and does not 
       * claim to be considered "best practice".
       */
    
    #include <gtk/gtk.h>
    
    static GListModel* create_model()
    {
        GListStore *store;
        store = g_list_store_new(GTK_TYPE_STRING_OBJECT);
        g_list_store_append(store, gtk_string_object_new("Washington,D.C."));
        g_list_store_append(store, gtk_string_object_new("London"));
        g_list_store_append(store, gtk_string_object_new("Paris"));
        g_list_store_append(store, gtk_string_object_new("Berlin"));
        g_list_store_append(store, gtk_string_object_new("Rom"));
    
        return G_LIST_MODEL (store);
    }
    
    /*
     * Create a class derived from Widget that contains the controls 
     * and implements the Layout Manager.
     */
    
    #define LISTVFL_TYPE_LAYOUT (listvfl_layout_get_type())
    
    G_DECLARE_FINAL_TYPE (ListvflLayout, listvfl_layout, LISTVFL, LAYOUT, 
    GtkWidget)
    
    struct _ListvflLayout
    {
        GtkWidget parent_instance;
     
        GtkWidget *scrolledwindow;  
        GtkWidget *listview;
        GtkWidget *label;
        GtkWidget *btndelete;
        GtkWidget *btnaz;
        GtkWidget *btnza;
        GtkWidget *btnadd;
        GtkWidget *entry;
        GtkSingleSelection *selection;
        guint position;
      };
    
    G_DEFINE_TYPE (ListvflLayout, listvfl_layout, GTK_TYPE_WIDGET)
    
    static void
    setup_list_item_cb (GtkListItemFactory *factory, GtkListItem *list_item)
    {
        GtkWidget*label = gtk_label_new (NULL);
        gtk_list_item_set_child (GTK_LIST_ITEM(list_item), label);
    }
    
    static void
    bind_list_item_cb (GtkListItemFactory *factory, GtkListItem *list_item, 
    gpointer listvfllayout)
    {
        GtkWidget *label = gtk_list_item_get_child(list_item);
        GtkStringObject *str = gtk_list_item_get_item(list_item);
        const char *string = gtk_string_object_get_string(str);
        gtk_label_set_text(GTK_LABEL(label), string);
    
        if (gtk_list_item_get_selected(list_item))
        {
            ListvflLayout  *widget = (ListvflLayout*)listvfllayout;
            GtkWidget *label1 = GTK_WIDGET(widget->label);
            gchar *format = "<span foreground='blue' 
    background='yellow'size='x-large'>\%s </span>";
            gchar *markup;
            markup = g_markup_printf_escaped (format,string);
            gtk_label_set_markup (GTK_LABEL(label1),markup);        
            g_free (markup);
            widget->position = gtk_list_item_get_position(list_item);
        }
    }
    
    // not in use !!
    static void
    selection_changed(GObject *object, GParamSpec *pspec, GtkWidget 
    *listvfllayout)
    {
    //  GtkListItem *list_item 
    =gtk_single_selection_get_selected_item(GTK_SINGLE_SELECTION(object));
    //  pos = gtk_single_selection_get_selected(GTK_SINGLE_SELECTION(object));
    //  const char *string = 
    gtk_string_object_get_string(GTK_STRING_OBJECT(list_item));
    //  ListvflLayout  *widget = (ListvflLayout*)listvfllayout;
    //  gtk_label_set_label(GTK_LABEL(widget->label),string);
    }
    
    gint sort_dec(gconstpointer *a, gconstpointer *b, gpointer un_used)
    {
        const char *string1 = 
    gtk_string_object_get_string(GTK_STRING_OBJECT(a));
        const char *string2 = 
    gtk_string_object_get_string(GTK_STRING_OBJECT(b));
        return g_strcmp0 (string1, string2);
    }
    
    gint sort_asc(gconstpointer *a, gconstpointer *b, gpointer un_used)
    {
        const char *string1 = gtk_string_object_get_string(GTK_STRING_OBJECT(a));
        const char *string2 = 
    gtk_string_object_get_string(GTK_STRING_OBJECT(b));
        return g_strcmp0 (string2, string1);
    }
    
    static void sort_az(GtkWidget * btnaz, gpointer listvfllayout)
    {
        ListvflLayout *widget = LISTVFL_LAYOUT(listvfllayout);
        GListModel *model = gtk_single_selection_get_model(widget->selection);
        GListStore *store = G_LIST_STORE(model);
        g_list_store_sort(store,(GCompareDataFunc)sort_dec,NULL);
    }
    
    static void sort_za(GtkWidget *btnza, gpointer listvfllayout)
    {
        ListvflLayout *widget = LISTVFL_LAYOUT(listvfllayout);
        GListModel *model = gtk_single_selection_get_model(widget->selection);
        GListStore *store = G_LIST_STORE(model);
        g_list_store_sort(store,(GCompareDataFunc)sort_asc,NULL);
    }
    
    static void delete(GtkWidget *btndelete, gpointer listvfllayout)
    {
        ListvflLayout *widget = LISTVFL_LAYOUT(listvfllayout);
        GListModel *store = gtk_single_selection_get_model(widget->selection);
        if (widget->position == 0) gtk_label_set_label(GTK_LABEL(widget- 
       >label),"-empty-");
        if(g_list_model_get_n_items(store)) 
    g_list_store_remove(G_LIST_STORE(store), widget->position);
    }
    
    static void add(GtkWidget *btnadd, gpointer listvfllayout)
    {
        ListvflLayout *widget = LISTVFL_LAYOUT(listvfllayout);
        GtkEntryBuffer *buffer;
        const char *text;
        buffer = gtk_entry_get_buffer(GTK_ENTRY(widget->entry));
        text = gtk_entry_buffer_get_text(buffer);
        if (strlen(text) > 0)
        {
            GListModel *store = gtk_single_selection_get_model(widget- 
       >selection);
            g_list_store_append(G_LIST_STORE(store), 
    gtk_string_object_new(text));
        }
        gtk_entry_buffer_delete_text(buffer,0,-1);
    }
    
    static void
    listvfl_layout_dispose (GObject *object)
    {
        ListvflLayout *self = LISTVFL_LAYOUT (object);
        g_clear_pointer (&self->scrolledwindow, gtk_widget_unparent);  
        g_clear_pointer (&self->label, gtk_widget_unparent);
        g_clear_pointer (&self->btndelete, gtk_widget_unparent);
        g_clear_pointer (&self->btnaz, gtk_widget_unparent);
        g_clear_pointer (&self->btnza, gtk_widget_unparent);
        g_clear_pointer (&self->btnadd, gtk_widget_unparent);
        g_clear_pointer (&self->entry, gtk_widget_unparent);
        G_OBJECT_CLASS (listvfl_layout_parent_class)->dispose (object);
    }
    
    static void
    listvfl_layout_class_init (ListvflLayoutClass *class)
    {
        GObjectClass *object_class = G_OBJECT_CLASS(class);
        GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
        object_class->dispose = listvfl_layout_dispose;
        // Layout manager
        gtk_widget_class_set_layout_manager_type 
    (widget_class,GTK_TYPE_CONSTRAINT_LAYOUT);
    }
    
    // Creating the Conistraints for the Layout
    static void
    build_constraints (ListvflLayout *self, GtkConstraintLayout *manager)
    {
        const char *const vfl[] = {
            "H:|-20-[label(110)]-60-[btndelete(110)]-(<=0)-|",
            "H:|-20-[scrolledwindow(110)]-60-[btnaz(50)]-10-[btnza(50)]-(<=0)-|",
            "H:|-20-[scrolledwindow(110)]-60-[entry(110)]-(<=0)-|",
            "H:|-20-[scrolledwindow(110)]-60-[btnadd(110)]-(<=0)-|",
            "V:|-20-[label(30)]-20-[scrolledwindow(250)]-(<=0)-|",
            "V:|-20-[btndelete(30)]-10-[btnaz(20)]-10-[entry(30)]-10-[btnadd(30)]-(<=0)-|",
            "V:|-20-[btndelete(30)]-10-[btnza(20)]-(<=0)-|",
            
        };
    
    GError *error = NULL;
    
    gtk_constraint_layout_add_constraints_from_description (manager, vfl, G_N_ELEMENTS (vfl),
                0,0,
                &error,
                "label", self->label,
                "btndelete", self->btndelete,
                "scrolledwindow", self->scrolledwindow, 
                "listview", self->listview,
                "btnaz", self->btnaz,
                "btnza", self->btnza,
                "entry", self->entry,
                "btnadd", self->btnadd,
                NULL);
       if (error != NULL)
       {
        g_printerr("VFL parsing Error: %s\n", error->message);
        g_error_free(error);
       }
    }
    
    //Initializing the class
    static void
    listvfl_layout_init (ListvflLayout *self)
    {
        GtkListItemFactory *factory;    
        GListModel *model;      
        self->position = 0;
    
        GtkWidget *widget = GTK_WIDGET (self);
    
        self->label = gtk_label_new(NULL);
        gtk_widget_set_parent(self->label,widget);
    
        self->btndelete = gtk_button_new_with_label("Delete");
        gtk_widget_add_css_class(self->btndelete, "destructive-action");
        gtk_widget_set_parent(self->btndelete,widget);
        g_signal_connect(self->btndelete,"clicked",G_CALLBACK(delete),self);
    
        self->listview = gtk_list_view_new(NULL,NULL);        
        model = create_model();
        self->selection = gtk_single_selection_new(G_LIST_MODEL(model));
        gtk_single_selection_set_autoselect(self->selection, TRUE);
        gtk_list_view_set_model(GTK_LIST_VIEW(self- 
       >listview),GTK_SELECTION_MODEL(self->selection));
        g_signal_connect (self->selection,"notify::selected", 
    G_CALLBACK(selection_changed),self);
    
        factory = gtk_signal_list_item_factory_new();
        g_signal_connect (factory, "setup", G_CALLBACK (setup_list_item_cb), 
    NULL);
        g_signal_connect (factory, "bind",G_CALLBACK (bind_list_item_cb),self);
        gtk_list_view_set_factory (GTK_LIST_VIEW (self->listview),factory);
    
        self->scrolledwindow = gtk_scrolled_window_new();     
        gtk_widget_set_parent(self->scrolledwindow,widget);  
        gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self- 
       >scrolledwindow),self->listview);  
    
        self->btnaz = gtk_button_new_with_label("a-z");
        gtk_widget_set_parent(self->btnaz,widget);
        g_signal_connect(self->btnaz,"clicked",G_CALLBACK(sort_az),self);
    
        self->btnza = gtk_button_new_with_label("z-a");
        gtk_widget_set_parent(self->btnza,widget);
        g_signal_connect(self->btnza,"clicked",G_CALLBACK(sort_za),self);
    
        self->entry = gtk_entry_new();
        gtk_widget_set_parent(self->entry,widget);
        gtk_widget_set_focus_child(GTK_WIDGET(self),self->entry);
        g_signal_connect(self->entry,"activate",G_CALLBACK(add),self);
    
        self->btnadd = gtk_button_new_with_label("Add");
        gtk_widget_add_css_class(self->btnadd, "suggested-action");
        gtk_widget_set_parent(self->btnadd,widget);
        g_signal_connect(self->btnadd,"clicked",G_CALLBACK(add),self);
    
        //Import and initialize the widget's layout manager
        GtkLayoutManager *manager = gtk_widget_get_layout_manager (GTK_WIDGET 
    (self));
        build_constraints(self, GTK_CONSTRAINT_LAYOUT (manager));
    }
    
    //From here, a applicationswindow is created  and the class is added via a 
    box
    static void
    activate (GtkApplication *app, gpointer user_date)
    {
        GtkWidget *window;
        GtkWidget *box, *listvfllayout;
    
        window = gtk_window_new();
        gtk_window_set_title (GTK_WINDOW (window), "ListView editable with 
    VFL");
        gtk_widget_set_size_request (window, 200,400);
        g_object_add_weak_pointer (G_OBJECT (window),(gpointer *)&window);
        box = gtk_box_new(GTK_ORIENTATION_VERTICAL,12);
        gtk_window_set_child(GTK_WINDOW (window), box);
        listvfllayout = g_object_new(listvfl_layout_get_type(), NULL);
        gtk_widget_set_hexpand(listvfllayout, TRUE);
        gtk_widget_set_vexpand(listvfllayout, TRUE);
        gtk_box_append (GTK_BOX (box), listvfllayout);
        gtk_application_add_window(app, GTK_WINDOW(window));
        gtk_window_present (GTK_WINDOW(window));
    }
    
    int
    main (int argc, char **argv)
    {
        GtkApplication *app;
        int status;
    
        app = gtk_application_new 
    ("holger.test.viewflf",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;
    }
    

    And this is what the test program should look like