18

I need to be able to explore a GTK GUI's structure programmatically. I have the GtkWidget and I want to find any children of that widget. Now I know that GtkContainer's have a function to find children and that GtkContainer is derived from GtkWidget.

Is there anyway I can check if a widget is a GtkContainer and then perform the cast? If not, is there any other way I can discover the GtkWidget's that are children of the one I have?

Jonathan Sternberg
  • 6,421
  • 7
  • 39
  • 58

4 Answers4

18

Yes, there are macros for every GObject type to check whether an object is of that type:

if(GTK_IS_CONTAINER(widget)) {
    GList *children = gtk_container_get_children(GTK_CONTAINER(widget));
    ...
}

If the widget is a GtkBin it has only one child. In that case, the following is simpler than dealing with a GList:

if(GTK_IS_BIN(widget)) {
    GtkWidget *child = gtk_bin_get_child(GTK_BIN(widget));
    ...
}
ptomato
  • 56,175
  • 13
  • 112
  • 165
7

In the following code I have implemented find_child function which recursively searches for the widget by name. Ideas build on the answer of @ptomato and from the following PHP example:

    #include <gtk/gtk.h>

    GtkWidget*
    find_child(GtkWidget* parent, const gchar* name)
    {
            if (g_strcasecmp(gtk_widget_get_name((GtkWidget*)parent), (gchar*)name) == 0) { 
                    return parent;
            }

            if (GTK_IS_BIN(parent)) {
                    GtkWidget *child = gtk_bin_get_child(GTK_BIN(parent));
                    return find_child(child, name);
            }

            if (GTK_IS_CONTAINER(parent)) {
                    GList *children = gtk_container_get_children(GTK_CONTAINER(parent));
                    while ((children = g_list_next(children)) != NULL) {
                            GtkWidget* widget = find_child(children->data, name);
                            if (widget != NULL) {
                                    return widget;
                            }
                    }
            }

            return NULL;
    }

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

            GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
            GtkWidget *frame = gtk_frame_new(NULL);
            GtkWidget *vbox1 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
            GtkWidget *textView = gtk_text_view_new();
            GtkWidget *hbox1 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
            GtkWidget *button1 = gtk_button_new_with_label("button1");
            gtk_widget_set_name(button1, "btn1");
            GtkWidget *button2 = gtk_button_new_with_label("button2");
            gtk_widget_set_name(button2, "btn2");

            gtk_window_set_title(GTK_WINDOW(window), "Hello");
            gtk_container_set_border_width(GTK_CONTAINER(window), 10);
            gtk_window_set_default_size(GTK_WINDOW(window), 450, 400);

            gtk_container_add(GTK_CONTAINER(window), frame);
            gtk_container_add(GTK_CONTAINER(frame), vbox1);

            gtk_box_pack_start(GTK_BOX(vbox1), textView, 1, 1, 0);
            gtk_box_pack_start(GTK_BOX(vbox1), hbox1, 0, 1, 0);
            gtk_box_pack_start(GTK_BOX(hbox1), button1, 0, 1, 0);
            gtk_box_pack_start(GTK_BOX(hbox1), button2, 0, 1, 0);

            gtk_widget_show_all(window);

            g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

            GtkWidget* child = find_child(window, "btn2");
            if (child == button2) {
                    g_print("found it!\n");
            } else {
                    g_print("not found it!\n");
            }

            gtk_main();
            return 0;
    }
Darius Kucinskas
  • 10,193
  • 12
  • 57
  • 79
  • 3
    thanks for the code, it was very helpful and helped me solve my problem. However, I found one bug I wanted to share with people who use it: because in find_child you call gtk_container_get_children() and then immediately call g_list_next_children(), you never explore the first node in the tree. I fixed this with a do/while loop, although you still need to be sure the first item isn't NULL. – Brian N Nov 22 '16 at 00:29
  • This code has subtle bug: In the GTK_IS_CONTAINER part of find_child, you iterate over the children in "while ((children == g_list_next()" loop. This is wrong, it skips the first child! – Marc Balmer Jun 06 '20 at 10:33
  • I can't find any documentation for `GTK_IS_BIN`. Is it gone? If so, how do we check the widget's type? – TheTechRobo the Nerd Sep 16 '22 at 20:58
  • Update: You can get the glib Type, get its parent, and check if it's a bin. – TheTechRobo the Nerd Sep 16 '22 at 23:45
0

I created these functions that are based on the same principal as the accepted answer.

In find_child_by_index, depth is equal to the amount of indices you need to pass. The first index is for the children of parent, the second index is for the grandchildren and so forth.

void print_children_helper(GtkWidget* parent, int indent_size, int depth)
{
    for (int i = 0; i < depth * indent_size; i++)
        printf(" ");
    printf("%s\n", gtk_widget_get_name(parent));

    GList* children = NULL;
    if (GTK_IS_CONTAINER(parent))
        children = gtk_container_get_children(GTK_CONTAINER(parent));

    while (children != NULL)
    {
        print_children(children->data, indent_size, depth + 1);
        children = children->next;
    }
}

void print_children(GtkWidget* parent, int indent_size)
{
    print_children_helper(parent, indent_size, 0);
}

GtkWidget* find_child_by_name(GtkWidget* parent, const gchar* name)
{
    if (g_strcmp0(gtk_widget_get_name(parent), name) == 0)
        return parent;

    GList* children = NULL;
    if (GTK_IS_CONTAINER(parent))
        children = gtk_container_get_children(GTK_CONTAINER(parent));

    while (children != NULL)
    {
        GtkWidget* widget = find_child_by_name(children->data, name);

        if (widget != NULL)
            return widget;

        children = children->next;
    }

    return NULL;
}

GtkWidget* find_child_by_index(GtkWidget* parent, int depth, ...)
{
    va_list argp;
    va_start(argp, depth);

    for (int i = 0; i < depth; i++)
    {
        int index = va_arg(argp, int);

        GList* children = NULL;
        if (GTK_IS_CONTAINER(parent))
            children = gtk_container_get_children(GTK_CONTAINER(parent));

        for (int j = 0; j < index; j++)
            if (children != NULL)
                children = children->next;

        if (children != NULL)
            parent = children->data;
        else
            return NULL;
    }

    va_end(argp);
    return parent;
}
samildeli
  • 1
  • 1
  • 2
0

A different approach is needed if using GTK4 as GtkContainer no longer exists. See migration guide

A simple function traversing the widget heirarchy:

void print_widget_names(GtkWidget* parent, int level) {
  for (int i=0; i < level; i++) { 
    g_print("    ");
  }
  level++;
  g_print("%s\n", gtk_widget_get_name(GTK_WIDGET(parent)));
  GtkWidget *widget = gtk_widget_get_first_child(GTK_WIDGET(parent));
  GtkWidget *next;
  if (widget != NULL) {
    print_widget_names(widget, level);
    while ((next = gtk_widget_get_next_sibling(widget)) != NULL) {
      widget = next;
      print_widget_names(widget, level);
    }
  }
}
Ryan
  • 614
  • 4
  • 11