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