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