Callback function doesn't modify widget

,

I am trying to write a GTK 4 app in C language. I want to make a callback function that reacts to item chosen from a dropdown widget and updates the entry widget with the value of selected item.
My function works only if I make entry widget a global variable and use it directly in the function. I would prefer to pass a pointer to that widget and make callback function modify it this way. Other functions that were passed pointers to widgets worked, but this one doesn’t want to.

//main.c
#include <gtk/gtk.h>
#include <windows.h>
#include <stdio.h>
#include <stdint.h>

#include "uiElements.h"


static void baudrateDropdownSelectCallback(GtkDropDown *dropdown, void *data[]);
static void activateApp(GApplication *app);


int main (int argc, char *argv[]) {

  //starting application
  return startApp("exe.GTKforSO", G_APPLICATION_DEFAULT_FLAGS, activateApp, argc, argv);
}

static void activateApp (GApplication *app) {

  static GtkWidget *win;
  static GtkWidget *box;

  //window
  win=initWindow(app, "GTKforSO", 400, 200);

  //box
  box=initBox(GTK_ORIENTATION_HORIZONTAL, 0);
    gtk_window_set_child(GTK_WINDOW(win), box);

  static GtkWidget *baudrateDropdown;
  static GtkWidget *baudratesBox;
  static GtkWidget *baudrateEntry;
  static GtkEntryBuffer *baudrateEntryBuffer;


    /** the important part **/
      baudratesBox=initBox(GTK_ORIENTATION_HORIZONTAL, 10);
        gtk_box_append(GTK_BOX(box), baudratesBox);

      char *baudrates[]={"50", "75", "110", "134",
          "150", "200", "300", "600", "1200", "1800",
          "2400", "4800", "9600", "19200", "28800",
          "38400", "57600", "76800", "115200", "230400",
          "460800", "576000", "921600", NULL};
      baudrateDropdown=initDropDown(baudrates, 18); //dropdown widget with 'baudrates' list of items and 18th item selected
        gtk_box_append(GTK_BOX(baudratesBox), baudrateDropdown);

      void *selectedItem=gtk_drop_down_get_selected_item(GTK_DROP_DOWN(baudrateDropdown)); //getting selected item from dropdown widget
      const char *selectedBaudrate=gtk_string_object_get_string(GTK_STRING_OBJECT(selectedItem)); //getting item value as string
      baudrateEntryBuffer=gtk_entry_buffer_new((const char*)selectedBaudrate, strlen(selectedBaudrate)); //new entry buffer widget
      baudrateEntry=gtk_entry_new_with_buffer(GTK_ENTRY_BUFFER(baudrateEntryBuffer)); //new entry widget with buffer 'baudrateEntryBuffer'

      void *entryAndBuffer[2]={baudrateEntry, baudrateEntryBuffer}; //data to pass to callback
      g_signal_connect(baudrateDropdown, "notify::selected-item", G_CALLBACK(baudrateDropdownSelectCallback), entryAndBuffer); //connection to callback
        gtk_box_append(GTK_BOX(baudratesBox), baudrateEntry);
    /**/


  gtk_window_present(GTK_WINDOW(win));
}

static void baudrateDropdownSelectCallback(GtkDropDown *dropdown, void *data[]) {

    void *newlySelectedItem=gtk_drop_down_get_selected_item(GTK_DROP_DOWN(dropdown)); //getting selected item from dropdown widget
    char *selected=gtk_string_object_get_string(GTK_STRING_OBJECT(newlySelectedItem)); //getting item value as string

    GtkEntryBuffer *buffer;
    buffer=gtk_entry_buffer_new(selected, strlen(selected));
    data[1]=buffer; //replacing old buffer with new one

    gtk_entry_set_buffer(GTK_ENTRY(data[0]), data[1]);       //this doesn't work
  //gtk_entry_set_buffer(GTK_ENTRY(baudrateEntry), data[1]); //this works if static GtkWidget *baudrateEntry; is global variable
}
//uiElements.h
#ifndef UI_ELEMENTS_H
#define UI_ELEMENTS_H

#include <gtk/gtk.h>

typedef enum {
    NO_ENTRY=0,
    WITH_ENTRY=1
} withEntry;

int startApp(char *id, GApplicationFlags flags, void (*activateFunc), int argc, char *argv[]);
GtkWidget *initWindow(GApplication  *app, char *windowTitle, int windowWidth, int windowHeight);
GtkWidget *initBox(GtkOrientation orientation, int spacing);
GtkWidget *initDropDown(char *list[], uint8_t selectedByDefault);


