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