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