cgccgtkgnomegtk4

Callback function doesn't modify widget


I am trying to make a GTK 4 app written in C. I want to make a callback function that reacts to newly selected item from 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);
}


Solution

  • Firstly: Many thanks to yvs2014 from discourse.gnome.org for help!


    Apparently the callback function should be declared as:

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

    The solution is probably related to this function implementation from GNOME GTK Repository:

    static void
    selected_item_changed (GtkDropDown *self,
                           GParamSpec  *pspec,
                           GtkListItem *list_item)
    {
      GtkWidget *box;
      GtkWidget *icon;
      box = gtk_list_item_get_child (list_item);
      icon = gtk_widget_get_last_child (box);
      if (gtk_drop_down_get_selected_item (self) == gtk_list_item_get_item (list_item))
        gtk_widget_set_opacity (icon, 1.0);
      else
        gtk_widget_set_opacity (icon, 0.0);
    }
    


    It also works with structures. Here is a working code that replaces void *data[] with WidgetStruct *data.

    //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, void *unused, 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, void *unused, 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);
    }