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