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