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