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