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