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