|
|
|
| GUI Clinic Home |
||
| Example 14 Described | Line-by-Line Explanation | ||
Previous: Annotated Example 13 Example 14 is a bit simpler than example 13. It is basically the main example that shows how to create custom dialog controls. It's main screen consists of an editable text box, a check box, an Exit button, and a custom control: a clock. The clock shows the system time and updates whenever the time changes (usually every second) to keep up-to-date. The way the clock works is discussed below. Here is a screen shot: ![]()
Here's how example 14 works. Some of the code has been re-formatted, but none has been changed. First we have include files and declarations for globals. Nothing too special here. The editable text box will use the_string for its text, and the_time will be used by the clock. Straightforward stuff: /*
* Example program for the Allegro library,
* by Shawn Hargreaves.
*
* This program demonstrates how to write
* your own GUI objects.
*/
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include "allegro.h"
#include "example.h"
/* for the d_edit_proc() object */
char the_string[32] = "Change Me!";
/* the current time, for the clock object */
struct tm the_time;
Next! The next routine is a helper function used by the clock object. It does just what it looks like it would do: it draws a hand on the clock face! It does this by calculating the angle that it needs to be at, and drawing a line of the given length: /* helper function to draw a hand on
* the clock
*/
void draw_hand(BITMAP *bmp, int value, int range,
int v2, int range2, fixed length,
int color)
{
fixed angle;
fixed x, y;
int w, h;
angle = ((itofix(value) * 256) / range) +
((itofix(v2) * 256) / (range * range2)) -
itofix(64);
x = fmul(fcos(angle), length);
y = fmul(fsin(angle), length);
w = bmp->w / 2;
h = bmp->h / 2;
line(bmp, w, h, w + fixtoi(x*w), h + fixtoi(y*h),
color);
}
Next comes the main part of the example: the custom clock object. Since its dialog procedure is pretty long, let's look at it piece by piece. The dialog procedure doesn't do anything before it handles messages. It just declares some variables. /* custom dialog procedure for the clock object */
int clock_proc(int msg, DIALOG *d, int c)
{
time_t current_time;
struct tm *t;
BITMAP *temp;
fixed angle, x, y;
/* process the message */
switch (msg) {
Next, we'll look at the initialization code. When the clock receives a MSG_START, it does a couple of things. First, it determines the current time, and stores it in the_time. Next, since drawing the whole clock face every time the object needs to be drawn would be inefficient, it creates its own bitmap and draws the face (minus the hands) onto it. That way, when the clock is drawn it can just blit the bitmap to the screen and draw the hands on top of it. The pointer to this new bitmap is stored in the DIALOG's dp field /* initialise when we get a start message */
case MSG_START:
/* store the current time */
current_time = time(NULL);
t = localtime(¤t_time);
the_time = *t;
/* draw the clock background onto */
/* a memory bitmap */
temp = create_bitmap(d->w, d->h);
clear_to_color(temp, d->bg);
/* draw borders and a nobble in the middle */
circle(temp, temp->w/2, temp->h/2,
temp->w/2-1, d->fg);
circlefill(temp, temp->w/2, temp->h/2,
2, d->fg);
/* draw ticks around the edge */
for (angle = 0; angle < itofix(256);
angle += itofix(256)/12) {
x = fcos(angle);
y = fsin(angle);
line(temp, temp->w/2+fixtoi(x*temp->w*15/32),
temp->h/2+fixtoi(y*temp->w*15/32),
temp->w/2+fixtoi(x*temp->w/2),
temp->h/2+fixtoi(y*temp->w/2), d->fg);
}
/* store the clock background bitmap in d->dp */
d->dp = temp;
break;
Next, we have the handler for the MSG_END message. There isn't much to do here; it just cleans up by destroying the clock face bitmap. /* shutdown when we get an end message */
case MSG_END:
/* destroy the clock background bitmap */
destroy_bitmap(d->dp);
break;
Coming up next is the MSG_IDLE message handler. This messages is sent by the GUI when it isn't doing anything important, so this is a perfect chance to update the clock object's time. That's just what this does. If the time has changed, the clock sends itself a MSG_DRAW message, which updates its on-screen appearance. Notice the big comment in the code below: /* update the clock in response to idle messages */
case MSG_IDLE:
/* read the current time */
current_time = time(NULL);
t = localtime(¤t_time);
/* check if it has changed */
if ((the_time.tm_sec != t->tm_sec) ||
(the_time.tm_min != t->tm_min) ||
(the_time.tm_hour != t->tm_hour)) {
the_time = *t;
/* Redraw ourselves if the time has changed.
* Note that we have to turn the mouse off
* before doing this: the dialog manager turns
* it off whenever it sends us a draw message,
* but we are sending the message ourselves
* here so we are responsible for making sure
* the mouse is off first. Also note the use of
* the SEND_MESSAGE macro rather than a simple
* recursive call to clock_proc(). This vectors
* the call through the function pointer in the
* dialog object, which allows other object
* procedures to hook it, for example a
* different type of clock could process the draw
* messages itself but pass idle messages on to
* this procedure.
*/
show_mouse(NULL);
SEND_MESSAGE(d, MSG_DRAW, 0);
show_mouse(screen);
}
break;
Finally, there's the MSG_DRAW handler. This would be kind of long if it weren't for the fact that the initialization code for the clock creates a bitmap with a pre-drawn clock face. Now, drawing the clock is easy: First, it creates a temporary bitmap to draw onto. Then, it blits the blank clock face onto this bitmap. Next, it draws the clock's three hands. Lastly, it draws the clock onto the screen and destroys the temporary bitmap. Take a look: /* draw the clock in response to draw messages */
case MSG_DRAW:
/* draw onto a temporary memory bitmap */
/* to prevent flicker */
temp = create_bitmap(d->w, d->h);
/* copy the clock background onto */
/* the temporary bitmap */
blit(d->dp, temp, 0, 0, 0, 0, d->w, d->h);
/* draw the hands */
draw_hand(temp, the_time.tm_sec, 60,
0, 1, itofix(9)/10, d->fg);
draw_hand(temp, the_time.tm_min, 60,
the_time.tm_sec, 60, itofix(5)/6, d->fg);
draw_hand(temp, the_time.tm_hour, 12,
the_time.tm_min, 60, itofix(1)/2, d->fg);
/* copy the temporary bitmap onto the screen */
blit(temp, screen, 0, 0, d->x, d->y, d->w, d->h);
destroy_bitmap(temp);
break;
The last part of the dialog procedure just returns D_O_K. /* always return OK status, since the clock doesn't
* ever need to close the dialog or get the input
* focus.
*/
return D_O_K;
Whew! Okay. The next thing the program does is declare and initialize the main dialog. Let's go through it item by item. First is a d_clear_proc object. All it does is clear the screen whenever the dialog is drawn, so we don't have to worry about overdraw on previously-drawn screen data. Next is the editable text box. Its caption (the dp field) is the_string, which was declared earlier. Then comes the check box. Its shortcut key is set to 't', so whenever the user presses the T key, the object is checked (or unchecked, depending on its current state). It's caption is set to "&Toggle Me"; the "T" character will be underlined. Fourth, we have the custom clock object. The proc pointer points to the custom clock_proc dialog procedure. It doesn't need any extra outside data, so the d1, d2, and dp fields are left alone. Next, we have the Exit button. The thing that makes it an Exit button is that the D_EXIT flag is set. This means that when the button is clicked, it will return D_CLOSE, which will cause the GUI manager to close the dialog. Since the whole program is a dialog, that means the program will end. Finally, the dialog is terminated by a DIALOG will a NULL proc pointer. DIALOG the_dialog[] =
{
/* (dialog proc) (x) (y) (w) (h)
(fg) (bg) (key) (flags)
(d1) (d2) (dp) */
{ d_clear_proc, 0, 0, 0, 0,
255, 0, 0, 0,
0, 0, NULL },
{ d_edit_proc, 32, 32, 256, 8,
255, 0, 0, 0,
16, 0, the_string },
{ d_check_proc, 32, 64, 88, 12,
255, 0, 't', 0,
0, 0, "&Toggle Me" },
{ clock_proc, 192, 64, 64, 64,
255, 0, 0, 0,
0, 0, NULL },
{ d_button_proc, 120, 160, 80, 16,
255, 0, 0, D_EXIT,
0, 0, "Exit" },
{ NULL, 0, 0, 0, 0,
0, 0, 0, 0,
0, 0, NULL }
};
The last thing in this program is its very simple main() function. It just initializes the program and runs the dialog. Check it out: int main()
{
allegro_init();
install_keyboard();
install_mouse();
install_timer();
set_gfx_mode(GFX_VGA, 320, 200, 0, 0);
set_pallete(desktop_pallete);
do_dialog(the_dialog, -1);
return 0;
}
Well, that's it! Most of this example was pretty straightforward. The clock is a good example of how to create a completely new GUI object. Its code may seem complex at first, but breaking it into pieces helps make it easier to understand. Next: Annotated Example 30 |