TLDR: widgets in expanders (GtkExpander
s) don’t receive destroy signals. Is this a bug or intended behaviour, and if it is the latter, why/what are the exact rules for when the destroy signal is not emitted?
As I understand it, when their parent is destroyed (for example a window being closed) all children should receive the destroy signal as they are freed. This can be useful for freeing data associated with the widget, and is behaviour depended upon by g_signal_connect_data (the callback for freeing the data is never emitted when the instance this function is called on is inside a GtkExpander
)
Yet it seems that in GTK4, this does not function correctly when the child is inside an expander. Even when the parent of the expander is destroyed, the child of the expander does not ever see a destroy signal.
I have tried to create a minimal example to demonstrate this: it compiles with both GTK3 and GTK4
(cc -o main main.c $(pkg-config --cflags --libs gtk4)
, or swap out gtk4
for gtk+-3.0
to use GTK3) and uses preprocessing bits to make the very minor tweaks needed to make it compatible with both APIs.
What’s important, though, is that upon running and closing the window, with GTK3 it prints messages for both buttons being destroyed, yet with GTK4 only the second button which is not in an expander is caused to print a message by receiving the destroy signal.
#include <gtk/gtk.h>
#include <stdio.h>
#if GTK_MAJOR_VERSION == 3 // the following are just aliases, and resulting behaviour should be the same regardless of version of gtk
void gtk_expander_set_child (GtkExpander *parent, GtkWidget *child) { gtk_container_add(GTK_CONTAINER(parent), child); }
void gtk_window_set_child (GtkWindow *parent, GtkWidget *child) { gtk_container_add(GTK_CONTAINER(parent), child); }
void gtk_box_append (GtkBox *parent, GtkWidget *child) { gtk_container_add(GTK_CONTAINER(parent), child); }
#endif
static void btn1_destroyed(GtkButton *btn, gpointer user_data) { printf("Button 1 destroyed!\n"); }
static void btn2_destroyed(GtkButton *btn, gpointer user_data) { printf("Button 2 destroyed!\n"); }
static void activate(GApplication *app, gpointer user_data)
{
GtkWidget *win = gtk_application_window_new(GTK_APPLICATION(app)); // create window
gtk_window_set_default_size(GTK_WINDOW(win), 200, 200); // set default size
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); // 10px padding
gtk_window_set_child(GTK_WINDOW(win), box); // add box to window
GtkWidget *btn1 = gtk_button_new_with_label("Button 1"); // create first button
GtkWidget *expander = gtk_expander_new("Expander"); // create expander
gtk_expander_set_child(GTK_EXPANDER(expander), btn1); // add first button to expander
GtkWidget *btn2 = gtk_button_new_with_label("Button 2"); // create second button
gtk_box_append(GTK_BOX(box), expander); // add expander to box
gtk_box_append(GTK_BOX(box), btn2); // add second button to box
g_signal_connect(btn1, "destroy", G_CALLBACK(btn1_destroyed), NULL); // for each button, add a callback to print a message when the destroy signal is received
g_signal_connect(btn2, "destroy", G_CALLBACK(btn2_destroyed), NULL);
#if GTK_MAJOR_VERSION == 3 // whichever way of showing the window is used in this version of gtk
gtk_widget_show_all(win);
#else
gtk_widget_show(win);
#endif
}
int main(int argc, char *argv[]) // basic main function to set up application and activate it
{
GtkApplication *app = gtk_application_new("com.test", G_APPLICATION_FLAGS_NONE);
g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
int status = g_application_run(G_APPLICATION(app), argc, argv);
g_object_unref(app);
return status;
}