Creating your own GTK+ widget & adding it to Glade Palette

Tutorial By: Ishan Chattopadhyaya

Introduction

In this tutorial we would be creating a new GTK+ widget (based upon GDK) and be adding it to the Glade Palette so that you can use the widget you created in your own GTK+ programs that you create with Glade. For this tutorial we would create a simple widget that works as a clock and call it GtkClock (see figure 1).


Figure 1: The GtkClock widget


Figure 2: The final result

The code we would be writing to create the widget would be for GTK+ 2.0. We would be adding the widget to Glade version >= 1.1.2. For this tutorial, I have used Glade-2.0.0, the latest that is. Nevertheless, I am very sure that only with a minor changes to the code, the same thing can be achieved with GTK+ 1.2 and Glade 0.6.4 as well. Glade can be downloaded from: GLADE-FTP-site. Figure 2 shows our widget in the Glade's palette window.

Creating the new Widget: What is GTK+ Widget Factory

The new widget, GtkClock, would consist of two files, viz. gtkclock.c & gtkclock.h. Whatever code required to make the widget, goes into these files. Writing these files entirely from scratch might be difficult in the beginning, esp. if one doesn't have enough experience of creating widgets. So, in order to simplify things, a very neat tool, GTK+ Widget Factory (GWF), can be used to create the framework inside these two files, upon which the entire widget can be built. GWF can be found here (Win32 version of GWF is here).

Even if you decide not to use GWF, then also you can follow this tutorial and achieve your goal. All you need to do in that case would be to use the files that GWF outputs from here: gtkclock-initial.c & gtkclock-initial.h.


Figure 3: GTK+ Widget Factory

Building the Widget files: gtkclock.c & gtkclock.h

  1. Create a directory, e.g. /home/<username>/GtkClock
  2. Open up GWF. Fill in the details regarding your widget, save your project and build the files (write source). Save the project in the above directory.

For the Parent and Parent Include, I found out that whatever you supply, it doesn't reflect anything upon the generated files. So just go ahead and fill in whatever you want.

Editing the gtkclock.c & gtkclock.h files

First of all, open the gtkclock.h file (that GWF generates) and find the following part of the code (Before making changes):

Filename: gtkclock.h

Before making changes

...
1: guint gtk_clock_get_type (void);
2: GtkClock* gtk_clock_new (void);
3: void gtk_clock_construct (GtkClock *clock);
...

 

After making changes

...
1: guint gtk_clock_get_type (void);
2: GtkWidget* gtk_clock_new (void);
3: void gtk_clock_construct (GtkClock *clock);
...

Now change the 'GtkClock* gtk_clock_new (void)' to 'GtkWidget* gtk_clock_new (void)' in line 2. This has to be done so as to conform to GTK+ standards. This function is defined in gtkclock.c and thus changes have to be made to the definition as well. So, open the file and make the following changes to the gtk_clock_new () function.

Filename: gtkclock.c

Before making changes

GtkClock*
gtk_clock_new(void)
{
GtkClock *clock;
   clock = GTK_CLOCK( gtk_type_new ( gtk_clock_get_type()));
   gtk_clock_construct(clock);
   return clock;
}

 

After making changes

1: GtkWidget*
2: gtk_clock_new(void)
3: {
4: GtkWidget *clock;
5:    clock = gtk_type_new ( gtk_clock_get_type());
6:    // gtk_clock_construct(clock);
7:    return clock;
8: }

This function, gtk_clock_new () is called when a new object of GtkClock widget class is created. It returns the type of GtkClock. Here, we've commented out line 6 because we do not want to construct the clock through this function. We would call this function at a through another function that is executed when the widget is initialised.

Now, we would be adding the data members to the GtkClock Class. For a clock class, it is obvious that the data members be hour, min and sec. So, in the header file (GtkClock.h), add these items to the struct _GtkClock:

Filename: gtkclock.h

Before making changes

typedef struct _GtkClock	GtkClock;
typedef struct _GtkClockClass GtkClockClass;
struct _GtkClock
{
  GtkWidget parent;
};

 

After making changes

1: typedef struct _GtkClock	GtkClock;
2: typedef struct _GtkClockClass GtkClockClass;
3: struct _GtkClock
4: {
5:   GtkWidget parent;
6:   int hour;       // Value of hours is stored here
7:   int min;        // Value of minutes is stored here
8:   int sec;        // Value of seconds is stored here
9: };

Let's now focus on to drawing part of the widget. Before drawing the widget onto the screen, first of all, a default size has to be allocated to widget's drawing area. Our function for this would be: gtk_clock_size_request (). Of course, the user/app developer can resize the widget after instantiating it. The subsequent gtk_clock_realize () function is for creating the drawing area while taking the allocated size of the widget into consideration.

