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