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