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