rnd-20020321-1-src
[rocksndiamonds.git] / src / libgame / gadgets.c
1 /***********************************************************
2 * Artsoft Retro-Game Library                               *
3 *----------------------------------------------------------*
4 * (c) 1994-2001 Artsoft Entertainment                      *
5 *               Holger Schemel                             *
6 *               Detmolder Strasse 189                      *
7 *               33604 Bielefeld                            *
8 *               Germany                                    *
9 *               e-mail: info@artsoft.org                   *
10 *----------------------------------------------------------*
11 * gadgets.c                                                *
12 ***********************************************************/
13
14 #include <stdarg.h>
15 #include <string.h>
16
17 #include "gadgets.h"
18 #include "text.h"
19 #include "misc.h"
20
21
22 /* values for DrawGadget() */
23 #define DG_UNPRESSED            0
24 #define DG_PRESSED              1
25 #define DG_BUFFERED             0
26 #define DG_DIRECT               1
27
28 static struct GadgetInfo *gadget_list_first_entry = NULL;
29 static struct GadgetInfo *gadget_list_last_entry = NULL;
30 static int next_free_gadget_id = 1;
31 static boolean gadget_id_wrapped = FALSE;
32
33 static struct GadgetInfo *getGadgetInfoFromGadgetID(int id)
34 {
35   struct GadgetInfo *gi = gadget_list_first_entry;
36
37   while (gi && gi->id != id)
38     gi = gi->next;
39
40   return gi;
41 }
42
43 static int getNewGadgetID()
44 {
45   int id = next_free_gadget_id++;
46
47   if (next_free_gadget_id <= 0)         /* counter overrun */
48   {
49     gadget_id_wrapped = TRUE;           /* now we must check each ID */
50     next_free_gadget_id = 0;
51   }
52
53   if (gadget_id_wrapped)
54   {
55     next_free_gadget_id++;
56     while (getGadgetInfoFromGadgetID(next_free_gadget_id) != NULL)
57       next_free_gadget_id++;
58   }
59
60   if (next_free_gadget_id <= 0)         /* cannot get new gadget id */
61     Error(ERR_EXIT, "too much gadgets -- this should not happen");
62
63   return id;
64 }
65
66 static struct GadgetInfo *getGadgetInfoFromMousePosition(int mx, int my)
67 {
68   struct GadgetInfo *gi = gadget_list_first_entry;
69
70   while (gi)
71   {
72     if (gi->mapped &&
73         mx >= gi->x && mx < gi->x + gi->width &&
74         my >= gi->y && my < gi->y + gi->height)
75         break;
76
77     gi = gi->next;
78   }
79
80   return gi;
81 }
82
83 static void default_callback_info(void *ptr)
84 {
85 #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         /* take care here: "boolean" is typedef'ed as "unsigned char",
313            which gets promoted to "int" */
314         gi->checked = (boolean)va_arg(ap, int);
315         break;
316
317       case GDI_RADIO_NR:
318         gi->radio_nr = va_arg(ap, unsigned long);
319         break;
320
321       case GDI_NUMBER_VALUE:
322         gi->text.number_value = va_arg(ap, long);
323         sprintf(gi->text.value, "%d", gi->text.number_value);
324         gi->text.cursor_position = strlen(gi->text.value);
325         break;
326
327       case GDI_NUMBER_MIN:
328         gi->text.number_min = va_arg(ap, long);
329         if (gi->text.number_value < gi->text.number_min)
330         {
331           gi->text.number_value = gi->text.number_min;
332           sprintf(gi->text.value, "%d", gi->text.number_value);
333         }
334         break;
335
336       case GDI_NUMBER_MAX:
337         gi->text.number_max = va_arg(ap, long);
338         if (gi->text.number_value > gi->text.number_max)
339         {
340           gi->text.number_value = gi->text.number_max;
341           sprintf(gi->text.value, "%d", gi->text.number_value);
342         }
343         break;
344
345       case GDI_TEXT_VALUE:
346         {
347           int max_textsize = MAX_GADGET_TEXTSIZE;
348
349           if (gi->text.size)
350             max_textsize = MIN(gi->text.size, MAX_GADGET_TEXTSIZE - 1);
351
352           strncpy(gi->text.value, va_arg(ap, char *), max_textsize);
353           gi->text.value[max_textsize] = '\0';
354           gi->text.cursor_position = strlen(gi->text.value);
355         }
356         break;
357
358       case GDI_TEXT_SIZE:
359         {
360           int tag_value = va_arg(ap, int);
361           int max_textsize = MIN(tag_value, MAX_GADGET_TEXTSIZE - 1);
362
363           gi->text.size = max_textsize;
364           gi->text.value[max_textsize] = '\0';
365         }
366         break;
367
368       case GDI_TEXT_FONT:
369         gi->text.font_type = va_arg(ap, int);
370         break;
371
372       case GDI_DESIGN_UNPRESSED:
373         gi->design[GD_BUTTON_UNPRESSED].bitmap = va_arg(ap, Bitmap *);
374         gi->design[GD_BUTTON_UNPRESSED].x = va_arg(ap, int);
375         gi->design[GD_BUTTON_UNPRESSED].y = va_arg(ap, int);
376         break;
377
378       case GDI_DESIGN_PRESSED:
379         gi->design[GD_BUTTON_PRESSED].bitmap = va_arg(ap, Bitmap *);
380         gi->design[GD_BUTTON_PRESSED].x = va_arg(ap, int);
381         gi->design[GD_BUTTON_PRESSED].y = va_arg(ap, int);
382         break;
383
384       case GDI_ALT_DESIGN_UNPRESSED:
385         gi->alt_design[GD_BUTTON_UNPRESSED].bitmap= va_arg(ap, Bitmap *);
386         gi->alt_design[GD_BUTTON_UNPRESSED].x = va_arg(ap, int);
387         gi->alt_design[GD_BUTTON_UNPRESSED].y = va_arg(ap, int);
388         break;
389
390       case GDI_ALT_DESIGN_PRESSED:
391         gi->alt_design[GD_BUTTON_PRESSED].bitmap = va_arg(ap, Bitmap *);
392         gi->alt_design[GD_BUTTON_PRESSED].x = va_arg(ap, int);
393         gi->alt_design[GD_BUTTON_PRESSED].y = va_arg(ap, int);
394         break;
395
396       case GDI_BORDER_SIZE:
397         gi->border.size = va_arg(ap, int);
398         break;
399
400       case GDI_TEXTINPUT_DESIGN_WIDTH:
401         gi->border.width = va_arg(ap, int);
402         break;
403
404       case GDI_DECORATION_DESIGN:
405         gi->deco.design.bitmap = va_arg(ap, Bitmap *);
406         gi->deco.design.x = va_arg(ap, int);
407         gi->deco.design.y = va_arg(ap, int);
408         break;
409
410       case GDI_DECORATION_POSITION:
411         gi->deco.x = va_arg(ap, int);
412         gi->deco.y = va_arg(ap, int);
413         break;
414
415       case GDI_DECORATION_SIZE:
416         gi->deco.width = va_arg(ap, int);
417         gi->deco.height = va_arg(ap, int);
418         break;
419
420       case GDI_DECORATION_SHIFTING:
421         gi->deco.xshift = va_arg(ap, int);
422         gi->deco.yshift = va_arg(ap, int);
423         break;
424
425       case GDI_EVENT_MASK:
426         gi->event_mask = va_arg(ap, unsigned long);
427         break;
428
429       case GDI_AREA_SIZE:
430         gi->drawing.area_xsize = va_arg(ap, int);
431         gi->drawing.area_ysize = va_arg(ap, int);
432
433         /* determine dependent values for drawing area gadget, if needed */
434         if (gi->width == 0 && gi->height == 0 &&
435             gi->drawing.item_xsize !=0 && gi->drawing.item_ysize !=0)
436         {
437           gi->width = gi->drawing.area_xsize * gi->drawing.item_xsize;
438           gi->height = gi->drawing.area_ysize * gi->drawing.item_ysize;
439         }
440         else if (gi->drawing.item_xsize == 0 && gi->drawing.item_ysize == 0 &&
441                  gi->width != 0 && gi->height != 0)
442         {
443           gi->drawing.item_xsize = gi->width / gi->drawing.area_xsize;
444           gi->drawing.item_ysize = gi->height / gi->drawing.area_ysize;
445         }
446         break;
447
448       case GDI_ITEM_SIZE:
449         gi->drawing.item_xsize = va_arg(ap, int);
450         gi->drawing.item_ysize = va_arg(ap, int);
451
452         /* determine dependent values for drawing area gadget, if needed */
453         if (gi->width == 0 && gi->height == 0 &&
454             gi->drawing.area_xsize !=0 && gi->drawing.area_ysize !=0)
455         {
456           gi->width = gi->drawing.area_xsize * gi->drawing.item_xsize;
457           gi->height = gi->drawing.area_ysize * gi->drawing.item_ysize;
458         }
459         else if (gi->drawing.area_xsize == 0 && gi->drawing.area_ysize == 0 &&
460                  gi->width != 0 && gi->height != 0)
461         {
462           gi->drawing.area_xsize = gi->width / gi->drawing.item_xsize;
463           gi->drawing.area_ysize = gi->height / gi->drawing.item_ysize;
464         }
465         break;
466
467       case GDI_SCROLLBAR_ITEMS_MAX:
468         gi->scrollbar.items_max = va_arg(ap, int);
469         break;
470
471       case GDI_SCROLLBAR_ITEMS_VISIBLE:
472         gi->scrollbar.items_visible = va_arg(ap, int);
473         break;
474
475       case GDI_SCROLLBAR_ITEM_POSITION:
476         gi->scrollbar.item_position = va_arg(ap, int);
477         break;
478
479       case GDI_CALLBACK_INFO:
480         gi->callback_info = va_arg(ap, gadget_function);
481         break;
482
483       case GDI_CALLBACK_ACTION:
484         gi->callback_action = va_arg(ap, gadget_function);
485         break;
486
487       default:
488         Error(ERR_EXIT, "HandleGadgetTags(): unknown tag %d", tag);
489     }
490
491     tag = va_arg(ap, int);      /* read next tag */
492   }
493
494   /* check if gadget complete */
495   if (gi->type != GD_TYPE_DRAWING_AREA &&
496       (!gi->design[GD_BUTTON_UNPRESSED].bitmap ||
497        !gi->design[GD_BUTTON_PRESSED].bitmap))
498     Error(ERR_EXIT, "gadget incomplete (missing Bitmap)");
499
500   /* adjust gadget values in relation to other gadget values */
501
502   if (gi->type & GD_TYPE_TEXTINPUT)
503   {
504     int font_width = getFontWidth(FS_SMALL, gi->text.font_type);
505     int font_height = getFontHeight(FS_SMALL, gi->text.font_type);
506
507     gi->width = 2 * gi->border.size + (gi->text.size + 1) * font_width;
508     gi->height = 2 * gi->border.size + font_height;
509   }
510
511   if (gi->type & GD_TYPE_TEXTINPUT_NUMERIC)
512   {
513     struct GadgetTextInput *text = &gi->text;
514     int value = text->number_value;
515
516     text->number_value = (value < text->number_min ? text->number_min :
517                           value > text->number_max ? text->number_max :
518                           value);
519
520     sprintf(text->value, "%d", text->number_value);
521   }
522
523   if (gi->type & GD_TYPE_SCROLLBAR)
524   {
525     struct GadgetScrollbar *gs = &gi->scrollbar;
526
527     if (gi->width == 0 || gi->height == 0 ||
528         gs->items_max == 0 || gs->items_visible == 0)
529       Error(ERR_EXIT, "scrollbar gadget incomplete (missing tags)");
530
531     /* calculate internal scrollbar values */
532     gs->size_max = (gi->type == GD_TYPE_SCROLLBAR_VERTICAL ?
533                     gi->height : gi->width);
534     gs->size = gs->size_max * gs->items_visible / gs->items_max;
535     gs->position = gs->size_max * gs->item_position / gs->items_max;
536     gs->position_max = gs->size_max - gs->size;
537     gs->correction = gs->size_max / gs->items_max / 2;
538
539     /* finetuning for maximal right/bottom position */
540     if (gs->item_position == gs->items_max - gs->items_visible)
541       gs->position = gs->position_max;
542   }
543 }
544
545 void ModifyGadget(struct GadgetInfo *gi, int first_tag, ...)
546 {
547   va_list ap;
548
549   va_start(ap, first_tag);
550   HandleGadgetTags(gi, first_tag, ap);
551   va_end(ap);
552
553   RedrawGadget(gi);
554 }
555
556 void RedrawGadget(struct GadgetInfo *gi)
557 {
558   if (gi->mapped)
559     DrawGadget(gi, gi->state, DG_DIRECT);
560 }
561
562 struct GadgetInfo *CreateGadget(int first_tag, ...)
563 {
564   struct GadgetInfo *new_gadget = checked_malloc(sizeof(struct GadgetInfo));
565   va_list ap;
566
567   /* always start with reliable default values */
568   memset(new_gadget, 0, sizeof(struct GadgetInfo));     /* zero all fields */
569   new_gadget->id = getNewGadgetID();
570   new_gadget->callback_info = default_callback_info;
571   new_gadget->callback_action = default_callback_action;
572
573   va_start(ap, first_tag);
574   HandleGadgetTags(new_gadget, first_tag, ap);
575   va_end(ap);
576
577   /* insert new gadget into global gadget list */
578   if (gadget_list_last_entry)
579   {
580     gadget_list_last_entry->next = new_gadget;
581     gadget_list_last_entry = gadget_list_last_entry->next;
582   }
583   else
584     gadget_list_first_entry = gadget_list_last_entry = new_gadget;
585
586   return new_gadget;
587 }
588
589 void FreeGadget(struct GadgetInfo *gi)
590 {
591   struct GadgetInfo *gi_previous = gadget_list_first_entry;
592
593   while (gi_previous && gi_previous->next != gi)
594     gi_previous = gi_previous->next;
595
596   if (gi == gadget_list_first_entry)
597     gadget_list_first_entry = gi->next;
598
599   if (gi == gadget_list_last_entry)
600     gadget_list_last_entry = gi_previous;
601
602   gi_previous->next = gi->next;
603   free(gi);
604 }
605
606 static void CheckRangeOfNumericInputGadget(struct GadgetInfo *gi)
607 {
608   if (gi->type != GD_TYPE_TEXTINPUT_NUMERIC)
609     return;
610
611   gi->text.number_value = atoi(gi->text.value);
612
613   if (gi->text.number_value < gi->text.number_min)
614     gi->text.number_value = gi->text.number_min;
615   if (gi->text.number_value > gi->text.number_max)
616     gi->text.number_value = gi->text.number_max;
617
618   sprintf(gi->text.value, "%d", gi->text.number_value);
619
620   if (gi->text.cursor_position < 0)
621     gi->text.cursor_position = 0;
622   else if (gi->text.cursor_position > strlen(gi->text.value))
623     gi->text.cursor_position = strlen(gi->text.value);
624 }
625
626 /* global pointer to gadget actually in use (when mouse button pressed) */
627 static struct GadgetInfo *last_gi = NULL;
628
629 static void MapGadgetExt(struct GadgetInfo *gi, boolean redraw)
630 {
631   if (gi == NULL || gi->mapped)
632     return;
633
634   gi->mapped = TRUE;
635
636   if (redraw)
637     DrawGadget(gi, DG_UNPRESSED, DG_BUFFERED);
638 }
639
640 void MapGadget(struct GadgetInfo *gi)
641 {
642   MapGadgetExt(gi, TRUE);
643 }
644
645 void UnmapGadget(struct GadgetInfo *gi)
646 {
647   if (gi == NULL || !gi->mapped)
648     return;
649
650   gi->mapped = FALSE;
651
652   if (gi == last_gi)
653     last_gi = NULL;
654 }
655
656 #define MAX_NUM_GADGETS         1024
657 #define MULTIMAP_UNMAP          (1 << 0)
658 #define MULTIMAP_REMAP          (1 << 1)
659 #define MULTIMAP_REDRAW         (1 << 2)
660 #define MULTIMAP_PLAYFIELD      (1 << 3)
661 #define MULTIMAP_DOOR_1         (1 << 4)
662 #define MULTIMAP_DOOR_2         (1 << 5)
663 #define MULTIMAP_ALL            (MULTIMAP_PLAYFIELD | \
664                                  MULTIMAP_DOOR_1 | \
665                                  MULTIMAP_DOOR_2)
666
667 static void MultiMapGadgets(int mode)
668 {
669   struct GadgetInfo *gi = gadget_list_first_entry;
670   static boolean map_state[MAX_NUM_GADGETS];
671   int map_count = 0;
672
673   while (gi)
674   {
675     if ((mode & MULTIMAP_PLAYFIELD &&
676          gi->x < gfx.sx + gfx.sxsize) ||
677         (mode & MULTIMAP_DOOR_1 &&
678          gi->x >= gfx.dx && gi->y < gfx.dy + gfx.dysize) ||
679         (mode & MULTIMAP_DOOR_2 &&
680          gi->x >= gfx.dx && gi->y > gfx.dy + gfx.dysize) ||
681         (mode & MULTIMAP_ALL) == MULTIMAP_ALL)
682     {
683       if (mode & MULTIMAP_UNMAP)
684       {
685         map_state[map_count++ % MAX_NUM_GADGETS] = gi->mapped;
686         UnmapGadget(gi);
687       }
688       else
689       {
690         if (map_state[map_count++ % MAX_NUM_GADGETS])
691           MapGadgetExt(gi, (mode & MULTIMAP_REDRAW));
692       }
693     }
694
695     gi = gi->next;
696   }
697 }
698
699 void UnmapAllGadgets()
700 {
701   MultiMapGadgets(MULTIMAP_ALL | MULTIMAP_UNMAP);
702 }
703
704 void RemapAllGadgets()
705 {
706   MultiMapGadgets(MULTIMAP_ALL | MULTIMAP_REMAP);
707 }
708
709 boolean anyTextGadgetActive()
710 {
711   return (last_gi && last_gi->type & GD_TYPE_TEXTINPUT && last_gi->mapped);
712 }
713
714 void ClickOnGadget(struct GadgetInfo *gi, int button)
715 {
716   /* simulate releasing mouse button over last gadget, if still pressed */
717   if (button_status)
718     HandleGadgets(-1, -1, 0);
719
720   /* simulate pressing mouse button over specified gadget */
721   HandleGadgets(gi->x, gi->y, button);
722
723   /* simulate releasing mouse button over specified gadget */
724   HandleGadgets(gi->x, gi->y, 0);
725 }
726
727 void HandleGadgets(int mx, int my, int button)
728 {
729   static struct GadgetInfo *last_info_gi = NULL;
730   static unsigned long pressed_delay = 0;
731   static int last_button = 0;
732   static int last_mx = 0, last_my = 0;
733   int scrollbar_mouse_pos = 0;
734   struct GadgetInfo *new_gi, *gi;
735   boolean press_event;
736   boolean release_event;
737   boolean mouse_moving;
738   boolean gadget_pressed;
739   boolean gadget_pressed_repeated;
740   boolean gadget_moving;
741   boolean gadget_moving_inside;
742   boolean gadget_moving_off_borders;
743   boolean gadget_released;
744   boolean gadget_released_inside;
745   boolean gadget_released_off_borders;
746   boolean changed_position = FALSE;
747
748   /* check if there are any gadgets defined */
749   if (gadget_list_first_entry == NULL)
750     return;
751
752   /* check which gadget is under the mouse pointer */
753   new_gi = getGadgetInfoFromMousePosition(mx, my);
754
755   /* check if button state has changed since last invocation */
756   press_event = (button != 0 && last_button == 0);
757   release_event = (button == 0 && last_button != 0);
758   last_button = button;
759
760   /* check if mouse has been moved since last invocation */
761   mouse_moving = ((mx != last_mx || my != last_my) && motion_status);
762   last_mx = mx;
763   last_my = my;
764
765   /* special treatment for text and number input gadgets */
766   if (anyTextGadgetActive() && button != 0 && !motion_status)
767   {
768     struct GadgetInfo *gi = last_gi;
769
770     if (new_gi == last_gi)
771     {
772       /* if mouse button pressed inside activated text gadget, set cursor */
773       gi->text.cursor_position =
774         (mx - gi->x - gi->border.size) /
775         getFontWidth(FS_SMALL, gi->text.font_type);
776
777       if (gi->text.cursor_position < 0)
778         gi->text.cursor_position = 0;
779       else if (gi->text.cursor_position > strlen(gi->text.value))
780         gi->text.cursor_position = strlen(gi->text.value);
781
782       DrawGadget(gi, DG_PRESSED, DG_DIRECT);
783     }
784     else
785     {
786       /* if mouse button pressed outside text input gadget, deactivate it */
787       CheckRangeOfNumericInputGadget(gi);
788       DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
789
790       gi->event.type = GD_EVENT_TEXT_LEAVING;
791
792       if (gi->event_mask & GD_EVENT_TEXT_LEAVING)
793         gi->callback_action(gi);
794
795       last_gi = NULL;
796     }
797   }
798
799   gadget_pressed =
800     (button != 0 && last_gi == NULL && new_gi != NULL && press_event);
801   gadget_pressed_repeated =
802     (button != 0 && last_gi != NULL && new_gi == last_gi);
803
804   gadget_released =             (release_event && last_gi != NULL);
805   gadget_released_inside =      (gadget_released && new_gi == last_gi);
806   gadget_released_off_borders = (gadget_released && new_gi != last_gi);
807
808   gadget_moving =             (button != 0 && last_gi != NULL && mouse_moving);
809   gadget_moving_inside =      (gadget_moving && new_gi == last_gi);
810   gadget_moving_off_borders = (gadget_moving && new_gi != last_gi);
811
812   /* if new gadget pressed, store this gadget  */
813   if (gadget_pressed)
814     last_gi = new_gi;
815
816   /* 'gi' is actually handled gadget */
817   gi = last_gi;
818
819   /* if gadget is scrollbar, choose mouse position value */
820   if (gi && gi->type & GD_TYPE_SCROLLBAR)
821     scrollbar_mouse_pos =
822       (gi->type == GD_TYPE_SCROLLBAR_HORIZONTAL ? mx - gi->x : my - gi->y);
823
824   /* if mouse button released, no gadget needs to be handled anymore */
825   if (button == 0 && last_gi && !(last_gi->type & GD_TYPE_TEXTINPUT))
826     last_gi = NULL;
827
828   /* modify event position values even if no gadget is pressed */
829   if (button == 0 && !release_event)
830     gi = new_gi;
831
832   if (gi)
833   {
834     int last_x = gi->event.x;
835     int last_y = gi->event.y;
836
837     gi->event.x = mx - gi->x;
838     gi->event.y = my - gi->y;
839
840     if (gi->type == GD_TYPE_DRAWING_AREA)
841     {
842       gi->event.x /= gi->drawing.item_xsize;
843       gi->event.y /= gi->drawing.item_ysize;
844
845       if (last_x != gi->event.x || last_y != gi->event.y)
846         changed_position = TRUE;
847     }
848   }
849
850   /* handle gadget popup info text */
851   if (last_info_gi != new_gi ||
852       (new_gi && new_gi->type == GD_TYPE_DRAWING_AREA && changed_position))
853   {
854     last_info_gi = new_gi;
855
856     if (new_gi != NULL && (button == 0 || new_gi == last_gi))
857     {
858       new_gi->event.type = 0;
859       new_gi->callback_info(new_gi);
860     }
861     else
862       default_callback_info(NULL);
863   }
864
865   if (gadget_pressed)
866   {
867     if (gi->type == GD_TYPE_CHECK_BUTTON)
868     {
869       gi->checked = !gi->checked;
870     }
871     else if (gi->type == GD_TYPE_RADIO_BUTTON)
872     {
873       struct GadgetInfo *rgi = gadget_list_first_entry;
874
875       while (rgi)
876       {
877         if (rgi->mapped &&
878             rgi->type == GD_TYPE_RADIO_BUTTON &&
879             rgi->radio_nr == gi->radio_nr &&
880             rgi != gi)
881         {
882           rgi->checked = FALSE;
883           DrawGadget(rgi, DG_UNPRESSED, DG_DIRECT);
884         }
885
886         rgi = rgi->next;
887       }
888
889       gi->checked = TRUE;
890     }
891     else if (gi->type & GD_TYPE_SCROLLBAR)
892     {
893       int mpos, gpos;
894
895       if (gi->type == GD_TYPE_SCROLLBAR_HORIZONTAL)
896       {
897         mpos = mx;
898         gpos = gi->x;
899       }
900       else
901       {
902         mpos = my;
903         gpos = gi->y;
904       }
905
906       if (mpos >= gpos + gi->scrollbar.position &&
907           mpos < gpos + gi->scrollbar.position + gi->scrollbar.size)
908       {
909         /* drag scrollbar */
910         gi->scrollbar.drag_position =
911           scrollbar_mouse_pos - gi->scrollbar.position;
912       }
913       else
914       {
915         /* click scrollbar one scrollbar length up/left or down/right */
916
917         struct GadgetScrollbar *gs = &gi->scrollbar;
918         int old_item_position = gs->item_position;
919
920         changed_position = FALSE;
921
922         gs->item_position +=
923           gs->items_visible * (mpos < gpos + gi->scrollbar.position ? -1 : +1);
924
925         if (gs->item_position < 0)
926           gs->item_position = 0;
927         if (gs->item_position > gs->items_max - gs->items_visible)
928           gs->item_position = gs->items_max - gs->items_visible;
929
930         if (old_item_position != gs->item_position)
931         {
932           gi->event.item_position = gs->item_position;
933           changed_position = TRUE;
934         }
935
936         ModifyGadget(gi, GDI_SCROLLBAR_ITEM_POSITION, gs->item_position,
937                      GDI_END);
938
939         gi->state = GD_BUTTON_UNPRESSED;
940         gi->event.type = GD_EVENT_MOVING;
941         gi->event.off_borders = FALSE;
942
943         if (gi->event_mask & GD_EVENT_MOVING && changed_position)
944           gi->callback_action(gi);
945
946         /* don't handle this scrollbar anymore while mouse button pressed */
947         last_gi = NULL;
948
949         return;
950       }
951     }
952
953     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
954
955     gi->state = GD_BUTTON_PRESSED;
956     gi->event.type = GD_EVENT_PRESSED;
957     gi->event.button = button;
958     gi->event.off_borders = FALSE;
959
960     /* initialize delay counter */
961     DelayReached(&pressed_delay, 0);
962
963     if (gi->event_mask & GD_EVENT_PRESSED)
964       gi->callback_action(gi);
965   }
966
967   if (gadget_pressed_repeated)
968   {
969     gi->event.type = GD_EVENT_PRESSED;
970
971     if (gi->event_mask & GD_EVENT_REPEATED &&
972         DelayReached(&pressed_delay, GADGET_FRAME_DELAY))
973       gi->callback_action(gi);
974   }
975
976   if (gadget_moving)
977   {
978     if (gi->type & GD_TYPE_BUTTON)
979     {
980       if (gadget_moving_inside && gi->state == GD_BUTTON_UNPRESSED)
981         DrawGadget(gi, DG_PRESSED, DG_DIRECT);
982       else if (gadget_moving_off_borders && gi->state == GD_BUTTON_PRESSED)
983         DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
984     }
985
986     if (gi->type & GD_TYPE_SCROLLBAR)
987     {
988       struct GadgetScrollbar *gs = &gi->scrollbar;
989       int old_item_position = gs->item_position;
990
991       gs->position = scrollbar_mouse_pos - gs->drag_position;
992
993       if (gs->position < 0)
994         gs->position = 0;
995       if (gs->position > gs->position_max)
996         gs->position = gs->position_max;
997
998       gs->item_position =
999         gs->items_max * (gs->position + gs->correction) / gs->size_max;
1000
1001       if (gs->item_position < 0)
1002         gs->item_position = 0;
1003       if (gs->item_position > gs->items_max - 1)
1004         gs->item_position = gs->items_max - 1;
1005
1006       if (old_item_position != gs->item_position)
1007       {
1008         gi->event.item_position = gs->item_position;
1009         changed_position = TRUE;
1010       }
1011
1012       DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1013     }
1014
1015     gi->state = (gadget_moving_inside || gi->type & GD_TYPE_SCROLLBAR ?
1016                  GD_BUTTON_PRESSED : GD_BUTTON_UNPRESSED);
1017     gi->event.type = GD_EVENT_MOVING;
1018     gi->event.off_borders = gadget_moving_off_borders;
1019
1020     if (gi->event_mask & GD_EVENT_MOVING && changed_position &&
1021         (gadget_moving_inside || gi->event_mask & GD_EVENT_OFF_BORDERS))
1022       gi->callback_action(gi);
1023   }
1024
1025   if (gadget_released_inside)
1026   {
1027     if (!(gi->type & GD_TYPE_TEXTINPUT))
1028       DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1029
1030     gi->state = GD_BUTTON_UNPRESSED;
1031     gi->event.type = GD_EVENT_RELEASED;
1032
1033     if (gi->event_mask & GD_EVENT_RELEASED)
1034       gi->callback_action(gi);
1035   }
1036
1037   if (gadget_released_off_borders)
1038   {
1039     if (gi->type & GD_TYPE_SCROLLBAR)
1040       DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1041
1042     gi->event.type = GD_EVENT_RELEASED;
1043
1044     if (gi->event_mask & GD_EVENT_RELEASED &&
1045         gi->event_mask & GD_EVENT_OFF_BORDERS)
1046       gi->callback_action(gi);
1047   }
1048 }
1049
1050 void HandleGadgetsKeyInput(Key key)
1051 {
1052   struct GadgetInfo *gi = last_gi;
1053   char text[MAX_GADGET_TEXTSIZE];
1054   int text_length;
1055   int cursor_pos;
1056   char letter;
1057   boolean legal_letter;
1058
1059   if (gi == NULL || !(gi->type & GD_TYPE_TEXTINPUT) || !gi->mapped)
1060     return;
1061
1062   text_length = strlen(gi->text.value);
1063   cursor_pos = gi->text.cursor_position;
1064   letter = getCharFromKey(key);
1065   legal_letter = (gi->type == GD_TYPE_TEXTINPUT_NUMERIC ?
1066                   letter >= '0' && letter <= '9' :
1067                   letter != 0);
1068
1069   if (legal_letter && text_length < gi->text.size)
1070   {
1071     strcpy(text, gi->text.value);
1072     strcpy(&gi->text.value[cursor_pos + 1], &text[cursor_pos]);
1073     gi->text.value[cursor_pos] = letter;
1074     gi->text.cursor_position++;
1075     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1076   }
1077   else if (key == KSYM_Left && cursor_pos > 0)
1078   {
1079     gi->text.cursor_position--;
1080     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1081   }
1082   else if (key == KSYM_Right && cursor_pos < text_length)
1083   {
1084     gi->text.cursor_position++;
1085     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1086   }
1087   else if (key == KSYM_BackSpace && cursor_pos > 0)
1088   {
1089     strcpy(text, gi->text.value);
1090     strcpy(&gi->text.value[cursor_pos - 1], &text[cursor_pos]);
1091     gi->text.cursor_position--;
1092     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1093   }
1094   else if (key == KSYM_Delete && cursor_pos < text_length)
1095   {
1096     strcpy(text, gi->text.value);
1097     strcpy(&gi->text.value[cursor_pos], &text[cursor_pos + 1]);
1098     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1099   }
1100   else if (key == KSYM_Return)
1101   {
1102     CheckRangeOfNumericInputGadget(gi);
1103     DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1104
1105     gi->event.type = GD_EVENT_TEXT_RETURN;
1106
1107     if (gi->event_mask & GD_EVENT_TEXT_RETURN)
1108       gi->callback_action(gi);
1109
1110     last_gi = NULL;
1111   }
1112 }