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