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