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