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