rnd-20030828-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
26 #define DG_BUFFERED             0
27 #define DG_DIRECT               1
28
29 static struct GadgetInfo *gadget_list_first_entry = NULL;
30 static struct GadgetInfo *gadget_list_last_entry = NULL;
31 static int next_free_gadget_id = 1;
32 static boolean gadget_id_wrapped = FALSE;
33
34 static struct GadgetInfo *getGadgetInfoFromGadgetID(int id)
35 {
36   struct GadgetInfo *gi = gadget_list_first_entry;
37
38   while (gi && gi->id != id)
39     gi = gi->next;
40
41   return gi;
42 }
43
44 static int getNewGadgetID()
45 {
46   int id = next_free_gadget_id++;
47
48   if (next_free_gadget_id <= 0)         /* counter overrun */
49   {
50     gadget_id_wrapped = TRUE;           /* now we must check each ID */
51     next_free_gadget_id = 0;
52   }
53
54   if (gadget_id_wrapped)
55   {
56     next_free_gadget_id++;
57     while (getGadgetInfoFromGadgetID(next_free_gadget_id) != NULL)
58       next_free_gadget_id++;
59   }
60
61   if (next_free_gadget_id <= 0)         /* cannot get new gadget id */
62     Error(ERR_EXIT, "too much gadgets -- this should not happen");
63
64   return id;
65 }
66
67 #if 0
68 void DUMP_GADGET_MAP_STATE()
69 {
70   struct GadgetInfo *gi = gadget_list_first_entry;
71
72   while (gi != NULL)
73   {
74     printf("-XXX-1-> '%s': %s\n",
75            gi->info_text, (gi->mapped ? "mapped" : "not mapped"));
76
77     gi = gi->next;
78   }
79 }
80 #endif
81
82 static struct GadgetInfo *getGadgetInfoFromMousePosition(int mx, int my)
83 {
84   struct GadgetInfo *gi;
85
86   /* open selectboxes may overlap other active gadgets, so check them first */
87   for (gi = gadget_list_first_entry; gi != NULL; gi = gi->next)
88   {
89     if (gi->mapped && gi->active &&
90         gi->type & GD_TYPE_SELECTBOX && gi->selectbox.open &&
91         mx >= gi->selectbox.x && mx < gi->selectbox.x + gi->selectbox.width &&
92         my >= gi->selectbox.y && my < gi->selectbox.y + gi->selectbox.height)
93       return gi;
94   }
95
96   /* check all other gadgets */
97   for (gi = gadget_list_first_entry; gi != NULL; gi = gi->next)
98   {
99     if (gi->mapped && gi->active &&
100         mx >= gi->x && mx < gi->x + gi->width &&
101         my >= gi->y && my < gi->y + gi->height)
102       return gi;
103   }
104
105   return NULL;
106 }
107
108 #if 1
109
110 static void setTextAreaCursorExt(struct GadgetInfo *gi, boolean set_cursor_pos)
111 {
112   char *text = gi->textarea.value;
113   int area_xsize = gi->textarea.xsize;
114   int area_ysize = gi->textarea.ysize;
115   int cursor_position = gi->textarea.cursor_position;
116   int cursor_x = gi->textarea.cursor_x;
117   int cursor_y = gi->textarea.cursor_y;
118   int pos = 0;
119   int x = 0;
120   int y = 0;
121
122   while (*text)
123   {
124     if (set_cursor_pos)         /* x/y => position */
125     {
126       if (y == cursor_y && (x == cursor_x || (x < cursor_x && *text == '\n')))
127         break;
128     }
129     else                        /* position => x/y */
130     {
131       if (pos == cursor_position)
132         break;
133     }
134
135     if (x + 1 >= area_xsize || *text == '\n')
136     {
137       if (y + 1 >= area_ysize)
138         break;
139
140       x = 0;
141       y++;
142     }
143     else
144       x++;
145
146     text++;
147     pos++;
148   }
149
150   if (y >= area_ysize)
151   {
152     x = area_xsize - 1;
153   }
154
155 #if 0
156   printf("::: %d, %d [%d]\n", cursor_x, cursor_y, cursor_position);
157 #endif
158
159   gi->textarea.cursor_x = x;
160   gi->textarea.cursor_y = y;
161   gi->textarea.cursor_x_preferred = x;
162   gi->textarea.cursor_position = pos;
163 }
164
165 static void setTextAreaCursorXY(struct GadgetInfo *gi, int x, int y)
166 {
167   gi->textarea.cursor_x = x;
168   gi->textarea.cursor_y = y;
169
170   setTextAreaCursorExt(gi, TRUE);
171 }
172
173 static void setTextAreaCursorPosition(struct GadgetInfo *gi, int pos)
174 {
175   gi->textarea.cursor_position = pos;
176
177   setTextAreaCursorExt(gi, FALSE);
178 }
179
180 #else
181
182 static void setTextAreaCursorPosition(struct GadgetInfo *gi, int x, int y)
183 {
184   char *text = gi->textarea.value;
185   int area_xsize = gi->textarea.xsize;
186   int area_ysize = gi->textarea.ysize;
187   int cursor_x = 0;
188   int cursor_y = 0;
189   int cursor_position = 0;
190
191   while (*text && cursor_y < area_ysize)
192   {
193     char buffer[MAX_OUTPUT_LINESIZE + 1];
194     int i;
195
196     for (i=0; i < area_xsize && *text && *text != '\n'; i++)
197       buffer[i] = *text++;
198     buffer[i] = '\0';
199
200 #if 1
201     if (i == 0 && *text == '\n')
202     {
203       text++;
204       cursor_position++;
205     }
206 #endif
207
208     if (x == -1 && y == -1)             /* get x/y from cursor position */
209     {
210       if (cursor_position + i >= gi->textarea.cursor_position)
211       {
212 #if 0
213         printf("::: cursor: %d + %d >= %d\n", cursor_position, i,
214                gi->textarea.cursor_position);
215 #endif
216
217         cursor_x = gi->textarea.cursor_position - cursor_position;
218         cursor_position = gi->textarea.cursor_position;
219
220         break;
221       }
222     }
223
224     if (cursor_y == y || !*text)        /* correct y position found */
225     {
226       cursor_x = MIN(i, x);
227       cursor_position += cursor_x;
228
229       break;
230     }
231     else
232       cursor_position += i;
233
234 #if 0
235     if (*text == '\n')
236     {
237       text++;
238       cursor_position++;
239
240       if (i == area_xsize)
241         cursor_y++;
242     }
243 #endif
244
245     cursor_y++;
246   }
247
248   if (cursor_x >= area_xsize)
249   {
250     cursor_x = 0;
251     cursor_y++;
252   }
253
254   if (cursor_y >= area_ysize)
255   {
256     cursor_x = area_xsize - 1;
257     cursor_y = area_ysize - 1;
258   }
259
260 #if 0
261   printf("::: %d, %d [%d]\n", cursor_x, cursor_y, cursor_position);
262 #endif
263
264   gi->textarea.cursor_x = cursor_x;
265   gi->textarea.cursor_y = cursor_y;
266   gi->textarea.cursor_x_preferred = cursor_x;
267   gi->textarea.cursor_position = cursor_position;
268 }
269
270 #endif
271
272 static void default_callback_info(void *ptr)
273 {
274   return;
275 }
276
277 static void default_callback_action(void *ptr)
278 {
279   return;
280 }
281
282 static void DrawGadget(struct GadgetInfo *gi, boolean pressed, boolean direct)
283 {
284   int state = (pressed ? GD_BUTTON_PRESSED : GD_BUTTON_UNPRESSED);
285   struct GadgetDesign *gd = (!gi->active ? &gi->alt_design[state] :
286                              gi->checked ? &gi->alt_design[state] :
287                              &gi->design[state]);
288   boolean redraw_selectbox = FALSE;
289
290   switch (gi->type)
291   {
292     case GD_TYPE_NORMAL_BUTTON:
293     case GD_TYPE_CHECK_BUTTON:
294     case GD_TYPE_RADIO_BUTTON:
295       BlitBitmapOnBackground(gd->bitmap, drawto,
296                              gd->x, gd->y, gi->width, gi->height,
297                              gi->x, gi->y);
298       if (gi->deco.design.bitmap)
299         BlitBitmap(gi->deco.design.bitmap, drawto,
300                    gi->deco.design.x, gi->deco.design.y,
301                    gi->deco.width, gi->deco.height,
302                    gi->x + gi->deco.x + (pressed ? gi->deco.xshift : 0),
303                    gi->y + gi->deco.y + (pressed ? gi->deco.yshift : 0));
304       break;
305
306     case GD_TYPE_TEXT_BUTTON:
307       {
308         int i;
309         int font_nr = (gi->active ? gi->font_active : gi->font);
310         int font_width = getFontWidth(font_nr);
311         int border_x = gi->border.xsize;
312         int border_y = gi->border.ysize;
313         int text_size = strlen(gi->textbutton.value);
314         int text_start = (gi->width - text_size * font_width) / 2;
315
316         /* left part of gadget */
317         BlitBitmapOnBackground(gd->bitmap, drawto, gd->x, gd->y,
318                                border_x, gi->height, gi->x, gi->y);
319
320         /* middle part of gadget */
321         for (i=0; i < gi->textbutton.size; i++)
322           BlitBitmapOnBackground(gd->bitmap, drawto, gd->x + border_x, gd->y,
323                                  font_width, gi->height,
324                                  gi->x + border_x + i * font_width, gi->y);
325
326         /* right part of gadget */
327         BlitBitmapOnBackground(gd->bitmap, drawto,
328                                gd->x + gi->border.width - border_x, gd->y,
329                                border_x, gi->height,
330                                gi->x + gi->width - border_x, gi->y);
331
332         /* gadget text value */
333         DrawTextExt(drawto,
334                     gi->x + text_start + (pressed ? gi->deco.xshift : 0),
335                     gi->y + border_y   + (pressed ? gi->deco.yshift : 0),
336                     gi->textbutton.value, font_nr, BLIT_MASKED);
337       }
338       break;
339
340     case GD_TYPE_TEXT_INPUT_ALPHANUMERIC:
341     case GD_TYPE_TEXT_INPUT_NUMERIC:
342       {
343         int i;
344         char cursor_letter;
345         char cursor_string[2];
346         char text[MAX_GADGET_TEXTSIZE + 1];
347         int font_nr = (pressed ? gi->font_active : gi->font);
348         int font_width = getFontWidth(font_nr);
349         int border_x = gi->border.xsize;
350         int border_y = gi->border.ysize;
351
352         /* left part of gadget */
353         BlitBitmapOnBackground(gd->bitmap, drawto, gd->x, gd->y,
354                                border_x, gi->height, gi->x, gi->y);
355
356         /* middle part of gadget */
357         for (i=0; i < gi->textinput.size + 1; i++)
358           BlitBitmapOnBackground(gd->bitmap, drawto, gd->x + border_x, gd->y,
359                                  font_width, gi->height,
360                                  gi->x + border_x + i * font_width, gi->y);
361
362         /* right part of gadget */
363         BlitBitmapOnBackground(gd->bitmap, drawto,
364                                gd->x + gi->border.width - border_x, gd->y,
365                                border_x, gi->height,
366                                gi->x + gi->width - border_x, gi->y);
367
368         /* set text value */
369         strcpy(text, gi->textinput.value);
370         strcat(text, " ");
371
372         /* gadget text value */
373         DrawTextExt(drawto,
374                     gi->x + border_x, gi->y + border_y, text,
375                     font_nr, BLIT_MASKED);
376
377         cursor_letter = gi->textinput.value[gi->textinput.cursor_position];
378         cursor_string[0] = (cursor_letter != '\0' ? cursor_letter : ' ');
379         cursor_string[1] = '\0';
380
381 #if 0
382         if (pressed)
383           printf("::: PRESSED!\n");
384 #endif
385
386         /* draw cursor, if active */
387         if (pressed)
388           DrawTextExt(drawto,
389                       gi->x + border_x +
390                       gi->textinput.cursor_position * font_width,
391                       gi->y + border_y, cursor_string,
392                       font_nr, BLIT_INVERSE);
393       }
394       break;
395
396     case GD_TYPE_TEXT_AREA:
397       {
398         int i;
399         char cursor_letter;
400         char cursor_string[2];
401         int font_nr = (pressed ? gi->font_active : gi->font);
402         int font_width = getFontWidth(font_nr);
403         int font_height = getFontHeight(font_nr);
404         int border_x = gi->border.xsize;
405         int border_y = gi->border.ysize;
406         int gd_height = 2 * border_y + font_height;
407
408         /* top left part of gadget border */
409         BlitBitmapOnBackground(gd->bitmap, drawto, gd->x, gd->y,
410                                border_x, border_y, gi->x, gi->y);
411
412         /* top middle part of gadget border */
413         for (i=0; i < gi->textarea.xsize; i++)
414           BlitBitmapOnBackground(gd->bitmap, drawto, gd->x + border_x, gd->y,
415                                  font_width, border_y,
416                                  gi->x + border_x + i * font_width, gi->y);
417
418         /* top right part of gadget border */
419         BlitBitmapOnBackground(gd->bitmap, drawto,
420                                gd->x + gi->border.width - border_x, gd->y,
421                                border_x, border_y,
422                                gi->x + gi->width - border_x, gi->y);
423
424         /* left and right part of gadget border for each row */
425         for (i=0; i < gi->textarea.ysize; i++)
426         {
427           BlitBitmapOnBackground(gd->bitmap, drawto, gd->x, gd->y + border_y,
428                                  border_x, font_height,
429                                  gi->x, gi->y + border_y + i * font_height);
430           BlitBitmapOnBackground(gd->bitmap, drawto,
431                                  gd->x + gi->border.width - border_x,
432                                  gd->y + border_y,
433                                  border_x, font_height,
434                                  gi->x + gi->width - border_x,
435                                  gi->y + border_y + i * font_height);
436         }
437
438         /* bottom left part of gadget border */
439         BlitBitmapOnBackground(gd->bitmap, drawto,
440                                gd->x, gd->y + gd_height - border_y,
441                                border_x, border_y,
442                                gi->x, gi->y + gi->height - border_y);
443
444         /* bottom middle part of gadget border */
445         for (i=0; i < gi->textarea.xsize; i++)
446           BlitBitmapOnBackground(gd->bitmap, drawto,
447                                  gd->x + border_x,
448                                  gd->y + gd_height - border_y,
449                                  font_width, border_y,
450                                  gi->x + border_x + i * font_width,
451                                  gi->y + gi->height - border_y);
452
453         /* bottom right part of gadget border */
454         BlitBitmapOnBackground(gd->bitmap, drawto,
455                                gd->x + gi->border.width - border_x,
456                                gd->y + gd_height - border_y,
457                                border_x, border_y,
458                                gi->x + gi->width - border_x,
459                                gi->y + gi->height - border_y);
460
461         ClearRectangleOnBackground(drawto,
462                                    gi->x + border_x,
463                                    gi->y + border_y,
464                                    gi->width - 2 * border_x,
465                                    gi->height - 2 * border_y);
466
467         /* gadget text value */
468         DrawTextToTextArea(gi->x + border_x, gi->y + border_y,
469                            gi->textarea.value, font_nr,
470                            gi->textarea.xsize, gi->textarea.ysize);
471
472         cursor_letter = gi->textarea.value[gi->textarea.cursor_position];
473         cursor_string[0] = (cursor_letter != '\0' ? cursor_letter : ' ');
474         cursor_string[1] = '\0';
475
476 #if 0
477         printf("::: '%s' [%d, %d] [%d] [%d -> '%s']\n",
478                gi->textarea.value,
479                gi->textarea.cursor_x, gi->textarea.cursor_y,
480                gi->textarea.cursor_position,
481                pressed, cursor_string);
482 #endif
483
484         /* draw cursor, if active */
485         if (pressed)
486           DrawTextExt(drawto,
487                       gi->x + border_x + gi->textarea.cursor_x * font_width,
488                       gi->y + border_y + gi->textarea.cursor_y * font_height,
489                       cursor_string,
490                       font_nr, BLIT_INVERSE);
491       }
492       break;
493
494     case GD_TYPE_SELECTBOX:
495       {
496         int i;
497         char text[MAX_GADGET_TEXTSIZE + 1];
498         int font_nr = (pressed ? gi->font_active : gi->font);
499         int font_width = getFontWidth(font_nr);
500         int font_height = getFontHeight(font_nr);
501         int border_x = gi->border.xsize;
502         int border_y = gi->border.ysize;
503         int button = gi->border.xsize_selectbutton;
504         int width_inner = gi->border.width - button - 2 * border_x;
505         int box_width = gi->selectbox.width;
506         int box_height = gi->selectbox.height;
507
508         /* left part of gadget */
509         BlitBitmapOnBackground(gd->bitmap, drawto, gd->x, gd->y,
510                                border_x, gi->height, gi->x, gi->y);
511
512         /* middle part of gadget */
513         for (i=0; i < gi->selectbox.size; i++)
514           BlitBitmapOnBackground(gd->bitmap, drawto, gd->x + border_x, gd->y,
515                                  font_width, gi->height,
516                                  gi->x + border_x + i * font_width, gi->y);
517
518         /* button part of gadget */
519         BlitBitmapOnBackground(gd->bitmap, drawto,
520                                gd->x + border_x + width_inner, gd->y,
521                                button, gi->height,
522                                gi->x + gi->width - border_x - button, gi->y);
523
524         /* right part of gadget */
525         BlitBitmapOnBackground(gd->bitmap, drawto,
526                                gd->x + gi->border.width - border_x, gd->y,
527                                border_x, gi->height,
528                                gi->x + gi->width - border_x, gi->y);
529
530         /* set text value */
531         strncpy(text, gi->selectbox.options[gi->selectbox.index].text,
532                 gi->selectbox.size);
533         text[gi->selectbox.size] = '\0';
534
535         /* gadget text value */
536         DrawTextExt(drawto, gi->x + border_x, gi->y + border_y, text,
537                     font_nr, BLIT_MASKED);
538
539         if (pressed)
540         {
541           if (!gi->selectbox.open)
542           {
543             gi->selectbox.open = TRUE;
544             gi->selectbox.stay_open = FALSE;
545             gi->selectbox.current_index = gi->selectbox.index;
546
547             /* save background under selectbox */
548             BlitBitmap(drawto, gfx.field_save_buffer,
549                        gi->selectbox.x,     gi->selectbox.y,
550                        gi->selectbox.width, gi->selectbox.height,
551                        gi->selectbox.x,     gi->selectbox.y);
552           }
553
554           /* draw open selectbox */
555
556           /* top left part of gadget border */
557           BlitBitmapOnBackground(gd->bitmap, drawto, gd->x, gd->y,
558                                  border_x, border_y,
559                                  gi->selectbox.x, gi->selectbox.y);
560
561           /* top middle part of gadget border */
562           for (i=0; i < gi->selectbox.size; i++)
563             BlitBitmapOnBackground(gd->bitmap, drawto, gd->x + border_x, gd->y,
564                                    font_width, border_y,
565                                    gi->selectbox.x + border_x + i * font_width,
566                                    gi->selectbox.y);
567
568           /* top button part of gadget border */
569           BlitBitmapOnBackground(gd->bitmap, drawto,
570                                  gd->x + border_x + width_inner, gd->y,
571                                  button, border_y,
572                                  gi->selectbox.x + box_width -border_x -button,
573                                  gi->selectbox.y);
574
575           /* top right part of gadget border */
576           BlitBitmapOnBackground(gd->bitmap, drawto,
577                                  gd->x + gi->border.width - border_x, gd->y,
578                                  border_x, border_y,
579                                  gi->selectbox.x + box_width - border_x,
580                                  gi->selectbox.y);
581
582           /* left and right part of gadget border for each row */
583           for (i=0; i < gi->selectbox.num_values; i++)
584           {
585             BlitBitmapOnBackground(gd->bitmap, drawto, gd->x, gd->y + border_y,
586                                    border_x, font_height,
587                                    gi->selectbox.x,
588                                    gi->selectbox.y + border_y + i*font_height);
589             BlitBitmapOnBackground(gd->bitmap, drawto,
590                                    gd->x + gi->border.width - border_x,
591                                    gd->y + border_y,
592                                    border_x, font_height,
593                                    gi->selectbox.x + box_width - border_x,
594                                    gi->selectbox.y + border_y + i*font_height);
595           }
596
597           /* bottom left part of gadget border */
598           BlitBitmapOnBackground(gd->bitmap, drawto,
599                                  gd->x, gd->y + gi->height - border_y,
600                                  border_x, border_y,
601                                  gi->selectbox.x,
602                                  gi->selectbox.y + box_height - border_y);
603
604           /* bottom middle part of gadget border */
605           for (i=0; i < gi->selectbox.size; i++)
606             BlitBitmapOnBackground(gd->bitmap, drawto,
607                                    gd->x + border_x,
608                                    gd->y + gi->height - border_y,
609                                    font_width, border_y,
610                                    gi->selectbox.x + border_x + i * font_width,
611                                    gi->selectbox.y + box_height - border_y);
612
613           /* bottom button part of gadget border */
614           BlitBitmapOnBackground(gd->bitmap, drawto,
615                                  gd->x + border_x + width_inner,
616                                  gd->y + gi->height - border_y,
617                                  button, border_y,
618                                  gi->selectbox.x + box_width -border_x -button,
619                                  gi->selectbox.y + box_height - border_y);
620
621           /* bottom right part of gadget border */
622           BlitBitmapOnBackground(gd->bitmap, drawto,
623                                  gd->x + gi->border.width - border_x,
624                                  gd->y + gi->height - border_y,
625                                  border_x, border_y,
626                                  gi->selectbox.x + box_width - border_x,
627                                  gi->selectbox.y + box_height - border_y);
628
629           ClearRectangleOnBackground(drawto,
630                                      gi->selectbox.x + border_x,
631                                      gi->selectbox.y + border_y,
632                                      gi->selectbox.width - 2 * border_x,
633                                      gi->selectbox.height - 2 * border_y);
634
635           /* selectbox text values */
636           for (i=0; i < gi->selectbox.num_values; i++)
637           {
638             int mask_mode;
639
640             if (i == gi->selectbox.current_index)
641             {
642               FillRectangle(drawto,
643                             gi->selectbox.x + border_x,
644                             gi->selectbox.y + border_y + i * font_height,
645                             gi->selectbox.width - 2 * border_x, font_height,
646                             gi->selectbox.inverse_color);
647
648               strncpy(text, gi->selectbox.options[i].text, gi->selectbox.size);
649               text[1 + gi->selectbox.size] = '\0';
650
651               mask_mode = BLIT_INVERSE;
652             }
653             else
654             {
655               strncpy(text, gi->selectbox.options[i].text, gi->selectbox.size);
656               text[gi->selectbox.size] = '\0';
657
658               mask_mode = BLIT_MASKED;
659             }
660
661             DrawTextExt(drawto,
662                         gi->selectbox.x + border_x,
663                         gi->selectbox.y + border_y + i * font_height, text,
664                         font_nr, mask_mode);
665           }
666
667           redraw_selectbox = TRUE;
668         }
669         else if (gi->selectbox.open)
670         {
671           gi->selectbox.open = FALSE;
672
673           /* redraw closed selectbox */
674           DrawGadget(gi, FALSE, FALSE);
675
676           /* restore background under selectbox */
677           BlitBitmap(gfx.field_save_buffer, drawto,
678                      gi->selectbox.x,     gi->selectbox.y,
679                      gi->selectbox.width, gi->selectbox.height,
680                      gi->selectbox.x,     gi->selectbox.y);
681
682           redraw_selectbox = TRUE;
683         }
684       }
685       break;
686
687     case GD_TYPE_SCROLLBAR_VERTICAL:
688       {
689         int i;
690         int xpos = gi->x;
691         int ypos = gi->y + gi->scrollbar.position;
692         int design_full = gi->width;
693         int design_body = design_full - 2 * gi->border.ysize;
694         int size_full = gi->scrollbar.size;
695         int size_body = size_full - 2 * gi->border.ysize;
696         int num_steps = size_body / design_body;
697         int step_size_remain = size_body - num_steps * design_body;
698
699         /* clear scrollbar area */
700         ClearRectangleOnBackground(backbuffer, gi->x, gi->y,
701                                    gi->width, gi->height);
702
703         /* upper part of gadget */
704         BlitBitmapOnBackground(gd->bitmap, drawto,
705                                gd->x, gd->y,
706                                gi->width, gi->border.ysize,
707                                xpos, ypos);
708
709         /* middle part of gadget */
710         for (i=0; i<num_steps; i++)
711           BlitBitmapOnBackground(gd->bitmap, drawto,
712                                  gd->x, gd->y + gi->border.ysize,
713                                  gi->width, design_body,
714                                  xpos,
715                                  ypos + gi->border.ysize + i * design_body);
716
717         /* remaining middle part of gadget */
718         if (step_size_remain > 0)
719           BlitBitmapOnBackground(gd->bitmap, drawto,
720                                  gd->x,  gd->y + gi->border.ysize,
721                                  gi->width, step_size_remain,
722                                  xpos,
723                                  ypos + gi->border.ysize
724                                  + num_steps * design_body);
725
726         /* lower part of gadget */
727         BlitBitmapOnBackground(gd->bitmap, drawto,
728                                gd->x, gd->y + design_full - gi->border.ysize,
729                                gi->width, gi->border.ysize,
730                                xpos, ypos + size_full - gi->border.ysize);
731       }
732       break;
733
734     case GD_TYPE_SCROLLBAR_HORIZONTAL:
735       {
736         int i;
737         int xpos = gi->x + gi->scrollbar.position;
738         int ypos = gi->y;
739         int design_full = gi->height;
740         int design_body = design_full - 2 * gi->border.xsize;
741         int size_full = gi->scrollbar.size;
742         int size_body = size_full - 2 * gi->border.xsize;
743         int num_steps = size_body / design_body;
744         int step_size_remain = size_body - num_steps * design_body;
745
746         /* clear scrollbar area */
747         ClearRectangleOnBackground(backbuffer, gi->x, gi->y,
748                                    gi->width, gi->height);
749
750         /* left part of gadget */
751         BlitBitmapOnBackground(gd->bitmap, drawto,
752                                gd->x, gd->y,
753                                gi->border.xsize, gi->height,
754                                xpos, ypos);
755
756         /* middle part of gadget */
757         for (i=0; i<num_steps; i++)
758           BlitBitmapOnBackground(gd->bitmap, drawto,
759                                  gd->x + gi->border.xsize, gd->y,
760                                  design_body, gi->height,
761                                  xpos + gi->border.xsize + i * design_body,
762                                  ypos);
763
764         /* remaining middle part of gadget */
765         if (step_size_remain > 0)
766           BlitBitmapOnBackground(gd->bitmap, drawto,
767                                  gd->x + gi->border.xsize, gd->y,
768                                  step_size_remain, gi->height,
769                                  xpos + gi->border.xsize
770                                  + num_steps * design_body,
771                                  ypos);
772
773         /* right part of gadget */
774         BlitBitmapOnBackground(gd->bitmap, drawto,
775                                gd->x + design_full - gi->border.xsize, gd->y,
776                                gi->border.xsize, gi->height,
777                                xpos + size_full - gi->border.xsize, ypos);
778       }
779       break;
780
781     default:
782       return;
783   }
784
785   if (direct)
786   {
787     BlitBitmap(drawto, window,
788                gi->x, gi->y, gi->width, gi->height, gi->x, gi->y);
789
790     if (gi->type == GD_TYPE_SELECTBOX && redraw_selectbox)
791       BlitBitmap(drawto, window,
792                  gi->selectbox.x,     gi->selectbox.y,
793                  gi->selectbox.width, gi->selectbox.height,
794                  gi->selectbox.x,     gi->selectbox.y);
795   }
796   else
797     redraw_mask |= (gi->x < gfx.sx + gfx.sxsize ? REDRAW_FIELD :
798                     gi->y < gfx.dy + gfx.dysize ? REDRAW_DOOR_1 :
799                     gi->y > gfx.vy ? REDRAW_DOOR_2 : REDRAW_DOOR_3);
800 }
801
802 static void HandleGadgetTags(struct GadgetInfo *gi, int first_tag, va_list ap)
803 {
804   int tag = first_tag;
805
806   while (tag != GDI_END)
807   {
808     switch(tag)
809     {
810       case GDI_CUSTOM_ID:
811         gi->custom_id = va_arg(ap, int);
812         break;
813
814       case GDI_CUSTOM_TYPE_ID:
815         gi->custom_type_id = va_arg(ap, int);
816         break;
817
818       case GDI_INFO_TEXT:
819         {
820           int max_textsize = MAX_INFO_TEXTSIZE - 1;
821           char *text = va_arg(ap, char *);
822
823           if (text != NULL)
824             strncpy(gi->info_text, text, max_textsize);
825           else
826             max_textsize = 0;
827
828           gi->info_text[max_textsize] = '\0';
829         }
830         break;
831
832       case GDI_X:
833         gi->x = va_arg(ap, int);
834         break;
835
836       case GDI_Y:
837         gi->y = va_arg(ap, int);
838         break;
839
840       case GDI_WIDTH:
841         gi->width = va_arg(ap, int);
842         break;
843
844       case GDI_HEIGHT:
845         gi->height = va_arg(ap, int);
846         break;
847
848       case GDI_TYPE:
849         gi->type = va_arg(ap, unsigned long);
850         break;
851
852       case GDI_STATE:
853         gi->state = va_arg(ap, unsigned long);
854         break;
855
856       case GDI_ACTIVE:
857         /* take care here: "boolean" is typedef'ed as "unsigned char",
858            which gets promoted to "int" */
859         gi->active = (boolean)va_arg(ap, int);
860         break;
861
862       case GDI_DIRECT_DRAW:
863         /* take care here: "boolean" is typedef'ed as "unsigned char",
864            which gets promoted to "int" */
865         gi->direct_draw = (boolean)va_arg(ap, int);
866         break;
867
868       case GDI_CHECKED:
869         /* take care here: "boolean" is typedef'ed as "unsigned char",
870            which gets promoted to "int" */
871         gi->checked = (boolean)va_arg(ap, int);
872         break;
873
874       case GDI_RADIO_NR:
875         gi->radio_nr = va_arg(ap, unsigned long);
876         break;
877
878       case GDI_NUMBER_VALUE:
879         gi->textinput.number_value = va_arg(ap, long);
880         sprintf(gi->textinput.value, "%d", gi->textinput.number_value);
881         gi->textinput.cursor_position = strlen(gi->textinput.value);
882         break;
883
884       case GDI_NUMBER_MIN:
885         gi->textinput.number_min = va_arg(ap, long);
886         if (gi->textinput.number_value < gi->textinput.number_min)
887         {
888           gi->textinput.number_value = gi->textinput.number_min;
889           sprintf(gi->textinput.value, "%d", gi->textinput.number_value);
890         }
891         break;
892
893       case GDI_NUMBER_MAX:
894         gi->textinput.number_max = va_arg(ap, long);
895         if (gi->textinput.number_value > gi->textinput.number_max)
896         {
897           gi->textinput.number_value = gi->textinput.number_max;
898           sprintf(gi->textinput.value, "%d", gi->textinput.number_value);
899         }
900         break;
901
902       case GDI_TEXT_VALUE:
903         {
904           int max_textsize = MAX_GADGET_TEXTSIZE;
905
906           if (gi->textinput.size)
907             max_textsize = MIN(gi->textinput.size, MAX_GADGET_TEXTSIZE - 1);
908
909           strncpy(gi->textinput.value, va_arg(ap, char *), max_textsize);
910           gi->textinput.value[max_textsize] = '\0';
911           gi->textinput.cursor_position = strlen(gi->textinput.value);
912
913           /* same tag also used for other gadget definitions */
914           strcpy(gi->textbutton.value, gi->textinput.value);
915           strcpy(gi->textarea.value, gi->textinput.value);
916         }
917         break;
918
919       case GDI_TEXT_SIZE:
920         {
921           int tag_value = va_arg(ap, int);
922           int max_textsize = MIN(tag_value, MAX_GADGET_TEXTSIZE - 1);
923
924           gi->textinput.size = max_textsize;
925           gi->textinput.value[max_textsize] = '\0';
926
927           /* same tag also used for other gadget definitions */
928           strcpy(gi->textbutton.value, gi->textinput.value);
929           gi->textbutton.size = gi->textinput.size;
930           gi->selectbox.size = gi->textinput.size;
931         }
932         break;
933
934       case GDI_TEXT_FONT:
935         gi->font = va_arg(ap, int);
936         if (gi->font_active == 0)
937           gi->font_active = gi->font;
938         break;
939
940       case GDI_TEXT_FONT_ACTIVE:
941         gi->font_active = va_arg(ap, int);
942         break;
943
944       case GDI_SELECTBOX_OPTIONS:
945         gi->selectbox.options = va_arg(ap, struct ValueTextInfo *);
946         break;
947
948       case GDI_SELECTBOX_INDEX:
949         gi->selectbox.index = va_arg(ap, int);
950         break;
951
952       case GDI_DESIGN_UNPRESSED:
953         gi->design[GD_BUTTON_UNPRESSED].bitmap = va_arg(ap, Bitmap *);
954         gi->design[GD_BUTTON_UNPRESSED].x = va_arg(ap, int);
955         gi->design[GD_BUTTON_UNPRESSED].y = va_arg(ap, int);
956         break;
957
958       case GDI_DESIGN_PRESSED:
959         gi->design[GD_BUTTON_PRESSED].bitmap = va_arg(ap, Bitmap *);
960         gi->design[GD_BUTTON_PRESSED].x = va_arg(ap, int);
961         gi->design[GD_BUTTON_PRESSED].y = va_arg(ap, int);
962         break;
963
964       case GDI_ALT_DESIGN_UNPRESSED:
965         gi->alt_design[GD_BUTTON_UNPRESSED].bitmap= va_arg(ap, Bitmap *);
966         gi->alt_design[GD_BUTTON_UNPRESSED].x = va_arg(ap, int);
967         gi->alt_design[GD_BUTTON_UNPRESSED].y = va_arg(ap, int);
968         break;
969
970       case GDI_ALT_DESIGN_PRESSED:
971         gi->alt_design[GD_BUTTON_PRESSED].bitmap = va_arg(ap, Bitmap *);
972         gi->alt_design[GD_BUTTON_PRESSED].x = va_arg(ap, int);
973         gi->alt_design[GD_BUTTON_PRESSED].y = va_arg(ap, int);
974         break;
975
976       case GDI_BORDER_SIZE:
977         gi->border.xsize = va_arg(ap, int);
978         gi->border.ysize = va_arg(ap, int);
979         break;
980
981       case GDI_BORDER_SIZE_SELECTBUTTON:
982         gi->border.xsize_selectbutton = va_arg(ap, int);
983         break;
984
985       case GDI_DESIGN_WIDTH:
986         gi->border.width = va_arg(ap, int);
987         break;
988
989       case GDI_DECORATION_DESIGN:
990         gi->deco.design.bitmap = va_arg(ap, Bitmap *);
991         gi->deco.design.x = va_arg(ap, int);
992         gi->deco.design.y = va_arg(ap, int);
993         break;
994
995       case GDI_DECORATION_POSITION:
996         gi->deco.x = va_arg(ap, int);
997         gi->deco.y = va_arg(ap, int);
998         break;
999
1000       case GDI_DECORATION_SIZE:
1001         gi->deco.width = va_arg(ap, int);
1002         gi->deco.height = va_arg(ap, int);
1003         break;
1004
1005       case GDI_DECORATION_SHIFTING:
1006         gi->deco.xshift = va_arg(ap, int);
1007         gi->deco.yshift = va_arg(ap, int);
1008         break;
1009
1010       case GDI_EVENT_MASK:
1011         gi->event_mask = va_arg(ap, unsigned long);
1012         break;
1013
1014       case GDI_AREA_SIZE:
1015         gi->drawing.area_xsize = va_arg(ap, int);
1016         gi->drawing.area_ysize = va_arg(ap, int);
1017
1018         /* determine dependent values for drawing area gadget, if needed */
1019         if (gi->width == 0 && gi->height == 0 &&
1020             gi->drawing.item_xsize !=0 && gi->drawing.item_ysize !=0)
1021         {
1022           gi->width = gi->drawing.area_xsize * gi->drawing.item_xsize;
1023           gi->height = gi->drawing.area_ysize * gi->drawing.item_ysize;
1024         }
1025         else if (gi->drawing.item_xsize == 0 && gi->drawing.item_ysize == 0 &&
1026                  gi->width != 0 && gi->height != 0)
1027         {
1028           gi->drawing.item_xsize = gi->width / gi->drawing.area_xsize;
1029           gi->drawing.item_ysize = gi->height / gi->drawing.area_ysize;
1030         }
1031
1032         /* same tag also used for other gadget definitions */
1033         gi->textarea.xsize = gi->drawing.area_xsize;
1034         gi->textarea.ysize = gi->drawing.area_ysize;
1035
1036         if (gi->type & GD_TYPE_TEXT_AREA)       /* force recalculation */
1037         {
1038           gi->width = 0;
1039           gi->height = 0;
1040         }
1041
1042         break;
1043
1044       case GDI_ITEM_SIZE:
1045         gi->drawing.item_xsize = va_arg(ap, int);
1046         gi->drawing.item_ysize = va_arg(ap, int);
1047
1048         /* determine dependent values for drawing area gadget, if needed */
1049         if (gi->width == 0 && gi->height == 0 &&
1050             gi->drawing.area_xsize !=0 && gi->drawing.area_ysize !=0)
1051         {
1052           gi->width = gi->drawing.area_xsize * gi->drawing.item_xsize;
1053           gi->height = gi->drawing.area_ysize * gi->drawing.item_ysize;
1054         }
1055         else if (gi->drawing.area_xsize == 0 && gi->drawing.area_ysize == 0 &&
1056                  gi->width != 0 && gi->height != 0)
1057         {
1058           gi->drawing.area_xsize = gi->width / gi->drawing.item_xsize;
1059           gi->drawing.area_ysize = gi->height / gi->drawing.item_ysize;
1060         }
1061         break;
1062
1063       case GDI_SCROLLBAR_ITEMS_MAX:
1064         gi->scrollbar.items_max = va_arg(ap, int);
1065         break;
1066
1067       case GDI_SCROLLBAR_ITEMS_VISIBLE:
1068         gi->scrollbar.items_visible = va_arg(ap, int);
1069         break;
1070
1071       case GDI_SCROLLBAR_ITEM_POSITION:
1072         gi->scrollbar.item_position = va_arg(ap, int);
1073         break;
1074
1075       case GDI_CALLBACK_INFO:
1076         gi->callback_info = va_arg(ap, gadget_function);
1077         break;
1078
1079       case GDI_CALLBACK_ACTION:
1080         gi->callback_action = va_arg(ap, gadget_function);
1081         break;
1082
1083       default:
1084         Error(ERR_EXIT, "HandleGadgetTags(): unknown tag %d", tag);
1085     }
1086
1087     tag = va_arg(ap, int);      /* read next tag */
1088   }
1089
1090   /* check if gadget is complete */
1091   if (gi->type != GD_TYPE_DRAWING_AREA &&
1092       (!gi->design[GD_BUTTON_UNPRESSED].bitmap ||
1093        !gi->design[GD_BUTTON_PRESSED].bitmap))
1094     Error(ERR_EXIT, "gadget incomplete (missing Bitmap)");
1095
1096   /* adjust gadget values in relation to other gadget values */
1097
1098   if (gi->type & GD_TYPE_TEXT_INPUT)
1099   {
1100     int font_nr = gi->font_active;
1101     int font_width = getFontWidth(font_nr);
1102     int font_height = getFontHeight(font_nr);
1103     int border_xsize = gi->border.xsize;
1104     int border_ysize = gi->border.ysize;
1105
1106     gi->width  = 2 * border_xsize + (gi->textinput.size + 1) * font_width;
1107     gi->height = 2 * border_ysize + font_height;
1108   }
1109
1110   if (gi->type & GD_TYPE_SELECTBOX)
1111   {
1112     int font_nr = gi->font_active;
1113     int font_width = getFontWidth(font_nr);
1114     int font_height = getFontHeight(font_nr);
1115     int border_xsize = gi->border.xsize;
1116     int border_ysize = gi->border.ysize;
1117     int button_size = gi->border.xsize_selectbutton;
1118     int bottom_screen_border = gfx.sy + gfx.sysize - font_height;
1119     Bitmap *src_bitmap;
1120     int src_x, src_y;
1121
1122     gi->width  = 2 * border_xsize + gi->textinput.size*font_width +button_size;
1123     gi->height = 2 * border_ysize + font_height;
1124
1125     if (gi->selectbox.options == NULL)
1126       Error(ERR_EXIT, "selectbox gadget incomplete (missing options array)");
1127
1128     gi->selectbox.num_values = 0;
1129     while (gi->selectbox.options[gi->selectbox.num_values].text != NULL)
1130       gi->selectbox.num_values++;
1131
1132     /* calculate values for open selectbox */
1133     gi->selectbox.width = gi->width;
1134     gi->selectbox.height =
1135       2 * border_ysize + gi->selectbox.num_values * font_height;
1136
1137     gi->selectbox.x = gi->x;
1138     gi->selectbox.y = gi->y + gi->height;
1139     if (gi->selectbox.y + gi->selectbox.height > bottom_screen_border)
1140       gi->selectbox.y = gi->y - gi->selectbox.height;
1141     if (gi->selectbox.y < 0)
1142       gi->selectbox.y = bottom_screen_border - gi->selectbox.height;
1143
1144     getFontCharSource(font_nr, FONT_ASCII_CURSOR, &src_bitmap, &src_x, &src_y);
1145     src_x += font_width / 2;
1146     src_y += font_height / 2;
1147     gi->selectbox.inverse_color = GetPixel(src_bitmap, src_x, src_y);
1148
1149     /* always start with closed selectbox */
1150     gi->selectbox.open = FALSE;
1151   }
1152
1153   if (gi->type & GD_TYPE_TEXT_INPUT_NUMERIC)
1154   {
1155     struct GadgetTextInput *text = &gi->textinput;
1156     int value = text->number_value;
1157
1158     text->number_value = (value < text->number_min ? text->number_min :
1159                           value > text->number_max ? text->number_max :
1160                           value);
1161
1162     sprintf(text->value, "%d", text->number_value);
1163   }
1164
1165   if (gi->type & GD_TYPE_TEXT_BUTTON)
1166   {
1167     int font_nr = gi->font_active;
1168     int font_width = getFontWidth(font_nr);
1169     int font_height = getFontHeight(font_nr);
1170     int border_xsize = gi->border.xsize;
1171     int border_ysize = gi->border.ysize;
1172
1173     gi->width  = 2 * border_xsize + gi->textbutton.size * font_width;
1174     gi->height = 2 * border_ysize + font_height;
1175   }
1176
1177   if (gi->type & GD_TYPE_SCROLLBAR)
1178   {
1179     struct GadgetScrollbar *gs = &gi->scrollbar;
1180
1181     if (gi->width == 0 || gi->height == 0 ||
1182         gs->items_max == 0 || gs->items_visible == 0)
1183       Error(ERR_EXIT, "scrollbar gadget incomplete (missing tags)");
1184
1185     /* calculate internal scrollbar values */
1186     gs->size_max = (gi->type == GD_TYPE_SCROLLBAR_VERTICAL ?
1187                     gi->height : gi->width);
1188     gs->size = gs->size_max * gs->items_visible / gs->items_max;
1189     gs->position = gs->size_max * gs->item_position / gs->items_max;
1190     gs->position_max = gs->size_max - gs->size;
1191     gs->correction = gs->size_max / gs->items_max / 2;
1192
1193     /* finetuning for maximal right/bottom position */
1194     if (gs->item_position == gs->items_max - gs->items_visible)
1195       gs->position = gs->position_max;
1196   }
1197
1198   if (gi->type & GD_TYPE_TEXT_AREA)
1199   {
1200     int font_nr = gi->font_active;
1201     int font_width = getFontWidth(font_nr);
1202     int font_height = getFontHeight(font_nr);
1203     int border_xsize = gi->border.xsize;
1204     int border_ysize = gi->border.ysize;
1205
1206     if (gi->width == 0 || gi->height == 0)
1207     {
1208       gi->width  = 2 * border_xsize + gi->textarea.xsize * font_width;
1209       gi->height = 2 * border_ysize + gi->textarea.ysize * font_height;
1210     }
1211     else
1212     {
1213       gi->textarea.xsize = (gi->width  - 2 * border_xsize) / font_width;
1214       gi->textarea.ysize = (gi->height - 2 * border_ysize) / font_height;
1215     }
1216   }
1217 }
1218
1219 void ModifyGadget(struct GadgetInfo *gi, int first_tag, ...)
1220 {
1221   va_list ap;
1222
1223   va_start(ap, first_tag);
1224   HandleGadgetTags(gi, first_tag, ap);
1225   va_end(ap);
1226
1227   RedrawGadget(gi);
1228 }
1229
1230 void RedrawGadget(struct GadgetInfo *gi)
1231 {
1232   if (gi->mapped)
1233     DrawGadget(gi, gi->state, gi->direct_draw);
1234 }
1235
1236 struct GadgetInfo *CreateGadget(int first_tag, ...)
1237 {
1238   struct GadgetInfo *new_gadget = checked_calloc(sizeof(struct GadgetInfo));
1239   va_list ap;
1240
1241   /* always start with reliable default values */
1242   new_gadget->id = getNewGadgetID();
1243   new_gadget->callback_info = default_callback_info;
1244   new_gadget->callback_action = default_callback_action;
1245   new_gadget->active = TRUE;
1246   new_gadget->direct_draw = TRUE;
1247
1248   new_gadget->next = NULL;
1249
1250   va_start(ap, first_tag);
1251   HandleGadgetTags(new_gadget, first_tag, ap);
1252   va_end(ap);
1253
1254   /* insert new gadget into global gadget list */
1255   if (gadget_list_last_entry)
1256   {
1257     gadget_list_last_entry->next = new_gadget;
1258     gadget_list_last_entry = gadget_list_last_entry->next;
1259   }
1260   else
1261     gadget_list_first_entry = gadget_list_last_entry = new_gadget;
1262
1263   return new_gadget;
1264 }
1265
1266 void FreeGadget(struct GadgetInfo *gi)
1267 {
1268   struct GadgetInfo *gi_previous = gadget_list_first_entry;
1269
1270   while (gi_previous != NULL && gi_previous->next != gi)
1271     gi_previous = gi_previous->next;
1272
1273   if (gi == gadget_list_first_entry)
1274     gadget_list_first_entry = gi->next;
1275
1276   if (gi == gadget_list_last_entry)
1277     gadget_list_last_entry = gi_previous;
1278
1279   if (gi_previous != NULL)
1280     gi_previous->next = gi->next;
1281
1282   free(gi);
1283 }
1284
1285 static void CheckRangeOfNumericInputGadget(struct GadgetInfo *gi)
1286 {
1287   if (gi->type != GD_TYPE_TEXT_INPUT_NUMERIC)
1288     return;
1289
1290   gi->textinput.number_value = atoi(gi->textinput.value);
1291
1292   if (gi->textinput.number_value < gi->textinput.number_min)
1293     gi->textinput.number_value = gi->textinput.number_min;
1294   if (gi->textinput.number_value > gi->textinput.number_max)
1295     gi->textinput.number_value = gi->textinput.number_max;
1296
1297   sprintf(gi->textinput.value, "%d", gi->textinput.number_value);
1298
1299   if (gi->textinput.cursor_position < 0)
1300     gi->textinput.cursor_position = 0;
1301   else if (gi->textinput.cursor_position > strlen(gi->textinput.value))
1302     gi->textinput.cursor_position = strlen(gi->textinput.value);
1303 }
1304
1305 /* global pointer to gadget actually in use (when mouse button pressed) */
1306 static struct GadgetInfo *last_gi = NULL;
1307
1308 static void MapGadgetExt(struct GadgetInfo *gi, boolean redraw)
1309 {
1310   if (gi == NULL || gi->mapped)
1311     return;
1312
1313   gi->mapped = TRUE;
1314
1315   if (redraw)
1316     DrawGadget(gi, DG_UNPRESSED, DG_BUFFERED);
1317 }
1318
1319 void MapGadget(struct GadgetInfo *gi)
1320 {
1321   MapGadgetExt(gi, TRUE);
1322 }
1323
1324 void UnmapGadget(struct GadgetInfo *gi)
1325 {
1326   if (gi == NULL || !gi->mapped)
1327     return;
1328
1329   gi->mapped = FALSE;
1330
1331   if (gi == last_gi)
1332     last_gi = NULL;
1333 }
1334
1335 #define MAX_NUM_GADGETS         1024
1336 #define MULTIMAP_UNMAP          (1 << 0)
1337 #define MULTIMAP_REMAP          (1 << 1)
1338 #define MULTIMAP_REDRAW         (1 << 2)
1339 #define MULTIMAP_PLAYFIELD      (1 << 3)
1340 #define MULTIMAP_DOOR_1         (1 << 4)
1341 #define MULTIMAP_DOOR_2         (1 << 5)
1342 #define MULTIMAP_ALL            (MULTIMAP_PLAYFIELD | \
1343                                  MULTIMAP_DOOR_1 | \
1344                                  MULTIMAP_DOOR_2)
1345
1346 static void MultiMapGadgets(int mode)
1347 {
1348   struct GadgetInfo *gi = gadget_list_first_entry;
1349   static boolean map_state[MAX_NUM_GADGETS];
1350   int map_count = 0;
1351
1352   while (gi != NULL)
1353   {
1354     if ((mode & MULTIMAP_PLAYFIELD &&
1355          gi->x < gfx.sx + gfx.sxsize) ||
1356         (mode & MULTIMAP_DOOR_1 &&
1357          gi->x >= gfx.dx && gi->y < gfx.dy + gfx.dysize) ||
1358         (mode & MULTIMAP_DOOR_2 &&
1359          gi->x >= gfx.dx && gi->y > gfx.dy + gfx.dysize) ||
1360         (mode & MULTIMAP_ALL) == MULTIMAP_ALL)
1361     {
1362       if (mode & MULTIMAP_UNMAP)
1363       {
1364         map_state[map_count++ % MAX_NUM_GADGETS] = gi->mapped;
1365         UnmapGadget(gi);
1366       }
1367       else
1368       {
1369         if (map_state[map_count++ % MAX_NUM_GADGETS])
1370           MapGadgetExt(gi, (mode & MULTIMAP_REDRAW));
1371       }
1372     }
1373
1374     gi = gi->next;
1375   }
1376 }
1377
1378 void UnmapAllGadgets()
1379 {
1380   MultiMapGadgets(MULTIMAP_ALL | MULTIMAP_UNMAP);
1381 }
1382
1383 void RemapAllGadgets()
1384 {
1385   MultiMapGadgets(MULTIMAP_ALL | MULTIMAP_REMAP);
1386 }
1387
1388 static boolean anyTextInputGadgetActive()
1389 {
1390   return (last_gi && (last_gi->type & GD_TYPE_TEXT_INPUT) && last_gi->mapped);
1391 }
1392
1393 static boolean anyTextAreaGadgetActive()
1394 {
1395   return (last_gi && (last_gi->type & GD_TYPE_TEXT_AREA) && last_gi->mapped);
1396 }
1397
1398 static boolean anySelectboxGadgetActive()
1399 {
1400   return (last_gi && (last_gi->type & GD_TYPE_SELECTBOX) && last_gi->mapped);
1401 }
1402
1403 boolean anyTextGadgetActive()
1404 {
1405   return (anyTextInputGadgetActive() ||
1406           anyTextAreaGadgetActive() ||
1407           anySelectboxGadgetActive());
1408 }
1409
1410 void ClickOnGadget(struct GadgetInfo *gi, int button)
1411 {
1412   /* simulate releasing mouse button over last gadget, if still pressed */
1413   if (button_status)
1414     HandleGadgets(-1, -1, 0);
1415
1416   /* simulate pressing mouse button over specified gadget */
1417   HandleGadgets(gi->x, gi->y, button);
1418
1419   /* simulate releasing mouse button over specified gadget */
1420   HandleGadgets(gi->x, gi->y, 0);
1421 }
1422
1423 void HandleGadgets(int mx, int my, int button)
1424 {
1425   static struct GadgetInfo *last_info_gi = NULL;
1426   static unsigned long pressed_delay = 0;
1427   static int last_button = 0;
1428   static int last_mx = 0, last_my = 0;
1429   int scrollbar_mouse_pos = 0;
1430   struct GadgetInfo *new_gi, *gi;
1431   boolean press_event;
1432   boolean release_event;
1433   boolean mouse_moving;
1434   boolean gadget_pressed;
1435   boolean gadget_pressed_repeated;
1436   boolean gadget_moving;
1437   boolean gadget_moving_inside;
1438   boolean gadget_moving_off_borders;
1439   boolean gadget_released;
1440   boolean gadget_released_inside;
1441   boolean gadget_released_inside_select_line;
1442   boolean gadget_released_inside_select_area;
1443   boolean gadget_released_off_borders;
1444   boolean changed_position = FALSE;
1445
1446   /* check if there are any gadgets defined */
1447   if (gadget_list_first_entry == NULL)
1448     return;
1449
1450   /* simulated release of mouse button over last gadget */
1451   if (mx == -1 && my == -1 && button == 0)
1452   {
1453     mx = last_mx;
1454     my = last_my;
1455   }
1456
1457   /* check which gadget is under the mouse pointer */
1458   new_gi = getGadgetInfoFromMousePosition(mx, my);
1459
1460   /* check if button state has changed since last invocation */
1461   press_event = (button != 0 && last_button == 0);
1462   release_event = (button == 0 && last_button != 0);
1463   last_button = button;
1464
1465   /* check if mouse has been moved since last invocation */
1466   mouse_moving = ((mx != last_mx || my != last_my) && motion_status);
1467   last_mx = mx;
1468   last_my = my;
1469
1470 #if 1
1471
1472 #if 1
1473
1474   /* if mouse button pressed outside text or selectbox gadget, deactivate it */
1475   if (anyTextGadgetActive() &&
1476       button != 0 && !motion_status && new_gi != last_gi)
1477   {
1478     CheckRangeOfNumericInputGadget(last_gi);    /* in case of numeric gadget */
1479
1480     DrawGadget(last_gi, DG_UNPRESSED, last_gi->direct_draw);
1481
1482     last_gi->event.type = GD_EVENT_TEXT_LEAVING;
1483
1484     if (last_gi->event_mask & GD_EVENT_TEXT_LEAVING)
1485       last_gi->callback_action(last_gi);
1486
1487     last_gi = NULL;
1488   }
1489
1490 #else
1491
1492   /* special treatment for leaving text and number input gadgets */
1493   if (anyTextInputGadgetActive() &&
1494       button != 0 && !motion_status && new_gi != last_gi)
1495   {
1496     /* if mouse button pressed outside text input gadget, deactivate it */
1497     CheckRangeOfNumericInputGadget(last_gi);
1498     DrawGadget(last_gi, DG_UNPRESSED, last_gi->direct_draw);
1499
1500     last_gi->event.type = GD_EVENT_TEXT_LEAVING;
1501
1502     if (last_gi->event_mask & GD_EVENT_TEXT_LEAVING)
1503       last_gi->callback_action(last_gi);
1504
1505     last_gi = NULL;
1506   }
1507
1508   /* special treatment for leaving text area gadgets */
1509   if (anyTextAreaGadgetActive() &&
1510       button != 0 && !motion_status && new_gi != last_gi)
1511   {
1512     /* if mouse button pressed outside text input gadget, deactivate it */
1513     DrawGadget(last_gi, DG_UNPRESSED, last_gi->direct_draw);
1514
1515     last_gi->event.type = GD_EVENT_TEXT_LEAVING;
1516
1517     if (last_gi->event_mask & GD_EVENT_TEXT_LEAVING)
1518       last_gi->callback_action(last_gi);
1519
1520     last_gi = NULL;
1521   }
1522
1523   /* special treatment for leaving selectbox gadgets */
1524   if (anySelectboxGadgetActive() &&
1525       button != 0 && !motion_status && new_gi != last_gi)
1526   {
1527     /* if mouse button pressed outside selectbox gadget, deactivate it */
1528     DrawGadget(last_gi, DG_UNPRESSED, last_gi->direct_draw);
1529
1530     last_gi->event.type = GD_EVENT_TEXT_LEAVING;
1531
1532     if (last_gi->event_mask & GD_EVENT_TEXT_LEAVING)
1533       last_gi->callback_action(last_gi);
1534
1535     last_gi = NULL;
1536   }
1537
1538 #endif
1539
1540 #else
1541
1542   /* special treatment for text and number input gadgets */
1543   if (anyTextInputGadgetActive() && button != 0 && !motion_status)
1544   {
1545     struct GadgetInfo *gi = last_gi;
1546
1547     if (new_gi == last_gi)
1548     {
1549       int old_cursor_position = gi->textinput.cursor_position;
1550
1551       /* if mouse button pressed inside activated text gadget, set cursor */
1552       gi->textinput.cursor_position =
1553         (mx - gi->x - gi->border.xsize) / getFontWidth(gi->font);
1554
1555       if (gi->textinput.cursor_position < 0)
1556         gi->textinput.cursor_position = 0;
1557       else if (gi->textinput.cursor_position > strlen(gi->textinput.value))
1558         gi->textinput.cursor_position = strlen(gi->textinput.value);
1559
1560       if (gi->textinput.cursor_position != old_cursor_position)
1561         DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1562     }
1563     else
1564     {
1565       /* if mouse button pressed outside text input gadget, deactivate it */
1566       CheckRangeOfNumericInputGadget(gi);
1567       DrawGadget(gi, DG_UNPRESSED, gi->direct_draw);
1568
1569       gi->event.type = GD_EVENT_TEXT_LEAVING;
1570
1571       if (gi->event_mask & GD_EVENT_TEXT_LEAVING)
1572         gi->callback_action(gi);
1573
1574       last_gi = NULL;
1575     }
1576   }
1577
1578   /* special treatment for text area gadgets */
1579   if (anyTextAreaGadgetActive() && button != 0 && !motion_status)
1580   {
1581     struct GadgetInfo *gi = last_gi;
1582
1583     if (new_gi == last_gi)
1584     {
1585       int old_cursor_position = gi->textarea.cursor_position;
1586       int x = (mx - gi->x - gi->border.xsize) / getFontWidth(gi->font);
1587       int y = (my - gi->y - gi->border.ysize) / getFontHeight(gi->font);
1588
1589       x = (x < 0 ? 0 : x >= gi->textarea.xsize ? gi->textarea.xsize - 1 : x);
1590       y = (y < 0 ? 0 : y >= gi->textarea.ysize ? gi->textarea.ysize - 1 : y);
1591
1592       setTextAreaCursorXY(gi, x, y);
1593
1594 #if 0
1595       printf("::: %d -----> %d\n",
1596              old_cursor_position,
1597              gi->textarea.cursor_position);
1598 #endif
1599
1600       if (gi->textarea.cursor_position != old_cursor_position)
1601         DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1602     }
1603     else
1604     {
1605       /* if mouse button pressed outside text input gadget, deactivate it */
1606       DrawGadget(gi, DG_UNPRESSED, gi->direct_draw);
1607
1608       gi->event.type = GD_EVENT_TEXT_LEAVING;
1609
1610       if (gi->event_mask & GD_EVENT_TEXT_LEAVING)
1611         gi->callback_action(gi);
1612
1613       last_gi = NULL;
1614     }
1615   }
1616
1617   /* special treatment for selectbox gadgets */
1618   if (anySelectboxGadgetActive() && button != 0 && !motion_status)
1619   {
1620     struct GadgetInfo *gi = last_gi;
1621
1622     if (new_gi == last_gi)
1623     {
1624       int old_index = gi->selectbox.current_index;
1625
1626       /* if mouse button pressed inside activated selectbox, select value */
1627       if (my >= gi->selectbox.y && my < gi->selectbox.y + gi->selectbox.height)
1628         gi->selectbox.current_index =
1629           (my - gi->selectbox.y - gi->border.ysize) / getFontHeight(gi->font);
1630
1631       if (gi->selectbox.current_index < 0)
1632         gi->selectbox.current_index = 0;
1633       else if (gi->selectbox.current_index > gi->selectbox.num_values - 1)
1634         gi->selectbox.current_index = gi->selectbox.num_values - 1;
1635
1636       if (gi->selectbox.current_index != old_index)
1637         DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1638     }
1639     else
1640     {
1641       /* if mouse button pressed outside selectbox gadget, deactivate it */
1642       DrawGadget(gi, DG_UNPRESSED, gi->direct_draw);
1643
1644       gi->event.type = GD_EVENT_TEXT_LEAVING;
1645
1646       if (gi->event_mask & GD_EVENT_TEXT_LEAVING)
1647         gi->callback_action(gi);
1648
1649       last_gi = NULL;
1650     }
1651   }
1652
1653 #endif
1654
1655   gadget_pressed =
1656     (button != 0 && last_gi == NULL && new_gi != NULL && press_event);
1657   gadget_pressed_repeated =
1658     (button != 0 && last_gi != NULL && new_gi == last_gi);
1659
1660   gadget_released =             (release_event && last_gi != NULL);
1661   gadget_released_inside =      (gadget_released && new_gi == last_gi);
1662   gadget_released_off_borders = (gadget_released && new_gi != last_gi);
1663
1664   gadget_moving =             (button != 0 && last_gi != NULL && mouse_moving);
1665   gadget_moving_inside =      (gadget_moving && new_gi == last_gi);
1666   gadget_moving_off_borders = (gadget_moving && new_gi != last_gi);
1667
1668   /* when handling selectbox, set additional state values */
1669   if (gadget_released_inside && (last_gi->type & GD_TYPE_SELECTBOX))
1670   {
1671     struct GadgetInfo *gi = last_gi;
1672
1673     gadget_released_inside_select_line =
1674       (mx >= gi->x && mx < gi->x + gi->width &&
1675        my >= gi->y && my < gi->y + gi->height);
1676     gadget_released_inside_select_area =
1677       (mx >= gi->selectbox.x && mx < gi->selectbox.x + gi->selectbox.width &&
1678        my >= gi->selectbox.y && my < gi->selectbox.y + gi->selectbox.height);
1679   }
1680   else
1681   {
1682     gadget_released_inside_select_line = FALSE;
1683     gadget_released_inside_select_area = FALSE;
1684   }
1685
1686   /* if new gadget pressed, store this gadget  */
1687   if (gadget_pressed)
1688     last_gi = new_gi;
1689
1690   /* 'gi' is actually handled gadget */
1691   gi = last_gi;
1692
1693   /* if gadget is scrollbar, choose mouse position value */
1694   if (gi && gi->type & GD_TYPE_SCROLLBAR)
1695     scrollbar_mouse_pos =
1696       (gi->type == GD_TYPE_SCROLLBAR_HORIZONTAL ? mx - gi->x : my - gi->y);
1697
1698   /* if mouse button released, no gadget needs to be handled anymore */
1699   if (gadget_released)
1700   {
1701     if (last_gi->type & GD_TYPE_SELECTBOX &&
1702         (gadget_released_inside_select_line ||
1703          gadget_released_off_borders))               /* selectbox stays open */
1704       gi->selectbox.stay_open = TRUE;
1705     else if (!(last_gi->type & GD_TYPE_TEXT_INPUT ||
1706                last_gi->type & GD_TYPE_TEXT_AREA))  /* text input stays open */
1707       last_gi = NULL;
1708   }
1709
1710   /* modify event position values even if no gadget is pressed */
1711   if (button == 0 && !release_event)
1712     gi = new_gi;
1713
1714   if (gi != NULL)
1715   {
1716     int last_x = gi->event.x;
1717     int last_y = gi->event.y;
1718
1719     gi->event.x = mx - gi->x;
1720     gi->event.y = my - gi->y;
1721
1722     if (gi->type == GD_TYPE_DRAWING_AREA)
1723     {
1724       gi->event.x /= gi->drawing.item_xsize;
1725       gi->event.y /= gi->drawing.item_ysize;
1726
1727       if (last_x != gi->event.x || last_y != gi->event.y)
1728         changed_position = TRUE;
1729     }
1730     else if (gi->type & GD_TYPE_TEXT_INPUT && button != 0 && !motion_status)
1731     {
1732       int old_cursor_position = gi->textinput.cursor_position;
1733
1734       /* if mouse button pressed inside activated text gadget, set cursor */
1735       gi->textinput.cursor_position =
1736         (mx - gi->x - gi->border.xsize) / getFontWidth(gi->font);
1737
1738       if (gi->textinput.cursor_position < 0)
1739         gi->textinput.cursor_position = 0;
1740       else if (gi->textinput.cursor_position > strlen(gi->textinput.value))
1741         gi->textinput.cursor_position = strlen(gi->textinput.value);
1742
1743       if (gi->textinput.cursor_position != old_cursor_position)
1744         DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1745     }
1746     else if (gi->type & GD_TYPE_TEXT_AREA && button != 0 && !motion_status)
1747     {
1748       int old_cursor_position = gi->textarea.cursor_position;
1749       int x = (mx - gi->x - gi->border.xsize) / getFontWidth(gi->font);
1750       int y = (my - gi->y - gi->border.ysize) / getFontHeight(gi->font);
1751
1752       x = (x < 0 ? 0 : x >= gi->textarea.xsize ? gi->textarea.xsize - 1 : x);
1753       y = (y < 0 ? 0 : y >= gi->textarea.ysize ? gi->textarea.ysize - 1 : y);
1754
1755       setTextAreaCursorXY(gi, x, y);
1756
1757       if (gi->textarea.cursor_position != old_cursor_position)
1758         DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1759     }
1760     else if (gi->type & GD_TYPE_SELECTBOX)
1761     {
1762       int old_index = gi->selectbox.current_index;
1763
1764       /* if mouse moving inside activated selectbox, select value */
1765       if (my >= gi->selectbox.y && my < gi->selectbox.y + gi->selectbox.height)
1766         gi->selectbox.current_index =
1767           (my - gi->selectbox.y - gi->border.ysize) / getFontHeight(gi->font);
1768
1769       if (gi->selectbox.current_index < 0)
1770         gi->selectbox.current_index = 0;
1771       else if (gi->selectbox.current_index > gi->selectbox.num_values - 1)
1772         gi->selectbox.current_index = gi->selectbox.num_values - 1;
1773
1774       if (gi->selectbox.current_index != old_index)
1775         DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1776     }
1777   }
1778
1779   /* handle gadget popup info text */
1780   if (last_info_gi != new_gi ||
1781       (new_gi && new_gi->type == GD_TYPE_DRAWING_AREA && changed_position))
1782   {
1783     if (new_gi != NULL && (button == 0 || new_gi == last_gi))
1784     {
1785       new_gi->event.type = GD_EVENT_INFO_ENTERING;
1786       new_gi->callback_info(new_gi);
1787     }
1788     else if (last_info_gi != NULL)
1789     {
1790       last_info_gi->event.type = GD_EVENT_INFO_LEAVING;
1791       last_info_gi->callback_info(last_info_gi);
1792
1793 #if 0
1794       default_callback_info(NULL);
1795
1796       printf("It seems that we are leaving gadget [%s]!\n",
1797              (last_info_gi != NULL &&
1798               last_info_gi->info_text != NULL ?
1799               last_info_gi->info_text : ""));
1800 #endif
1801     }
1802
1803     last_info_gi = new_gi;
1804   }
1805
1806   if (gadget_pressed)
1807   {
1808     if (gi->type == GD_TYPE_CHECK_BUTTON)
1809     {
1810       gi->checked = !gi->checked;
1811     }
1812     else if (gi->type == GD_TYPE_RADIO_BUTTON)
1813     {
1814       struct GadgetInfo *rgi = gadget_list_first_entry;
1815
1816       while (rgi)
1817       {
1818         if (rgi->mapped &&
1819             rgi->type == GD_TYPE_RADIO_BUTTON &&
1820             rgi->radio_nr == gi->radio_nr &&
1821             rgi != gi)
1822         {
1823           rgi->checked = FALSE;
1824           DrawGadget(rgi, DG_UNPRESSED, rgi->direct_draw);
1825         }
1826
1827         rgi = rgi->next;
1828       }
1829
1830       gi->checked = TRUE;
1831     }
1832     else if (gi->type & GD_TYPE_SCROLLBAR)
1833     {
1834       int mpos, gpos;
1835
1836       if (gi->type == GD_TYPE_SCROLLBAR_HORIZONTAL)
1837       {
1838         mpos = mx;
1839         gpos = gi->x;
1840       }
1841       else
1842       {
1843         mpos = my;
1844         gpos = gi->y;
1845       }
1846
1847       if (mpos >= gpos + gi->scrollbar.position &&
1848           mpos < gpos + gi->scrollbar.position + gi->scrollbar.size)
1849       {
1850         /* drag scrollbar */
1851         gi->scrollbar.drag_position =
1852           scrollbar_mouse_pos - gi->scrollbar.position;
1853       }
1854       else
1855       {
1856         /* click scrollbar one scrollbar length up/left or down/right */
1857
1858         struct GadgetScrollbar *gs = &gi->scrollbar;
1859         int old_item_position = gs->item_position;
1860
1861         changed_position = FALSE;
1862
1863         gs->item_position +=
1864           gs->items_visible * (mpos < gpos + gi->scrollbar.position ? -1 : +1);
1865
1866         if (gs->item_position < 0)
1867           gs->item_position = 0;
1868         if (gs->item_position > gs->items_max - gs->items_visible)
1869           gs->item_position = gs->items_max - gs->items_visible;
1870
1871         if (old_item_position != gs->item_position)
1872         {
1873           gi->event.item_position = gs->item_position;
1874           changed_position = TRUE;
1875         }
1876
1877         ModifyGadget(gi, GDI_SCROLLBAR_ITEM_POSITION, gs->item_position,
1878                      GDI_END);
1879
1880         gi->state = GD_BUTTON_UNPRESSED;
1881         gi->event.type = GD_EVENT_MOVING;
1882         gi->event.off_borders = FALSE;
1883
1884         if (gi->event_mask & GD_EVENT_MOVING && changed_position)
1885           gi->callback_action(gi);
1886
1887         /* don't handle this scrollbar anymore while mouse button pressed */
1888         last_gi = NULL;
1889
1890         return;
1891       }
1892     }
1893
1894     /* !!! bad for TEXT_INPUT ... !!! */
1895     DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1896
1897     gi->state = GD_BUTTON_PRESSED;
1898     gi->event.type = GD_EVENT_PRESSED;
1899     gi->event.button = button;
1900     gi->event.off_borders = FALSE;
1901
1902     /* initialize delay counter */
1903     DelayReached(&pressed_delay, 0);
1904
1905     if (gi->event_mask & GD_EVENT_PRESSED)
1906       gi->callback_action(gi);
1907   }
1908
1909 #if 0
1910   if (!mouse_moving)
1911     printf("::: PRESSED...?\n");
1912 #endif
1913
1914   if (gadget_pressed_repeated)
1915   {
1916     gi->event.type = GD_EVENT_PRESSED;
1917
1918     if (gi->event_mask & GD_EVENT_REPEATED &&
1919         DelayReached(&pressed_delay, GADGET_FRAME_DELAY))
1920       gi->callback_action(gi);
1921   }
1922
1923   if (gadget_moving)
1924   {
1925     if (gi->type & GD_TYPE_BUTTON)
1926     {
1927       if (gadget_moving_inside && gi->state == GD_BUTTON_UNPRESSED)
1928         DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1929       else if (gadget_moving_off_borders && gi->state == GD_BUTTON_PRESSED)
1930         DrawGadget(gi, DG_UNPRESSED, gi->direct_draw);
1931     }
1932     else if (gi->type & GD_TYPE_SELECTBOX)
1933     {
1934       int old_index = gi->selectbox.current_index;
1935
1936       /* if mouse moving inside activated selectbox, select value */
1937       if (my >= gi->selectbox.y && my < gi->selectbox.y + gi->selectbox.height)
1938         gi->selectbox.current_index =
1939           (my - gi->selectbox.y - gi->border.ysize) / getFontHeight(gi->font);
1940
1941       if (gi->selectbox.current_index < 0)
1942         gi->selectbox.current_index = 0;
1943       else if (gi->selectbox.current_index > gi->selectbox.num_values - 1)
1944         gi->selectbox.current_index = gi->selectbox.num_values - 1;
1945
1946       if (gi->selectbox.current_index != old_index)
1947         DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1948     }
1949     else if (gi->type & GD_TYPE_SCROLLBAR)
1950     {
1951       struct GadgetScrollbar *gs = &gi->scrollbar;
1952       int old_item_position = gs->item_position;
1953
1954       gs->position = scrollbar_mouse_pos - gs->drag_position;
1955
1956       if (gs->position < 0)
1957         gs->position = 0;
1958       if (gs->position > gs->position_max)
1959         gs->position = gs->position_max;
1960
1961       gs->item_position =
1962         gs->items_max * (gs->position + gs->correction) / gs->size_max;
1963
1964       if (gs->item_position < 0)
1965         gs->item_position = 0;
1966       if (gs->item_position > gs->items_max - 1)
1967         gs->item_position = gs->items_max - 1;
1968
1969       if (old_item_position != gs->item_position)
1970       {
1971         gi->event.item_position = gs->item_position;
1972         changed_position = TRUE;
1973       }
1974
1975       DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1976     }
1977
1978     gi->state = (gadget_moving_inside || gi->type & GD_TYPE_SCROLLBAR ?
1979                  GD_BUTTON_PRESSED : GD_BUTTON_UNPRESSED);
1980     gi->event.type = GD_EVENT_MOVING;
1981     gi->event.off_borders = gadget_moving_off_borders;
1982
1983     if (gi->event_mask & GD_EVENT_MOVING && changed_position &&
1984         (gadget_moving_inside || gi->event_mask & GD_EVENT_OFF_BORDERS))
1985       gi->callback_action(gi);
1986   }
1987
1988   if (gadget_released_inside)
1989   {
1990     boolean deactivate_gadget = TRUE;
1991
1992     if (gi->type & GD_TYPE_SELECTBOX)
1993     {
1994       if (gadget_released_inside_select_line ||
1995           gadget_released_off_borders)              /* selectbox stays open */
1996         deactivate_gadget = FALSE;
1997       else
1998         gi->selectbox.index = gi->selectbox.current_index;
1999     }
2000
2001     if (deactivate_gadget &&
2002         !(gi->type & GD_TYPE_TEXT_INPUT ||
2003           gi->type & GD_TYPE_TEXT_AREA))            /* text input stays open */
2004       DrawGadget(gi, DG_UNPRESSED, gi->direct_draw);
2005
2006     gi->state = GD_BUTTON_UNPRESSED;
2007     gi->event.type = GD_EVENT_RELEASED;
2008
2009     if ((gi->event_mask & GD_EVENT_RELEASED) && deactivate_gadget)
2010       gi->callback_action(gi);
2011   }
2012
2013   if (gadget_released_off_borders)
2014   {
2015     if (gi->type & GD_TYPE_SCROLLBAR)
2016       DrawGadget(gi, DG_UNPRESSED, gi->direct_draw);
2017
2018     gi->event.type = GD_EVENT_RELEASED;
2019
2020     if (gi->event_mask & GD_EVENT_RELEASED &&
2021         gi->event_mask & GD_EVENT_OFF_BORDERS)
2022       gi->callback_action(gi);
2023   }
2024
2025   /* handle gadgets unmapped/mapped between pressing and releasing */
2026   if (release_event && !gadget_released && new_gi)
2027     new_gi->state = GD_BUTTON_UNPRESSED;
2028 }
2029
2030 static void insertCharIntoTextArea(struct GadgetInfo *gi, char c)
2031 {
2032   char text[MAX_GADGET_TEXTSIZE];
2033   int cursor_position = gi->textarea.cursor_position;
2034
2035   if (strlen(gi->textarea.value) == MAX_GADGET_TEXTSIZE) /* no space left */
2036     return;
2037
2038 #if 0
2039   printf("::: '%s' + '%c'", gi->textarea.value, c);
2040 #endif
2041
2042   strcpy(text, gi->textarea.value);
2043   strcpy(&gi->textarea.value[cursor_position + 1], &text[cursor_position]);
2044   gi->textarea.value[cursor_position] = c;
2045
2046 #if 0
2047   printf(" => '%s'\n", gi->textarea.value);
2048 #endif
2049
2050   setTextAreaCursorPosition(gi, gi->textarea.cursor_position + 1);
2051 }
2052
2053 void HandleGadgetsKeyInput(Key key)
2054 {
2055   struct GadgetInfo *gi = last_gi;
2056
2057   if (gi == NULL || !gi->mapped ||
2058       !(gi->type & GD_TYPE_TEXT_INPUT ||
2059         gi->type & GD_TYPE_TEXT_AREA ||
2060         gi->type & GD_TYPE_SELECTBOX))
2061     return;
2062
2063   if (key == KSYM_Return)       /* valid for both text input and selectbox */
2064   {
2065     if (gi->type & GD_TYPE_TEXT_INPUT)
2066       CheckRangeOfNumericInputGadget(gi);
2067     else if (gi->type & GD_TYPE_SELECTBOX)
2068       gi->selectbox.index = gi->selectbox.current_index;
2069
2070     if (gi->type & GD_TYPE_TEXT_AREA)
2071     {
2072       insertCharIntoTextArea(gi, '\n');
2073
2074       DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2075     }
2076     else
2077     {
2078       DrawGadget(gi, DG_UNPRESSED, gi->direct_draw);
2079
2080       gi->event.type = GD_EVENT_TEXT_RETURN;
2081
2082       last_gi = NULL;
2083     }
2084
2085     if (gi->event_mask & GD_EVENT_TEXT_RETURN)
2086       gi->callback_action(gi);
2087   }
2088   else if (gi->type & GD_TYPE_TEXT_INPUT)       /* only valid for text input */
2089   {
2090     char text[MAX_GADGET_TEXTSIZE];
2091     int text_length = strlen(gi->textinput.value);
2092     int cursor_pos = gi->textinput.cursor_position;
2093     char letter = getCharFromKey(key);
2094     boolean legal_letter = (gi->type == GD_TYPE_TEXT_INPUT_NUMERIC ?
2095                             letter >= '0' && letter <= '9' :
2096                             letter != 0);
2097
2098     if (legal_letter && text_length < gi->textinput.size)
2099     {
2100       strcpy(text, gi->textinput.value);
2101       strcpy(&gi->textinput.value[cursor_pos + 1], &text[cursor_pos]);
2102       gi->textinput.value[cursor_pos] = letter;
2103       gi->textinput.cursor_position++;
2104
2105       DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2106     }
2107     else if (key == KSYM_Left && cursor_pos > 0)
2108     {
2109       gi->textinput.cursor_position--;
2110
2111       DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2112     }
2113     else if (key == KSYM_Right && cursor_pos < text_length)
2114     {
2115       gi->textinput.cursor_position++;
2116
2117       DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2118     }
2119     else if (key == KSYM_BackSpace && cursor_pos > 0)
2120     {
2121       strcpy(text, gi->textinput.value);
2122       strcpy(&gi->textinput.value[cursor_pos - 1], &text[cursor_pos]);
2123       gi->textinput.cursor_position--;
2124
2125       DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2126     }
2127     else if (key == KSYM_Delete && cursor_pos < text_length)
2128     {
2129       strcpy(text, gi->textinput.value);
2130       strcpy(&gi->textinput.value[cursor_pos], &text[cursor_pos + 1]);
2131
2132       DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2133     }
2134   }
2135   else if (gi->type & GD_TYPE_TEXT_AREA)        /* only valid for text area */
2136   {
2137     char text[MAX_GADGET_TEXTSIZE];
2138     int text_length = strlen(gi->textarea.value);
2139     int area_ysize = gi->textarea.ysize;
2140     int cursor_x_pref = gi->textarea.cursor_x_preferred;
2141     int cursor_y = gi->textarea.cursor_y;
2142     int cursor_pos = gi->textarea.cursor_position;
2143     char letter = getCharFromKey(key);
2144     boolean legal_letter = (letter != 0);
2145
2146 #if 0
2147     printf("::: KEY: %x!\n", key);
2148 #endif
2149
2150     if (legal_letter)
2151     {
2152       insertCharIntoTextArea(gi, letter);
2153
2154       DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2155     }
2156     else if (key == KSYM_Left && cursor_pos > 0)
2157     {
2158       setTextAreaCursorPosition(gi, gi->textarea.cursor_position - 1);
2159
2160       DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2161     }
2162     else if (key == KSYM_Right && cursor_pos < text_length)
2163     {
2164       setTextAreaCursorPosition(gi, gi->textarea.cursor_position + 1);
2165
2166       DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2167     }
2168     else if (key == KSYM_Up && cursor_y > 0)
2169     {
2170       setTextAreaCursorXY(gi, cursor_x_pref, cursor_y - 1);
2171       gi->textarea.cursor_x_preferred = cursor_x_pref;
2172
2173       DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2174     }
2175     else if (key == KSYM_Down && cursor_y < area_ysize - 1)
2176     {
2177       setTextAreaCursorXY(gi, cursor_x_pref, cursor_y + 1);
2178       gi->textarea.cursor_x_preferred = cursor_x_pref;
2179
2180       DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2181     }
2182     else if (key == KSYM_BackSpace && cursor_pos > 0)
2183     {
2184       strcpy(text, gi->textarea.value);
2185       strcpy(&gi->textarea.value[cursor_pos - 1], &text[cursor_pos]);
2186
2187       setTextAreaCursorPosition(gi, gi->textarea.cursor_position - 1);
2188
2189       DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2190     }
2191     else if (key == KSYM_Delete && cursor_pos < text_length)
2192     {
2193       strcpy(text, gi->textarea.value);
2194       strcpy(&gi->textarea.value[cursor_pos], &text[cursor_pos + 1]);
2195
2196       DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2197     }
2198   }
2199   else if (gi->type & GD_TYPE_SELECTBOX)        /* only valid for selectbox */
2200   {
2201     int index = gi->selectbox.current_index;
2202     int num_values = gi->selectbox.num_values;
2203
2204     if (key == KSYM_Up && index > 0)
2205     {
2206       gi->selectbox.current_index--;
2207
2208       DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2209     }
2210     else if (key == KSYM_Down && index < num_values - 1)
2211     {
2212       gi->selectbox.current_index++;
2213
2214       DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2215     }
2216   }
2217 }