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