Search code examples
cgtk3

GTK+, C-based software, corruption with Images


I'm writing a bit of code for a digital-read-out/controller for a home CNC mill, target is a Pi with TFT/LCD, and I've elected to use C, which I know, and GTK+3, which I know rather less (fumbling in the dark, springs to mind).

One page of the controller (scheduled via an update-call from the main application/GTK timer event, depending on active page) is supposed to present 3 (or whatever) buttons, with a visual indicator of button states. Button images are loaded from .png files.

On creation, I load the selected/de-selected images and store in a structure for each button. On refresh I change the state of a selected/de-selected image and update the button image with the appropriate image. Or that's the theory. On init I default the image to de-selected. I can change to selected easily enough, but a subsequent call to deselected image results in a GTK error message.

Function play_init() is called to create the page content (prefix "play..." - this was going to be a template 'page' to test different GTK approaches - this was to fix this same problem implemented in a different way without breaking the main code base).

Each 'page' creates a container that the main() appends to a GTK notebook, so this start-up code doesn't worry about the window setup - that's elsewhere.

In the code section below I've hacked a fair bit and added debug code to default to de-selected, then change to selected, back to de-selected and so-on, all with an "I'm here" debug statement. This to avoid any issues with pointers falling out of scope, etc to try to understand where I'm going wrong. I know it's related to setting the button image, but can't figure out what I've missed here. Really, the image selection should be (is) managed in the containing event-box event handler... but dumping the debug code here just helps to keep things together.

All the debug text (1,2,3,4,5 etc) is just so that I can find the point where the code starts to fail. I've recently added the gtk_button_set_image(.., NULL) to try to "unload" the image prior to setting the new image. Nada, no change.

/****************************************************
 * Local type definitions
 ****************************************************/
enum buttons {START, PAUSE, STOP};
typedef enum buttons button_t;

struct play_eb_buttons
{
   GtkWidget *button;      // Button holding the button image
   GtkWidget *im_selc;     // "Selected" image
   GtkWidget *im_deselc;   // "De-selected" image
   GtkWidget *eb;          // Event box containing button
   GtkWidget *parent;      // Points to the parent of the event box
   bool       selc;        // New selection state
   bool       last_selc;   // Previous selection state
};
typedef struct play_eb_buttons play_eb_button_t;

/****************************************************
 * Code Implementation                ** INTERNALS **
 ****************************************************/
void playSetSelcState(play_eb_button_t *button, bool selc_state)
{
   button->selc = selc_state;
   if ((button->selc == true ) && (button->last_selc == false ))
   {
      printf("Setting image (selc) 0x%llX\n", button->im_selc);
      gtk_button_set_image(GTK_BUTTON(button->button), button->im_selc);
   }
   else if ((button->selc == false ) && (button->last_selc == true))
   {
      printf("Setting image (deselc) 0x%llX\n", button->im_deselc);
      gtk_button_set_image(GTK_BUTTON(button->button), button->im_deselc);
   }
   button->last_selc = button->selc;

}

gint playgreen_press_callback( GtkWidget *widget, GdkEvent  *event, gpointer   callback_data )
{
   printf("Green callback invoked\n");
   playSetSelcState(&play_eb_button[START], true);
   playSetSelcState(&play_eb_button[PAUSE], false);
   playSetSelcState(&play_eb_button[STOP],  false);
   return TRUE;
}
gint playyellow_press_callback( GtkWidget *widget, GdkEvent  *event, gpointer   callback_data )
{
   printf("Yellow callback invoked\n");
   playSetSelcState(&play_eb_button[START], false);
   playSetSelcState(&play_eb_button[PAUSE], true);
   playSetSelcState(&play_eb_button[STOP],  false);
   return TRUE;
}
gint playred_press_callback( GtkWidget *widget, GdkEvent  *event, gpointer   callback_data )
{
   printf("Red callback invoked\n");
   playSetSelcState(&play_eb_button[START], false);
   playSetSelcState(&play_eb_button[PAUSE], false);
   playSetSelcState(&play_eb_button[STOP],  true);
   return TRUE;

}