Here's the code for the gtk_clock_size_request () and gtk_clock_realize ()

Filename: gtkclock.c

New functions added (in any suitable place you like)

/* Add these prototypes somewhere in the beginning of the file */
1: static void gtk_clock_size_request (GtkWidget *widget, GtkRequisition *req);
2: static void gtk_clock_realize (GtkWidget *widget);

.....
.....       
3: static void gtk_clock_size_request (GtkWidget *widget, GtkRequisition *req)
4: {
5: req->width = 200; // Default value
6: req->height = 200; // Default value
7: }
8: static void gtk_clock_realize (GtkWidget *widget)
9: {
10:  GtkClock *drawing_area;
11:  GdkWindowAttr attributes;
12:  gint attributes_mask;
13:  g_return_if_fail (widget != NULL);
14:  g_return_if_fail (GTK_IS_CLOCK (widget));
15:  drawing_area = GTK_CLOCK (widget);
16:  GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
17:  attributes.window_type = GDK_WINDOW_CHILD;
18:  attributes.x = widget->allocation.x;
19:  attributes.y = widget->allocation.y;
20:  attributes.width = widget->allocation.width;
30:  attributes.height = widget->allocation.height;
31:  attributes.wclass = GDK_INPUT_OUTPUT;
32:  attributes.visual = gtk_widget_get_visual (widget);
33:  attributes.colormap = gtk_widget_get_colormap (widget);
34:  attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK;
35:  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
36:  widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
         &attributes, attributes_mask);
37:  gdk_window_set_user_data (widget->window, drawing_area);
38:  widget->style = gtk_style_attach (widget->style, widget->window);
39:  gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
40:}

The size_request () function is self-explanatory. What the realize () function does is described as follows:

Quoted from: GTK+ 2.0 Tutorial

First, we have a function that does the work of creating the X window. Notice that a mask is passed to the function gdk_window_new () which specifies which fields of the GdkWindowAttr structure actually have data in them (the remaining fields will be given default values). Also worth noting is the way the event mask of the widget is created. We call gtk_widget_get_events () to retrieve the event mask that the user has specified for this widget (with gtk_widget_set_events ()), and add the events that we are interested in ourselves.

After creating the window, we set its style and background, and put a pointer to the widget in the user data field of the GdkWindow. This last step allows GTK to dispatch events for this window to the correct widget.

Having written the functions for size requisition and realisation of the widget, we'll have to specify that, when instantiating the widgets, these functions should be used instead of the GtkWidget's default routines. For this, make the following changes to the gtk_clock_class_init () function:

Filename: gtkclock.c

Before making changes

static void
gtk_clock_class_init (GtkClockClass *klass)
{
GtkObjectClass *object_class;
object_class = (GtkObjectClass*) klass; object_class->destroy = gtk_clock_destroy; parent_class = gtk_type_class (gtk_widget_get_type()); }
 

 

After making changes

1: static void
2: gtk_clock_class_init (GtkClockClass *klass)
3: {
4: GtkObjectClass *object_class;
5: GtkWidgetClass *widget_class;
6:   object_class = (GtkObjectClass*) klass;
7:   widget_class = (GtkWidgetClass*) klass;
8:   object_class->destroy = gtk_clock_destroy;
9:   parent_class = gtk_type_class (gtk_widget_get_type());

10:  /* Overides the default methods */
11:  widget_class->realize = gtk_clock_realize;
12:  widget_class->size_request = gtk_clock_size_request;
13: }

Now, we'll add one of the most important functions, gtk_clock_init(). This function is responsible for the initiation of the widget. In this function, we would be calling a function gtk_timeout_add () that would call the construct () function to build the clock. Since we want to have a real-time clock, we want the clock to be updated continuously. For this we'll use the gtk_timeout_add () function. It's 1st argument is the interval of time (in milliseconds) between each call to the function (2nd argument) that has to be executed, i.e. the gtk_clock_construct () function in this case.

Filename: gtkclock.c

Update the following function

1: static void
2: gtk_clock_init (GtkClock *clock)
3: {
4: /* Make the clock, refresh after every 1ms */
5: gtk_timeout_add (1, gtk_clock_construct, (gpointer)(clock));
6: }

The gtk_timeout_add () function requires its 2nd argument to be a function with the following prototype:
gint function (gpointer data);
(Return value 0 to stop the function being executed, non-zero to continue the timeout)

It's thus clear that we should change the function declaration & definition for the gtk_clock_construct () function. So, here's the complete function after having made the changes to its definition.

Filename: gtkclock.c

Update the following function

