Gtk_box_new() returning pointer to an already existing GtkBox in GTK4

Hello,
I’m currently helping with the development of a simulator that requires large amounts of data to be displayed in the form of tables. The tables themselves are GtkColumnViews that contain boxes on each column. The creation of said boxes is handled by a GtkListItemFactory and a setup callback, and the data updates are handled by a bind callback.

	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_address_cb),NULL);
	GtkColumnViewColumn *column = gtk_column_view_column_new(M_ADDR, factory);
	gtk_column_view_append_column (GTK_COLUMN_VIEW (column_view), column);
	g_object_unref (column);

The setup callback:

static void setup_cb(GtkSignalListItemFactory *factory, GObject *listitem) {
	GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
	GtkWidget *label = gtk_label_new(NULL);

	// The box is compacted with some CSS
	g_object_set(label,"height-request", 5, NULL);
	apply_css(box, CSS_COMPACT, CSS_COMPACT_R);


	// The box is assigned both style providers (Read and Write)
	GdkDisplay *display = gtk_widget_get_display(box);
	gtk_style_context_add_provider_for_display(
		display,
		GTK_STYLE_PROVIDER(write_provider),
		GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
	);
	gtk_style_context_add_provider_for_display(
		display,
		GTK_STYLE_PROVIDER(read_provider),
		GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
	);

	gtk_box_append(GTK_BOX(box), label);
	gtk_list_item_set_child(GTK_LIST_ITEM(listitem), box);
}

The bind callback:

static void bind_address_cb(GtkSignalListItemFactory *factory, GtkListItem *listitem) {
	// The elements of the cell get created
	GtkWidget *box = gtk_list_item_get_child(listitem);
	GtkWidget *label = gtk_widget_get_first_child(box);
	MemoryLine *item = gtk_list_item_get_item(GTK_LIST_ITEM(listitem));

	// A pointer to the widget is saved
	item->widget[ADDRESS] = box;

	// A string with the address gets created
	char *string = g_strdup_printf("0x%x", item->address);
	gtk_label_set_text(GTK_LABEL(label), string);
	g_free(string);

	// The color of the background is set to be the same as the item's.
	if (item->color_changed[ADDRESS] == TRUE && item->color != NULL) {
		set_memory_widget_background_color(box, item, ADDRESS);
	}
}

Some of the rows have CSS styling applied to them by the following GtkCssProvider:

	char *css = g_strdup_printf(CSS_READ_R, READ_COLOR);		// The color is calculated
	read_provider = gtk_css_provider_new();
	gtk_css_provider_load_from_string(read_provider, css);

When applying some CSS to some of the rows at the top, the same CSS also gets applied to some of the ones near the bottom. When the table is twice as large, it gets applied to some elements in the middle and some in the bottom as well. This is the function that applies CSS to a certain cell of the table:

static void set_memory_widget_background_color(GtkWidget *widget, MemoryLine *item, int column) {
	//  The previous CSS class is removed from the item
	gtk_widget_remove_css_class(item->widget[column], CSS_READ);
	gtk_widget_remove_css_class(item->widget[column], CSS_WRITE);

	// If the item has been read
	if (g_strcmp0(item->color, READ_COLOR) == 0) {
		// The read selector is assigned to the widget
		gtk_widget_add_css_class(widget, CSS_READ);
	} else if (g_strcmp0(item->color, WRITE_COLOR) == 0) {
		// The write selector is assigned to the widget
		gtk_widget_add_css_class(widget, CSS_WRITE);
	}
	item->color_changed[column] = FALSE;
}

After checking with GDB, both rows contain pointers to the same box, so when the CSS is applied to the first one, it is also applied to the rows that have the same pointers at the bottom.

Is this some memory saving feature ? If so, is there a way to disable it ?

Thanks in advance.

gtk_box_new() will always return a new object.

GTK’s list widgets do recycle list items, but obviously each widget is only used for one row at a time. That’s why setup and bind are separate signals.

gtk_style_context_add_provider_for_display() is global (hence ‘for_display’), so it doesn’t make sense to call it in setup. You can call it once, and all widgets will be able to use it.

I’ve moved the gtk_style_context_add_provider_for_display() out of the setup bind, but I’m still facing the issue of CSS applying to rows at the bottom. The way the CSS is applied to a row is by fetching the box with GtkWidget *box = gtk_list_item_get_child(listitem); in the bind callback and then adding the CSS class to that box.

I save a pointer to the widget to later clear the style from elsewhere if needed, and that’s how I noticed that some rows have the same box.

This is how the table looks; the three green boxes at the top from 0x8000040 to 0x800008c have the correct style, but at the bottom, some of the boxes are also styled, which shouldn’t be the case:
Cells

This happens because they are the same boxes as the ones on top, and when the bind callback gets triggered for the ones that are at the top, the style is changed in both positions of the table.

The bind callback of the bottom-most rows doesn’t get called unless the table is scrolled down, if that helps.

What could be the cause ?

I think the issue is that you’re not removing the CSS class from the widget if it’s recycled from a row that has the color to one that doesn’t. You could remove it unconditionally in unbind, or just ensure that you remove it when appropriate in bind. gtk_widget_has_css_class() may be of use.

Also, if the contents of a row can change dynamically, you need to connect to signals in bind to update the widgets’ appearance (and disconnect in unbind).

I’ve tried to remove the style unconditionally in unbind and it does not produce great results because unbind gets called as soon as the next row gets it’s style applied. I’ve tried with the first column of the ColumnView and added some print information to check when the callbacks are getting executed, and unbind is called almost immediately after binding another column:
Unbind
Result

I have not tried removing it in bind, but I don’t think it will work. Sure, it would remove the style once the bottom rows are binded, but when you scroll back up not all the top rows get binded again (I’ve checked with the previous prints if binding happens when doing this), therefore, the style will not get applied again if you scroll back up.

As an alternative I’ve also tried adding a new styled box as the only child of the box that i was trying to style previously, and adding the text to said styled box, but the same thing happens, since the pointer to the list item is the same, whatever is applied at the top will get applied at the bottom.

The problem is that the boxes at the top are recycled no matter what. Is there a way to disable that behavior entirely ? It seems incompatible with GTK’s pholosophy of using CSS to style widgets.

I’m willing to compromise the performance impact of an slightly larger table for a simpler codebase and a functional, straightforward way to style a ColumnView. If you have any ideas or alternatives, let me know.

Thanks for the help so far

After much experimentation I found out that what triggers the recycling is adding elements after the first bind. For instance, the addresses and the content were already set up after the same bind. If I tried to populate an empty column with some text after the first bind, and that column didn’t have any text previously, it would repeat as shown before with CSS.

My solution was to create a CSS provider that had a the background-color property set to transparent: .bg-color-none { background-color: transparent}, and apply it to every single row. Therefore, because every row already had CSS styling applied to it, modifying the style would not trigger the recycling.

This doesn’t work in the setup, it has to be done unconditionally in the bind:

static void bind_content_cb(GtkSignalListItemFactory *factory, GtkListItem *listitem) {
	// The elements of the cell get created
	GtkWidget *box = gtk_list_item_get_child(listitem);
	GtkWidget *label = gtk_widget_get_first_child(box);
	MemoryLine *item = gtk_list_item_get_item(GTK_LIST_ITEM(listitem));

	...

	// If the item has no color yet, it gets assigned a transparent bg.
	// This is required or else some rows at the bottom of large tables will also get styled
	gtk_widget_add_css_class(box, CSS_NONE);
	...

I don’t know the reason why this happens, it seems counter intuitive and more like a bug than anything else. Whatever the case may be, thanks for the help.