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