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