void playCreateButton( GtkWidget *parent, char *selc, char *deselc, char *name, int col, int row, GCallback func, play_eb_button_t *button )
{
   GtkWidget *label;

   button->button    = gtk_button_new();
   button->eb        = gtk_event_box_new();
   button->parent    = parent;
   button->selc      = false;
   button->last_selc = false;
   button->im_selc   = gtk_image_new_from_file(selc);
   button->im_deselc = gtk_image_new_from_file(deselc);
   gtk_widget_set_vexpand(button->im_selc, TRUE);
   gtk_widget_set_vexpand(button->im_deselc, TRUE);
   printf("1\n");
   gtk_button_set_image(GTK_BUTTON(button->button), NULL);
   gtk_button_set_image(GTK_BUTTON(button->button), button->im_deselc);
   printf("2\n");
   gtk_button_set_image(GTK_BUTTON(button->button), NULL);
   gtk_button_set_image(GTK_BUTTON(button->button), button->im_selc);
   printf("3\n");
   gtk_button_set_image(GTK_BUTTON(button->button), NULL);
   gtk_button_set_image(GTK_BUTTON(button->button), button->im_deselc);
   printf("4\n");
   gtk_button_set_image(GTK_BUTTON(button->button), NULL);
   gtk_button_set_image(GTK_BUTTON(button->button), button->im_selc);
   printf("5\n");
   gtk_button_set_image(GTK_BUTTON(button->button), NULL);
   gtk_button_set_image(GTK_BUTTON(button->button), button->im_deselc);
   printf("6\n");
   gtk_button_set_image(GTK_BUTTON(button->button), NULL);
   gtk_container_add(GTK_CONTAINER(button->eb), button->button);
   gtk_grid_attach(GTK_GRID(parent), button->eb, col,row, 1, 1);
   g_signal_connect (button->eb, "button_press_event", G_CALLBACK (func), NULL);

//   eb = gtk_event_box_new();
//   label = gtk_label_new(name);
//   gtk_container_add(GTK_CONTAINER(eb), label);
//   gtk_widget_set_name(label, "buttons");
//   gtk_grid_attach(GTK_GRID(parent), eb, col+1, row, 1, 1);
//   g_signal_connect (eb, "button_press_event", G_CALLBACK (func), NULL);

   printf("'%s' at 0x%16llX and 0x%16llX\n", name, button->im_selc, button->im_deselc);
}

/****************************************************
 * Code Implementation                ** EXTERNALS **
 ****************************************************/
/*
 * play_init()
 *
 * Setup for the complete DRO page for the GUI
 */
void play_init()
{
   GtkWidget *grid;

   play_container = gtk_event_box_new();
   grid = gtk_grid_new();
   gtk_container_add(GTK_CONTAINER(play_container), grid);

   gtk_widget_modify_bg(grid, GTK_STATE_NORMAL, &colour_black);

   playCreateButton(grid, "push-button-green.png" ,"push-button-green-dselc.png",  "Run!",  0, 1, (GCallback)playgreen_press_callback,  &play_eb_button[START]);
   playCreateButton(grid, "push-button-yellow.png","push-button-yellow-dselc.png", "Pause", 0, 2, (GCallback)playyellow_press_callback, &play_eb_button[PAUSE]);
   playCreateButton(grid, "push-button-red.png"   ,"push-button-red-dselc.png",    "STOP",  0, 3, (GCallback)playred_press_callback,    &play_eb_button[STOP]);
   playSetSelcState(&play_eb_button[START], true);
   playSetSelcState(&play_eb_button[START], false);

}

Output from my machine (host: HP Z600, running Ubuntu 20.04)

Hostname = 'mike-HP-Z600-Workstation'
Initialising
State = Connecting
1
2
3

(SiegDROsGtk3:556896): Gtk-CRITICAL **: 19:42:24.182: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
4

(SiegDROsGtk3:556896): Gtk-CRITICAL **: 19:42:24.182: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
5

(SiegDROsGtk3:556896): Gtk-CRITICAL **: 19:42:24.182: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
6
'Run!' at 0x    56446A49E2B0 and 0x    56446A49E420
1
2
3

(SiegDROsGtk3:556896): Gtk-CRITICAL **: 19:42:24.185: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
4

