rnd-20030629-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 #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     int bottom_screen_border = gfx.sy + gfx.sysize - font_height;
832     Bitmap *src_bitmap;
833     int src_x, src_y;
834
835     gi->width  = 2 * border_xsize + gi->text.size * font_width + button_size;
836     gi->height = 2 * border_ysize + font_height;
837
838     if (gi->selectbox.options == NULL)
839       Error(ERR_EXIT, "selectbox gadget incomplete (missing options array)");
840
841     gi->selectbox.num_values = 0;
842     while (gi->selectbox.options[gi->selectbox.num_values].text != NULL)
843       gi->selectbox.num_values++;
844
845     /* calculate values for open selectbox */
846     gi->selectbox.width = gi->width;
847     gi->selectbox.height =
848       2 * border_ysize + gi->selectbox.num_values * font_height;
849
850     gi->selectbox.x = gi->x;
851     gi->selectbox.y = gi->y + gi->height;
852     if (gi->selectbox.y + gi->selectbox.height > bottom_screen_border)
853       gi->selectbox.y = gi->y - gi->selectbox.height;
854     if (gi->selectbox.y < 0)
855       gi->selectbox.y = bottom_screen_border - gi->selectbox.height;
856
857     getFontCharSource(font_nr, FONT_ASCII_CURSOR, &src_bitmap, &src_x, &src_y);
858     src_x += font_width / 2;
859     src_y += font_height / 2;
860     gi->selectbox.inverse_color = GetPixel(src_bitmap, src_x, src_y);
861
862     /* always start with closed selectbox */
863     gi->selectbox.open = FALSE;
864   }
865
866   if (gi->type & GD_TYPE_TEXTINPUT_NUMERIC)
867   {
868     struct GadgetTextInput *text = &gi->text;
869     int value = text->number_value;
870
871     text->number_value = (value < text->number_min ? text->number_min :
872                           value > text->number_max ? text->number_max :
873                           value);
874
875     sprintf(text->value, "%d", text->number_value);
876   }
877
878   if (gi->type & GD_TYPE_TEXT_BUTTON)
879   {
880     int font_nr = gi->font_active;
881     int font_width = getFontWidth(font_nr);
882     int font_height = getFontHeight(font_nr);
883     int border_xsize = gi->border.xsize;
884     int border_ysize = gi->border.ysize;
885
886     gi->width  = 2 * border_xsize + gi->textbutton.size * font_width;
887     gi->height = 2 * border_ysize + font_height;
888   }
889
890   if (gi->type & GD_TYPE_SCROLLBAR)
891   {
892     struct GadgetScrollbar *gs = &gi->scrollbar;
893
894     if (gi->width == 0 || gi->height == 0 ||
895         gs->items_max == 0 || gs->items_visible == 0)
896       Error(ERR_EXIT, "scrollbar gadget incomplete (missing tags)");
897
898     /* calculate internal scrollbar values */
899     gs->size_max = (gi->type == GD_TYPE_SCROLLBAR_VERTICAL ?
900                     gi->height : gi->width);
901     gs->size = gs->size_max * gs->items_visible / gs->items_max;
902     gs->position = gs->size_max * gs->item_position / gs->items_max;
903     gs->position_max = gs->size_max - gs->size;
904     gs->correction = gs->size_max / gs->items_max / 2;
905
906     /* finetuning for maximal right/bottom position */
907     if (gs->item_position == gs->items_max - gs->items_visible)
908       gs->position = gs->position_max;
909   }
910 }
911
912 void ModifyGadget(struct GadgetInfo *gi, int first_tag, ...)
913 {
914   va_list ap;
915
916   va_start(ap, first_tag);
917   HandleGadgetTags(gi, first_tag, ap);
918   va_end(ap);
919
920   RedrawGadget(gi);
921 }
922
923 void RedrawGadget(struct GadgetInfo *gi)
924 {
925   if (gi->mapped)
926     DrawGadget(gi, gi->state, DG_DIRECT);
927 }
928
929 struct GadgetInfo *CreateGadget(int first_tag, ...)
930 {
931   struct GadgetInfo *new_gadget = checked_calloc(sizeof(struct GadgetInfo));
932   va_list ap;
933
934   /* always start with reliable default values */
935   new_gadget->id = getNewGadgetID();
936   new_gadget->callback_info = default_callback_info;
937   new_gadget->callback_action = default_callback_action;
938   new_gadget->active = TRUE;
939   new_gadget->next = NULL;
940
941   va_start(ap, first_tag);
942   HandleGadgetTags(new_gadget, first_tag, ap);
943   va_end(ap);
944
945   /* insert new gadget into global gadget list */
946   if (gadget_list_last_entry)
947   {
948     gadget_list_last_entry->next = new_gadget;
949     gadget_list_last_entry = gadget_list_last_entry->next;
950   }
951   else
952     gadget_list_first_entry = gadget_list_last_entry = new_gadget;
953
954   return new_gadget;
955 }
956
957 void FreeGadget(struct GadgetInfo *gi)
958 {
959   struct GadgetInfo *gi_previous = gadget_list_first_entry;
960
961   while (gi_previous != NULL && gi_previous->next != gi)
962     gi_previous = gi_previous->next;
963
964   if (gi == gadget_list_first_entry)
965     gadget_list_first_entry = gi->next;
966
967   if (gi == gadget_list_last_entry)
968     gadget_list_last_entry = gi_previous;
969
970   if (gi_previous != NULL)
971     gi_previous->next = gi->next;
972
973   free(gi);
974 }
975
976 static void CheckRangeOfNumericInputGadget(struct GadgetInfo *gi)
977 {
978   if (gi->type != GD_TYPE_TEXTINPUT_NUMERIC)
979     return;
980
981   gi->text.number_value = atoi(gi->text.value);
982
983   if (gi->text.number_value < gi->text.number_min)
984     gi->text.number_value = gi->text.number_min;
985   if (gi->text.number_value > gi->text.number_max)
986     gi->text.number_value = gi->text.number_max;
987
988   sprintf(gi->text.value, "%d", gi->text.number_value);
989
990   if (gi->text.cursor_position < 0)
991     gi->text.cursor_position = 0;
992   else if (gi->text.cursor_position > strlen(gi->text.value))
993     gi->text.cursor_position = strlen(gi->text.value);
994 }
995
996 /* global pointer to gadget actually in use (when mouse button pressed) */
997 static struct GadgetInfo *last_gi = NULL;
998
999 static void MapGadgetExt(struct GadgetInfo *gi, boolean redraw)
1000 {
1001   if (gi == NULL || gi->mapped)
1002     return;
1003
1004   gi->mapped = TRUE;
1005
1006   if (redraw)
1007     DrawGadget(gi, DG_UNPRESSED, DG_BUFFERED);
1008 }
1009
1010 void MapGadget(struct GadgetInfo *gi)
1011 {
1012   MapGadgetExt(gi, TRUE);
1013 }
1014
1015 void UnmapGadget(struct GadgetInfo *gi)
1016 {
1017   if (gi == NULL || !gi->mapped)
1018     return;
1019
1020   gi->mapped = FALSE;
1021
1022   if (gi == last_gi)
1023     last_gi = NULL;
1024 }
1025
1026 #define MAX_NUM_GADGETS         1024
1027 #define MULTIMAP_UNMAP          (1 << 0)
1028 #define MULTIMAP_REMAP          (1 << 1)
1029 #define MULTIMAP_REDRAW         (1 << 2)
1030 #define MULTIMAP_PLAYFIELD      (1 << 3)
1031 #define MULTIMAP_DOOR_1         (1 << 4)
1032 #define MULTIMAP_DOOR_2         (1 << 5)
1033 #define MULTIMAP_ALL            (MULTIMAP_PLAYFIELD | \
1034                                  MULTIMAP_DOOR_1 | \
1035                                  MULTIMAP_DOOR_2)
1036
1037 static void MultiMapGadgets(int mode)
1038 {
1039   struct GadgetInfo *gi = gadget_list_first_entry;
1040   static boolean map_state[MAX_NUM_GADGETS];
1041   int map_count = 0;
1042
1043   while (gi != NULL)
1044   {
1045     if ((mode & MULTIMAP_PLAYFIELD &&
1046          gi->x < gfx.sx + gfx.sxsize) ||
1047         (mode & MULTIMAP_DOOR_1 &&
1048          gi->x >= gfx.dx && gi->y < gfx.dy + gfx.dysize) ||
1049         (mode & MULTIMAP_DOOR_2 &&
1050          gi->x >= gfx.dx && gi->y > gfx.dy + gfx.dysize) ||
1051         (mode & MULTIMAP_ALL) == MULTIMAP_ALL)
1052     {
1053       if (mode & MULTIMAP_UNMAP)
1054       {
1055         map_state[map_count++ % MAX_NUM_GADGETS] = gi->mapped;
1056         UnmapGadget(gi);
1057       }
1058       else
1059       {
1060         if (map_state[map_count++ % MAX_NUM_GADGETS])
1061           MapGadgetExt(gi, (mode & MULTIMAP_REDRAW));
1062       }
1063     }
1064
1065     gi = gi->next;
1066   }
1067 }
1068
1069 void UnmapAllGadgets()
1070 {
1071   MultiMapGadgets(MULTIMAP_ALL | MULTIMAP_UNMAP);
1072 }
1073
1074 void RemapAllGadgets()
1075 {
1076   MultiMapGadgets(MULTIMAP_ALL | MULTIMAP_REMAP);
1077 }
1078
1079 boolean anyTextInputGadgetActive()
1080 {
1081   return (last_gi && (last_gi->type & GD_TYPE_TEXTINPUT) && last_gi->mapped);
1082 }
1083
1084 boolean anySelectboxGadgetActive()
1085 {
1086   return (last_gi && (last_gi->type & GD_TYPE_SELECTBOX) && last_gi->mapped);
1087 }
1088
1089 boolean anyTextGadgetActive()
1090 {
1091   return (anyTextInputGadgetActive() || anySelectboxGadgetActive());
1092 }
1093
1094 void ClickOnGadget(struct GadgetInfo *gi, int button)
1095 {
1096   /* simulate releasing mouse button over last gadget, if still pressed */
1097   if (button_status)
1098     HandleGadgets(-1, -1, 0);
1099
1100   /* simulate pressing mouse button over specified gadget */
1101   HandleGadgets(gi->x, gi->y, button);
1102
1103   /* simulate releasing mouse button over specified gadget */
1104   HandleGadgets(gi->x, gi->y, 0);
1105 }
1106
1107 void HandleGadgets(int mx, int my, int button)
1108 {
1109   static struct GadgetInfo *last_info_gi = NULL;
1110   static unsigned long pressed_delay = 0;
1111   static int last_button = 0;
1112   static int last_mx = 0, last_my = 0;
1113   int scrollbar_mouse_pos = 0;
1114   struct GadgetInfo *new_gi, *gi;
1115   boolean press_event;
1116   boolean release_event;
1117   boolean mouse_moving;
1118   boolean gadget_pressed;
1119   boolean gadget_pressed_repeated;
1120   boolean gadget_moving;
1121   boolean gadget_moving_inside;
1122   boolean gadget_moving_off_borders;
1123   boolean gadget_released;
1124   boolean gadget_released_inside;
1125   boolean gadget_released_inside_select_line;
1126   boolean gadget_released_inside_select_area;
1127   boolean gadget_released_off_borders;
1128   boolean changed_position = FALSE;
1129
1130   /* check if there are any gadgets defined */
1131   if (gadget_list_first_entry == NULL)
1132     return;
1133
1134   /* check which gadget is under the mouse pointer */
1135   new_gi = getGadgetInfoFromMousePosition(mx, my);
1136
1137   /* check if button state has changed since last invocation */
1138   press_event = (button != 0 && last_button == 0);
1139   release_event = (button == 0 && last_button != 0);
1140   last_button = button;
1141
1142   /* check if mouse has been moved since last invocation */
1143   mouse_moving = ((mx != last_mx || my != last_my) && motion_status);
1144   last_mx = mx;
1145   last_my = my;
1146
1147   /* special treatment for text and number input gadgets */
1148   if (anyTextInputGadgetActive() && button != 0 && !motion_status)
1149   {
1150     struct GadgetInfo *gi = last_gi;
1151
1152     if (new_gi == last_gi)
1153     {
1154       int old_cursor_position = gi->text.cursor_position;
1155
1156       /* if mouse button pressed inside activated text gadget, set cursor */
1157       gi->text.cursor_position =
1158         (mx - gi->x - gi->border.xsize) / getFontWidth(gi->font);
1159
1160       if (gi->text.cursor_position < 0)
1161         gi->text.cursor_position = 0;
1162       else if (gi->text.cursor_position > strlen(gi->text.value))
1163         gi->text.cursor_position = strlen(gi->text.value);
1164
1165       if (gi->text.cursor_position != old_cursor_position)
1166         DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1167     }
1168     else
1169     {
1170       /* if mouse button pressed outside text input gadget, deactivate it */
1171       CheckRangeOfNumericInputGadget(gi);
1172       DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1173
1174       gi->event.type = GD_EVENT_TEXT_LEAVING;
1175
1176       if (gi->event_mask & GD_EVENT_TEXT_LEAVING)
1177         gi->callback_action(gi);
1178
1179       last_gi = NULL;
1180     }
1181   }
1182
1183   /* special treatment for selectbox gadgets */
1184   if (anySelectboxGadgetActive() && button != 0 && !motion_status)
1185   {
1186     struct GadgetInfo *gi = last_gi;
1187
1188     if (new_gi == last_gi)
1189     {
1190       int old_index = gi->selectbox.current_index;
1191
1192       /* if mouse button pressed inside activated selectbox, select value */
1193       if (my >= gi->selectbox.y && my < gi->selectbox.y + gi->selectbox.height)
1194         gi->selectbox.current_index =
1195           (my - gi->selectbox.y - gi->border.xsize) / getFontWidth(gi->font);
1196
1197       if (gi->selectbox.current_index < 0)
1198         gi->selectbox.current_index = 0;
1199       else if (gi->selectbox.current_index > gi->selectbox.num_values - 1)
1200         gi->selectbox.current_index = gi->selectbox.num_values - 1;
1201
1202       if (gi->selectbox.current_index != old_index)
1203         DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1204     }
1205     else
1206     {
1207       /* if mouse button pressed outside selectbox gadget, deactivate it */
1208       DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1209
1210       gi->event.type = GD_EVENT_TEXT_LEAVING;
1211
1212       if (gi->event_mask & GD_EVENT_TEXT_LEAVING)
1213         gi->callback_action(gi);
1214
1215       last_gi = NULL;
1216     }
1217   }
1218
1219   gadget_pressed =
1220     (button != 0 && last_gi == NULL && new_gi != NULL && press_event);
1221   gadget_pressed_repeated =
1222     (button != 0 && last_gi != NULL && new_gi == last_gi);
1223
1224   gadget_released =             (release_event && last_gi != NULL);
1225   gadget_released_inside =      (gadget_released && new_gi == last_gi);
1226   gadget_released_off_borders = (gadget_released && new_gi != last_gi);
1227
1228   gadget_moving =             (button != 0 && last_gi != NULL && mouse_moving);
1229   gadget_moving_inside =      (gadget_moving && new_gi == last_gi);
1230   gadget_moving_off_borders = (gadget_moving && new_gi != last_gi);
1231
1232   /* when handling selectbox, set additional state values */
1233   if (gadget_released_inside && (last_gi->type & GD_TYPE_SELECTBOX))
1234   {
1235     struct GadgetInfo *gi = last_gi;
1236
1237     gadget_released_inside_select_line =
1238       (mx >= gi->x && mx < gi->x + gi->width &&
1239        my >= gi->y && my < gi->y + gi->height);
1240     gadget_released_inside_select_area =
1241       (mx >= gi->selectbox.x && mx < gi->selectbox.x+gi->selectbox.width &&
1242        my >= gi->selectbox.y && my < gi->selectbox.y+gi->selectbox.height);
1243   }
1244   else
1245   {
1246     gadget_released_inside_select_line = FALSE;
1247     gadget_released_inside_select_area = FALSE;
1248   }
1249
1250   /* if new gadget pressed, store this gadget  */
1251   if (gadget_pressed)
1252     last_gi = new_gi;
1253
1254   /* 'gi' is actually handled gadget */
1255   gi = last_gi;
1256
1257   /* if gadget is scrollbar, choose mouse position value */
1258   if (gi && gi->type & GD_TYPE_SCROLLBAR)
1259     scrollbar_mouse_pos =
1260       (gi->type == GD_TYPE_SCROLLBAR_HORIZONTAL ? mx - gi->x : my - gi->y);
1261
1262   /* if mouse button released, no gadget needs to be handled anymore */
1263   if (gadget_released)
1264   {
1265     if ((last_gi->type & GD_TYPE_SELECTBOX) &&
1266         (gadget_released_inside_select_line ||
1267          gadget_released_off_borders))              /* selectbox stays open */
1268       gi->selectbox.stay_open = TRUE;
1269     else if (!(last_gi->type & GD_TYPE_TEXTINPUT))  /* text input stays open */
1270       last_gi = NULL;
1271   }
1272
1273   /* modify event position values even if no gadget is pressed */
1274   if (button == 0 && !release_event)
1275     gi = new_gi;
1276
1277   if (gi != NULL)
1278   {
1279     int last_x = gi->event.x;
1280     int last_y = gi->event.y;
1281
1282     gi->event.x = mx - gi->x;
1283     gi->event.y = my - gi->y;
1284
1285     if (gi->type == GD_TYPE_DRAWING_AREA)
1286     {
1287       gi->event.x /= gi->drawing.item_xsize;
1288       gi->event.y /= gi->drawing.item_ysize;
1289
1290       if (last_x != gi->event.x || last_y != gi->event.y)
1291         changed_position = TRUE;
1292     }
1293     else if (gi->type & GD_TYPE_SELECTBOX)
1294     {
1295       int old_index = gi->selectbox.current_index;
1296
1297       /* if mouse moving inside activated selectbox, select value */
1298       if (my >= gi->selectbox.y && my < gi->selectbox.y + gi->selectbox.height)
1299         gi->selectbox.current_index =
1300           (my - gi->selectbox.y - gi->border.xsize) / getFontWidth(gi->font);
1301
1302       if (gi->selectbox.current_index < 0)
1303         gi->selectbox.current_index = 0;
1304       else if (gi->selectbox.current_index > gi->selectbox.num_values - 1)
1305         gi->selectbox.current_index = gi->selectbox.num_values - 1;
1306
1307       if (gi->selectbox.current_index != old_index)
1308         DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1309     }
1310   }
1311
1312   /* handle gadget popup info text */
1313   if (last_info_gi != new_gi ||
1314       (new_gi && new_gi->type == GD_TYPE_DRAWING_AREA && changed_position))
1315   {
1316     if (new_gi != NULL && (button == 0 || new_gi == last_gi))
1317     {
1318       new_gi->event.type = GD_EVENT_INFO_ENTERING;
1319       new_gi->callback_info(new_gi);
1320     }
1321     else if (last_info_gi != NULL)
1322     {
1323       last_info_gi->event.type = GD_EVENT_INFO_LEAVING;
1324       last_info_gi->callback_info(last_info_gi);
1325
1326 #if 0
1327       default_callback_info(NULL);
1328
1329       printf("It seems that we are leaving gadget [%s]!\n",
1330              (last_info_gi != NULL &&
1331               last_info_gi->info_text != NULL ?
1332               last_info_gi->info_text : ""));
1333 #endif
1334     }
1335
1336     last_info_gi = new_gi;
1337   }
1338
1339   if (gadget_pressed)
1340   {
1341     if (gi->type == GD_TYPE_CHECK_BUTTON)
1342     {
1343       gi->checked = !gi->checked;
1344     }
1345     else if (gi->type == GD_TYPE_RADIO_BUTTON)
1346     {
1347       struct GadgetInfo *rgi = gadget_list_first_entry;
1348
1349       while (rgi)
1350       {
1351         if (rgi->mapped &&
1352             rgi->type == GD_TYPE_RADIO_BUTTON &&
1353             rgi->radio_nr == gi->radio_nr &&
1354             rgi != gi)
1355         {
1356           rgi->checked = FALSE;
1357           DrawGadget(rgi, DG_UNPRESSED, DG_DIRECT);
1358         }
1359
1360         rgi = rgi->next;
1361       }
1362
1363       gi->checked = TRUE;
1364     }
1365     else if (gi->type & GD_TYPE_SCROLLBAR)
1366     {
1367       int mpos, gpos;
1368
1369       if (gi->type == GD_TYPE_SCROLLBAR_HORIZONTAL)
1370       {
1371         mpos = mx;
1372         gpos = gi->x;
1373       }
1374       else
1375       {
1376         mpos = my;
1377         gpos = gi->y;
1378       }
1379
1380       if (mpos >= gpos + gi->scrollbar.position &&
1381           mpos < gpos + gi->scrollbar.position + gi->scrollbar.size)
1382       {
1383         /* drag scrollbar */
1384         gi->scrollbar.drag_position =
1385           scrollbar_mouse_pos - gi->scrollbar.position;
1386       }
1387       else
1388       {
1389         /* click scrollbar one scrollbar length up/left or down/right */
1390
1391         struct GadgetScrollbar *gs = &gi->scrollbar;
1392         int old_item_position = gs->item_position;
1393
1394         changed_position = FALSE;
1395
1396         gs->item_position +=
1397           gs->items_visible * (mpos < gpos + gi->scrollbar.position ? -1 : +1);
1398
1399         if (gs->item_position < 0)
1400           gs->item_position = 0;
1401         if (gs->item_position > gs->items_max - gs->items_visible)
1402           gs->item_position = gs->items_max - gs->items_visible;
1403
1404         if (old_item_position != gs->item_position)
1405         {
1406           gi->event.item_position = gs->item_position;
1407           changed_position = TRUE;
1408         }
1409
1410         ModifyGadget(gi, GDI_SCROLLBAR_ITEM_POSITION, gs->item_position,
1411                      GDI_END);
1412
1413         gi->state = GD_BUTTON_UNPRESSED;
1414         gi->event.type = GD_EVENT_MOVING;
1415         gi->event.off_borders = FALSE;
1416
1417         if (gi->event_mask & GD_EVENT_MOVING && changed_position)
1418           gi->callback_action(gi);
1419
1420         /* don't handle this scrollbar anymore while mouse button pressed */
1421         last_gi = NULL;
1422
1423         return;
1424       }
1425     }
1426
1427     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1428
1429     gi->state = GD_BUTTON_PRESSED;
1430     gi->event.type = GD_EVENT_PRESSED;
1431     gi->event.button = button;
1432     gi->event.off_borders = FALSE;
1433
1434     /* initialize delay counter */
1435     DelayReached(&pressed_delay, 0);
1436
1437     if (gi->event_mask & GD_EVENT_PRESSED)
1438       gi->callback_action(gi);
1439   }
1440
1441   if (gadget_pressed_repeated)
1442   {
1443     gi->event.type = GD_EVENT_PRESSED;
1444
1445     if (gi->event_mask & GD_EVENT_REPEATED &&
1446         DelayReached(&pressed_delay, GADGET_FRAME_DELAY))
1447       gi->callback_action(gi);
1448   }
1449
1450   if (gadget_moving)
1451   {
1452     if (gi->type & GD_TYPE_BUTTON)
1453     {
1454       if (gadget_moving_inside && gi->state == GD_BUTTON_UNPRESSED)
1455         DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1456       else if (gadget_moving_off_borders && gi->state == GD_BUTTON_PRESSED)
1457         DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1458     }
1459     else if (gi->type & GD_TYPE_SELECTBOX)
1460     {
1461       int old_index = gi->selectbox.current_index;
1462
1463       /* if mouse moving inside activated selectbox, select value */
1464       if (my >= gi->selectbox.y && my < gi->selectbox.y + gi->selectbox.height)
1465         gi->selectbox.current_index =
1466           (my - gi->selectbox.y - gi->border.xsize) / getFontWidth(gi->font);
1467
1468       if (gi->selectbox.current_index < 0)
1469         gi->selectbox.current_index = 0;
1470       else if (gi->selectbox.current_index > gi->selectbox.num_values - 1)
1471         gi->selectbox.current_index = gi->selectbox.num_values - 1;
1472
1473       if (gi->selectbox.current_index != old_index)
1474         DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1475     }
1476     else if (gi->type & GD_TYPE_SCROLLBAR)
1477     {
1478       struct GadgetScrollbar *gs = &gi->scrollbar;
1479       int old_item_position = gs->item_position;
1480
1481       gs->position = scrollbar_mouse_pos - gs->drag_position;
1482
1483       if (gs->position < 0)
1484         gs->position = 0;
1485       if (gs->position > gs->position_max)
1486         gs->position = gs->position_max;
1487
1488       gs->item_position =
1489         gs->items_max * (gs->position + gs->correction) / gs->size_max;
1490
1491       if (gs->item_position < 0)
1492         gs->item_position = 0;
1493       if (gs->item_position > gs->items_max - 1)
1494         gs->item_position = gs->items_max - 1;
1495
1496       if (old_item_position != gs->item_position)
1497       {
1498         gi->event.item_position = gs->item_position;
1499         changed_position = TRUE;
1500       }
1501
1502       DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1503     }
1504
1505     gi->state = (gadget_moving_inside || gi->type & GD_TYPE_SCROLLBAR ?
1506                  GD_BUTTON_PRESSED : GD_BUTTON_UNPRESSED);
1507     gi->event.type = GD_EVENT_MOVING;
1508     gi->event.off_borders = gadget_moving_off_borders;
1509
1510     if (gi->event_mask & GD_EVENT_MOVING && changed_position &&
1511         (gadget_moving_inside || gi->event_mask & GD_EVENT_OFF_BORDERS))
1512       gi->callback_action(gi);
1513   }
1514
1515   if (gadget_released_inside)
1516   {
1517     boolean deactivate_gadget = TRUE;
1518
1519     if (gi->type & GD_TYPE_SELECTBOX)
1520     {
1521       if (gadget_released_inside_select_line ||
1522           gadget_released_off_borders)              /* selectbox stays open */
1523         deactivate_gadget = FALSE;
1524       else
1525         gi->selectbox.index = gi->selectbox.current_index;
1526     }
1527
1528     if (deactivate_gadget &&
1529         !(gi->type & GD_TYPE_TEXTINPUT))            /* text input stays open */
1530       DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1531
1532     gi->state = GD_BUTTON_UNPRESSED;
1533     gi->event.type = GD_EVENT_RELEASED;
1534
1535     if ((gi->event_mask & GD_EVENT_RELEASED) && deactivate_gadget)
1536       gi->callback_action(gi);
1537   }
1538
1539   if (gadget_released_off_borders)
1540   {
1541     if (gi->type & GD_TYPE_SCROLLBAR)
1542       DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1543
1544     gi->event.type = GD_EVENT_RELEASED;
1545
1546     if (gi->event_mask & GD_EVENT_RELEASED &&
1547         gi->event_mask & GD_EVENT_OFF_BORDERS)
1548       gi->callback_action(gi);
1549   }
1550 }
1551
1552 void HandleGadgetsKeyInput(Key key)
1553 {
1554   struct GadgetInfo *gi = last_gi;
1555
1556   if (gi == NULL || !gi->mapped ||
1557       !((gi->type & GD_TYPE_TEXTINPUT) || (gi->type & GD_TYPE_SELECTBOX)))
1558     return;
1559
1560   if (key == KSYM_Return)       /* valid for both text input and selectbox */
1561   {
1562     if (gi->type & GD_TYPE_TEXTINPUT)
1563       CheckRangeOfNumericInputGadget(gi);
1564     else if (gi->type & GD_TYPE_SELECTBOX)
1565       gi->selectbox.index = gi->selectbox.current_index;
1566
1567     DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1568
1569     gi->event.type = GD_EVENT_TEXT_RETURN;
1570
1571     if (gi->event_mask & GD_EVENT_TEXT_RETURN)
1572       gi->callback_action(gi);
1573
1574     last_gi = NULL;
1575   }
1576   else if (gi->type & GD_TYPE_TEXTINPUT)        /* only valid for text input */
1577   {
1578     char text[MAX_GADGET_TEXTSIZE];
1579     int text_length = strlen(gi->text.value);
1580     int cursor_pos = gi->text.cursor_position;
1581     char letter = getCharFromKey(key);
1582     boolean legal_letter = (gi->type == GD_TYPE_TEXTINPUT_NUMERIC ?
1583                             letter >= '0' && letter <= '9' :
1584                             letter != 0);
1585
1586     if (legal_letter && text_length < gi->text.size)
1587     {
1588       strcpy(text, gi->text.value);
1589       strcpy(&gi->text.value[cursor_pos + 1], &text[cursor_pos]);
1590       gi->text.value[cursor_pos] = letter;
1591       gi->text.cursor_position++;
1592
1593       DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1594     }
1595     else if (key == KSYM_Left && cursor_pos > 0)
1596     {
1597       gi->text.cursor_position--;
1598       DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1599     }
1600     else if (key == KSYM_Right && cursor_pos < text_length)
1601     {
1602       gi->text.cursor_position++;
1603       DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1604     }
1605     else if (key == KSYM_BackSpace && cursor_pos > 0)
1606     {
1607       strcpy(text, gi->text.value);
1608       strcpy(&gi->text.value[cursor_pos - 1], &text[cursor_pos]);
1609       gi->text.cursor_position--;
1610       DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1611     }
1612     else if (key == KSYM_Delete && cursor_pos < text_length)
1613     {
1614       strcpy(text, gi->text.value);
1615       strcpy(&gi->text.value[cursor_pos], &text[cursor_pos + 1]);
1616       DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1617     }
1618   }
1619   else if (gi->type & GD_TYPE_SELECTBOX)        /* only valid for selectbox */
1620   {
1621     int index = gi->selectbox.current_index;
1622     int num_values = gi->selectbox.num_values;
1623
1624     if (key == KSYM_Up && index > 0)
1625     {
1626       gi->selectbox.current_index--;
1627       DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1628     }
1629     else if (key == KSYM_Down && index < num_values - 1)
1630     {
1631       gi->selectbox.current_index++;
1632       DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1633     }
1634   }
1635 }