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.
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;
}