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