Yet more gtk tutorial





Here's another days work on the tutorial.  If anyone is getting annoyed at
these lengthy posts, just let me know.

What is the purprose of reimplementing things like malloc and printf with
functions in glib (g_print and g_malloc etc) ? 

There are a few other questions in the document, usually in square braces
that I'd like to find out eventually.

As usual, feel free to contribute in any way you feel fit.

Thanks,

Ian

-----------------------------------------------------------

INTRODUCTION:

GTK (GIMP Toolkit) was originally developed as a toolkit for the GIMP
(General Image Manipulation Program).  GTK is built on top of GDK (GIMP
Drawing Kit)  which is basically wrapper around the Xlib functions.  It's
called the GIMP toolkit because it was original written for developing
the GIMP, but has now been used in several free software projects.  The
authors are

Peter Mattis    (petm@xcf.berkeley.edu)
Spencer Kimball (spencer@xcf.berkeley.edu)
Josh MacDonald  (jmacd@xcf.berkeley.edu)

GTK is essentially an object oriented API.  Although written completely in
C, it is implemented using the idea of classes, and callback functions
(pointers to functions). 

There is also a third component called glib which contains a few
replacements for printf, malloc etc.  (someone please explain to me why..)

This tutorial is a compilation of other documentation found on gtk, as
well as some I've added and changed.  It is by no means complete.  This
tutorial assumes a good understanding of C, and how to create C programs. 
Note that there is also a C++ API for gtk (gtk--) in the works, so if you
prefer to use C++, you should look into this instead. 


GETTING STARTED

The first thing to do of course, is download the gtk source and install
it.  You can either get the distro with GIMP, or d/l it from Peter
Mattis's home dir ftp.xcf.berkely.edu/pub/pmattis (Hope this is ok :) ..
may change ?). 

We'll start with the simplest GTK program possible.  This program will
create a 200x200 pixel window and has no way of exiting except to be
killed using the shell or window manager. 

#include <gtk/gtk.h>

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

  gtk_init (&argc, &argv);

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_widget_show (window);

  gtk_main ();

  return 0;
}

All programs will of course include the gtk/gtk.h which declares the
variables and functions used in your gtk application. 

The next line

gtk_init (&argc, &argv);

calls the function gtk_init(gint *argc, gchar ***argv) which is always
called in any gtk application.  This will setup a few things for us such
as the default visual and colormap.  This then proceeds to call
gdk_init(gint *argc, gchar ***argv).  This function initializes the
library for use, sets up default signal handlers, and checks the
arguements passed to your application on the command line, looking for one
of the following: 

--display
--debug-level
--no-xshm 
--sync
--show-events
--no-show-events

It then removes these from the arguement list, and leaves anything it does
not recognize for your application to parse or ignore. This creates a set
of standard arguments excepted by all gtk applications. 

The next two lines of code create and display a window.

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_widget_show (window);

The GTK_WINDOW_TOPLEVEL argument specifies that we want the window to
undergo window manager decoration and placement. Rather than create a
window of 0x0 size, a window without children is set to 200x200 by default
so you can still manipulate it. 

The last line enters the GTK main processing loop.

  gtk_main ();

gtk_main() is another call you will see in every gtk application.  When
control reaches this point, gtk will sleep waiting for X events to occur. 
In our simple example however, no events will be registered.


HELLO WORLD IN GTK

