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