rnd-20030823-2-src
[rocksndiamonds.git] / src / libgame / gadgets.c
1 /***********************************************************
2 * Artsoft Retro-Game Library                               *
3 *----------------------------------------------------------*
4 * (c) 1994-2002 Artsoft Entertainment                      *
5 *               Holger Schemel                             *
6 *               Detmolder Strasse 189                      *
7 *               33604 Bielefeld                            *
8 *               Germany                                    *
9 *               e-mail: info@artsoft.org                   *
10 *----------------------------------------------------------*
11 * gadgets.c                                                *
12 ***********************************************************/
13
14 #include <stdarg.h>
15 #include <string.h>
16
17 #include "gadgets.h"
18 #include "text.h"
19 #include "misc.h"
20
21
22 /* values for DrawGadget() */
23 #define DG_UNPRESSED            0
24 #define DG_PRESSED              1
25
26 #define DG_BUFFERED             0
27 #define DG_DIRECT               1
28
29 static struct GadgetInfo *gadget_list_first_entry = NULL;
30 static struct GadgetInfo *gadget_list_last_entry = NULL;
31 static int next_free_gadget_id = 1;
32 static boolean gadget_id_wrapped = FALSE;
33
34 static struct GadgetInfo *getGadgetInfoFromGadgetID(int id)
35 {
36   struct GadgetInfo *gi = gadget_list_first_entry;
37
38   while (gi && gi->id != id)
39     gi = gi->next;
40
41   return gi;
42 }
43
44 static int getNewGadgetID()
45 {
46   int id = next_free_gadget_id++;
47
48   if (next_free_gadget_id <= 0)         /* counter overrun */
49   {
50     gadget_id_wrapped = TRUE;           /* now we must check each ID */
51     next_free_gadget_id = 0;
52   }
53
54   if (gadget_id_wrapped)
55   {
56     next_free_gadget_id++;
57     while (getGadgetInfoFromGadgetID(next_free_gadget_id) != NULL)
58       next_free_gadget_id++;
59   }
60
61   if (next_free_gadget_id <= 0)         /* cannot get new gadget id */
62     Error(ERR_EXIT, "too much gadgets -- this should not happen");
63
64   return id;
65 }
66
67 #if 0
68 void DUMP_GADGET_MAP_STATE()
69 {
70   struct GadgetInfo *gi = gadget_list_first_entry;
71
72   while (gi != NULL)
73   {
74     printf("-XXX-1-> '%s': %s\n",
75            gi->info_text, (gi->mapped ? "mapped" : "not mapped"));
76
77     gi = gi->next;
78   }
79 }
80 #endif
81
82 static struct GadgetInfo *getGadgetInfoFromMousePosition(int mx, int my)
83 {
84   struct GadgetInfo *gi;
85
86   /* open selectboxes may overlap other active gadgets, so check them first */
87   for (gi = gadget_list_first_entry; gi != NULL; gi = gi->next)
88   {
89     if (gi->mapped && gi->active &&
90         gi->type & GD_TYPE_SELECTBOX && gi->selectbox.open &&
91         mx >= gi->selectbox.x && mx < gi->selectbox.x + gi->selectbox.width &&
92         my >= gi->selectbox.y && my < gi->selectbox.y + gi->selectbox.height)
93       return gi;
94   }
95
96   /* check all other gadgets */
97   for (gi = gadget_list_first_entry; gi != NULL; gi = gi->next)
98   {
99     if (gi->mapped && gi->active &&
100         mx >= gi->x && mx < gi->x + gi->width &&
101         my >= gi->y && my < gi->y + gi->height)
102       return gi;
103   }
104
105   return NULL;
106 }
107
108 static void default_callback_info(void *ptr)
109 {
110   return;
111 }
112
113 static void default_callback_action(void *ptr)
114 {
115   return;
116 }
117
118 static void DrawGadget(struct GadgetInfo *gi, boolean pressed, boolean direct)
119 {
120   int state = (pressed ? GD_BUTTON_PRESSED : GD_BUTTON_UNPRESSED);
121   struct GadgetDesign *gd = (!gi->active ? &gi->alt_design[state] :
122                              gi->checked ? &gi->alt_design[state] :
123                              &gi->design[state]);
124   boolean redraw_selectbox = FALSE;
125
126   switch (gi->type)
127   {
128     case GD_TYPE_NORMAL_BUTTON:
129     case GD_TYPE_CHECK_BUTTON:
130     case GD_TYPE_RADIO_BUTTON:
131       BlitBitmapOnBackground(gd->bitmap, drawto,
132                              gd->x, gd->y, gi->width, gi->height,
133                              gi->x, gi->y);
134       if (gi->deco.design.bitmap)
135         BlitBitmap(gi->deco.design.bitmap, drawto,
136                    gi->deco.design.x, gi->deco.design.y,
137                    gi->deco.width, gi->deco.height,
138                    gi->x + gi->deco.x + (pressed ? gi->deco.xshift : 0),
139                    gi->y + gi->deco.y + (pressed ? gi->deco.yshift : 0));
140       break;
141
142     case GD_TYPE_TEXT_BUTTON:
143       {
144         int i;
145         int font_nr = (gi->active ? gi->font_active : gi->font);
146         int font_width = getFontWidth(font_nr);
147         int border_x = gi->border.xsize;
148         int border_y = gi->border.ysize;
149         int text_size = strlen(gi->textbutton.value);
150         int text_start = (gi->width - text_size * font_width) / 2;
151
152         /* left part of gadget */
153         BlitBitmapOnBackground(gd->bitmap, drawto, gd->x, gd->y,
154                                border_x, gi->height, gi->x, gi->y);
155
156         /* middle part of gadget */
157         for (i=0; i < gi->textbutton.size; i++)
158           BlitBitmapOnBackground(gd->bitmap, drawto, gd->x + border_x, gd->y,
159                                  font_width, gi->height,
160                                  gi->x + border_x + i * font_width, gi->y);
161
162         /* right part of gadget */
163         BlitBitmapOnBackground(gd->bitmap, drawto,
164                                gd->x + gi->border.width - border_x, gd->y,
165                                border_x, gi->height,
166                                gi->x + gi->width - border_x, gi->y);
167
168         /* gadget text value */
169         DrawTextExt(drawto,
170                     gi->x + text_start + (pressed ? gi->deco.xshift : 0),
171                     gi->y + border_y   + (pressed ? gi->deco.yshift : 0),
172                     gi->textbutton.value, font_nr, BLIT_MASKED);
173       }
174       break;
175
176     case GD_TYPE_TEXTINPUT_ALPHANUMERIC:
177     case GD_TYPE_TEXTINPUT_NUMERIC:
178       {
179         int i;
180         char cursor_letter;
181         char cursor_string[2];
182         char text[MAX_GADGET_TEXTSIZE + 1];
183         int font_nr = (pressed ? gi->font_active : gi->font);
184         int font_width = getFontWidth(font_nr);
185         int border_x = gi->border.xsize;
186         int border_y = gi->border.ysize;
187
188         /* left part of gadget */
189         BlitBitmapOnBackground(gd->bitmap, drawto, gd->x, gd->y,
190                                border_x, gi->height, gi->x, gi->y);
191
192         /* middle part of gadget */
193         for (i=0; i < gi->text.size + 1; i++)
194           BlitBitmapOnBackground(gd->bitmap, drawto, gd->x + border_x, gd->y,
195                                  font_width, gi->height,
196                                  gi->x + border_x + i * font_width, gi->y);
197
198         /* right part of gadget */
199         BlitBitmapOnBackground(gd->bitmap, drawto,
200                                gd->x + gi->border.width - border_x, gd->y,
201                                border_x, gi->height,
202                                gi->x + gi->width - border_x, gi->y);
203
204         /* set text value */
205         strcpy(text, gi->text.value);
206         strcat(text, " ");
207
208         /* gadget text value */
209         DrawTextExt(drawto,
210                     gi->x + border_x, gi->y + border_y, text,
211                     font_nr, BLIT_MASKED);
212
213         cursor_letter = gi->text.value[gi->text.cursor_position];
214         cursor_string[0] = (cursor_letter != '\0' ? cursor_letter : ' ');
215         cursor_string[1] = '\0';
216
217         /* draw cursor, if active */
218         if (pressed)
219           DrawTextExt(drawto,
220                       gi->x + border_x + gi->text.cursor_position * font_width,
221                       gi->y + border_y, cursor_string,
222                       font_nr, BLIT_INVERSE);
223       }
224       break;
225
226     case GD_TYPE_SELECTBOX:
227       {
228         int i;
229         char text[MAX_GADGET_TEXTSIZE + 1];
230         int font_nr = (pressed ? gi->font_active : gi->font);
231         int font_width = getFontWidth(font_nr);
232         int font_height = getFontHeight(font_nr);
233         int border_x = gi->border.xsize;
234         int border_y = gi->border.ysize;
235         int button = gi->border.xsize_selectbutton;
236         int width_inner = gi->border.width - button - 2 * border_x;
237         int box_width = gi->selectbox.width;
238         int box_height = gi->selectbox.height;
239
240         /* left part of gadget */
241         BlitBitmapOnBackground(gd->bitmap, drawto, gd->x, gd->y,
242                                border_x, gi->height, gi->x, gi->y);
243
244         /* middle part of gadget */
245         for (i=0; i < gi->selectbox.size; i++)
246           BlitBitmapOnBackground(gd->bitmap, drawto, gd->x + border_x, gd->y,
247                                  font_width, gi->height,
248                                  gi->x + border_x + i * font_width, gi->y);
249
250         /* button part of gadget */
251         BlitBitmapOnBackground(gd->bitmap, drawto,
252                                gd->x + border_x + width_inner, gd->y,
253                                button, gi->height,
254                                gi->x + gi->width - border_x - button, gi->y);
255
256         /* right part of gadget */
257         BlitBitmapOnBackground(gd->bitmap, drawto,
258                                gd->x + gi->border.width - border_x, gd->y,
259                                border_x, gi->height,
260                                gi->x + gi->width - border_x, gi->y);
261
262         /* set text value */
263         strncpy(text, gi->selectbox.options[gi->selectbox.index].text,
264                 gi->selectbox.size);
265         text[gi->selectbox.size] = '\0';
266
267         /* gadget text value */
268         DrawTextExt(drawto, gi->x + border_x, gi->y + border_y, text,
269                     font_nr, BLIT_MASKED);
270
271         if (pressed)
272         {
273           if (!gi->selectbox.open)
274           {
275             gi->selectbox.open = TRUE;
276             gi->selectbox.stay_open = FALSE;
277             gi->selectbox.current_index = gi->selectbox.index;
278
279             /* save background under selectbox */
280             BlitBitmap(drawto, gfx.field_save_buffer,
281                        gi->selectbox.x,     gi->selectbox.y,
282                        gi->selectbox.width, gi->selectbox.height,
283                        gi->selectbox.x,     gi->selectbox.y);
284           }
285
286           /* draw open selectbox */
287
288           /* top left part of gadget border */
289           BlitBitmapOnBackground(gd->bitmap, drawto, gd->x, gd->y,
290                                  border_x, border_y,
291                                  gi->selectbox.x, gi->selectbox.y);
292
293           /* top middle part of gadget border */
294           for (i=0; i < gi->selectbox.size; i++)
295             BlitBitmapOnBackground(gd->bitmap, drawto, gd->x + border_x, gd->y,
296                                    font_width, border_y,
297                                    gi->selectbox.x + border_x + i * font_width,
298                                    gi->selectbox.y);
299
300           /* top button part of gadget border */
301           BlitBitmapOnBackground(gd->bitmap, drawto,
302                                  gd->x + border_x + width_inner, gd->y,
303                                  button, border_y,
304                                  gi->selectbox.x + box_width -border_x -button,
305                                  gi->selectbox.y);
306
307           /* top right part of gadget border */
308           BlitBitmapOnBackground(gd->bitmap, drawto,
309                                  gd->x + gi->border.width - border_x, gd->y,
310                                  border_x, border_y,
311                                  gi->selectbox.x + box_width - border_x,
312                                  gi->selectbox.y);
313
314           /* left and right part of gadget border for each row */
315           for (i=0; i < gi->selectbox.num_values; i++)
316           {
317             BlitBitmapOnBackground(gd->bitmap, drawto, gd->x, gd->y + border_y,
318                                    border_x, font_height,
319                                    gi->selectbox.x,
320                                    gi->selectbox.y + border_y + i*font_height);
321             BlitBitmapOnBackground(gd->bitmap, drawto,
322                                    gd->x + gi->border.width - border_x,
323                                    gd->y + border_y,
324                                    border_x, font_height,
325                                    gi->selectbox.x + box_width - border_x,
326                                    gi->selectbox.y + border_y + i*font_height);
327           }
328
329           /* bottom left part of gadget border */
330           BlitBitmapOnBackground(gd->bitmap, drawto,
331                                  gd->x, gd->y + gi->height - border_y,
332                                  border_x, border_y,
333                                  gi->selectbox.x,
334                                  gi->selectbox.y + box_height - border_y);
335
336           /* bottom middle part of gadget border */
337           for (i=0; i < gi->selectbox.size; i++)
338             BlitBitmapOnBackground(gd->bitmap, drawto,
339                                    gd->x + border_x,
340                                    gd->y + gi->height - border_y,
341                                    font_width, border_y,
342                                    gi->selectbox.x + border_x + i * font_width,
343                                    gi->selectbox.y + box_height - border_y);
344
345           /* bottom button part of gadget border */
346           BlitBitmapOnBackground(gd->bitmap, drawto,
347                                  gd->x + border_x + width_inner,
348                                  gd->y + gi->height - border_y,
349                                  button, border_y,
350                                  gi->selectbox.x + box_width -border_x -button,
351                                  gi->selectbox.y + box_height - border_y);
352
353           /* bottom right part of gadget border */
354           BlitBitmapOnBackground(gd->bitmap, drawto,
355                                  gd->x + gi->border.width - border_x,
356                                  gd->y + gi->height - border_y,
357                                  border_x, border_y,
358                                  gi->selectbox.x + box_width - border_x,
359                                  gi->selectbox.y + box_height - border_y);
360
361           ClearRectangleOnBackground(drawto,
362                                      gi->selectbox.x + border_x,
363                                      gi->selectbox.y + border_y,
364                                      gi->selectbox.width - 2 * border_x,
365                                      gi->selectbox.height - 2 * border_y);
366
367           /* selectbox text values */
368           for (i=0; i < gi->selectbox.num_values; i++)
369           {
370             int mask_mode;
371
372             if (i == gi->selectbox.current_index)
373             {
374               FillRectangle(drawto,
375                             gi->selectbox.x + border_x,
376                             gi->selectbox.y + border_y + i * font_height,
377                             gi->selectbox.width - 2 * border_x, font_height,
378                             gi->selectbox.inverse_color);
379
380               strncpy(text, gi->selectbox.options[i].text, gi->selectbox.size);
381               text[1 + gi->selectbox.size] = '\0';
382
383               mask_mode = BLIT_INVERSE;
384             }
385             else
386             {
387               strncpy(text, gi->selectbox.options[i].text, gi->selectbox.size);
388               text[gi->selectbox.size] = '\0';
389
390               mask_mode = BLIT_MASKED;
391             }
392
393             DrawTextExt(drawto,
394                         gi->selectbox.x + border_x,
395                         gi->selectbox.y + border_y + i * font_height, text,
396                         font_nr, mask_mode);
397           }
398
399           redraw_selectbox = TRUE;
400         }
401         else if (gi->selectbox.open)
402         {
403           gi->selectbox.open = FALSE;
404
405           /* redraw closed selectbox */
406           DrawGadget(gi, FALSE, FALSE);
407
408           /* restore background under selectbox */
409           BlitBitmap(gfx.field_save_buffer, drawto,
410                      gi->selectbox.x,     gi->selectbox.y,
411                      gi->selectbox.width, gi->selectbox.height,
412                      gi->selectbox.x,     gi->selectbox.y);
413
414           redraw_selectbox = TRUE;
415         }
416       }
417       break;
418
419     case GD_TYPE_SCROLLBAR_VERTICAL:
420       {
421         int i;
422         int xpos = gi->x;
423         int ypos = gi->y + gi->scrollbar.position;
424         int design_full = gi->width;
425         int design_body = design_full - 2 * gi->border.ysize;
426         int size_full = gi->scrollbar.size;
427         int size_body = size_full - 2 * gi->border.ysize;
428         int num_steps = size_body / design_body;
429         int step_size_remain = size_body - num_steps * design_body;
430
431         /* clear scrollbar area */
432         ClearRectangleOnBackground(backbuffer, gi->x, gi->y,
433                                    gi->width, gi->height);
434
435         /* upper part of gadget */
436         BlitBitmapOnBackground(gd->bitmap, drawto,
437                                gd->x, gd->y,
438                                gi->width, gi->border.ysize,
439                                xpos, ypos);
440
441         /* middle part of gadget */
442         for (i=0; i<num_steps; i++)
443           BlitBitmapOnBackground(gd->bitmap, drawto,
444                                  gd->x, gd->y + gi->border.ysize,
445                                  gi->width, design_body,
446                                  xpos,
447                                  ypos + gi->border.ysize + i * design_body);
448
449         /* remaining middle part of gadget */
450         if (step_size_remain > 0)
451           BlitBitmapOnBackground(gd->bitmap, drawto,
452                                  gd->x,  gd->y + gi->border.ysize,
453                                  gi->width, step_size_remain,
454                                  xpos,
455                                  ypos + gi->border.ysize
456                                  + num_steps * design_body);
457
458         /* lower part of gadget */
459         BlitBitmapOnBackground(gd->bitmap, drawto,
460                                gd->x, gd->y + design_full - gi->border.ysize,
461                                gi->width, gi->border.ysize,
462                                xpos, ypos + size_full - gi->border.ysize);
463       }
464       break;
465
466     case GD_TYPE_SCROLLBAR_HORIZONTAL:
467       {
468         int i;
469         int xpos = gi->x + gi->scrollbar.position;
470         int ypos = gi->y;
471         int design_full = gi->height;
472         int design_body = design_full - 2 * gi->border.xsize;
473         int size_full = gi->scrollbar.size;
474         int size_body = size_full - 2 * gi->border.xsize;
475         int num_steps = size_body / design_body;
476         int step_size_remain = size_body - num_steps * design_body;
477
478         /* clear scrollbar area */
479         ClearRectangleOnBackground(backbuffer, gi->x, gi->y,
480                                    gi->width, gi->height);
481
482         /* left part of gadget */
483         BlitBitmapOnBackground(gd->bitmap, drawto,
484                                gd->x, gd->y,
485                                gi->border.xsize, gi->height,
486                                xpos, ypos);
487
488         /* middle part of gadget */
489         for (i=0; i<num_steps; i++)
490           BlitBitmapOnBackground(gd->bitmap, drawto,
491                                  gd->x + gi->border.xsize, gd->y,
492                                  design_body, gi->height,
493                                  xpos + gi->border.xsize + i * design_body,
494                                  ypos);
495
496         /* remaining middle part of gadget */
497         if (step_size_remain > 0)
498           BlitBitmapOnBackground(gd->bitmap, drawto,
499                                  gd->x + gi->border.xsize, gd->y,
500                                  step_size_remain, gi->height,
501                                  xpos + gi->border.xsize
502                                  + num_steps * design_body,
503                                  ypos);
504
505         /* right part of gadget */
506         BlitBitmapOnBackground(gd->bitmap, drawto,
507                                gd->x + design_full - gi->border.xsize, gd->y,
508                                gi->border.xsize, gi->height,
509                                xpos + size_full - gi->border.xsize, ypos);
510       }
511       break;
512
513     default:
514       return;
515   }
516
517   if (direct)
518   {
519     BlitBitmap(drawto, window,
520                gi->x, gi->y, gi->width, gi->height, gi->x, gi->y);
521
522     if (gi->type == GD_TYPE_SELECTBOX && redraw_selectbox)
523       BlitBitmap(drawto, window,
524                  gi->selectbox.x,     gi->selectbox.y,
525                  gi->selectbox.width, gi->selectbox.height,
526                  gi->selectbox.x,     gi->selectbox.y);
527   }
528   else
529     redraw_mask |= (gi->x < gfx.sx + gfx.sxsize ? REDRAW_FIELD :
530                     gi->y < gfx.dy + gfx.dysize ? REDRAW_DOOR_1 :
531                     gi->y > gfx.vy ? REDRAW_DOOR_2 : REDRAW_DOOR_3);
532 }
533
534 static void HandleGadgetTags(struct GadgetInfo *gi, int first_tag, va_list ap)
535 {
536   int tag = first_tag;
537
538   while (tag != GDI_END)
539   {
540     switch(tag)
541     {
542       case GDI_CUSTOM_ID:
543         gi->custom_id = va_arg(ap, int);
544         break;
545
546       case GDI_CUSTOM_TYPE_ID:
547         gi->custom_type_id = va_arg(ap, int);
548         break;
549
550       case GDI_INFO_TEXT:
551         {
552           int max_textsize = MAX_INFO_TEXTSIZE - 1;
553           char *text = va_arg(ap, char *);
554
555           if (text != NULL)
556             strncpy(gi->info_text, text, max_textsize);
557           else
558             max_textsize = 0;
559
560           gi->info_text[max_textsize] = '\0';
561         }
562         break;
563
564       case GDI_X:
565         gi->x = va_arg(ap, int);
566         break;
567
568       case GDI_Y:
569         gi->y = va_arg(ap, int);
570         break;
571
572       case GDI_WIDTH:
573         gi->width = va_arg(ap, int);
574         break;
575
576       case GDI_HEIGHT:
577         gi->height = va_arg(ap, int);
578         break;
579
580       case GDI_TYPE:
581         gi->type = va_arg(ap, unsigned long);
582         break;
583
584       case GDI_STATE:
585         gi->state = va_arg(ap, unsigned long);
586         break;
587
588       case GDI_ACTIVE:
589         /* take care here: "boolean" is typedef'ed as "unsigned char",
590            which gets promoted to "int" */
591         gi->active = (boolean)va_arg(ap, int);
592         break;
593
594       case GDI_DIRECT_DRAW:
595         /* take care here: "boolean" is typedef'ed as "unsigned char",
596            which gets promoted to "int" */
597         gi->direct_draw = (boolean)va_arg(ap, int);
598         break;
599
600       case GDI_CHECKED:
601         /* take care here: "boolean" is typedef'ed as "unsigned char",
602            which gets promoted to "int" */
603         gi->checked = (boolean)va_arg(ap, int);
604         break;
605
606       case GDI_RADIO_NR:
607         gi->radio_nr = va_arg(ap, unsigned long);
608         break;
609
610       case GDI_NUMBER_VALUE:
611         gi->text.number_value = va_arg(ap, long);
612         sprintf(gi->text.value, "%d", gi->text.number_value);
613         gi->text.cursor_position = strlen(gi->text.value);
614         break;
615
616       case GDI_NUMBER_MIN:
617         gi->text.number_min = va_arg(ap, long);
618         if (gi->text.number_value < gi->text.number_min)
619         {
620           gi->text.number_value = gi->text.number_min;
621           sprintf(gi->text.value, "%d", gi->text.number_value);
622         }
623         break;
624
625       case GDI_NUMBER_MAX:
626         gi->text.number_max = va_arg(ap, long);
627         if (gi->text.number_value > gi->text.number_max)
628         {
629           gi->text.number_value = gi->text.number_max;
630           sprintf(gi->text.value, "%d", gi->text.number_value);
631         }
632         break;
633
634       case GDI_TEXT_VALUE:
635         {
636           int max_textsize = MAX_GADGET_TEXTSIZE;
637
638           if (gi->text.size)
639             max_textsize = MIN(gi->text.size, MAX_GADGET_TEXTSIZE - 1);
640
641           strncpy(gi->text.value, va_arg(ap, char *), max_textsize);
642           gi->text.value[max_textsize] = '\0';
643           gi->text.cursor_position = strlen(gi->text.value);
644
645           /* same tag also used for textbutton definition */
646           strcpy(gi->textbutton.value, gi->text.value);
647         }
648         break;
649
650       case GDI_TEXT_SIZE:
651         {
652           int tag_value = va_arg(ap, int);
653           int max_textsize = MIN(tag_value, MAX_GADGET_TEXTSIZE - 1);
654
655           gi->text.size = max_textsize;
656           gi->text.value[max_textsize] = '\0';
657
658           /* same tag also used for textbutton and selectbox definition */
659           strcpy(gi->textbutton.value, gi->text.value);
660           gi->textbutton.size = gi->text.size;
661           gi->selectbox.size = gi->text.size;
662         }
663         break;
664
665       case GDI_TEXT_FONT:
666         gi->font = va_arg(ap, int);
667         if (gi->font_active == 0)
668           gi->font_active = gi->font;
669         break;
670
671       case GDI_TEXT_FONT_ACTIVE:
672         gi->font_active = va_arg(ap, int);
673         break;
674
675       case GDI_SELECTBOX_OPTIONS:
676         gi->selectbox.options = va_arg(ap, struct ValueTextInfo *);
677         break;
678
679       case GDI_SELECTBOX_INDEX:
680         gi->selectbox.index = va_arg(ap, int);
681         break;
682
683       case GDI_DESIGN_UNPRESSED:
684         gi->design[GD_BUTTON_UNPRESSED].bitmap = va_arg(ap, Bitmap *);
685         gi->design[GD_BUTTON_UNPRESSED].x = va_arg(ap, int);
686         gi->design[GD_BUTTON_UNPRESSED].y = va_arg(ap, int);
687         break;
688
689       case GDI_DESIGN_PRESSED:
690         gi->design[GD_BUTTON_PRESSED].bitmap = va_arg(ap, Bitmap *);
691         gi->design[GD_BUTTON_PRESSED].x = va_arg(ap, int);
692         gi->design[GD_BUTTON_PRESSED].y = va_arg(ap, int);
693         break;
694
695       case GDI_ALT_DESIGN_UNPRESSED:
696         gi->alt_design[GD_BUTTON_UNPRESSED].bitmap= va_arg(ap, Bitmap *);
697         gi->alt_design[GD_BUTTON_UNPRESSED].x = va_arg(ap, int);
698         gi->alt_design[GD_BUTTON_UNPRESSED].y = va_arg(ap, int);
699         break;
700
701       case GDI_ALT_DESIGN_PRESSED:
702         gi->alt_design[GD_BUTTON_PRESSED].bitmap = va_arg(ap, Bitmap *);
703         gi->alt_design[GD_BUTTON_PRESSED].x = va_arg(ap, int);
704         gi->alt_design[GD_BUTTON_PRESSED].y = va_arg(ap, int);
705         break;
706
707       case GDI_BORDER_SIZE:
708         gi->border.xsize = va_arg(ap, int);
709         gi->border.ysize = va_arg(ap, int);
710         break;
711
712       case GDI_BORDER_SIZE_SELECTBUTTON:
713         gi->border.xsize_selectbutton = va_arg(ap, int);
714         break;
715
716       case GDI_DESIGN_WIDTH:
717         gi->border.width = va_arg(ap, int);
718         break;
719
720       case GDI_DECORATION_DESIGN:
721         gi->deco.design.bitmap = va_arg(ap, Bitmap *);
722         gi->deco.design.x = va_arg(ap, int);
723         gi->deco.design.y = va_arg(ap, int);
724         break;
725
726       case GDI_DECORATION_POSITION:
727         gi->deco.x = va_arg(ap, int);
728         gi->deco.y = va_arg(ap, int);
729         break;
730
731       case GDI_DECORATION_SIZE:
732         gi->deco.width = va_arg(ap, int);
733         gi->deco.height = va_arg(ap, int);
734         break;
735
736       case GDI_DECORATION_SHIFTING:
737         gi->deco.xshift = va_arg(ap, int);
738         gi->deco.yshift = va_arg(ap, int);
739         break;
740
741       case GDI_EVENT_MASK:
742         gi->event_mask = va_arg(ap, unsigned long);
743         break;
744
745       case GDI_AREA_SIZE:
746         gi->drawing.area_xsize = va_arg(ap, int);
747         gi->drawing.area_ysize = va_arg(ap, int);
748
749         /* determine dependent values for drawing area gadget, if needed */
750         if (gi->width == 0 && gi->height == 0 &&
751             gi->drawing.item_xsize !=0 && gi->drawing.item_ysize !=0)
752         {
753           gi->width = gi->drawing.area_xsize * gi->drawing.item_xsize;
754           gi->height = gi->drawing.area_ysize * gi->drawing.item_ysize;
755         }
756         else if (gi->drawing.item_xsize == 0 && gi->drawing.item_ysize == 0 &&
757                  gi->width != 0 && gi->height != 0)
758         {
759           gi->drawing.item_xsize = gi->width / gi->drawing.area_xsize;
760           gi->drawing.item_ysize = gi->height / gi->drawing.area_ysize;
761         }
762         break;
763
764       case GDI_ITEM_SIZE:
765         gi->drawing.item_xsize = va_arg(ap, int);
766         gi->drawing.item_ysize = va_arg(ap, int);
767
768         /* determine dependent values for drawing area gadget, if needed */
769         if (gi->width == 0 && gi->height == 0 &&
770             gi->drawing.area_xsize !=0 && gi->drawing.area_ysize !=0)
771         {
772           gi->width = gi->drawing.area_xsize * gi->drawing.item_xsize;
773           gi->height = gi->drawing.area_ysize * gi->drawing.item_ysize;
774         }
775         else if (gi->drawing.area_xsize == 0 && gi->drawing.area_ysize == 0 &&
776                  gi->width != 0 && gi->height != 0)
777         {
778           gi->drawing.area_xsize = gi->width / gi->drawing.item_xsize;
779           gi->drawing.area_ysize = gi->height / gi->drawing.item_ysize;
780         }
781         break;
782
783       case GDI_SCROLLBAR_ITEMS_MAX:
784         gi->scrollbar.items_max = va_arg(ap, int);
785         break;
786
787       case GDI_SCROLLBAR_ITEMS_VISIBLE:
788         gi->scrollbar.items_visible = va_arg(ap, int);
789         break;
790
791       case GDI_SCROLLBAR_ITEM_POSITION:
792         gi->scrollbar.item_position = va_arg(ap, int);
793         break;
794
795       case GDI_CALLBACK_INFO:
796         gi->callback_info = va_arg(ap, gadget_function);
797         break;
798
799       case GDI_CALLBACK_ACTION:
800         gi->callback_action = va_arg(ap, gadget_function);
801         break;
802
803       default:
804         Error(ERR_EXIT, "HandleGadgetTags(): unknown tag %d", tag);
805     }
806
807     tag = va_arg(ap, int);      /* read next tag */
808   }
809
810   /* check if gadget is complete */
811   if (gi->type != GD_TYPE_DRAWING_AREA &&
812       (!gi->design[GD_BUTTON_UNPRESSED].bitmap ||
813        !gi->design[GD_BUTTON_PRESSED].bitmap))
814     Error(ERR_EXIT, "gadget incomplete (missing Bitmap)");
815
816   /* adjust gadget values in relation to other gadget values */
817
818   if (gi->type & GD_TYPE_TEXTINPUT)
819   {
820     int font_nr = gi->font_active;
821     int font_width = getFontWidth(font_nr);
822     int font_height = getFontHeight(font_nr);
823     int border_xsize = gi->border.xsize;
824     int border_ysize = gi->border.ysize;
825
826     gi->width  = 2 * border_xsize + (gi->text.size + 1) * font_width;
827     gi->height = 2 * border_ysize + font_height;
828   }
829
830   if (gi->type & GD_TYPE_SELECTBOX)
831   {
832     int font_nr = gi->font_active;
833     int font_width = getFontWidth(font_nr);
834     int font_height = getFontHeight(font_nr);
835     int border_xsize = gi->border.xsize;
836     int border_ysize = gi->border.ysize;
837     int button_size = gi->border.xsize_selectbutton;
838     int bottom_screen_border = gfx.sy + gfx.sysize - font_height;
839     Bitmap *src_bitmap;
840     int src_x, src_y;
841
842     gi->width  = 2 * border_xsize + gi->text.size * font_width + button_size;
843     gi->height = 2 * border_ysize + font_height;
844
845     if (gi->selectbox.options == NULL)
846       Error(ERR_EXIT, "selectbox gadget incomplete (missing options array)");
847
848     gi->selectbox.num_values = 0;
849     while (gi->selectbox.options[gi->selectbox.num_values].text != NULL)
850       gi->selectbox.num_values++;
851
852     /* calculate values for open selectbox */
853     gi->selectbox.width = gi->width;
854     gi->selectbox.height =
855       2 * border_ysize + gi->selectbox.num_values * font_height;
856
857     gi->selectbox.x = gi->x;
858     gi->selectbox.y = gi->y + gi->height;
859     if (gi->selectbox.y + gi->selectbox.height > bottom_screen_border)
860       gi->selectbox.y = gi->y - gi->selectbox.height;
861     if (gi->selectbox.y < 0)
862       gi->selectbox.y = bottom_screen_border - gi->selectbox.height;
863
864     getFontCharSource(font_nr, FONT_ASCII_CURSOR, &src_bitmap, &src_x, &src_y);
865     src_x += font_width / 2;
866     src_y += font_height / 2;
867     gi->selectbox.inverse_color = GetPixel(src_bitmap, src_x, src_y);
868
869     /* always start with closed selectbox */
870     gi->selectbox.open = FALSE;
871   }
872
873   if (gi->type & GD_TYPE_TEXTINPUT_NUMERIC)
874   {
875     struct GadgetTextInput *text = &gi->text;
876     int value = text->number_value;
877
878     text->number_value = (value < text->number_min ? text->number_min :
879                           value > text->number_max ? text->number_max :
880                           value);
881
882     sprintf(text->value, "%d", text->number_value);
883   }
884
885   if (gi->type & GD_TYPE_TEXT_BUTTON)
886   {
887     int font_nr = gi->font_active;
888     int font_width = getFontWidth(font_nr);
889     int font_height = getFontHeight(font_nr);
890     int border_xsize = gi->border.xsize;
891     int border_ysize = gi->border.ysize;
892
893     gi->width  = 2 * border_xsize + gi->textbutton.size * font_width;
894     gi->height = 2 * border_ysize + font_height;
895   }
896
897   if (gi->type & GD_TYPE_SCROLLBAR)
898   {
899     struct GadgetScrollbar *gs = &gi->scrollbar;
900
901     if (gi->width == 0 || gi->height == 0 ||
902         gs->items_max == 0 || gs->items_visible == 0)
903       Error(ERR_EXIT, "scrollbar gadget incomplete (missing tags)");
904
905     /* calculate internal scrollbar values */
906     gs->size_max = (gi->type == GD_TYPE_SCROLLBAR_VERTICAL ?
907                     gi->height : gi->width);
908     gs->size = gs->size_max * gs->items_visible / gs->items_max;
909     gs->position = gs->size_max * gs->item_position / gs->items_max;
910     gs->position_max = gs->size_max - gs->size;
911     gs->correction = gs->size_max / gs->items_max / 2;
912
913     /* finetuning for maximal right/bottom position */
914     if (gs->item_position == gs->items_max - gs->items_visible)
915       gs->position = gs->position_max;
916   }
917 }
918
919 void ModifyGadget(struct GadgetInfo *gi, int first_tag, ...)
920 {
921   va_list ap;
922
923   va_start(ap, first_tag);
924   HandleGadgetTags(gi, first_tag, ap);
925   va_end(ap);
926
927   RedrawGadget(gi);
928 }
929
930 void RedrawGadget(struct GadgetInfo *gi)
931 {
932   if (gi->mapped)
933     DrawGadget(gi, gi->state, gi->direct_draw);
934 }
935
936 struct GadgetInfo *CreateGadget(int first_tag, ...)
937 {
938   struct GadgetInfo *new_gadget = checked_calloc(sizeof(struct GadgetInfo));
939   va_list ap;
940
941   /* always start with reliable default values */
942   new_gadget->id = getNewGadgetID();
943   new_gadget->callback_info = default_callback_info;
944   new_gadget->callback_action = default_callback_action;
945   new_gadget->active = TRUE;
946   new_gadget->direct_draw = TRUE;
947
948   new_gadget->next = NULL;
949
950   va_start(ap, first_tag);
951   HandleGadgetTags(new_gadget, first_tag, ap);
952   va_end(ap);
953
954   /* insert new gadget into global gadget list */
955   if (gadget_list_last_entry)
956   {
957     gadget_list_last_entry->next = new_gadget;
958     gadget_list_last_entry = gadget_list_last_entry->next;
959   }
960   else
961     gadget_list_first_entry = gadget_list_last_entry = new_gadget;
962
963   return new_gadget;
964 }
965
966 void FreeGadget(struct GadgetInfo *gi)
967 {
968   struct GadgetInfo *gi_previous = gadget_list_first_entry;
969
970   while (gi_previous != NULL && gi_previous->next != gi)
971     gi_previous = gi_previous->next;
972
973   if (gi == gadget_list_first_entry)
974     gadget_list_first_entry = gi->next;
975
976   if (gi == gadget_list_last_entry)
977     gadget_list_last_entry = gi_previous;
978
979   if (gi_previous != NULL)
980     gi_previous->next = gi->next;
981
982   free(gi);
983 }
984
985 static void CheckRangeOfNumericInputGadget(struct GadgetInfo *gi)
986 {
987   if (gi->type != GD_TYPE_TEXTINPUT_NUMERIC)
988     return;
989
990   gi->text.number_value = atoi(gi->text.value);
991
992   if (gi->text.number_value < gi->text.number_min)
993     gi->text.number_value = gi->text.number_min;
994   if (gi->text.number_value > gi->text.number_max)
995     gi->text.number_value = gi->text.number_max;
996
997   sprintf(gi->text.value, "%d", gi->text.number_value);
998
999   if (gi->text.cursor_position < 0)
1000     gi->text.cursor_position = 0;
1001   else if (gi->text.cursor_position > strlen(gi->text.value))
1002     gi->text.cursor_position = strlen(gi->text.value);
1003 }
1004
1005 /* global pointer to gadget actually in use (when mouse button pressed) */
1006 static struct GadgetInfo *last_gi = NULL;
1007
1008 static void MapGadgetExt(struct GadgetInfo *gi, boolean redraw)
1009 {
1010   if (gi == NULL || gi->mapped)
1011     return;
1012
1013   gi->mapped = TRUE;
1014
1015   if (redraw)
1016     DrawGadget(gi, DG_UNPRESSED, DG_BUFFERED);
1017 }
1018
1019 void MapGadget(struct GadgetInfo *gi)
1020 {
1021   MapGadgetExt(gi, TRUE);
1022 }
1023
1024 void UnmapGadget(struct GadgetInfo *gi)
1025 {
1026   if (gi == NULL || !gi->mapped)
1027     return;
1028
1029   gi->mapped = FALSE;
1030
1031   if (gi == last_gi)
1032     last_gi = NULL;
1033 }
1034
1035 #define MAX_NUM_GADGETS         1024
1036 #define MULTIMAP_UNMAP          (1 << 0)
1037 #define MULTIMAP_REMAP          (1 << 1)
1038 #define MULTIMAP_REDRAW         (1 << 2)
1039 #define MULTIMAP_PLAYFIELD      (1 << 3)
1040 #define MULTIMAP_DOOR_1         (1 << 4)
1041 #define MULTIMAP_DOOR_2         (1 << 5)
1042 #define MULTIMAP_ALL            (MULTIMAP_PLAYFIELD | \
1043                                  MULTIMAP_DOOR_1 | \
1044                                  MULTIMAP_DOOR_2)
1045
1046 static void MultiMapGadgets(int mode)
1047 {
1048   struct GadgetInfo *gi = gadget_list_first_entry;
1049   static boolean map_state[MAX_NUM_GADGETS];
1050   int map_count = 0;
1051
1052   while (gi != NULL)
1053   {
1054     if ((mode & MULTIMAP_PLAYFIELD &&
1055          gi->x < gfx.sx + gfx.sxsize) ||
1056         (mode & MULTIMAP_DOOR_1 &&
1057          gi->x >= gfx.dx && gi->y < gfx.dy + gfx.dysize) ||
1058         (mode & MULTIMAP_DOOR_2 &&
1059          gi->x >= gfx.dx && gi->y > gfx.dy + gfx.dysize) ||
1060         (mode & MULTIMAP_ALL) == MULTIMAP_ALL)
1061     {
1062       if (mode & MULTIMAP_UNMAP)
1063       {
1064         map_state[map_count++ % MAX_NUM_GADGETS] = gi->mapped;
1065         UnmapGadget(gi);
1066       }
1067       else
1068       {
1069         if (map_state[map_count++ % MAX_NUM_GADGETS])
1070           MapGadgetExt(gi, (mode & MULTIMAP_REDRAW));
1071       }
1072     }
1073
1074     gi = gi->next;
1075   }
1076 }
1077
1078 void UnmapAllGadgets()
1079 {
1080   MultiMapGadgets(MULTIMAP_ALL | MULTIMAP_UNMAP);
1081 }
1082
1083 void RemapAllGadgets()
1084 {
1085   MultiMapGadgets(MULTIMAP_ALL | MULTIMAP_REMAP);
1086 }
1087
1088 boolean anyTextInputGadgetActive()
1089 {
1090   return (last_gi && (last_gi->type & GD_TYPE_TEXTINPUT) && last_gi->mapped);
1091 }
1092
1093 boolean anySelectboxGadgetActive()
1094 {
1095   return (last_gi && (last_gi->type & GD_TYPE_SELECTBOX) && last_gi->mapped);
1096 }
1097
1098 boolean anyTextGadgetActive()
1099 {
1100   return (anyTextInputGadgetActive() || anySelectboxGadgetActive());
1101 }
1102
1103 void ClickOnGadget(struct GadgetInfo *gi, int button)
1104 {
1105   /* simulate releasing mouse button over last gadget, if still pressed */
1106   if (button_status)
1107     HandleGadgets(-1, -1, 0);
1108
1109   /* simulate pressing mouse button over specified gadget */
1110   HandleGadgets(gi->x, gi->y, button);
1111
1112   /* simulate releasing mouse button over specified gadget */
1113   HandleGadgets(gi->x, gi->y, 0);
1114 }
1115
1116 void HandleGadgets(int mx, int my, int button)
1117 {
1118   static struct GadgetInfo *last_info_gi = NULL;
1119   static unsigned long pressed_delay = 0;
1120   static int last_button = 0;
1121   static int last_mx = 0, last_my = 0;
1122   int scrollbar_mouse_pos = 0;
1123   struct GadgetInfo *new_gi, *gi;
1124   boolean press_event;
1125   boolean release_event;
1126   boolean mouse_moving;
1127   boolean gadget_pressed;
1128   boolean gadget_pressed_repeated;
1129   boolean gadget_moving;
1130   boolean gadget_moving_inside;
1131   boolean gadget_moving_off_borders;
1132   boolean gadget_released;
1133   boolean gadget_released_inside;
1134   boolean gadget_released_inside_select_line;
1135   boolean gadget_released_inside_select_area;
1136   boolean gadget_released_off_borders;
1137   boolean changed_position = FALSE;
1138
1139   /* check if there are any gadgets defined */
1140   if (gadget_list_first_entry == NULL)
1141     return;
1142
1143   /* simulated release of mouse button over last gadget */
1144   if (mx == -1 && my == -1 && button == 0)
1145   {
1146     mx = last_mx;
1147     my = last_my;
1148   }
1149
1150   /* check which gadget is under the mouse pointer */
1151   new_gi = getGadgetInfoFromMousePosition(mx, my);
1152
1153   /* check if button state has changed since last invocation */
1154   press_event = (button != 0 && last_button == 0);
1155   release_event = (button == 0 && last_button != 0);
1156   last_button = button;
1157
1158   /* check if mouse has been moved since last invocation */
1159   mouse_moving = ((mx != last_mx || my != last_my) && motion_status);
1160   last_mx = mx;
1161   last_my = my;
1162
1163   /* special treatment for text and number input gadgets */
1164   if (anyTextInputGadgetActive() && button != 0 && !motion_status)
1165   {
1166     struct GadgetInfo *gi = last_gi;
1167
1168     if (new_gi == last_gi)
1169     {
1170       int old_cursor_position = gi->text.cursor_position;
1171
1172       /* if mouse button pressed inside activated text gadget, set cursor */
1173       gi->text.cursor_position =
1174         (mx - gi->x - gi->border.xsize) / getFontWidth(gi->font);
1175
1176       if (gi->text.cursor_position < 0)
1177         gi->text.cursor_position = 0;
1178       else if (gi->text.cursor_position > strlen(gi->text.value))
1179         gi->text.cursor_position = strlen(gi->text.value);
1180
1181       if (gi->text.cursor_position != old_cursor_position)
1182         DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1183     }
1184     else
1185     {
1186       /* if mouse button pressed outside text input gadget, deactivate it */
1187       CheckRangeOfNumericInputGadget(gi);
1188       DrawGadget(gi, DG_UNPRESSED, gi->direct_draw);
1189
1190       gi->event.type = GD_EVENT_TEXT_LEAVING;
1191
1192       if (gi->event_mask & GD_EVENT_TEXT_LEAVING)
1193         gi->callback_action(gi);
1194
1195       last_gi = NULL;
1196     }
1197   }
1198
1199   /* special treatment for selectbox gadgets */
1200   if (anySelectboxGadgetActive() && button != 0 && !motion_status)
1201   {
1202     struct GadgetInfo *gi = last_gi;
1203
1204     if (new_gi == last_gi)
1205     {
1206       int old_index = gi->selectbox.current_index;
1207
1208       /* if mouse button pressed inside activated selectbox, select value */
1209       if (my >= gi->selectbox.y && my < gi->selectbox.y + gi->selectbox.height)
1210         gi->selectbox.current_index =
1211           (my - gi->selectbox.y - gi->border.ysize) / getFontHeight(gi->font);
1212
1213       if (gi->selectbox.current_index < 0)
1214         gi->selectbox.current_index = 0;
1215       else if (gi->selectbox.current_index > gi->selectbox.num_values - 1)
1216         gi->selectbox.current_index = gi->selectbox.num_values - 1;
1217
1218       if (gi->selectbox.current_index != old_index)
1219         DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1220     }
1221     else
1222     {
1223       /* if mouse button pressed outside selectbox gadget, deactivate it */
1224       DrawGadget(gi, DG_UNPRESSED, gi->direct_draw);
1225
1226       gi->event.type = GD_EVENT_TEXT_LEAVING;
1227
1228       if (gi->event_mask & GD_EVENT_TEXT_LEAVING)
1229         gi->callback_action(gi);
1230
1231       last_gi = NULL;
1232     }
1233   }
1234
1235   gadget_pressed =
1236     (button != 0 && last_gi == NULL && new_gi != NULL && press_event);
1237   gadget_pressed_repeated =
1238     (button != 0 && last_gi != NULL && new_gi == last_gi);
1239
1240   gadget_released =             (release_event && last_gi != NULL);
1241   gadget_released_inside =      (gadget_released && new_gi == last_gi);
1242   gadget_released_off_borders = (gadget_released && new_gi != last_gi);
1243
1244   gadget_moving =             (button != 0 && last_gi != NULL && mouse_moving);
1245   gadget_moving_inside =      (gadget_moving && new_gi == last_gi);
1246   gadget_moving_off_borders = (gadget_moving && new_gi != last_gi);
1247
1248   /* when handling selectbox, set additional state values */
1249   if (gadget_released_inside && (last_gi->type & GD_TYPE_SELECTBOX))
1250   {
1251     struct GadgetInfo *gi = last_gi;
1252
1253     gadget_released_inside_select_line =
1254       (mx >= gi->x && mx < gi->x + gi->width &&
1255        my >= gi->y && my < gi->y + gi->height);
1256     gadget_released_inside_select_area =
1257       (mx >= gi->selectbox.x && mx < gi->selectbox.x + gi->selectbox.width &&
1258        my >= gi->selectbox.y && my < gi->selectbox.y + gi->selectbox.height);
1259   }
1260   else
1261   {
1262     gadget_released_inside_select_line = FALSE;
1263     gadget_released_inside_select_area = FALSE;
1264   }
1265
1266   /* if new gadget pressed, store this gadget  */
1267   if (gadget_pressed)
1268     last_gi = new_gi;
1269
1270   /* 'gi' is actually handled gadget */
1271   gi = last_gi;
1272
1273   /* if gadget is scrollbar, choose mouse position value */
1274   if (gi && gi->type & GD_TYPE_SCROLLBAR)
1275     scrollbar_mouse_pos =
1276       (gi->type == GD_TYPE_SCROLLBAR_HORIZONTAL ? mx - gi->x : my - gi->y);
1277
1278   /* if mouse button released, no gadget needs to be handled anymore */
1279   if (gadget_released)
1280   {
1281     if ((last_gi->type & GD_TYPE_SELECTBOX) &&
1282         (gadget_released_inside_select_line ||
1283          gadget_released_off_borders))              /* selectbox stays open */
1284       gi->selectbox.stay_open = TRUE;
1285     else if (!(last_gi->type & GD_TYPE_TEXTINPUT))  /* text input stays open */
1286       last_gi = NULL;
1287   }
1288
1289   /* modify event position values even if no gadget is pressed */
1290   if (button == 0 && !release_event)
1291     gi = new_gi;
1292
1293   if (gi != NULL)
1294   {
1295     int last_x = gi->event.x;
1296     int last_y = gi->event.y;
1297
1298     gi->event.x = mx - gi->x;
1299     gi->event.y = my - gi->y;
1300
1301     if (gi->type == GD_TYPE_DRAWING_AREA)
1302     {
1303       gi->event.x /= gi->drawing.item_xsize;
1304       gi->event.y /= gi->drawing.item_ysize;
1305
1306       if (last_x != gi->event.x || last_y != gi->event.y)
1307         changed_position = TRUE;
1308     }
1309     else if (gi->type & GD_TYPE_SELECTBOX)
1310     {
1311       int old_index = gi->selectbox.current_index;
1312
1313       /* if mouse moving inside activated selectbox, select value */
1314       if (my >= gi->selectbox.y && my < gi->selectbox.y + gi->selectbox.height)
1315         gi->selectbox.current_index =
1316           (my - gi->selectbox.y - gi->border.ysize) / getFontHeight(gi->font);
1317
1318       if (gi->selectbox.current_index < 0)
1319         gi->selectbox.current_index = 0;
1320       else if (gi->selectbox.current_index > gi->selectbox.num_values - 1)
1321         gi->selectbox.current_index = gi->selectbox.num_values - 1;
1322
1323       if (gi->selectbox.current_index != old_index)
1324         DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1325     }
1326   }
1327
1328   /* handle gadget popup info text */
1329   if (last_info_gi != new_gi ||
1330       (new_gi && new_gi->type == GD_TYPE_DRAWING_AREA && changed_position))
1331   {
1332     if (new_gi != NULL && (button == 0 || new_gi == last_gi))
1333     {
1334       new_gi->event.type = GD_EVENT_INFO_ENTERING;
1335       new_gi->callback_info(new_gi);
1336     }
1337     else if (last_info_gi != NULL)
1338     {
1339       last_info_gi->event.type = GD_EVENT_INFO_LEAVING;
1340       last_info_gi->callback_info(last_info_gi);
1341
1342 #if 0
1343       default_callback_info(NULL);
1344
1345       printf("It seems that we are leaving gadget [%s]!\n",
1346              (last_info_gi != NULL &&
1347               last_info_gi->info_text != NULL ?
1348               last_info_gi->info_text : ""));
1349 #endif
1350     }
1351
1352     last_info_gi = new_gi;
1353   }
1354
1355   if (gadget_pressed)
1356   {
1357     if (gi->type == GD_TYPE_CHECK_BUTTON)
1358     {
1359       gi->checked = !gi->checked;
1360     }
1361     else if (gi->type == GD_TYPE_RADIO_BUTTON)
1362     {
1363       struct GadgetInfo *rgi = gadget_list_first_entry;
1364
1365       while (rgi)
1366       {
1367         if (rgi->mapped &&
1368             rgi->type == GD_TYPE_RADIO_BUTTON &&
1369             rgi->radio_nr == gi->radio_nr &&
1370             rgi != gi)
1371         {
1372           rgi->checked = FALSE;
1373           DrawGadget(rgi, DG_UNPRESSED, rgi->direct_draw);
1374         }
1375
1376         rgi = rgi->next;
1377       }
1378
1379       gi->checked = TRUE;
1380     }
1381     else if (gi->type & GD_TYPE_SCROLLBAR)
1382     {
1383       int mpos, gpos;
1384
1385       if (gi->type == GD_TYPE_SCROLLBAR_HORIZONTAL)
1386       {
1387         mpos = mx;
1388         gpos = gi->x;
1389       }
1390       else
1391       {
1392         mpos = my;
1393         gpos = gi->y;
1394       }
1395
1396       if (mpos >= gpos + gi->scrollbar.position &&
1397           mpos < gpos + gi->scrollbar.position + gi->scrollbar.size)
1398       {
1399         /* drag scrollbar */
1400         gi->scrollbar.drag_position =
1401           scrollbar_mouse_pos - gi->scrollbar.position;
1402       }
1403       else
1404       {
1405         /* click scrollbar one scrollbar length up/left or down/right */
1406
1407         struct GadgetScrollbar *gs = &gi->scrollbar;
1408         int old_item_position = gs->item_position;
1409
1410         changed_position = FALSE;
1411
1412         gs->item_position +=
1413           gs->items_visible * (mpos < gpos + gi->scrollbar.position ? -1 : +1);
1414
1415         if (gs->item_position < 0)
1416           gs->item_position = 0;
1417         if (gs->item_position > gs->items_max - gs->items_visible)
1418           gs->item_position = gs->items_max - gs->items_visible;
1419
1420         if (old_item_position != gs->item_position)
1421         {
1422           gi->event.item_position = gs->item_position;
1423           changed_position = TRUE;
1424         }
1425
1426         ModifyGadget(gi, GDI_SCROLLBAR_ITEM_POSITION, gs->item_position,
1427                      GDI_END);
1428
1429         gi->state = GD_BUTTON_UNPRESSED;
1430         gi->event.type = GD_EVENT_MOVING;
1431         gi->event.off_borders = FALSE;
1432
1433         if (gi->event_mask & GD_EVENT_MOVING && changed_position)
1434           gi->callback_action(gi);
1435
1436         /* don't handle this scrollbar anymore while mouse button pressed */
1437         last_gi = NULL;
1438
1439         return;
1440       }
1441     }
1442
1443     DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1444
1445     gi->state = GD_BUTTON_PRESSED;
1446     gi->event.type = GD_EVENT_PRESSED;
1447     gi->event.button = button;
1448     gi->event.off_borders = FALSE;
1449
1450     /* initialize delay counter */
1451     DelayReached(&pressed_delay, 0);
1452
1453     if (gi->event_mask & GD_EVENT_PRESSED)
1454       gi->callback_action(gi);
1455   }
1456
1457   if (gadget_pressed_repeated)
1458   {
1459     gi->event.type = GD_EVENT_PRESSED;
1460
1461     if (gi->event_mask & GD_EVENT_REPEATED &&
1462         DelayReached(&pressed_delay, GADGET_FRAME_DELAY))
1463       gi->callback_action(gi);
1464   }
1465
1466   if (gadget_moving)
1467   {
1468     if (gi->type & GD_TYPE_BUTTON)
1469     {
1470       if (gadget_moving_inside && gi->state == GD_BUTTON_UNPRESSED)
1471         DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1472       else if (gadget_moving_off_borders && gi->state == GD_BUTTON_PRESSED)
1473         DrawGadget(gi, DG_UNPRESSED, gi->direct_draw);
1474     }
1475     else if (gi->type & GD_TYPE_SELECTBOX)
1476     {
1477       int old_index = gi->selectbox.current_index;
1478
1479       /* if mouse moving inside activated selectbox, select value */
1480       if (my >= gi->selectbox.y && my < gi->selectbox.y + gi->selectbox.height)
1481         gi->selectbox.current_index =
1482           (my - gi->selectbox.y - gi->border.ysize) / getFontHeight(gi->font);
1483
1484       if (gi->selectbox.current_index < 0)
1485         gi->selectbox.current_index = 0;
1486       else if (gi->selectbox.current_index > gi->selectbox.num_values - 1)
1487         gi->selectbox.current_index = gi->selectbox.num_values - 1;
1488
1489       if (gi->selectbox.current_index != old_index)
1490         DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1491     }
1492     else if (gi->type & GD_TYPE_SCROLLBAR)
1493     {
1494       struct GadgetScrollbar *gs = &gi->scrollbar;
1495       int old_item_position = gs->item_position;
1496
1497       gs->position = scrollbar_mouse_pos - gs->drag_position;
1498
1499       if (gs->position < 0)
1500         gs->position = 0;
1501       if (gs->position > gs->position_max)
1502         gs->position = gs->position_max;
1503
1504       gs->item_position =
1505         gs->items_max * (gs->position + gs->correction) / gs->size_max;
1506
1507       if (gs->item_position < 0)
1508         gs->item_position = 0;
1509       if (gs->item_position > gs->items_max - 1)
1510         gs->item_position = gs->items_max - 1;
1511
1512       if (old_item_position != gs->item_position)
1513       {
1514         gi->event.item_position = gs->item_position;
1515         changed_position = TRUE;
1516       }
1517
1518       DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1519     }
1520
1521     gi->state = (gadget_moving_inside || gi->type & GD_TYPE_SCROLLBAR ?
1522                  GD_BUTTON_PRESSED : GD_BUTTON_UNPRESSED);
1523     gi->event.type = GD_EVENT_MOVING;
1524     gi->event.off_borders = gadget_moving_off_borders;
1525
1526     if (gi->event_mask & GD_EVENT_MOVING && changed_position &&
1527         (gadget_moving_inside || gi->event_mask & GD_EVENT_OFF_BORDERS))
1528       gi->callback_action(gi);
1529   }
1530
1531   if (gadget_released_inside)
1532   {
1533     boolean deactivate_gadget = TRUE;
1534
1535     if (gi->type & GD_TYPE_SELECTBOX)
1536     {
1537       if (gadget_released_inside_select_line ||
1538           gadget_released_off_borders)              /* selectbox stays open */
1539         deactivate_gadget = FALSE;
1540       else
1541         gi->selectbox.index = gi->selectbox.current_index;
1542     }
1543
1544     if (deactivate_gadget &&
1545         !(gi->type & GD_TYPE_TEXTINPUT))            /* text input stays open */
1546       DrawGadget(gi, DG_UNPRESSED, gi->direct_draw);
1547
1548     gi->state = GD_BUTTON_UNPRESSED;
1549     gi->event.type = GD_EVENT_RELEASED;
1550
1551     if ((gi->event_mask & GD_EVENT_RELEASED) && deactivate_gadget)
1552       gi->callback_action(gi);
1553   }
1554
1555   if (gadget_released_off_borders)
1556   {
1557     if (gi->type & GD_TYPE_SCROLLBAR)
1558       DrawGadget(gi, DG_UNPRESSED, gi->direct_draw);
1559
1560     gi->event.type = GD_EVENT_RELEASED;
1561
1562     if (gi->event_mask & GD_EVENT_RELEASED &&
1563         gi->event_mask & GD_EVENT_OFF_BORDERS)
1564       gi->callback_action(gi);
1565   }
1566
1567   /* handle gadgets unmapped/mapped between pressing and releasing */
1568   if (release_event && !gadget_released && new_gi)
1569     new_gi->state = GD_BUTTON_UNPRESSED;
1570 }
1571
1572 void HandleGadgetsKeyInput(Key key)
1573 {
1574   struct GadgetInfo *gi = last_gi;
1575
1576   if (gi == NULL || !gi->mapped ||
1577       !((gi->type & GD_TYPE_TEXTINPUT) || (gi->type & GD_TYPE_SELECTBOX)))
1578     return;
1579
1580   if (key == KSYM_Return)       /* valid for both text input and selectbox */
1581   {
1582     if (gi->type & GD_TYPE_TEXTINPUT)
1583       CheckRangeOfNumericInputGadget(gi);
1584     else if (gi->type & GD_TYPE_SELECTBOX)
1585       gi->selectbox.index = gi->selectbox.current_index;
1586
1587     DrawGadget(gi, DG_UNPRESSED, gi->direct_draw);
1588
1589     gi->event.type = GD_EVENT_TEXT_RETURN;
1590
1591     if (gi->event_mask & GD_EVENT_TEXT_RETURN)
1592       gi->callback_action(gi);
1593
1594     last_gi = NULL;
1595   }
1596   else if (gi->type & GD_TYPE_TEXTINPUT)        /* only valid for text input */
1597   {
1598     char text[MAX_GADGET_TEXTSIZE];
1599     int text_length = strlen(gi->text.value);
1600     int cursor_pos = gi->text.cursor_position;
1601     char letter = getCharFromKey(key);
1602     boolean legal_letter = (gi->type == GD_TYPE_TEXTINPUT_NUMERIC ?
1603                             letter >= '0' && letter <= '9' :
1604                             letter != 0);
1605
1606     if (legal_letter && text_length < gi->text.size)
1607     {
1608       strcpy(text, gi->text.value);
1609       strcpy(&gi->text.value[cursor_pos + 1], &text[cursor_pos]);
1610       gi->text.value[cursor_pos] = letter;
1611       gi->text.cursor_position++;
1612
1613       DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1614     }
1615     else if (key == KSYM_Left && cursor_pos > 0)
1616     {
1617       gi->text.cursor_position--;
1618       DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1619     }
1620     else if (key == KSYM_Right && cursor_pos < text_length)
1621     {
1622       gi->text.cursor_position++;
1623       DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1624     }
1625     else if (key == KSYM_BackSpace && cursor_pos > 0)
1626     {
1627       strcpy(text, gi->text.value);
1628       strcpy(&gi->text.value[cursor_pos - 1], &text[cursor_pos]);
1629       gi->text.cursor_position--;
1630       DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1631     }
1632     else if (key == KSYM_Delete && cursor_pos < text_length)
1633     {
1634       strcpy(text, gi->text.value);
1635       strcpy(&gi->text.value[cursor_pos], &text[cursor_pos + 1]);
1636       DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1637     }
1638   }
1639   else if (gi->type & GD_TYPE_SELECTBOX)        /* only valid for selectbox */
1640   {
1641     int index = gi->selectbox.current_index;
1642     int num_values = gi->selectbox.num_values;
1643
1644     if (key == KSYM_Up && index > 0)
1645     {
1646       gi->selectbox.current_index--;
1647       DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1648     }
1649     else if (key == KSYM_Down && index < num_values - 1)
1650     {
1651       gi->selectbox.current_index++;
1652       DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1653     }
1654   }
1655 }