OK, now for a program with a widget (a button).  It's the classic hello
world ala gtk.  This is taken from the hello world that Raph wrote with
comments added.  This will make a good base for a gtk application. 

     #include <gtk/gtk.h>

     /* this is a callback function. the data arguments are ignored in this example..
      * More on callbacks below. */
     void hello (GtkWidget *widget, gpointer *data)
     {
        g_print ("Hello World\n");
     }
     
     /* another callback */
     void destroy (void)
     {
       gtk_main_quit ();
     }

     int main (int argc, char *argv[])
     {
       /* GtkWidget is the storage type for widgets */
       GtkWidget *window;
       GtkWidget *button;

       /* this is called in all gtk applications.  Arguements are parsed from
        * the command line and are returned to the application. */
       gtk_init (&argc, &argv);

       /* create a new window */
       window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
       
       /* when the window is given the "destroy" signal (this can be given
        * by the application, or the window manager, the function destroy
        * will be called as defined above.  The NULL passed to this function
        * is used to pass optional data into this "callback" function. */
       gtk_signal_connect (GTK_OBJECT (window), "destroy",
                           GTK_SIGNAL_FUNC (destroy), NULL);
       
       /* sets the border width of the window. */
       gtk_container_border_width (GTK_CONTAINER (window), 10);

       /* creates a new button with the label "Hello World". */
       button = gtk_button_new_with_label ("Hello World");

       /* When the button receives the "clicked" signal, it will call the
        * function hello() passing it NULL as it's arguement.  The hello() function is
        * defined above. */
       gtk_signal_connect (GTK_OBJECT (button), "clicked",
                           GTK_SIGNAL_FUNC (hello), NULL);
       
       /* This will cause the window to be destroyed by calling
        * gtk_widget_destroy(window) when "clicked.  Again, the destroy
        * signal could come from here, or the window manager. */
       gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
                                  GTK_SIGNAL_FUNC (gtk_widget_destroy),
                                  GTK_OBJECT (window));
				  
       /* this packs the button into the window (a gtk container). */
       gtk_container_add (GTK_CONTAINER (window), button);
       
       /* the final step is to display this newly created widget... */
       gtk_widget_show (button);

       /* and the window */
       gtk_widget_show (window);

       /* all gtk applications must have a gtk_main().  Control ends here
        * and waits for an event to occur (like a key press or mouse event). */
       gtk_main ();

       return 0;
     }


To compile use:

gcc -Wall -g -lgtk -lgdk -lglib -lX11 -lXext -lm hello_world.c -o hello_world

The libraries above must all be in your default search paths, if not, add
-L<library directory> and gcc will look in these directories for the needed
libraries.  For instance, on my Debian Linux box, I have to add
-L/usr/X11R6/lib for it to find the X11 libraries.

Now the explanation:

As you can see from this example, it is quite easy to build a simple gtk
application.  GTK is an event driven toolkit, which means it will sleep in
gtk_main until an event occurs and control is passed to the appropriate
function.  By passing in a pointer to the function to be called when the
appropriate event occurs (by connecting a "signal") we tell the
application what we want to have done with when a given event occurs.

(You may also add timeout functions and file descriptor checks (like
select()) to gtk_main().  More on this later.) 

The general steps to creating a widget in gtk are:

1) gtk_*_new - one of various functions to create a new widget.

2) connect all signals we wish to use to the appropriate handlers.

3) set the attributes of the widget.

4) pack the widget into a container using gtk_container_add() or
gtk_box_pack_start() (are there others ?).

5) gtk_widget_show()


EVENTS

When setting up a signal handler (better term ?) we use a call to
gtk_signal_connect().  The deceleration of this function is:

gint gtk_signal_connect (GtkObject *object, gchar *name,
                         GtkSignalFunc func, gpointer func_data);

There are a few things you'll notice right away from this example.  The
gint, gchar etc. are typedefs to int and char respectively.  This is done
for portabilities sake.  A good example is "gint32" which will be
typedef'd to a 32 bit integer for any platform, whether it be the 64 bit
alpha (int (or short int?)), or the 32 bit Intel (long int or int).  The
typedefs are very straight forward and intuitive.  They are all defined in
glib/glib.h (which gets included from gtk.h).

The fist arguement to gtk_signal_connect is the object which will generate
the signal, usually a widget (always?).  The second of course is the
signal name, followed by the function you wish to be called, and then a
pointer to the data you wish to pass to this called function. 

The callback function you specify should be of the form:

void callback_func(GtkWidget *widget, gpointer *callback_data);

Generally, the GtkWidget arguement in the callback is unused.

So, if we wanted to pass the above function some data when a button is
pressed, we use:

gtk_signal_connect (GTK_OBJECT (button), "clicked",
                    GTK_SIGNAL_FUNC (callback_func), (gpointer) callback_data);

