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