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