(SiegDROsGtk3:556896): Gtk-CRITICAL **: 19:42:24.185: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
5

(SiegDROsGtk3:556896): Gtk-CRITICAL **: 19:42:24.185: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
6
'Pause' at 0x    56446A49E700 and 0x    56446A49E870
1
2
3

(SiegDROsGtk3:556896): Gtk-CRITICAL **: 19:42:24.187: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
4

(SiegDROsGtk3:556896): Gtk-CRITICAL **: 19:42:24.187: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
5

(SiegDROsGtk3:556896): Gtk-CRITICAL **: 19:42:24.187: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
6
'STOP' at 0x    56446A49E870 and 0x    56446A49E2B0
Setting image (selc) 0x56446A49E2B0

(SiegDROsGtk3:556896): Gtk-CRITICAL **: 19:42:24.187: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
Setting image (deselc) 0x56446A49E420

(SiegDROsGtk3:556896): Gtk-CRITICAL **: 19:42:24.188: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
Setting image (selc) 0x56446A49E2B0

(SiegDROsGtk3:556896): Gtk-CRITICAL **: 19:42:26.806: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
Graphics terminated
Closing

I have tried GDB through this and watching the eb_button array expecting corruption or something, but that structure holds well. The error messages suggest the image is impacted after before call "3" (the image pointer remains non-null)

If I let the event handler code run I can get other GTK errors, all around the same call structure to set the button image...

(SiegDROsGtk3:556962): Gtk-CRITICAL **: 19:48:22.637: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
Setting image (selc) 0x558A215F72B0

(SiegDROsGtk3:556962): Gtk-CRITICAL **: 19:48:25.225: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
Setting image (deselc) 0x558A215F7420
Setting image (selc) 0x558A215F72B0

(SiegDROsGtk3:556962): Gtk-CRITICAL **: 19:48:30.341: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
Setting image (deselc) 0x558A215F7420

(SiegDROsGtk3:556962): GLib-GObject-CRITICAL **: 19:48:32.898: g_object_ref: assertion 'G_IS_OBJECT (object)' failed

(SiegDROsGtk3:556962): Gtk-CRITICAL **: 19:48:32.898: gtk_widget_get_parent: assertion 'GTK_IS_WIDGET (widget)' failed
Setting image (selc) 0x558A215F72B0

(SiegDROsGtk3:556962): Gtk-CRITICAL **: 19:48:35.458: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
Setting image (deselc) 0x558A215F7420

(SiegDROsGtk3:556962): Gtk-CRITICAL **: 19:48:38.022: gtk_button_set_image: assertion 'image == NULL || GTK_IS_WIDGET (image)' failed
Setting image (selc) 0x558A215F72B0

Running this without the initialisation debug stuff, the buttons are all de-selected, I can select e.g. Green, and it highlights with the correct selc'd image; Selecting e.g. yellow button the green does not de-select (get that error), and yellow successfully selects. Pressing Reg and yellow doesn't deselect (get the error) but red does. At some point I'll either lose the button image completely or sometimes get an image-not-loaded default image.

It looks as though successive calls to gtk_button_set_image is tanking the pre-loaded image.

Clearly I'm doing something daft, but I can't find a sensible google hit as to what it is that I'm doing wrong.

Help?!


Solution

  • That's because of reference counting. Detailed description can be found here, short version below.

    All GObjects start with rc (reference count, refcount) set to 1, but all widgets are GInitiallyUnowned (they have special flag, indicating that it doesn't have an owner yet).

    When you pack a widget to a container for the first time g_object_ref_sink is called, that takes ownership of a widget, sets rc to 1 and removes flag. Thus, when you replace image with a new one, previous image is dereferenced. When dereferenced, it's rc drops to 0 and object is automatically destroyed.

    You can inspect this behaviour in GDB by casting any object to (GObject*) and looking at it's ref_count field.

    What can be done:

    1. manually reference images. If you call _ref_sink, you take object ownership. When it's added to a container, it will be referenced (rc will be 2) and dereferenced when removed (rc will be 1, thus object will be still alive). You'll also need to dereference the image at the end of program.
    2. Store icon names instead of widgets and create new images in playSetSelcState