1: gint
2: gtk_clock_construct (gpointer data)
3: {
4: GtkClock *clock = data;
5: /* If any problem drawing, stop the timeout */ 6: if (!GTK_WIDGET_DRAWABLE (clock)) return 0;
7:   /* Get the time, and store the vals of hour, min, and secs as GtkClock class data */ 
8:   get_current_time ( &clock->hour, &clock->min, &clock->sec );
         
9:   /* Draw the clock */
10:  gtk_clock_draw (GTK_WIDGET(clock), NULL);
11:  return 1;
12: }

Now, here's the single most important function to draw the clock: gtk_clock_draw (). It actually draws the clock on the drawing area that we initialised in the realize () function. The main things that this draw () function does is:

  1. Initialise the colors that are to be used for the hands of the clock.
  2. Get the class data, i.e. the values of hour, min, and sec.
  3. Draw the marks for the minutes (in multiples of 5).
  4. Get the coordinates of the minutes, seconds & hours and draw lines from centre to those coordinates. These lines would symbolise the hands.

So, create the function as follows. Also, remember to add a prototype of the function in the beginning of the source file (gtkclock.c).

Filename: gtkclock.c

Add this prototype somewhere in the beginning of the file

1: static void
2: gtk_clock_draw (GtkWidget *widget, GdkRectangle *area)
;

New functions added (in any suitable place you like)

static void
gtk_clock_draw (GtkWidget *widget, GdkRectangle *area)
{
GtkClock *clock;
  int loop1; // Loop variable
  int max_radius; // Maximum Radius of the Clock
  int min=0, hour=0, sec=0; // Hours, Minutes & Seconds
  int yc, xc; // x-coordinate & y-coordinate
  int alloc_width, alloc_height; // Allocated height & width
  GdkColor colors[4]; // Colors for the hands
  GdkGC *clock_gc = gdk_gc_new (widget->window); // Graphics Context for drawing in the hands
  if (GTK_WIDGET_DRAWABLE (widget))
  {
    clock = GTK_CLOCK (widget);
    alloc_width = widget->allocation.width - 1;
    alloc_height = widget->allocation.height - 1;
    gdk_window_clear_area (widget->window, 0, 0, alloc_width, alloc_height);
    /* Store the colors */
    gdk_color_parse("Red", &colors[0]); // for minute's hand
    gdk_color_parse("DarkGreen", &colors[1]); // for hour's hand
    gdk_color_parse("RoyalBlue", &colors[2]); // for second's hand
    gdk_color_parse("Black", &colors[3]);
    /* Get the values from the GtkClock class */
    min = clock->min; hour = clock->hour; sec = clock->sec;
    /* Set max_radius with the lesser value between height or width */
    max_radius = (alloc_width < alloc_height)? (alloc_width/2) : (alloc_height/2);
    /* Draw the 12 points which signify the marks in the clock */
    for (loop1=0; loop1<60; loop1+=5)
    {
       gtk_clock_obtain_coordinates (&xc, &yc, max_radius, max_radius, loop1, max_radius-10, 60);
       gdk_gc_set_line_attributes (clock_gc, 10, GDK_LINE_SOLID, 0, 0);
       gdk_draw_line (widget->window, widget->style->black_gc, xc, yc,xc+1,yc+1);
       gdk_draw_line (widget->window, widget->style->black_gc, xc+1, yc,xc,yc+1);
    }
         
    /* Set line width to 2 */
    gdk_gc_set_line_attributes (clock_gc, 2, GDK_LINE_SOLID, 0, 0);
    /* Obtain coordinates for minutes and draw a line from center to that  point */
    gtk_clock_obtain_coordinates (&xc, &yc, max_radius, max_radius, min, max_radius, 60);
    gdk_colormap_alloc_color (gdk_colormap_get_system(), &colors[0], FALSE, TRUE);
    gdk_gc_set_foreground (clock_gc, colors+0); /* Red */
    gdk_draw_line (widget->window, clock_gc, xc, yc, max_radius, max_radius);
    /* Obtain coordinates for hour and draw a line from center to that point */
    gtk_clock_obtain_coordinates (&xc, &yc, max_radius, max_radius, (hour!=12)?hour*60+min:min, max_radius*0.65, 12*60);
    gdk_colormap_alloc_color (gdk_colormap_get_system(), &colors[1], FALSE, TRUE);
    gdk_gc_set_foreground (clock_gc, colors+1); /* Green */
    gdk_draw_line (widget->window, clock_gc, xc, yc, max_radius, max_radius);
    /* Obtain coordinates for seconds and draw a line from center to that point */
    gtk_clock_obtain_coordinates (&xc, &yc, max_radius, max_radius, sec, max_radius*.85, 60);
    gdk_colormap_alloc_color (gdk_colormap_get_system(), &colors[2], FALSE, TRUE);
    gdk_gc_set_foreground (clock_gc, colors+2); /* Blue */
    gdk_draw_line (widget->window, clock_gc, xc, yc, max_radius, max_radius);
    }
}
       

