Question

I notice that Glade only allows you to set an object to be passed within the user data part of a GTK callback.

Is there any way I can pass an integer value instead?

I have a set of menu items which I would like to point at the same callback function, however for a small section of the code I need to identify which menu item was the one which called the callback.

Note: All my signals are setup automatically with glade_xml_signal_autoconnect, and I would perfer to keep it this way.

Was it helpful?

Solution

In my opinion with respect to glade and callbacks one have to say goodbye to the notion of passing just one single element to callbacks. And thus stop using glade in order to configure the user data that is passed to a specific callback.

I get used to pass a struct named App to all callbacks that contains a pointer to every ui element that is loaded from the ui definitions file and already instantiated by GtkBuilder. This also stops the tedious task in glade of clicking back and forth between widgets only to set up the user data of the callbacks. In addition to the ui elements, most of the time this struct contains further elements that are important at runtime on application level.

A benefit of this approach is that you are not tempted to think about how to implement an individual callback that should act upon more than one element which is often the case. Some people tackle this by grouping the widgets of interest into a container. In order to pass all widgets to the callback they just pass the container. Then in the callback they get the widgets by calling gtk_container_get_children or similar functions. This approach makes callbacks illegible and reduce the fun while editing the code.

If every callback has all elements available that should be manipulated at runtime you don't have to care about implementing a single callback since every callback shares the same structure.

Further i created a helper macro that defines a pointer to an already instantiated element with the name of its glade id. This way the elements definitions in the code are always in sync with those that are displayed in glade. That makes renaming of widgets very easy (substitute the name in all sources + the glade file).

To illustrate this approach i have appended files of a sample program. Don't be scared of the number of files. Dividing a program into logical units/modules makes programming much simpler. To get a quick view of the whole project i created a git repository on github:

https://github.com/o8i12398z12h9h/gtk-sample-app

Just compare my callbacks.c with your callback file(s). I would be interested to know how they compare with respect to legibility and structure and taking into account that you may have the element-ids from glade still in mind.


callbacks.c:

#include "app.h"

void
button1_clicked_cb (GtkButton * button, App * app)
{
    GET_UI_ELEMENT (GtkEntry, entry1);

    if (gtk_entry_get_text_length (entry1) == 0)
        gtk_entry_set_text (entry1, "test");
    else
        gtk_entry_set_text (entry1, "");
}

void
button2_clicked_cb (GtkButton * button, App * app)
{
    gboolean active;

    GET_UI_ELEMENT (GtkSpinner, spinner1);
    GET_UI_ELEMENT (GtkWidget, eventbox1);

    g_object_get (G_OBJECT (spinner1), "active", &active,
                  NULL);

    if (active) {
        gtk_spinner_stop (spinner1);
        gtk_widget_override_background_color (eventbox1,
                                              GTK_STATE_FLAG_NORMAL,
                                              app->
                                              active_color);
    }
    else {
        gtk_spinner_start (spinner1);
        gtk_widget_override_background_color (eventbox1,
                                              GTK_STATE_FLAG_NORMAL,
                                              app->
                                              inactive_color);
    }
}

void
button3_clicked_cb (GtkButton * button, App * app)
{
    GdkRGBA bg = { 0, 0, 1, 1 };

    GET_UI_ELEMENT (GtkWidget, eventbox1);

    gtk_widget_override_background_color (eventbox1,
                                          GTK_STATE_FLAG_NORMAL,
                                          &bg);
}

void
button4_clicked_cb (GtkButton * button, App * app)
{
    const gchar *str;

    GET_UI_ELEMENT (GtkLabel, label1);

    str = gtk_label_get_text (label1);

    if (strcmp (str, "label") == 0) {
        gtk_label_set_text (label1, "NewText");
    }
    else {
        gtk_label_set_text (label1, "label");
    }
}

void
button5_clicked_cb (GtkButton * button, App * app)
{
    GET_UI_ELEMENT (GtkWidget, button1);
    GET_UI_ELEMENT (GtkWidget, button2);
    GET_UI_ELEMENT (GtkWidget, button4);

    g_signal_emit_by_name (button1, "clicked", app);
    g_signal_emit_by_name (button2, "clicked", app);
    g_signal_emit_by_name (button4, "clicked", app);
}

main.c:

#include "app.h"

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

    app = (App *) g_new (App, 1);

    gtk_init (&argc, &argv);

    app_init (app);

    GET_UI_ELEMENT (GtkWidget, window1);

    gtk_widget_show_all (window1);

    gtk_main ();

    return 0;
}

app.c:

#include "app.h"

GObject *
app_get_ui_element (App * app, const gchar * name)
{
    const gchar *s;
    GSList *list;

    list = app->objects;

    do {
        s = gtk_buildable_get_name (list->data);

        if (strcmp (s, name) == 0) {
            return list->data;
        }

    } while (list = g_slist_next (list));

    return NULL;
}

