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 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.
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
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
|
|
After making changes... |
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
|
|
After making changes1: GtkWidget* 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
|
|
After making changes1: typedef struct _GtkClock GtkClock; 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) 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
|
|
After making changes1: static void 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
|
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
|
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:
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 file1: static void New functions added (in any suitable place you like)static void 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 file1: 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)
|
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 file1: int 2: get_current_time (int *hr, int *mn, int *sc); New utility function added (at the end of the file)
|
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 PaletteTODO: Write me!
Note: For widget plugin to Glade, see http://wingtk.sourceforge.net/wglade.html
|
Other tutorials by Ishan Chattopadhyaya
Links
|