rnd-20021211-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)
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 = gadget_list_first_entry;
84
85   while (gi)
86   {
87     if (gi->mapped &&
88         mx >= gi->x && mx < gi->x + gi->width &&
89         my >= gi->y && my < gi->y + gi->height)
90         break;
91
92     gi = gi->next;
93   }
94
95   return gi;
96 }
97
98 static void default_callback_info(void *ptr)
99 {
100   return;
101 }
102
103 static void default_callback_action(void *ptr)
104 {
105   return;
106 }
107
108 static void DrawGadget(struct GadgetInfo *gi, boolean pressed, boolean direct)
109 {
110   int state = (pressed ? 1 : 0);
111   struct GadgetDesign *gd = (gi->checked ?
112                              &gi->alt_design[state] :
113                              &gi->design[state]);
114
115   switch (gi->type)
116   {
117     case GD_TYPE_NORMAL_BUTTON:
118     case GD_TYPE_CHECK_BUTTON:
119     case GD_TYPE_RADIO_BUTTON:
120       BlitBitmap(gd->bitmap, drawto,
121                  gd->x, gd->y, gi->width, gi->height, gi->x, gi->y);
122       if (gi->deco.design.bitmap)
123         BlitBitmap(gi->deco.design.bitmap, drawto,
124                    gi->deco.design.x, gi->deco.design.y,
125                    gi->deco.width, gi->deco.height,
126                    gi->x + gi->deco.x + (pressed ? gi->deco.xshift : 0),
127                    gi->y + gi->deco.y + (pressed ? gi->deco.yshift : 0));
128       break;
129
130     case GD_TYPE_TEXTINPUT_ALPHANUMERIC:
131     case GD_TYPE_TEXTINPUT_NUMERIC:
132       {
133         int i;
134         char cursor_letter;
135         char cursor_string[3];
136         char text[MAX_GADGET_TEXTSIZE + 1];
137         int font_type = gi->text.font_type;
138         int font_width = getFontWidth(FS_SMALL, font_type);
139         int border = gi->border.size;
140         strcpy(text, gi->text.value);
141         strcat(text, " ");
142
143         /* left part of gadget */
144         BlitBitmap(gd->bitmap, drawto,
145                    gd->x, gd->y, border, gi->height, gi->x, gi->y);
146
147         /* middle part of gadget */
148         for (i=0; i<=gi->text.size; i++)
149           BlitBitmap(gd->bitmap, drawto,
150                      gd->x + border, gd->y, font_width, gi->height,
151                      gi->x + border + i * font_width, gi->y);
152
153         /* right part of gadget */
154         BlitBitmap(gd->bitmap, drawto,
155                    gd->x + gi->border.width - border, gd->y,
156                    border, gi->height, gi->x + gi->width - border, gi->y);
157
158         /* gadget text value */
159         DrawText(gi->x + border, gi->y + border, text, FS_SMALL, font_type);
160
161         cursor_letter = gi->text.value[gi->text.cursor_position];
162         cursor_string[0] = '~';
163         cursor_string[1] = (cursor_letter != '\0' ? cursor_letter : ' ');
164         cursor_string[2] = '\0';
165
166         /* draw cursor, if active */
167         if (pressed)
168           DrawText(gi->x + border + gi->text.cursor_position * font_width,
169                    gi->y + border, cursor_string, FS_SMALL, font_type);
170       }
171       break;
172
173     case GD_TYPE_SCROLLBAR_VERTICAL:
174       {
175         int i;
176         int xpos = gi->x;
177         int ypos = gi->y + gi->scrollbar.position;
178         int design_full = gi->width;
179         int design_body = design_full - 2 * gi->border.size;
180         int size_full = gi->scrollbar.size;
181         int size_body = size_full - 2 * gi->border.size;
182         int num_steps = size_body / design_body;
183         int step_size_remain = size_body - num_steps * design_body;
184
185         /* clear scrollbar area */
186         ClearRectangle(backbuffer, gi->x, gi->y, gi->width, gi->height);
187
188         /* upper part of gadget */
189         BlitBitmap(gd->bitmap, drawto,
190                    gd->x, gd->y,
191                    gi->width, gi->border.size,
192                    xpos, ypos);
193
194         /* middle part of gadget */
195         for (i=0; i<num_steps; i++)
196           BlitBitmap(gd->bitmap, drawto,
197                      gd->x, gd->y + gi->border.size,
198                      gi->width, design_body,
199                      xpos, ypos + gi->border.size + i * design_body);
200
201         /* remaining middle part of gadget */
202         if (step_size_remain > 0)
203           BlitBitmap(gd->bitmap, drawto,
204                      gd->x,  gd->y + gi->border.size,
205                      gi->width, step_size_remain,
206                      xpos, ypos + gi->border.size + num_steps * design_body);
207
208         /* lower part of gadget */
209         BlitBitmap(gd->bitmap, drawto,
210                    gd->x, gd->y + design_full - gi->border.size,
211                    gi->width, gi->border.size,
212                    xpos, ypos + size_full - gi->border.size);
213       }
214       break;
215
216     case GD_TYPE_SCROLLBAR_HORIZONTAL:
217       {
218         int i;
219         int xpos = gi->x + gi->scrollbar.position;
220         int ypos = gi->y;
221         int design_full = gi->height;
222         int design_body = design_full - 2 * gi->border.size;
223         int size_full = gi->scrollbar.size;
224         int size_body = size_full - 2 * gi->border.size;
225         int num_steps = size_body / design_body;
226         int step_size_remain = size_body - num_steps * design_body;
227
228         /* clear scrollbar area */
229         ClearRectangle(backbuffer, gi->x, gi->y, gi->width, gi->height);
230
231         /* left part of gadget */
232         BlitBitmap(gd->bitmap, drawto,
233                    gd->x, gd->y,
234                    gi->border.size, gi->height,
235                    xpos, ypos);
236
237         /* middle part of gadget */
238         for (i=0; i<num_steps; i++)
239           BlitBitmap(gd->bitmap, drawto,
240                      gd->x + gi->border.size, gd->y,
241                      design_body, gi->height,
242                      xpos + gi->border.size + i * design_body, ypos);
243
244         /* remaining middle part of gadget */
245         if (step_size_remain > 0)
246           BlitBitmap(gd->bitmap, drawto,
247                      gd->x + gi->border.size, gd->y,
248                      step_size_remain, gi->height,
249                      xpos + gi->border.size + num_steps * design_body, ypos);
250
251         /* right part of gadget */
252         BlitBitmap(gd->bitmap, drawto,
253                    gd->x + design_full - gi->border.size, gd->y,
254                    gi->border.size, gi->height,
255                    xpos + size_full - gi->border.size, ypos);
256       }
257       break;
258
259     default:
260       return;
261   }
262
263   if (direct)
264     BlitBitmap(drawto, window,
265                gi->x, gi->y, gi->width, gi->height, gi->x, gi->y);
266   else
267     redraw_mask |= (gi->x < gfx.sx + gfx.sxsize ? REDRAW_FIELD :
268                     gi->y < gfx.dy + gfx.dysize ? REDRAW_DOOR_1 :
269                     gi->y > gfx.vy ? REDRAW_DOOR_2 : REDRAW_DOOR_3);
270 }
271
272 static void HandleGadgetTags(struct GadgetInfo *gi, int first_tag, va_list ap)
273 {
274   int tag = first_tag;
275
276   while (tag != GDI_END)
277   {
278     switch(tag)
279     {
280       case GDI_CUSTOM_ID:
281         gi->custom_id = va_arg(ap, int);
282         break;
283
284       case GDI_CUSTOM_TYPE_ID:
285         gi->custom_type_id = va_arg(ap, int);
286         break;
287
288       case GDI_INFO_TEXT:
289         {
290           int max_textsize = MAX_INFO_TEXTSIZE - 1;
291           char *text = va_arg(ap, char *);
292
293           if (text != NULL)
294             strncpy(gi->info_text, text, max_textsize);
295           else
296             max_textsize = 0;
297
298           gi->info_text[max_textsize] = '\0';
299         }
300         break;
301
302       case GDI_X:
303         gi->x = va_arg(ap, int);
304         break;
305
306       case GDI_Y:
307         gi->y = va_arg(ap, int);
308         break;
309
310       case GDI_WIDTH:
311         gi->width = va_arg(ap, int);
312         break;
313
314       case GDI_HEIGHT:
315         gi->height = va_arg(ap, int);
316         break;
317
318       case GDI_TYPE:
319         gi->type = va_arg(ap, unsigned long);
320         break;
321
322       case GDI_STATE:
323         gi->state = va_arg(ap, unsigned long);
324         break;
325
326       case GDI_CHECKED:
327         /* take care here: "boolean" is typedef'ed as "unsigned char",
328            which gets promoted to "int" */
329         gi->checked = (boolean)va_arg(ap, int);
330         break;
331
332       case GDI_RADIO_NR:
333         gi->radio_nr = va_arg(ap, unsigned long);
334         break;
335
336       case GDI_NUMBER_VALUE:
337         gi->text.number_value = va_arg(ap, long);
338         sprintf(gi->text.value, "%d", gi->text.number_value);
339         gi->text.cursor_position = strlen(gi->text.value);
340         break;
341
342       case GDI_NUMBER_MIN:
343         gi->text.number_min = va_arg(ap, long);
344         if (gi->text.number_value < gi->text.number_min)
345         {
346           gi->text.number_value = gi->text.number_min;
347           sprintf(gi->text.value, "%d", gi->text.number_value);
348         }
349         break;
350
351       case GDI_NUMBER_MAX:
352         gi->text.number_max = va_arg(ap, long);
353         if (gi->text.number_value > gi->text.number_max)
354         {
355           gi->text.number_value = gi->text.number_max;
356           sprintf(gi->text.value, "%d", gi->text.number_value);
357         }
358         break;
359
360       case GDI_TEXT_VALUE:
361         {
362           int max_textsize = MAX_GADGET_TEXTSIZE;
363
364           if (gi->text.size)
365             max_textsize = MIN(gi->text.size, MAX_GADGET_TEXTSIZE - 1);
366
367           strncpy(gi->text.value, va_arg(ap, char *), max_textsize);
368           gi->text.value[max_textsize] = '\0';
369           gi->text.cursor_position = strlen(gi->text.value);
370         }
371         break;
372
373       case GDI_TEXT_SIZE:
374         {
375           int tag_value = va_arg(ap, int);
376           int max_textsize = MIN(tag_value, MAX_GADGET_TEXTSIZE - 1);
377
378           gi->text.size = max_textsize;
379           gi->text.value[max_textsize] = '\0';
380         }
381         break;
382
383       case GDI_TEXT_FONT:
384         gi->text.font_type = va_arg(ap, int);
385         break;
386
387       case GDI_DESIGN_UNPRESSED:
388         gi->design[GD_BUTTON_UNPRESSED].bitmap = va_arg(ap, Bitmap *);
389         gi->design[GD_BUTTON_UNPRESSED].x = va_arg(ap, int);
390         gi->design[GD_BUTTON_UNPRESSED].y = va_arg(ap, int);
391         break;
392
393       case GDI_DESIGN_PRESSED:
394         gi->design[GD_BUTTON_PRESSED].bitmap = va_arg(ap, Bitmap *);
395         gi->design[GD_BUTTON_PRESSED].x = va_arg(ap, int);
396         gi->design[GD_BUTTON_PRESSED].y = va_arg(ap, int);
397         break;
398
399       case GDI_ALT_DESIGN_UNPRESSED:
400         gi->alt_design[GD_BUTTON_UNPRESSED].bitmap= va_arg(ap, Bitmap *);
401         gi->alt_design[GD_BUTTON_UNPRESSED].x = va_arg(ap, int);
402         gi->alt_design[GD_BUTTON_UNPRESSED].y = va_arg(ap, int);
403         break;
404
405       case GDI_ALT_DESIGN_PRESSED:
406         gi->alt_design[GD_BUTTON_PRESSED].bitmap = va_arg(ap, Bitmap *);
407         gi->alt_design[GD_BUTTON_PRESSED].x = va_arg(ap, int);
408         gi->alt_design[GD_BUTTON_PRESSED].y = va_arg(ap, int);
409         break;
410
411       case GDI_BORDER_SIZE:
412         gi->border.size = va_arg(ap, int);
413         break;
414
415       case GDI_TEXTINPUT_DESIGN_WIDTH:
416         gi->border.width = va_arg(ap, int);
417         break;
418
419       case GDI_DECORATION_DESIGN:
420         gi->deco.design.bitmap = va_arg(ap, Bitmap *);
421         gi->deco.design.x = va_arg(ap, int);
422         gi->deco.design.y = va_arg(ap, int);
423         break;
424
425       case GDI_DECORATION_POSITION:
426         gi->deco.x = va_arg(ap, int);
427         gi->deco.y = va_arg(ap, int);
428         break;
429
430       case GDI_DECORATION_SIZE:
431         gi->deco.width = va_arg(ap, int);
432         gi->deco.height = va_arg(ap, int);
433         break;
434
435       case GDI_DECORATION_SHIFTING:
436         gi->deco.xshift = va_arg(ap, int);
437         gi->deco.yshift = va_arg(ap, int);
438         break;
439
440       case GDI_EVENT_MASK:
441         gi->event_mask = va_arg(ap, unsigned long);
442         break;
443
444       case GDI_AREA_SIZE:
445         gi->drawing.area_xsize = va_arg(ap, int);
446         gi->drawing.area_ysize = va_arg(ap, int);
447
448         /* determine dependent values for drawing area gadget, if needed */
449         if (gi->width == 0 && gi->height == 0 &&
450             gi->drawing.item_xsize !=0 && gi->drawing.item_ysize !=0)
451         {
452           gi->width = gi->drawing.area_xsize * gi->drawing.item_xsize;
453           gi->height = gi->drawing.area_ysize * gi->drawing.item_ysize;
454         }
455         else if (gi->drawing.item_xsize == 0 && gi->drawing.item_ysize == 0 &&
456                  gi->width != 0 && gi->height != 0)
457         {
458           gi->drawing.item_xsize = gi->width / gi->drawing.area_xsize;
459           gi->drawing.item_ysize = gi->height / gi->drawing.area_ysize;
460         }
461         break;
462
463       case GDI_ITEM_SIZE:
464         gi->drawing.item_xsize = va_arg(ap, int);
465         gi->drawing.item_ysize = va_arg(ap, int);
466
467         /* determine dependent values for drawing area gadget, if needed */
468         if (gi->width == 0 && gi->height == 0 &&
469             gi->drawing.area_xsize !=0 && gi->drawing.area_ysize !=0)
470         {
471           gi->width = gi->drawing.area_xsize * gi->drawing.item_xsize;
472           gi->height = gi->drawing.area_ysize * gi->drawing.item_ysize;
473         }
474         else if (gi->drawing.area_xsize == 0 && gi->drawing.area_ysize == 0 &&
475                  gi->width != 0 && gi->height != 0)
476         {
477           gi->drawing.area_xsize = gi->width / gi->drawing.item_xsize;
478           gi->drawing.area_ysize = gi->height / gi->drawing.item_ysize;
479         }
480         break;
481
482       case GDI_SCROLLBAR_ITEMS_MAX:
483         gi->scrollbar.items_max = va_arg(ap, int);
484         break;
485
486       case GDI_SCROLLBAR_ITEMS_VISIBLE:
487         gi->scrollbar.items_visible = va_arg(ap, int);
488         break;
489
490       case GDI_SCROLLBAR_ITEM_POSITION:
491         gi->scrollbar.item_position = va_arg(ap, int);
492         break;
493
494       case GDI_CALLBACK_INFO:
495         gi->callback_info = va_arg(ap, gadget_function);
496         break;
497
498       case GDI_CALLBACK_ACTION:
499         gi->callback_action = va_arg(ap, gadget_function);
500         break;
501
502       default:
503         Error(ERR_EXIT, "HandleGadgetTags(): unknown tag %d", tag);
504     }
505
506     tag = va_arg(ap, int);      /* read next tag */
507   }
508
509   /* check if gadget complete */
510   if (gi->type != GD_TYPE_DRAWING_AREA &&
511       (!gi->design[GD_BUTTON_UNPRESSED].bitmap ||
512        !gi->design[GD_BUTTON_PRESSED].bitmap))
513     Error(ERR_EXIT, "gadget incomplete (missing Bitmap)");
514
515   /* adjust gadget values in relation to other gadget values */
516
517   if (gi->type & GD_TYPE_TEXTINPUT)
518   {
519     int font_width = getFontWidth(FS_SMALL, gi->text.font_type);
520     int font_height = getFontHeight(FS_SMALL, gi->text.font_type);
521
522     gi->width = 2 * gi->border.size + (gi->text.size + 1) * font_width;
523     gi->height = 2 * gi->border.size + font_height;
524   }
525
526   if (gi->type & GD_TYPE_TEXTINPUT_NUMERIC)
527   {
528     struct GadgetTextInput *text = &gi->text;
529     int value = text->number_value;
530
531     text->number_value = (value < text->number_min ? text->number_min :
532                           value > text->number_max ? text->number_max :
533                           value);
534
535     sprintf(text->value, "%d", text->number_value);
536   }
537
538   if (gi->type & GD_TYPE_SCROLLBAR)
539   {
540     struct GadgetScrollbar *gs = &gi->scrollbar;
541
542     if (gi->width == 0 || gi->height == 0 ||
543         gs->items_max == 0 || gs->items_visible == 0)
544       Error(ERR_EXIT, "scrollbar gadget incomplete (missing tags)");
545
546     /* calculate internal scrollbar values */
547     gs->size_max = (gi->type == GD_TYPE_SCROLLBAR_VERTICAL ?
548                     gi->height : gi->width);
549     gs->size = gs->size_max * gs->items_visible / gs->items_max;
550     gs->position = gs->size_max * gs->item_position / gs->items_max;
551     gs->position_max = gs->size_max - gs->size;
552     gs->correction = gs->size_max / gs->items_max / 2;
553
554     /* finetuning for maximal right/bottom position */
555     if (gs->item_position == gs->items_max - gs->items_visible)
556       gs->position = gs->position_max;
557   }
558 }
559
560 void ModifyGadget(struct GadgetInfo *gi, int first_tag, ...)
561 {
562   va_list ap;
563
564   va_start(ap, first_tag);
565   HandleGadgetTags(gi, first_tag, ap);
566   va_end(ap);
567
568   RedrawGadget(gi);
569 }
570
571 void RedrawGadget(struct GadgetInfo *gi)
572 {
573   if (gi->mapped)
574     DrawGadget(gi, gi->state, DG_DIRECT);
575 }
576
577 struct GadgetInfo *CreateGadget(int first_tag, ...)
578 {
579   struct GadgetInfo *new_gadget = checked_malloc(sizeof(struct GadgetInfo));
580   va_list ap;
581
582   /* always start with reliable default values */
583   memset(new_gadget, 0, sizeof(struct GadgetInfo));     /* zero all fields */
584   new_gadget->id = getNewGadgetID();
585   new_gadget->callback_info = default_callback_info;
586   new_gadget->callback_action = default_callback_action;
587
588   va_start(ap, first_tag);
589   HandleGadgetTags(new_gadget, first_tag, ap);
590   va_end(ap);
591
592   /* insert new gadget into global gadget list */
593   if (gadget_list_last_entry)
594   {
595     gadget_list_last_entry->next = new_gadget;
596     gadget_list_last_entry = gadget_list_last_entry->next;
597   }
598   else
599     gadget_list_first_entry = gadget_list_last_entry = new_gadget;
600
601   return new_gadget;
602 }
603
604 void FreeGadget(struct GadgetInfo *gi)
605 {
606   struct GadgetInfo *gi_previous = gadget_list_first_entry;
607
608   while (gi_previous && gi_previous->next != gi)
609     gi_previous = gi_previous->next;
610
611   if (gi == gadget_list_first_entry)
612     gadget_list_first_entry = gi->next;
613
614   if (gi == gadget_list_last_entry)
615     gadget_list_last_entry = gi_previous;
616
617   gi_previous->next = gi->next;
618   free(gi);
619 }
620
621 static void CheckRangeOfNumericInputGadget(struct GadgetInfo *gi)
622 {
623   if (gi->type != GD_TYPE_TEXTINPUT_NUMERIC)
624     return;
625
626   gi->text.number_value = atoi(gi->text.value);
627
628   if (gi->text.number_value < gi->text.number_min)
629     gi->text.number_value = gi->text.number_min;
630   if (gi->text.number_value > gi->text.number_max)
631     gi->text.number_value = gi->text.number_max;
632
633   sprintf(gi->text.value, "%d", gi->text.number_value);
634
635   if (gi->text.cursor_position < 0)
636     gi->text.cursor_position = 0;
637   else if (gi->text.cursor_position > strlen(gi->text.value))
638     gi->text.cursor_position = strlen(gi->text.value);
639 }
640
641 /* global pointer to gadget actually in use (when mouse button pressed) */
642 static struct GadgetInfo *last_gi = NULL;
643
644 static void MapGadgetExt(struct GadgetInfo *gi, boolean redraw)
645 {
646   if (gi == NULL || gi->mapped)
647     return;
648
649   gi->mapped = TRUE;
650
651   if (redraw)
652     DrawGadget(gi, DG_UNPRESSED, DG_BUFFERED);
653 }
654
655 void MapGadget(struct GadgetInfo *gi)
656 {
657   MapGadgetExt(gi, TRUE);
658 }
659
660 void UnmapGadget(struct GadgetInfo *gi)
661 {
662   if (gi == NULL || !gi->mapped)
663     return;
664
665   gi->mapped = FALSE;
666
667   if (gi == last_gi)
668     last_gi = NULL;
669 }
670
671 #define MAX_NUM_GADGETS         1024
672 #define MULTIMAP_UNMAP          (1 << 0)
673 #define MULTIMAP_REMAP          (1 << 1)
674 #define MULTIMAP_REDRAW         (1 << 2)
675 #define MULTIMAP_PLAYFIELD      (1 << 3)
676 #define MULTIMAP_DOOR_1         (1 << 4)
677 #define MULTIMAP_DOOR_2         (1 << 5)
678 #define MULTIMAP_ALL            (MULTIMAP_PLAYFIELD | \
679                                  MULTIMAP_DOOR_1 | \
680                                  MULTIMAP_DOOR_2)
681
682 static void MultiMapGadgets(int mode)
683 {
684   struct GadgetInfo *gi = gadget_list_first_entry;
685   static boolean map_state[MAX_NUM_GADGETS];
686   int map_count = 0;
687
688   while (gi)
689   {
690     if ((mode & MULTIMAP_PLAYFIELD &&
691          gi->x < gfx.sx + gfx.sxsize) ||
692         (mode & MULTIMAP_DOOR_1 &&
693          gi->x >= gfx.dx && gi->y < gfx.dy + gfx.dysize) ||
694         (mode & MULTIMAP_DOOR_2 &&
695          gi->x >= gfx.dx && gi->y > gfx.dy + gfx.dysize) ||
696         (mode & MULTIMAP_ALL) == MULTIMAP_ALL)
697     {
698       if (mode & MULTIMAP_UNMAP)
699       {
700         map_state[map_count++ % MAX_NUM_GADGETS] = gi->mapped;
701         UnmapGadget(gi);
702       }
703       else
704       {
705         if (map_state[map_count++ % MAX_NUM_GADGETS])
706           MapGadgetExt(gi, (mode & MULTIMAP_REDRAW));
707       }
708     }
709
710     gi = gi->next;
711   }
712 }
713
714 void UnmapAllGadgets()
715 {
716   MultiMapGadgets(MULTIMAP_ALL | MULTIMAP_UNMAP);
717 }
718
719 void RemapAllGadgets()
720 {
721   MultiMapGadgets(MULTIMAP_ALL | MULTIMAP_REMAP);
722 }
723
724 boolean anyTextGadgetActive()
725 {
726   return (last_gi && last_gi->type & GD_TYPE_TEXTINPUT && last_gi->mapped);
727 }
728
729 void ClickOnGadget(struct GadgetInfo *gi, int button)
730 {
731   /* simulate releasing mouse button over last gadget, if still pressed */
732   if (button_status)
733     HandleGadgets(-1, -1, 0);
734
735   /* simulate pressing mouse button over specified gadget */
736   HandleGadgets(gi->x, gi->y, button);
737
738   /* simulate releasing mouse button over specified gadget */
739   HandleGadgets(gi->x, gi->y, 0);
740 }
741
742 void HandleGadgets(int mx, int my, int button)
743 {
744   static struct GadgetInfo *last_info_gi = NULL;
745   static unsigned long pressed_delay = 0;
746   static int last_button = 0;
747   static int last_mx = 0, last_my = 0;
748   int scrollbar_mouse_pos = 0;
749   struct GadgetInfo *new_gi, *gi;
750   boolean press_event;
751   boolean release_event;
752   boolean mouse_moving;
753   boolean gadget_pressed;
754   boolean gadget_pressed_repeated;
755   boolean gadget_moving;
756   boolean gadget_moving_inside;
757   boolean gadget_moving_off_borders;
758   boolean gadget_released;
759   boolean gadget_released_inside;
760   boolean gadget_released_off_borders;
761   boolean changed_position = FALSE;
762
763   /* check if there are any gadgets defined */
764   if (gadget_list_first_entry == NULL)
765     return;
766
767   /* check which gadget is under the mouse pointer */
768   new_gi = getGadgetInfoFromMousePosition(mx, my);
769
770   /* check if button state has changed since last invocation */
771   press_event = (button != 0 && last_button == 0);
772   release_event = (button == 0 && last_button != 0);
773   last_button = button;
774
775   /* check if mouse has been moved since last invocation */
776   mouse_moving = ((mx != last_mx || my != last_my) && motion_status);
777   last_mx = mx;
778   last_my = my;
779
780   /* special treatment for text and number input gadgets */
781   if (anyTextGadgetActive() && button != 0 && !motion_status)
782   {
783     struct GadgetInfo *gi = last_gi;
784
785     if (new_gi == last_gi)
786     {
787       /* if mouse button pressed inside activated text gadget, set cursor */
788       gi->text.cursor_position =
789         (mx - gi->x - gi->border.size) /
790         getFontWidth(FS_SMALL, gi->text.font_type);
791
792       if (gi->text.cursor_position < 0)
793         gi->text.cursor_position = 0;
794       else if (gi->text.cursor_position > strlen(gi->text.value))
795         gi->text.cursor_position = strlen(gi->text.value);
796
797       DrawGadget(gi, DG_PRESSED, DG_DIRECT);
798     }
799     else
800     {
801       /* if mouse button pressed outside text input gadget, deactivate it */
802       CheckRangeOfNumericInputGadget(gi);
803       DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
804
805       gi->event.type = GD_EVENT_TEXT_LEAVING;
806
807       if (gi->event_mask & GD_EVENT_TEXT_LEAVING)
808         gi->callback_action(gi);
809
810       last_gi = NULL;
811     }
812   }
813
814   gadget_pressed =
815     (button != 0 && last_gi == NULL && new_gi != NULL && press_event);
816   gadget_pressed_repeated =
817     (button != 0 && last_gi != NULL && new_gi == last_gi);
818
819   gadget_released =             (release_event && last_gi != NULL);
820   gadget_released_inside =      (gadget_released && new_gi == last_gi);
821   gadget_released_off_borders = (gadget_released && new_gi != last_gi);
822
823   gadget_moving =             (button != 0 && last_gi != NULL && mouse_moving);
824   gadget_moving_inside =      (gadget_moving && new_gi == last_gi);
825   gadget_moving_off_borders = (gadget_moving && new_gi != last_gi);
826
827   /* if new gadget pressed, store this gadget  */
828   if (gadget_pressed)
829     last_gi = new_gi;
830
831   /* 'gi' is actually handled gadget */
832   gi = last_gi;
833
834   /* if gadget is scrollbar, choose mouse position value */
835   if (gi && gi->type & GD_TYPE_SCROLLBAR)
836     scrollbar_mouse_pos =
837       (gi->type == GD_TYPE_SCROLLBAR_HORIZONTAL ? mx - gi->x : my - gi->y);
838
839   /* if mouse button released, no gadget needs to be handled anymore */
840   if (button == 0 && last_gi && !(last_gi->type & GD_TYPE_TEXTINPUT))
841     last_gi = NULL;
842
843   /* modify event position values even if no gadget is pressed */
844   if (button == 0 && !release_event)
845     gi = new_gi;
846
847   if (gi)
848   {
849     int last_x = gi->event.x;
850     int last_y = gi->event.y;
851
852     gi->event.x = mx - gi->x;
853     gi->event.y = my - gi->y;
854
855     if (gi->type == GD_TYPE_DRAWING_AREA)
856     {
857       gi->event.x /= gi->drawing.item_xsize;
858       gi->event.y /= gi->drawing.item_ysize;
859
860       if (last_x != gi->event.x || last_y != gi->event.y)
861         changed_position = TRUE;
862     }
863   }
864
865   /* handle gadget popup info text */
866   if (last_info_gi != new_gi ||
867       (new_gi && new_gi->type == GD_TYPE_DRAWING_AREA && changed_position))
868   {
869     if (new_gi != NULL && (button == 0 || new_gi == last_gi))
870     {
871       new_gi->event.type = GD_EVENT_INFO_ENTERING;
872       new_gi->callback_info(new_gi);
873     }
874     else if (last_info_gi != NULL)
875     {
876       last_info_gi->event.type = GD_EVENT_INFO_LEAVING;
877       last_info_gi->callback_info(last_info_gi);
878
879 #if 0
880       default_callback_info(NULL);
881
882       printf("It seems that we are leaving gadget [%s]!\n",
883              (last_info_gi != NULL &&
884               last_info_gi->info_text != NULL ?
885               last_info_gi->info_text : ""));
886 #endif
887     }
888
889     last_info_gi = new_gi;
890   }
891
892   if (gadget_pressed)
893   {
894     if (gi->type == GD_TYPE_CHECK_BUTTON)
895     {
896       gi->checked = !gi->checked;
897     }
898     else if (gi->type == GD_TYPE_RADIO_BUTTON)
899     {
900       struct GadgetInfo *rgi = gadget_list_first_entry;
901
902       while (rgi)
903       {
904         if (rgi->mapped &&
905             rgi->type == GD_TYPE_RADIO_BUTTON &&
906             rgi->radio_nr == gi->radio_nr &&
907             rgi != gi)
908         {
909           rgi->checked = FALSE;
910           DrawGadget(rgi, DG_UNPRESSED, DG_DIRECT);
911         }
912
913         rgi = rgi->next;
914       }
915
916       gi->checked = TRUE;
917     }
918     else if (gi->type & GD_TYPE_SCROLLBAR)
919     {
920       int mpos, gpos;
921
922       if (gi->type == GD_TYPE_SCROLLBAR_HORIZONTAL)
923       {
924         mpos = mx;
925         gpos = gi->x;
926       }
927       else
928       {
929         mpos = my;
930         gpos = gi->y;
931       }
932
933       if (mpos >= gpos + gi->scrollbar.position &&
934           mpos < gpos + gi->scrollbar.position + gi->scrollbar.size)
935       {
936         /* drag scrollbar */
937         gi->scrollbar.drag_position =
938           scrollbar_mouse_pos - gi->scrollbar.position;
939       }
940       else
941       {
942         /* click scrollbar one scrollbar length up/left or down/right */
943
944         struct GadgetScrollbar *gs = &gi->scrollbar;
945         int old_item_position = gs->item_position;
946
947         changed_position = FALSE;
948
949         gs->item_position +=
950           gs->items_visible * (mpos < gpos + gi->scrollbar.position ? -1 : +1);
951
952         if (gs->item_position < 0)
953           gs->item_position = 0;
954         if (gs->item_position > gs->items_max - gs->items_visible)
955           gs->item_position = gs->items_max - gs->items_visible;
956
957         if (old_item_position != gs->item_position)
958         {
959           gi->event.item_position = gs->item_position;
960           changed_position = TRUE;
961         }
962
963         ModifyGadget(gi, GDI_SCROLLBAR_ITEM_POSITION, gs->item_position,
964                      GDI_END);
965
966         gi->state = GD_BUTTON_UNPRESSED;
967         gi->event.type = GD_EVENT_MOVING;
968         gi->event.off_borders = FALSE;
969
970         if (gi->event_mask & GD_EVENT_MOVING && changed_position)
971           gi->callback_action(gi);
972
973         /* don't handle this scrollbar anymore while mouse button pressed */
974         last_gi = NULL;
975
976         return;
977       }
978     }
979
980     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
981
982     gi->state = GD_BUTTON_PRESSED;
983     gi->event.type = GD_EVENT_PRESSED;
984     gi->event.button = button;
985     gi->event.off_borders = FALSE;
986
987     /* initialize delay counter */
988     DelayReached(&pressed_delay, 0);
989
990     if (gi->event_mask & GD_EVENT_PRESSED)
991       gi->callback_action(gi);
992   }
993
994   if (gadget_pressed_repeated)
995   {
996     gi->event.type = GD_EVENT_PRESSED;
997
998     if (gi->event_mask & GD_EVENT_REPEATED &&
999         DelayReached(&pressed_delay, GADGET_FRAME_DELAY))
1000       gi->callback_action(gi);
1001   }
1002
1003   if (gadget_moving)
1004   {
1005     if (gi->type & GD_TYPE_BUTTON)
1006     {
1007       if (gadget_moving_inside && gi->state == GD_BUTTON_UNPRESSED)
1008         DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1009       else if (gadget_moving_off_borders && gi->state == GD_BUTTON_PRESSED)
1010         DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1011     }
1012
1013     if (gi->type & GD_TYPE_SCROLLBAR)
1014     {
1015       struct GadgetScrollbar *gs = &gi->scrollbar;
1016       int old_item_position = gs->item_position;
1017
1018       gs->position = scrollbar_mouse_pos - gs->drag_position;
1019
1020       if (gs->position < 0)
1021         gs->position = 0;
1022       if (gs->position > gs->position_max)
1023         gs->position = gs->position_max;
1024
1025       gs->item_position =
1026         gs->items_max * (gs->position + gs->correction) / gs->size_max;
1027
1028       if (gs->item_position < 0)
1029         gs->item_position = 0;
1030       if (gs->item_position > gs->items_max - 1)
1031         gs->item_position = gs->items_max - 1;
1032
1033       if (old_item_position != gs->item_position)
1034       {
1035         gi->event.item_position = gs->item_position;
1036         changed_position = TRUE;
1037       }
1038
1039       DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1040     }
1041
1042     gi->state = (gadget_moving_inside || gi->type & GD_TYPE_SCROLLBAR ?
1043                  GD_BUTTON_PRESSED : GD_BUTTON_UNPRESSED);
1044     gi->event.type = GD_EVENT_MOVING;
1045     gi->event.off_borders = gadget_moving_off_borders;
1046
1047     if (gi->event_mask & GD_EVENT_MOVING && changed_position &&
1048         (gadget_moving_inside || gi->event_mask & GD_EVENT_OFF_BORDERS))
1049       gi->callback_action(gi);
1050   }
1051
1052   if (gadget_released_inside)
1053   {
1054     if (!(gi->type & GD_TYPE_TEXTINPUT))
1055       DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1056
1057     gi->state = GD_BUTTON_UNPRESSED;
1058     gi->event.type = GD_EVENT_RELEASED;
1059
1060     if (gi->event_mask & GD_EVENT_RELEASED)
1061       gi->callback_action(gi);
1062   }
1063
1064   if (gadget_released_off_borders)
1065   {
1066     if (gi->type & GD_TYPE_SCROLLBAR)
1067       DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1068
1069     gi->event.type = GD_EVENT_RELEASED;
1070
1071     if (gi->event_mask & GD_EVENT_RELEASED &&
1072         gi->event_mask & GD_EVENT_OFF_BORDERS)
1073       gi->callback_action(gi);
1074   }
1075 }
1076
1077 void HandleGadgetsKeyInput(Key key)
1078 {
1079   struct GadgetInfo *gi = last_gi;
1080   char text[MAX_GADGET_TEXTSIZE];
1081   int text_length;
1082   int cursor_pos;
1083   char letter;
1084   boolean legal_letter;
1085
1086   if (gi == NULL || !(gi->type & GD_TYPE_TEXTINPUT) || !gi->mapped)
1087     return;
1088
1089   text_length = strlen(gi->text.value);
1090   cursor_pos = gi->text.cursor_position;
1091   letter = getCharFromKey(key);
1092   legal_letter = (gi->type == GD_TYPE_TEXTINPUT_NUMERIC ?
1093                   letter >= '0' && letter <= '9' :
1094                   letter != 0);
1095
1096   if (legal_letter && text_length < gi->text.size)
1097   {
1098     strcpy(text, gi->text.value);
1099     strcpy(&gi->text.value[cursor_pos + 1], &text[cursor_pos]);
1100     gi->text.value[cursor_pos] = letter;
1101     gi->text.cursor_position++;
1102     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1103   }
1104   else if (key == KSYM_Left && cursor_pos > 0)
1105   {
1106     gi->text.cursor_position--;
1107     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1108   }
1109   else if (key == KSYM_Right && cursor_pos < text_length)
1110   {
1111     gi->text.cursor_position++;
1112     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1113   }
1114   else if (key == KSYM_BackSpace && cursor_pos > 0)
1115   {
1116     strcpy(text, gi->text.value);
1117     strcpy(&gi->text.value[cursor_pos - 1], &text[cursor_pos]);
1118     gi->text.cursor_position--;
1119     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1120   }
1121   else if (key == KSYM_Delete && cursor_pos < text_length)
1122   {
1123     strcpy(text, gi->text.value);
1124     strcpy(&gi->text.value[cursor_pos], &text[cursor_pos + 1]);
1125     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1126   }
1127   else if (key == KSYM_Return)
1128   {
1129     CheckRangeOfNumericInputGadget(gi);
1130     DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1131
1132     gi->event.type = GD_EVENT_TEXT_RETURN;
1133
1134     if (gi->event_mask & GD_EVENT_TEXT_RETURN)
1135       gi->callback_action(gi);
1136
1137     last_gi = NULL;
1138   }
1139 }