#endif /* UI_ELEMENTS_H */
//uiElements.c
#include "uiElements.h"

int startApp(char *id, GApplicationFlags flags, void (*activateFunc), int argc, char *argv[]) {
    GtkApplication *app;
    int stat;

    app=gtk_application_new(id, flags);
    g_signal_connect(app, "activate", G_CALLBACK(activateFunc), NULL);
    stat=g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);

    return stat;
}

GtkWidget *initWindow(GApplication  *app, char *windowTitle, int windowWidth, int windowHeight) {
    GtkWidget *win;
    win=gtk_application_window_new(GTK_APPLICATION(app));
    gtk_window_set_title(GTK_WINDOW(win), windowTitle);
    gtk_window_set_default_size(GTK_WINDOW(win), windowWidth, windowHeight);

    return win;
}

GtkWidget *initBox(GtkOrientation orientation, int spacing) {
    GtkWidget *box;
    box=gtk_box_new(orientation, spacing);
    gtk_box_set_homogeneous(GTK_BOX(box), TRUE);

    return box;
}

GtkWidget *initDropDown(char *list[], uint8_t selectedByDefault) {
    GtkStringList *stringList=gtk_string_list_new((const char * const *)list);
    GtkWidget *dropdown;
    dropdown=gtk_drop_down_new(G_LIST_MODEL(stringList), NULL);
    gtk_drop_down_set_selected(GTK_DROP_DOWN(dropdown), selectedByDefault);

    return(dropdown);
}

That array ceases to exist when the function returns. You need to allocate something on the heap. Typically you would use a struct here.

Which function do you have on your mind - callback or activateApp?
Also I actually tried to make a struct and pass widgets to callback function via struct. Unfortunately it didn’t work either.

That line is from activateApp(). Array vs struct isn’t the fundamental issue, even if a struct would better-suited for this application. You’re allocating it on the stack, so the pointer you pass to g_signal_connect() becomes invalid as soon as the function returns.

You should explicitly allocate a struct on the heap using malloc() or preferably g_new0().

I defined a structure with all widgets and used g_new0 to initialise it in main function. I added used widgets to this structure and passed whole structure to callback function. Unfortunately program crashes when I select an item from a dropdown list.

Whole code:

//main.c
#include <gtk/gtk.h>
#include <windows.h>
#include <stdio.h>
#include <stdint.h>

#include "uiElements.h"


typedef struct {

   GtkWidget *win;
   GtkWidget *box;
   GtkWidget *baudrateDropdown;
   GtkWidget *baudratesBox;
   GtkWidget *baudrateEntry;
   GtkEntryBuffer *baudrateEntryBuffer;

} WidgetStruct;

WidgetStruct widgetStr;
WidgetStruct *widgetStrPtr=&widgetStr;

static void baudrateDropdownSelectCallback(GtkDropDown *dropdown, WidgetStruct *data);
static void activateApp(GApplication *app);


int main (int argc, char *argv[]) {

  widgetStrPtr=g_new0(WidgetStruct, 1);

  //starting application
  return startApp("exe.GTKforSO", G_APPLICATION_DEFAULT_FLAGS, activateApp, argc, argv);
}

static void activateApp (GApplication *app) {

  static GtkWidget *win;
  static GtkWidget *box;

  //window
  win=initWindow(app, "GTKforSO", 400, 200);

  //box
  box=initBox(GTK_ORIENTATION_HORIZONTAL, 0);
    gtk_window_set_child(GTK_WINDOW(win), box);

  static GtkWidget *baudrateDropdown;
  static GtkWidget *baudratesBox;
  static GtkWidget *baudrateEntry;
  static GtkEntryBuffer *baudrateEntryBuffer;


    /** the important part **/
      baudratesBox=initBox(GTK_ORIENTATION_HORIZONTAL, 10);
        gtk_box_append(GTK_BOX(box), baudratesBox);

      char *baudrates[]={"50", "75", "110", "134",
          "150", "200", "300", "600", "1200", "1800",
          "2400", "4800", "9600", "19200", "28800",
          "38400", "57600", "76800", "115200", "230400",
          "460800", "576000", "921600", NULL};
      baudrateDropdown=initDropDown(baudrates, 18); //dropdown widget with 'baudrates' list of items and 18th item selected
        gtk_box_append(GTK_BOX(baudratesBox), baudrateDropdown);

      void *selectedItem=gtk_drop_down_get_selected_item(GTK_DROP_DOWN(baudrateDropdown)); //getting selected item from dropdown widget
      const char *selectedBaudrate=gtk_string_object_get_string(GTK_STRING_OBJECT(selectedItem)); //getting item value as string
      baudrateEntryBuffer=gtk_entry_buffer_new((const char*)selectedBaudrate, strlen(selectedBaudrate)); //new entry buffer widget
      baudrateEntry=gtk_entry_new_with_buffer(GTK_ENTRY_BUFFER(baudrateEntryBuffer)); //new entry widget with buffer 'baudrateEntryBuffer'

    widgetStrPtr->box=box;
    widgetStrPtr->baudrateDropdown=baudrateDropdown;
    widgetStrPtr->baudratesBox=baudratesBox;
    widgetStrPtr->baudrateEntry=baudrateEntry;
    widgetStrPtr->baudrateEntryBuffer=baudrateEntryBuffer;

      g_signal_connect(baudrateDropdown, "notify::selected-item", G_CALLBACK(baudrateDropdownSelectCallback), widgetStrPtr); //connection to callback
        gtk_box_append(GTK_BOX(baudratesBox), baudrateEntry);
    /**/


  gtk_window_present(GTK_WINDOW(win));
}

