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