This assumes of course that you have already created a button.  The
GTK_OBJECT, GTK_SIGNAL_FUNC are just macros that perform casting for us. 
It is a good idea to include these for readability. 

Let's take a look at a slightly improved hello world with better examples
of callbacks.  This will also introduce us to our next topic, packing
widgets. 

#include <gtk/gtk.h>

/* Our new improved callback.  The data passed to this function is printed
 * to stdout. */
void callback (GtkWidget *widget, gpointer *data)
{
    g_print ("Hello again - %s was pressed\n", (char *) data);
}

/* another callback */
void destroy (void)
{
    gtk_main_quit ();
}

int main (int argc, char *argv[])
{
    /* GtkWidget is the storage type for widgets */
    GtkWidget *window;
    GtkWidget *button;
    GtkWidget *box1;
    
    /* this is called in all gtk applications.  Arguements are parsed from
     * the command line and are returned to the application. */
    gtk_init (&argc, &argv);
    
    /* create a new window */
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    
    /* this is a new call, this just sets the title of our
     * new window to "Hello Buttons!" */
    gtk_window_set_title (GTK_WINDOW (window), "Hello Buttons!");
    
    /* It's a good idea to do this for all windows. */
    gtk_signal_connect (GTK_OBJECT (window), "destroy",
			GTK_SIGNAL_FUNC (destroy), NULL);


    /* sets the border width of the window. */
    gtk_container_border_width (GTK_CONTAINER (window), 10);
    
    /* we create a box to pack widgets into.  this is described in detail 
     * in the "packing" section below.  The box is not really visible, it
     * is just used as a tool to arrange widgets. */
    box1 = gtk_hbox_new(FALSE, 0);
    
    /* put the box into the main window. */
    gtk_container_add (GTK_CONTAINER (window), box1);
    
    /* creates a new button with the label "Hello World". */
    button = gtk_button_new_with_label ("Button 1");
    
    /* Now when the button is clicked, we call the "callback" function
     * with a pointer to "button 1" as it's arguement */
    gtk_signal_connect (GTK_OBJECT (button), "clicked",
			GTK_SIGNAL_FUNC (callback), (gpointer) "button 1");
    
    /* instead of gtk_container_add, we pack this button into the invisible 
     * box, which has been packed into the window. */
    gtk_box_pack_start(GTK_BOX(box1), button, TRUE, TRUE, 0);

    /* always remember this step, this tells gtk that our preparation for
     * this button is complete, and it can be displayed now. */
    gtk_widget_show(button);
    
    /* do these same steps again to create a second button */
    button = gtk_button_new_with_label ("Button 2");
    
    /* call the same callback function with a different arguement, 
     * passing a pointer to "button 2" instead. */
    gtk_signal_connect (GTK_OBJECT (button), "clicked",
			GTK_SIGNAL_FUNC (callback), (gpointer) "button 2");

    gtk_box_pack_start(GTK_BOX(box1), button, TRUE, TRUE, 0);

    /* we show the widgets in the reverse order we created them,
     * someone want to explain to me why ?? :) or is it even 
     * necessary ? */
    gtk_widget_show(button);
    
    gtk_widget_show(box1);

    gtk_widget_show (window);
    
    /* rest in gtk_main and wait for the fun to begin! */
    gtk_main ();
    
    return 0;
}

Compile this program using the same libraries as above.  You'll notice in
this example there is no easy way to exit the program, you have to use
your window manager or command line to kill it.  A good exercise for the
reader would be to insert a third "Quit" button that will exit the
program.  Try playing with resizing the window. 


PACKING

When creating an application, you'll want to put more than one button
inside a window.  Our first hello world example only used one widget so we
could simply use a gtk_container_add call to "pack" the widget into the
window.  But when you want to put more than one widget into a window, how
do you control where that widget is positioned ?  This is where packing
comes in. 

Most packing is done by creating boxes as in the example above.  These are
invisible widget containers that we can pack our widgets into and come in
two forms, a horizontal box, and a vertical box.  When packing widgets
into a horizontal box, the objects are inserted horizontally from left to
right or right to left depending on the call used. In a vertical box,
widgets are packed from top to bottom or vice versa.  You may use any
combination of boxes inside or beside other boxes to create the desired
effect.

