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