| 1 | = GObject Brain Dump = |
| 2 | |
| 3 | OK, so here's how it is. All GTK widgets are descended from GObject. GObject provides 4 things: |
| 4 | |
| 5 | == Reference counting == |
| 6 | This means you don't have to worry about how an object gets destroyed. It just goes away when the last reference is dropped. You can also add references to it to make sure it doesn't get destroyed while you're passing it around with {{{g_object_ref()}}}. |
| 7 | |
| 8 | For example, suppose you had a label (called "label") you wanted to move from one table (called "table1") to another (called "table2"): |
| 9 | {{{ |
| 10 | g_object_ref(label); |
| 11 | gtk_container_remove(GTK_CONTAINER(table1), label); |
| 12 | gtk_table_attach(GTK_TABLE(table2), label, 0, 1, 0, 1, 0, 0, 0, 0); |
| 13 | g_object_unref(label); |
| 14 | }}} |
| 15 | |
| 16 | == Inheritance == |
| 17 | You can create new types of widgets from existing types. This is a tutorial onto itself: http://www.gtk.org/tutorial/x2312.html |
| 18 | |
| 19 | == Properties == |
| 20 | If you think of data structures, in C they contain individual members of various types. For, example, the mythical {{{HumanBeing}}} data structure: |
| 21 | {{{ |
| 22 | typedef enum { |
| 23 | HUMAN_FEMALE, |
| 24 | HUMAN_MALE |
| 25 | } HumanGender; |
| 26 | |
| 27 | typedef struct { |
| 28 | char *first_name; |
| 29 | char *last_name; |
| 30 | double height; |
| 31 | HumanGender gender; |
| 32 | } HumanBeing; |
| 33 | }}} |
| 34 | You would access this data structure like so: {{{printf ("Human's first name is %s\n", ((HumanBeing *)some_human)->first_name);}}} |
| 35 | |
| 36 | GObject provides a way to formally identify such a structure member using a certain name which you can then use with {{{g_object_new()}}}, {{{g_object_set()}}}, and {{{g_object_get()}}}. So, if {{{HumanBeing}}} were a GObject, instead of using {{{some_human->first_name}}}, you could do |
| 37 | {{{ |
| 38 | char *first_name = NULL; |
| 39 | g_object_get (G_OBJECT (some_human), "first-name", &first_name, NULL); |
| 40 | printf ("Human's first name is %s\n", first_name) ; |
| 41 | g_free (first_name) ; |
| 42 | }}} |
| 43 | This doesn't look too appealing, but consider: Each subclass of GObject adds new properties to the list of properties of its parent calss, so, by the time you get to, say, {{{GtkWindow}}}, you can do something really cool, like, |
| 44 | {{{ |
| 45 | g_object_new (GTK_TYPE_WINDOW, "visible", TRUE, "title", "Demo App", "default-width", 320, "default-height", 240, |
| 46 | "child", g_object_new (GTK_TYPE_ALIGNMENT, "visible", TRUE, "xalign", 0.5, "yalign", 0.5, "xscale", 0.0, "yscale", 0.0, |
| 47 | "child", g_object_new (GTK_TYPE_BUTTON, "visible", TRUE, "label", GTK_STOCK_OK, "use-stock", TRUE, NULL), |
| 48 | NULL), |
| 49 | NULL); |
| 50 | }}} |
| 51 | {{{g_object_new()}}} allows you to create a new object of the type specified in its first argument, and set any number of the object's properties. You finish off the list of properties by giving it the {{{NULL}}} property at the end of the list. |
| 52 | {{{g_object_set()}}} works the same way, but modifies the properties of an existing object. |
| 53 | {{{g_object_get()}}} requires, for each property you specify, that you follow it up with a variable passed by reference, so that it may fill out the variable with the value. For example: |
| 54 | {{{ |
| 55 | char *button_label; |
| 56 | gboolean button_sensitive; |
| 57 | g_object_get (G_OBJECT (the_button, "label", &button_label, "sensitive", &button_sensitive, NULL); |
| 58 | }}} |
| 59 | Unlike data structure members, properties can be defined more rigorously. Values of a property can be restricted to a certain range, and every property has a default value. |
| 60 | |
| 61 | == Signals == |
| 62 | A signal is an object's means of informing those who are interested that something has happened to it. If you are interested in things that are happening to your object, you must pass a pointer to a function to {{{g_signal_connect}}} for each of the ''things'' you are interested in. All classes provide lists of such ''things'' - lets call them events. For example, when a button is clicked, all those interested will be informed. For a given button, say, {{{the_button}}}, you can express your interest in when it gets clicked, by connecting to its {{{"clicked"}}} signal: |
| 63 | {{{ |
| 64 | g_signal_connect (G_OBJECT (the_button), "clicked", (GCallback)the_button_got_clicked, NULL); |
| 65 | }}} |
| 66 | You normally do this right after creating the button, so that you don't miss any clicks. Now, this is fine and dandy, but you gotta ask yourself: What does {{{the_button_got_clicked}}} look like? Well, that depends on the event you're interested in. The documentation for a given signal tells you what kind of a function to pass to {{{g_signal_connect()}}}. For example, http://developer.gnome.org/doc/API/2.0/gtk/GtkButton.html#GtkButton-clicked tells you that, in order to hook up to a button's {{{"clicked"}}} signal, you must create {{{the_button_got_clicked}}} to be of the form |
| 67 | {{{ |
| 68 | static void the_button_got_clicked (GtkWidget *the_button, gpointer user_data); |
| 69 | }}} |
| 70 | In contrast, a {{{"key-press-event"}}} signal requires a different kind of function: http://developer.gnome.org/doc/API/2.0/gtk/GtkWidget.html#GtkWidget-key-press-event |
| 71 | |
| 72 | One thing you will notice though is that all signals have {{{gpointer user_data}}} as the last parameter. This way, you can pass some stuff into the function when you hook up the signal. You do this by giving a pointer to this stuff to {{{g_signal_connect()}}}. You put it in the last parameter (the one that's NULL in the above example). |
| 73 | |
| 74 | The bottom line is, you need to read the documentation for a given signal to know what kind of a function you need to write to properly receive it. If you write your function wrong, it will still work, but the arguments it receives will be gibberish - or segfault bait, if you prefer. |
| 75 | |
| 76 | Every GTK widget has its signals documented. For example, if you go to http://developer.gnome.org/doc/API/2.0/gtk/GtkButton.html you will find all the signals a {{{GtkButton}}} can have under the heading 'Signals'. |
| 77 | |
| 78 | Now, an interesting bit. Objects have properties, right? Well, it turns out that, for each property name an object has, there's a corresponding signal called {{{"notify::<property-name>"}}} and the function you hook up to it is always the same, no matter what {{{<property-name>}}} is: |
| 79 | {{{ |
| 80 | static void property_name_has_changed (GObject *the_object, GParamSpec *pspec, gpointer user_data); |
| 81 | }}} |
| 82 | |
| 83 | = Pulling It All Together = |
| 84 | |
| 85 | So, to illustrate signals and properties, here's a program: |
| 86 | {{{ |
| 87 | #include <gtk/gtk.h> |
| 88 | |
| 89 | static void button_clicked(GtkWidget *button, gpointer user_data); |
| 90 | static void button_notify_sensitive(GObject *button, GParamSpec *pspec, gpointer user_data); |
| 91 | |
| 92 | int main(int argc, char **argv) |
| 93 | { |
| 94 | GtkWidget *btn1 = NULL, *btn2 = NULL, *vbox = NULL; |
| 95 | |
| 96 | gtk_init(&argc, &argv); |
| 97 | |
| 98 | g_signal_connect ( |
| 99 | G_OBJECT(g_object_new(GTK_TYPE_WINDOW, "title", "Demo", "visible", TRUE, |
| 100 | "child", vbox = g_object_new(GTK_TYPE_VBOX, "visible", TRUE, NULL), |
| 101 | NULL)), "delete-event", (GCallback)gtk_main_quit, NULL); |
| 102 | |
| 103 | gtk_container_add(GTK_CONTAINER(vbox), |
| 104 | btn1 = g_object_new(GTK_TYPE_BUTTON, "visible", TRUE, "label", "Button 1", "sensitive", FALSE, NULL)) ; |
| 105 | gtk_container_add(GTK_CONTAINER(vbox), |
| 106 | btn2 = g_object_new(GTK_TYPE_BUTTON, "visible", TRUE, "label", "Button 2", NULL)) ; |
| 107 | |
| 108 | g_signal_connect(G_OBJECT(btn1), "clicked", (GCallback)button_clicked, NULL); |
| 109 | g_signal_connect(G_OBJECT(btn2), "clicked", (GCallback)button_clicked, NULL); |
| 110 | g_signal_connect(G_OBJECT(btn1), "notify::sensitive", (GCallback)button_notify_sensitive, btn2); |
| 111 | g_signal_connect(G_OBJECT(btn2), "notify::sensitive", (GCallback)button_notify_sensitive, btn1); |
| 112 | |
| 113 | gtk_main(); |
| 114 | |
| 115 | return 0; |
| 116 | } |
| 117 | |
| 118 | static void button_clicked(GtkWidget *button, gpointer user_data) |
| 119 | { |
| 120 | g_object_set(G_OBJECT(button), "sensitive", FALSE, NULL); |
| 121 | } |
| 122 | |
| 123 | static void button_notify_sensitive(GObject *button, GParamSpec *pspec, gpointer user_data) |
| 124 | { |
| 125 | gboolean i_am_sensitive; |
| 126 | GObject *the_other_button = G_OBJECT(user_data); |
| 127 | |
| 128 | g_object_get(button, "sensitive", &i_am_sensitive, NULL); |
| 129 | /* |
| 130 | * Here, we cannot simply always set the_other_button to !i_am_sensitive, because |
| 131 | * that'll cause a loop, Remember, we're being told both when the button goes from |
| 132 | * sensitive to insensitive /and/ when it goes from insensitive to sensitive. |
| 133 | */ |
| 134 | if (!i_am_sensitive) |
| 135 | g_object_set(the_other_button, "sensitive", TRUE, NULL); |
| 136 | } |
| 137 | }}} |