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