How to properly add images to buttons?

,

Hello.

I’m writing a program that incorporates play/pause button. So when a user clicks on the button its icon should change. I have decided that the best way to do this would be to create these icons and keep pointers to them and later just add them to the button using gtk_button_set_image.

Adding them for the first time to the button works (play icon when the program starts and pause icon when the button is pressed for the first time) but it doesn’t work for consequent additions.

I wrote a small program to show you what I mean.

#include <gtk/gtk.h>

struct Icons
{
	GtkWidget *play;
	GtkWidget *pause;
};

static void button_clicked(GtkWidget *button, struct Icons *icons)
{
	static gboolean play = TRUE;

	play = !play;

	if (play)
		gtk_button_set_image(GTK_BUTTON(button), icons->play);
	else
		gtk_button_set_image(GTK_BUTTON(button), icons->pause);
}

int main()
{
	gtk_init(NULL, NULL);

	GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	gtk_window_set_default_size(GTK_WINDOW(window), 300, 300);

	GtkWidget *button = gtk_button_new();

	struct Icons icons;

	icons.play = gtk_image_new_from_icon_name("media-playback-start", GTK_ICON_SIZE_BUTTON);
	icons.pause = gtk_image_new_from_icon_name("media-playback-pause", GTK_ICON_SIZE_BUTTON);

	gtk_button_set_image(GTK_BUTTON(button), icons.play);

	gtk_container_add(GTK_CONTAINER(window), button);

	g_signal_connect(window, "delete-event", G_CALLBACK(gtk_main_quit), NULL);
	g_signal_connect(button, "clicked", G_CALLBACK(button_clicked), &icons);

	gtk_widget_show_all(window);

	gtk_main();
}

Try clicking on the button more than once and you’ll see what I mean.

Why is this happening and how can I fix it? I have already found a solution, creating new icon every time the button is clicked, but that is very inefficient compared to what i had in mind when I encountered this problem so I hope I won’t have to use that.

Container widgets acquire a reference on their children when they are added, and release it when the children is removed. If the reference on the child is the last one, the widget gets destroyed.

When creating a new widget, though, the instance is given a “floating” reference; this floating reference is “sunk” by the container, so you can safely do something like:

  gtk_container_add (GTK_CONTAINER (button), gtk_label_new ("I'm a label"));

without leaking the label.

Calling gtk_button_set_image() will remove the current image child widget, and replace it with the one you pass. The first time you call it, the image widget will have the floating reference sunk, and the ownership transferred to the button; the second time, the button will release the reference it has on the old image widget, and acquire it on the new one.

So, what happens is this:

  1. play = gtk_image_new() → play reference count: 1, floating
  2. pause = gtk_image_new() → pause reference count: 1, floating
  3. gtk_button_set_image(play) → play reference count: 1, not floating
  4. gtk_button_set_image(pause) → play reference count: 0, pause reference count: 1, not floating

At this point, play has been destroyed, so your icons->play field points to garbage, and you’ll get a segmentation fault when you call gtk_button_set_image(button, icons->play).

You will need to acquire full ownership of the image widgets, so that they can survive the removal from the button:

  struct Icons icons;

  icons.play = gtk_image_new_from_icon_name ("media-playback-start", GTK_ICON_SIZE_BUTTON);
  icons.pause = gtk_image_new_from_icon_name ("media-playback-pause", GTK_ICON_SIZE_BUTTON);

  g_object_ref_sink (icons.play);
  g_object_ref_sink (icons.pause);

This way, the floating reference will already have been sunk, and gtk_button_set_image() will acquire a real reference on the widget.

Since your icons are going to exist for the duration of the program, you can simply leave them be, and let the OS reclaim the memory used once the process terminates; otherwise, you will need to release the reference you acquired on the widgets explicitly, to avoid a memory leak:

  gtk_main ();

  g_object_unref (icons.play);
  g_object_unref (icons.pause);

  return 0;

For more information:

1 Like

Thanks for the quick help and explanation @ebassi!
I still don’t understand all of what you wrote but I’ll read it more carefully later.
Thanks again.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.