In line , you'll notice a gtk_clock_obtain_coordinates () function called. This is another utility function to obtain the coordinated for the line to be drawn based upon the value (say, 15, if current time's minutes is 15), maximum value (60 minutes), coordinates of centre and maximum possible radius of the arms. The calculated coordinates ar stored in address pointed to by xcood & ycood that are passed into the function when called. For a derivation of the formula(e) used here to calculate the coordinates, click here. Here's the function to be added:

Filename: gtkclock.c

Include this file:

#include <math.h>

Add this prototype somewhere in the beginning of the file

1: void
2: gtk_clock_obtain_coordinates (int *xcood, int *ycood, int h, int k, 
                                int value, int max_radius, int max_val);

New utility function added (at the end of the file)

1: void 
2: gtk_clock_obtain_coordinates (int *xcood, int *ycood, int h, int k,
                                           int value, int max_radius, int max_val)
3: {
4: int yc=*ycood, xc=*xcood, f;
5: float t=0, s=0;
6:   t=(float)(value*360.0/(float)max_val) * 3.14159/180;
7:   s=sin(t/2);
8:   yc=(int)(k+ 2*max_radius*(pow(s,2)) -max_radius);
9:   f=-max_radius*max_radius+h*h+(yc-k)*(yc-k);
10:  if (value<max_val/2)
11:    xc=h + (float)(pow(h*h-f, .5));
12:  else
13:    xc=h - (float)(pow(h*h-f, .5));
14:  *ycood=yc; *xcood=xc;
15: }

Figure 3: Clock's variable parameters

Now, after all this, finally, let's write the utility function to obtain the system time. The time () function is used to obtain the no. of seconds since 1 January, 1970. Simple math would lead us to the present hour, minute and second. We'll call the function get_current_time (). It may be noted that time () works fine on Linux as well as on Windows. So, your widget should be portable. Here's the function. Also make sure that you declare the function at the beginning of the gtkclock.c file. It would be of no use to declare it in the header file, since the scope of the function is limited to the creation of the widget only, and is not meant for other developers to use while using your widget.

Filename: gtkclock.c

Add this prototype somewhere in the beginning of the file

1: int 
2: get_current_time (int *hr, int *mn, int *sc);

New utility function added (at the end of the file)

/* This code (get_current_time()) is full of bugs. 
* This is originally taken from the yearly project
* of a friend of mine. Maybe you could suggest me
* better code, or if I find time, I'll write a
* better function.
*/

1: gint 2: get_current_time (int *hr, int *mn, int *sc)
3: {
4: long hour = *hr, min = *mn, sec= *sc;
5:   hour = time(NULL);
6:   min=hour; sec = hour; sec=hour%60;
7:   min=min-min%60; min=min/60; min=min%60;
8:   hour=hour/(60*60); hour=hour%12; hour = (hour+6)%12;
9:   if (min<30) min = min+30;
10:  else min = min-30;
11:  if(!hour) hour=12; if (min>30) hour--; if(hour<1) hour=12;
12:  *sc = sec; *mn = min; *hr = hour;
13:  return 1;
14: }
 

This completes the creation of the widget. Here are the final gtkclock.c & gtkclock.h files. To test this widget separately, here's a test program:

Filename: testclock.c

1: #include <gtk/gtk.h>
2: #include "gtkclock.h"
3: int main (int argc, char *argv[])
4: {
5:   GtkWidget *window;
6:   GtkWidget *clock;
7:   gtk_init (&argc, &argv);
8:          
9:   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
10:  gtk_window_set_title (GTK_WINDOW (window), "GtkClock widget example...");
11:  gtk_signal_connect (GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC (gtk_main_quit), NULL);
12:  gtk_container_border_width (GTK_CONTAINER (window), 10);
13:  clock = gtk_clock_new ();
14:  gtk_widget_draw (clock, NULL);
15:  gtk_widget_show (clock);
16:  gtk_container_add (GTK_CONTAINER (window), clock);
17:  gtk_widget_show (window);
18:  gtk_main ();
19: }
 

Download the zip file for these 3 files here.

Adding the widget to Glade Palette

TODO: Write me!
Note: For widget plugin to Glade, see http://wingtk.sourceforge.net/wglade.html


Ishan Chattopadhyaya
Ishan Chattopadhyaya

Other tutorials by Ishan Chattopadhyaya

Links

Please Sign my guestbook here | My Homepage | My Email Address

SourceForge Logo

Visitor no. since 17th May 2004