static void baudrateDropdownSelectCallback(GtkDropDown *dropdown, WidgetStruct *data) {

    WidgetStruct *_widgetStr=data;

    void *newlySelectedItem=gtk_drop_down_get_selected_item(GTK_DROP_DOWN(dropdown)); //getting selected item from dropdown widget

    const char *selected=gtk_string_object_get_string(GTK_STRING_OBJECT(newlySelectedItem)); //getting item value as string

    gtk_editable_set_text(GTK_EDITABLE(_widgetStr->baudrateEntry), selected);
}

supposedly

- static void baudrateDropdownSelectCallback(GtkDropDown *dropdown, WidgetStruct *data);
+ static void baudrateDropdownSelectCallback(GtkDropDown *dropdown, void *unused, WidgetStruct *data);
1 Like

This change fixed the program and now function works properly, without crashing the program. Thank you!
Could you explain how does this work? I don’t understand the magic behind this code.

Idk, where have you found that function declaration that corrupt the stack?
if I’m not mistaken, in source it looks like gtk/gtkdropdown.c · main · GNOME / gtk · GitLab

1 Like

I tried making a callback function like for buttons, and the buttons callback functions I used looked like in this code from ToshioCP tutorial:

#include <gtk/gtk.h>

static void
click_cb (GtkButton *btn, GtkWindow *win) {
  gtk_window_destroy (win);
}

static void
app_activate (GApplication *app) {
  GtkWidget *win;
  GtkWidget *btn;

  win = gtk_application_window_new (GTK_APPLICATION (app));
  gtk_window_set_title (GTK_WINDOW (win), "lb3");
  gtk_window_set_default_size (GTK_WINDOW (win), 400, 300);

  btn = gtk_button_new_with_label ("Close");
  gtk_window_set_child (GTK_WINDOW (win), btn);
  g_signal_connect (btn, "clicked", G_CALLBACK (click_cb), win);

  gtk_window_present (GTK_WINDOW (win));
}

int
main (int argc, char **argv) {
  GtkApplication *app;
  int stat;

  app = gtk_application_new ("com.github.ToshioCP.lb3", G_APPLICATION_DEFAULT_FLAGS);
  g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
  stat =g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);
  return stat;
}

All previous callback functions I made followed this template and worked. For example this one also has another parameter:

static void portRefreshButtonCallback(GtkButton *button, GtkDropDown *portsDropdown) {
  findCommunicationPorts();

  GtkStringList *newList=gtk_string_list_new((const char * const *)portNames);
  GListModel *model = G_LIST_MODEL(newList);
  gtk_drop_down_set_model(GTK_DROP_DOWN(portsDropdown), G_LIST_MODEL(model));

  if (portNames[0]==NULL) {
    char *problem[2]={"Problem with detecting ports.",NULL};
    GtkStringList *newList=gtk_string_list_new((const char * const *)problem);
    GListModel *model = G_LIST_MODEL(newList);
    gtk_drop_down_set_model(GTK_DROP_DOWN(portsDropdown), G_LIST_MODEL(model));
  }

}

It may be that my documentation searching skills are poor and that’s why I made this mistake, but honestly I am not sure how I should have approached this problem.

I hope I understood your question correctly. If not, let me know.

Documentation says that “Handlers are documented as having type GCallback, but this is simply a generic placeholder type — an artifact of the GSignal APIs being polymorphic.”
I.e. signal handler for “clicked” has own arguments, “notify::selected-item” has another set and number of arguments, and so on. It’s how I understand it works.

1 Like

That would make sense. Also your explanation is much clearer than the official one from documentation.