To create a new horizontal box, we use a call to gtk_hbox_new(), and for
vertical boxes, gtk_vbox_new().  The gtk_box_pack_start() and
gtk_box_pack_end() functions are used to place objects inside of these
containers.  The gtk_box_pack_start() function will start at the top and
work its way down in a vbox, and pack left to right in an hbox. 
gtk_box_pack_end() will do the opposite, packing from bottom to top in a
vbox, and right to left in an hbox.  Using these functions allow us to
right justify or left justify our widgets and may be mixed in any way to
achieve the desired effect.  We will use gtk_box_pack_start() in most of
our examples.  An object may be another container or a widget.  And in
fact, many widgets are actually containers themselves including the
button, but we usually only use a label inside a button.

By using these calls, gtk knows where you want to place your widgets so it
can do automatic resizing and other nifty things.  there's also a number
of options as to how your widgets should be packed. As you can imagine,
this method gives us a quite a bit of flexibility when placing and
creating widgets. 

Because of this flexibility, packing boxes in GTK+ can be confusing at
first. There are a lot of options, and it's not immediately obvious how
they all fit together.  In the end however, there are basically five
different styles you can get. 

<p align=center> <img src="packbox1.gif" width=528 height=235> </p>

Each line contains one horizontal box (hbox) with several buttons. The
call to gtk_box_pack is shorthand for the call to pack each of the buttons
into the hbox. Each of the buttons is packed into the hbox the same way
(i.e. same arguments to the gtk_box_pack_start () function). 

This is the declaration of the gtk_box_pack_start function. 

void gtk_box_pack_start (GtkBox    *box,
                         GtkWidget *child,
                         gint       expand,
                         gint       fill,
                         gint       padding);

The first arguement is the box you are packing the object into, the second
is this object.  The objects will all be buttons for now, so we'll be
packing buttons into boxes. 

The expand argument to gtk_box_pack_start() or gtk_box_pack_end() controls
whether the widgets are laid out in the box to fill in all the extra space
in the box, (TRUE) or whether the box is shrunk to just fit the widgets
(FALSE).  Setting expand to FALSE will allow you to do right and left
justifying of your widgets.  Otherwise, they will all expand to fit in the
box, and the same effect could be achieved by using only one of
gtk_box_pack_start or pack_end functions. 

The fill argument to the gtk_box_pack functions control whether the extra
space is allocated to the objects themselves (TRUE), or as extra padding
in the box around these objects (FALSE). It only has an effect if the
expand argument is also TRUE. 

[does the above sound right to you ?]

When creating a new box, the function looks like this:

GtkWidget * gtk_hbox_new (gint homogeneous,
                          gint spacing);

The homogeneous argument to gtk_hbox_new (and the same for gtk_vbox_new) 
controls whether each object in the box has the same size (i.e. the same
width in an hbox, or the same height in a vbox). If it is set, the expand
argument to the gtk_box_pack routines is always turned on.

What's the difference between spacing (set when the box is created) and
adding (set when elements are packed)? Spacing is added between objects,
and padding is added on either side of an object.  The following figure
should make it clearer: 

<p align=center> <img src="packbox2.gif" width=509 height=213> </p>


WIDGETS

The stock GTK+ distro comes with the following widgets:

[list them]

We'll further our exploration of gtk by examining each widget in turn,
creating a few simple functions to display them.  Another good source is
the testgtk.c program that comes with gtk.  It can be found in
gtk/testgtk.c. 

BUTTONS

We've almost seen all there is to see of the button widget.  It's pretty
simple.  There is however two ways to create a button.  You can use the
gtk_button_new_with_label() to create a button with a label, or use
gtk_button_new() to create a blank button.  It's then up to you to pack a
label or pixmap into this new button.  To do this, create a new box, and
then pack your objects into this box using the usual gtk_box_pack_start,
and then use gtk_container_add to pack the box into the button. 

