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