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