here is an example of using gtk_button_new to create a button with a
picture and a label in it.  I've broken the code to create a box up from
the rest so you can use it in your programs. 

/* create a new hbox with an image and a label packed into it
 * and return the box.. This code is untested and I don't know if it
 * can work like this. */
GtkWidget *xpm_label_box (GtkWidget *window, char *xpm_filename, char *label_text)
{
   GtkWidget *box1;
   GtkWidget *label;
   GtkWidget *pixmapwid;
   GdkPixmap *pixmap;
   GdkBitmap *mask;
   GtkStyle *style;
   
   /* create box for xpm and label */
   box1 = gtk_hbox_new (FALSE, 0);
   gtk_container_border_width (GTK_CONTAINER (box1), 2);
         
   /* get style of button.. I assume it's to get the background color.
    * if someone knows the real reason, please enlighten me. */
   style = gtk_widget_get_style(button);
   
   /* now on to the xpm stuff.. load xpm */
   pixmap = gdk_pixmap_create_from_xpm (window, &mask,
                                        &style->bg[GTK_STATE_NORMAL],
                                        xpm_filename);
   pixmapwid = gtk_pixmap_new (pixmap, mask);
   
   /* create label for button */
   label = gtk_label_new (buttons[i].label);
   
   /* pack the pixmap and label into the box */
   gtk_box_pack_start (GTK_BOX (box1),
                       pixmapwid, FALSE, FALSE, 3);
   
   gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 3);
   
   return (box1);
}   

int main (int argc, char *argv[]);
{
   GtkWidget *window;
   GtkWidget *button;
   GtkWidget *box1;

   ...

   button = gtk_button_new();
   gtk_container_add (GTK_CONTAINER (window), button);
   
   ...
   
   /* this will return our newly created box with an image and
    * a label in it. */
   box1 = xpm_label_box(window, "somepic.xpm", "cool button");
      
   gtk_container_add (GTK_CONTAINER (button), box1);
   gtk_widget_show (box1);
   gtk_widget_show (button);
   gtk_widget_show (window);
}			    


MENUS

There are two ways to create menus, there's the easy way, and there's the
hard way.  Both have their uses, but you can usually use the menu_factory
(the easy way).  The "hard" way is to create all the menus using the calls
directly.  The easy way is to use the gtk_menu_factory calls.  This is
much simpler, but I've had problems because of the '/' that is used to
seperated menus.  Other than this, there is no real reason to use the
manual method (is this true ??) 

In the true tradition of teaching, we'll show you the hard
way first. :)

Let's look at the functions that are used to create menus.
This first function is used to create a new menu.

GtkWidget *gtk_menu_bar_new()

This rather self explanatory function creates a new menu bar.  You use
gtk_container_add to pack this into a window, or the box_pack functions to
pack it into a box - the same as buttons. 

GtkWidget *gtk_menu_new();

This function returns a pointer to a new menu, it is never actually shown
(with gtk_widget_show), it just holds the menu items.  Hopefully this will
become more clear when you look at the example below. 

The next two calls are used to create menu items that are packed into
the menu.

GtkWidget *gtk_menu_item_new()

and

GtkWidget *gtk_menu_item_new_with_label("label")	  

These calls are used to create the menus that are to be displayed. 
Remember to differentiate between a "menu" as created with gtk_menu_new
and a "menu item" as created by the gtk_menu_item_new functions. 

The new_with_label and plain new functions are just as you'd expect after
reading about the buttons.  One creates a new menu item with a label
already packed into it, and the other just creates a blank menu item. 

So, the steps are to create a menu using multiple calls to one of the
gtk_menu_item_new functions, using gtk_menu_append() to string them all
into one menu.  Then use gtk_menu_item_set_submenu() to insert these "menu
items" into a single "menu".  Then finally, a call to
gtk_menu_bar_append() which will set the "menu" onto the menu bar. 

[fix the above paragraph]

Well, let's go down and dirty with some easy examples:

--- CODE STARTS HERE ---- SNIP ---- SNIP ---- SNIP ---

[this copyright conflicts with my disclaimer at the bottom.  what will it
be Bawer ? :)  And I've already modified it too..]