void
app_init_colors (App * app)
{
    GdkRGBA active_color = { 1, 0, 0, 1 };
    GdkRGBA inactive_color = { 0, 1, 0, 1 };

    app->active_color = g_new0 (GdkRGBA, 1);
    app->inactive_color = g_new0 (GdkRGBA, 1);

    app->active_color = gdk_rgba_copy (&active_color);
    app->inactive_color = gdk_rgba_copy (&inactive_color);
}


void
app_init (App * app)
{
    GError *err = NULL;

    app->definitions = gtk_builder_new ();

    gtk_builder_add_from_file (app->definitions,
                               UI_DEFINITIONS_FILE, &err);

    if (err != NULL) {
        g_printerr
            ("Error while loading app definitions file: %s\n",
             err->message);
        g_error_free (err);
        gtk_main_quit ();
    }

    gtk_builder_connect_signals (app->definitions, app);

    app->objects = gtk_builder_get_objects (app->definitions);

    app_init_colors (app);
}

app.h:

#ifndef __APP__
#define __APP__

#include <gtk/gtk.h>

#define UI_DEFINITIONS_FILE "ui.glade"

#define GET_UI_ELEMENT(TYPE, ELEMENT)   TYPE *ELEMENT = (TYPE *) \
                                            app_get_ui_element(app, #ELEMENT);

typedef struct app_
{
    GtkBuilder *definitions;
    GSList *objects;

    GdkRGBA *active_color;
    GdkRGBA *inactive_color;

} App;

void app_init (App * );
GObject * app_get_ui_element (App * , const gchar * );

#endif

ui.glade:

<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <!-- interface-requires gtk+ 3.0 -->
  <object class="GtkWindow" id="window1">
    <property name="can_focus">False</property>
    <property name="border_width">20</property>
    <signal name="destroy" handler="gtk_main_quit" swapped="no"/>
    <child>
      <object class="GtkGrid" id="grid1">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="row_spacing">10</property>
        <property name="column_spacing">20</property>
        <child>
          <object class="GtkSpinner" id="spinner1">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
          </object>
          <packing>
            <property name="left_attach">1</property>
            <property name="top_attach">1</property>
            <property name="width">2</property>
            <property name="height">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkEventBox" id="eventbox1">
            <property name="height_request">50</property>
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <child>
              <placeholder/>
            </child>
          </object>
          <packing>
            <property name="left_attach">1</property>
            <property name="top_attach">2</property>
            <property name="width">2</property>
            <property name="height">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkEntry" id="entry1">
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="invisible_char">•</property>
            <property name="invisible_char_set">True</property>
          </object>
          <packing>
            <property name="left_attach">1</property>
            <property name="top_attach">0</property>
            <property name="width">2</property>
            <property name="height">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkLabel" id="label1">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="label" translatable="yes">label</property>
          </object>
          <packing>
            <property name="left_attach">1</property>
            <property name="top_attach">3</property>
            <property name="width">2</property>
            <property name="height">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkButtonBox" id="buttonbox1">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="spacing">10</property>
            <property name="layout_style">center</property>
            <child>
              <object class="GtkButton" id="button1">
                <property name="label" translatable="yes">toggle entry</property>
                <property name="use_action_appearance">False</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
                <property name="use_action_appearance">False</property>
                <signal name="clicked" handler="button1_clicked_cb" swapped="no"/>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">0</property>
              </packing>
            </child>
            <child>
              <object class="GtkButton" id="button2">
                <property name="label" translatable="yes">toggle spinner + bg</property>
                <property name="use_action_appearance">False</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
                <property name="use_action_appearance">False</property>
                <signal name="clicked" handler="button2_clicked_cb" swapped="no"/>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">1</property>
              </packing>
            </child>
            <child>
              <object class="GtkButton" id="button3">
                <property name="label" translatable="yes">set bg</property>
                <property name="use_action_appearance">False</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
                <property name="use_action_appearance">False</property>
                <signal name="clicked" handler="button3_clicked_cb" swapped="no"/>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">2</property>
              </packing>
            </child>
            <child>
              <object class="GtkButton" id="button4">
                <property name="label" translatable="yes">toggle label</property>
                <property name="use_action_appearance">False</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
                <property name="use_action_appearance">False</property>
                <signal name="clicked" handler="button4_clicked_cb" swapped="no"/>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">3</property>
              </packing>
            </child>
            <child>
              <object class="GtkButton" id="button5">
                <property name="label" translatable="yes">toggle everything</property>
                <property name="use_action_appearance">False</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
                <property name="use_action_appearance">False</property>
                <signal name="clicked" handler="button5_clicked_cb" swapped="no"/>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">4</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="left_attach">0</property>
            <property name="top_attach">5</property>
            <property name="width">4</property>
            <property name="height">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkSeparator" id="separator1">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
          </object>
          <packing>
            <property name="left_attach">0</property>
            <property name="top_attach">4</property>
            <property name="width">4</property>
            <property name="height">1</property>
          </packing>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
      </object>
    </child>
  </object>
</interface>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top