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