/* gtk_menus1.c -- (c) 1997 Bawer Dagdeviren 
*	This code produces a simple menu with three menu-entries	
*/
#include <gtk/gtk.h>

int main (int argc, char *argv[])
{
 
  GtkWidget *window; 
  GtkWidget *menu;
  GtkWidget *menu_bar;
  GtkWidget *main_menu; /* the "root" menu-item */
  GtkWidget *submenu_items; /* this is used to hold the sub-menus */
  char buf[128]; /* just a buf to make things simpler */
  int i;

  gtk_init (&argc, &argv);
  
  /* create a new window */

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW (window), "GTK Menu Test"); 
  gtk_signal_connect(GTK_OBJECT (window), "destroy", 
		     (GtkSignalFunc) gtk_exit, NULL);
  gtk_widget_show(window);
  
  /* Init the menu-widget, and remember -- never 
   * gtk_show_widget() the menu widget!! */
  menu = gtk_menu_new();
  
  /* Now it's time for our "root" menu-item, we'll call it "test-menu" 
   * because that's what it is =) */

  main_menu = gtk_menu_item_new_with_label("test-menu");
  
  gtk_widget_show(main_menu); 

  /* Next we make a little loop that makes three menu-entries for "test-menu". You'll notice a new call, namely gtk_menu_append. It is needed to add menu-entries to our menu.*/
  
  for(i = 0; i < 3; i++)
    {
      /* Copy the names to the buf. */
      sprintf(buf, "Test-undermenu - %d", i);
      
      /* Give the menu-item a name... */
      submenu_items = gtk_menu_item_new_with_label(buf);
      
      /* ...and add it to the menu. */
      gtk_menu_append(GTK_MENU (menu), submenu_items);
      
      /* Show the widget */
      gtk_widget_show(submenu_items);
    }
  
  /* Now we specify that we want "menu" to be the submenu for the "root" menu-item */
  gtk_menu_item_set_submenu(GTK_MENU_ITEM (main_menu), menu);

  /* Now we create a menu-bar to hold the menus and add it to our main window*/
  menu_bar = gtk_menu_bar_new();  
  gtk_container_add(GTK_CONTAINER(window), menu_bar);
  gtk_widget_show(menu_bar);

  /* And finally we append the menu-item to the menu-bar -- this is the "root" menu-item I have been raving about =) */
  gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), main_menu);
  
  gtk_main ();

  return 0;
}

--- CODE ENDS HERE ---- SNIP ---- SNIP ---- SNIP ---

Now let's discuss what we have learned here.  A call is first made to
"gtk_menu_item_new_with_label(...)" that simply returns a new menu_item,
in order to use this new widget you append it either to the menu-bar
widget or the menu widget. In the former case you will get a "root" menu
and in the other case you get a menu-entry that can be used as a submenu
later on.In order to make the submenu you just tell GTK which menu-item
will be the submenus parent (the "root" menu) by calling
"gtk_menu_item_set_submenu(...)" and giving it the menu-item and the menu
as arguments. So, you

see, after the call to gtk_menu_item_set_submenu(GTK_MENU_ITEM
(main_menu), menu) the actuall menu is now the "main_menu" widget (are you
with still with me?) and all we have to do now is to append it to the
menu_bar widget -- VOILA! We have our selves a menu!  

The reason why we shouldn't show the menu widget (try it, it won't wreck
your computer) is that when we do this, for reasons yet unrevealed to me
(FIXME), the menu-area doesn't react until after 2-3 clicks, this is not
desired. 


HOW TO ADD SUBMENUS TO SUBMENUS  


------------------------------


This tutorial is written and maintained by Ian Main <slow@intergate.bc.ca>
This is free software, you may use any of these examples in any of your
code without including any disclaimer or copyright notice. 

Many thanks to the contributors

Bawer Dagdeviren <chamele0n@geocities.com>  (tutorial on menus)

And to the following from which I stole^H^H^H^H^H borrowed some material

Raph Levien <raph@acm.org>
Peter Mattis <something>

And to all of you who commented and helped refine this document.

Thanks.



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]