rnd-20001202-2-src
[rocksndiamonds.git] / src / libgame / gadgets.c
1 /***********************************************************
2 *  Rocks'n'Diamonds -- McDuffin Strikes Back!              *
3 *----------------------------------------------------------*
4 *  (c) 1995-98 Artsoft Entertainment                       *
5 *              Holger Schemel                              *
6 *              Oststrasse 11a                              *
7 *              33604 Bielefeld                             *
8 *              phone: ++49 +521 290471                     *
9 *              email: aeglos@valinor.owl.de                *
10 *----------------------------------------------------------*
11 *  gadgets.c                                               *
12 ***********************************************************/
13
14 #include <stdarg.h>
15
16 #include "libgame.h"
17
18 #include "main_TMP.h"
19
20 #include "gadgets.h"
21
22 /* some positions in the video tape control window */
23 #define VIDEO_DATE_LABEL_XPOS   (VIDEO_DISPLAY1_XPOS)
24 #define VIDEO_DATE_LABEL_YPOS   (VIDEO_DISPLAY1_YPOS)
25 #define VIDEO_DATE_LABEL_XSIZE  (VIDEO_DISPLAY_XSIZE)
26 #define VIDEO_DATE_LABEL_YSIZE  (VIDEO_DISPLAY_YSIZE)
27 #define VIDEO_DATE_XPOS         (VIDEO_DISPLAY1_XPOS + 1)
28 #define VIDEO_DATE_YPOS         (VIDEO_DISPLAY1_YPOS + 14)
29 #define VIDEO_DATE_XSIZE        (VIDEO_DISPLAY_XSIZE)
30 #define VIDEO_DATE_YSIZE        16
31 #define VIDEO_REC_LABEL_XPOS    (VIDEO_DISPLAY2_XPOS)
32 #define VIDEO_REC_LABEL_YPOS    (VIDEO_DISPLAY2_YPOS)
33 #define VIDEO_REC_LABEL_XSIZE   20
34 #define VIDEO_REC_LABEL_YSIZE   12
35 #define VIDEO_REC_SYMBOL_XPOS   (VIDEO_DISPLAY2_XPOS + 20)
36 #define VIDEO_REC_SYMBOL_YPOS   (VIDEO_DISPLAY2_YPOS)
37 #define VIDEO_REC_SYMBOL_XSIZE  16
38 #define VIDEO_REC_SYMBOL_YSIZE  16
39 #define VIDEO_PLAY_LABEL_XPOS   (VIDEO_DISPLAY2_XPOS + 65)
40 #define VIDEO_PLAY_LABEL_YPOS   (VIDEO_DISPLAY2_YPOS)
41 #define VIDEO_PLAY_LABEL_XSIZE  22
42 #define VIDEO_PLAY_LABEL_YSIZE  12
43 #define VIDEO_PLAY_SYMBOL_XPOS  (VIDEO_DISPLAY2_XPOS + 52)
44 #define VIDEO_PLAY_SYMBOL_YPOS  (VIDEO_DISPLAY2_YPOS)
45 #define VIDEO_PLAY_SYMBOL_XSIZE 11
46 #define VIDEO_PLAY_SYMBOL_YSIZE 13
47 #define VIDEO_PAUSE_LABEL_XPOS  (VIDEO_DISPLAY2_XPOS)
48 #define VIDEO_PAUSE_LABEL_YPOS  (VIDEO_DISPLAY2_YPOS + 20)
49 #define VIDEO_PAUSE_LABEL_XSIZE 35
50 #define VIDEO_PAUSE_LABEL_YSIZE 8
51 #define VIDEO_PAUSE_SYMBOL_XPOS (VIDEO_DISPLAY2_XPOS + 35)
52 #define VIDEO_PAUSE_SYMBOL_YPOS (VIDEO_DISPLAY2_YPOS)
53 #define VIDEO_PAUSE_SYMBOL_XSIZE 17
54 #define VIDEO_PAUSE_SYMBOL_YSIZE 13
55 #define VIDEO_TIME_XPOS         (VIDEO_DISPLAY2_XPOS + 38)
56 #define VIDEO_TIME_YPOS         (VIDEO_DISPLAY2_YPOS + 14)
57 #define VIDEO_TIME_XSIZE        50
58 #define VIDEO_TIME_YSIZE        16
59
60 /* special */
61 #define VIDEO_PBEND_LABEL_XPOS  6
62 #define VIDEO_PBEND_LABEL_YPOS  220
63 #define VIDEO_PBEND_LABEL_XSIZE 35
64 #define VIDEO_PBEND_LABEL_YSIZE 30
65
66 #define VIDEO_STATE_OFF         (VIDEO_STATE_PLAY_OFF   |       \
67                                  VIDEO_STATE_REC_OFF    |       \
68                                  VIDEO_STATE_PAUSE_OFF  |       \
69                                  VIDEO_STATE_FFWD_OFF   |       \
70                                  VIDEO_STATE_PBEND_OFF  |       \
71                                  VIDEO_STATE_DATE_OFF   |       \
72                                  VIDEO_STATE_TIME_OFF)
73 #define VIDEO_PRESS_OFF         (VIDEO_PRESS_PLAY_OFF   |       \
74                                  VIDEO_PRESS_REC_OFF    |       \
75                                  VIDEO_PRESS_PAUSE_OFF  |       \
76                                  VIDEO_PRESS_STOP_OFF   |       \
77                                  VIDEO_PRESS_EJECT_OFF)
78 #define VIDEO_ALL_OFF           (VIDEO_STATE_OFF | VIDEO_PRESS_OFF)
79
80 #define VIDEO_STATE_ON          (VIDEO_STATE_PLAY_ON    |       \
81                                  VIDEO_STATE_REC_ON     |       \
82                                  VIDEO_STATE_PAUSE_ON   |       \
83                                  VIDEO_STATE_FFWD_ON    |       \
84                                  VIDEO_STATE_PBEND_ON   |       \
85                                  VIDEO_STATE_DATE_ON    |       \
86                                  VIDEO_STATE_TIME_ON)
87 #define VIDEO_PRESS_ON          (VIDEO_PRESS_PLAY_ON    |       \
88                                  VIDEO_PRESS_REC_ON     |       \
89                                  VIDEO_PRESS_PAUSE_ON   |       \
90                                  VIDEO_PRESS_STOP_ON    |       \
91                                  VIDEO_PRESS_EJECT_ON)
92 #define VIDEO_ALL_ON            (VIDEO_STATE_ON | VIDEO_PRESS_ON)
93
94 #define VIDEO_STATE             (VIDEO_STATE_ON | VIDEO_STATE_OFF)
95 #define VIDEO_PRESS             (VIDEO_PRESS_ON | VIDEO_PRESS_OFF)
96 #define VIDEO_ALL               (VIDEO_ALL_ON | VIDEO_ALL_OFF)
97
98
99 void DrawVideoDisplay(unsigned long state, unsigned long value)
100 {
101   int i;
102   int part_label = 0, part_symbol = 1;
103   int xpos = 0, ypos = 1, xsize = 2, ysize = 3;
104   static char *monatsname[12] =
105   {
106     "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
107     "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"
108   };
109   static int video_pos[5][2][4] =
110   {
111     {{ VIDEO_PLAY_LABEL_XPOS, VIDEO_PLAY_LABEL_YPOS,
112        VIDEO_PLAY_LABEL_XSIZE,VIDEO_PLAY_LABEL_YSIZE },
113      { VIDEO_PLAY_SYMBOL_XPOS, VIDEO_PLAY_SYMBOL_YPOS,
114        VIDEO_PLAY_SYMBOL_XSIZE,VIDEO_PLAY_SYMBOL_YSIZE }},
115
116     {{ VIDEO_REC_LABEL_XPOS, VIDEO_REC_LABEL_YPOS,
117        VIDEO_REC_LABEL_XSIZE,VIDEO_REC_LABEL_YSIZE },
118      { VIDEO_REC_SYMBOL_XPOS, VIDEO_REC_SYMBOL_YPOS,
119        VIDEO_REC_SYMBOL_XSIZE,VIDEO_REC_SYMBOL_YSIZE }},
120
121     {{ VIDEO_PAUSE_LABEL_XPOS, VIDEO_PAUSE_LABEL_YPOS,
122        VIDEO_PAUSE_LABEL_XSIZE,VIDEO_PAUSE_LABEL_YSIZE },
123      { VIDEO_PAUSE_SYMBOL_XPOS, VIDEO_PAUSE_SYMBOL_YPOS,
124        VIDEO_PAUSE_SYMBOL_XSIZE,VIDEO_PAUSE_SYMBOL_YSIZE }},
125
126     {{ VIDEO_DATE_LABEL_XPOS, VIDEO_DATE_LABEL_YPOS,
127        VIDEO_DATE_LABEL_XSIZE,VIDEO_DATE_LABEL_YSIZE },
128      { VIDEO_DATE_XPOS, VIDEO_DATE_YPOS,
129        VIDEO_DATE_XSIZE,VIDEO_DATE_YSIZE }},
130
131     {{ 0,0,
132        0,0 },
133      { VIDEO_TIME_XPOS, VIDEO_TIME_YPOS,
134        VIDEO_TIME_XSIZE,VIDEO_TIME_YSIZE }}
135   };
136
137   if (state & VIDEO_STATE_PBEND_OFF)
138   {
139     int cx = DOOR_GFX_PAGEX3, cy = DOOR_GFX_PAGEY2;
140
141     BlitBitmap(pix[PIX_DOOR], drawto,
142                cx + VIDEO_REC_LABEL_XPOS,
143                cy + VIDEO_REC_LABEL_YPOS,
144                VIDEO_PBEND_LABEL_XSIZE,
145                VIDEO_PBEND_LABEL_YSIZE,
146                VX + VIDEO_REC_LABEL_XPOS,
147                VY + VIDEO_REC_LABEL_YPOS);
148   }
149
150   for(i=0;i<10;i++)
151   {
152     if (state & (1<<i))
153     {
154       int pos = i/2, cx, cy = DOOR_GFX_PAGEY2;
155
156       if (i%2)                  /* i ungerade => STATE_ON / PRESS_OFF */
157         cx = DOOR_GFX_PAGEX4;
158       else
159         cx = DOOR_GFX_PAGEX3;   /* i gerade => STATE_OFF / PRESS_ON */
160
161       if (video_pos[pos][part_label][0] && value != VIDEO_DISPLAY_SYMBOL_ONLY)
162         BlitBitmap(pix[PIX_DOOR], drawto,
163                    cx + video_pos[pos][part_label][xpos],
164                    cy + video_pos[pos][part_label][ypos],
165                    video_pos[pos][part_label][xsize],
166                    video_pos[pos][part_label][ysize],
167                    VX + video_pos[pos][part_label][xpos],
168                    VY + video_pos[pos][part_label][ypos]);
169       if (video_pos[pos][part_symbol][0] && value != VIDEO_DISPLAY_LABEL_ONLY)
170         BlitBitmap(pix[PIX_DOOR], drawto,
171                    cx + video_pos[pos][part_symbol][xpos],
172                    cy + video_pos[pos][part_symbol][ypos],
173                    video_pos[pos][part_symbol][xsize],
174                    video_pos[pos][part_symbol][ysize],
175                    VX + video_pos[pos][part_symbol][xpos],
176                    VY + video_pos[pos][part_symbol][ypos]);
177     }
178   }
179
180   if (state & VIDEO_STATE_FFWD_ON)
181   {
182     int cx = DOOR_GFX_PAGEX4, cy = DOOR_GFX_PAGEY2;
183
184     BlitBitmap(pix[PIX_DOOR], drawto,
185                cx + VIDEO_PLAY_SYMBOL_XPOS,
186                cy + VIDEO_PLAY_SYMBOL_YPOS,
187                VIDEO_PLAY_SYMBOL_XSIZE - 2,
188                VIDEO_PLAY_SYMBOL_YSIZE,
189                VX + VIDEO_PLAY_SYMBOL_XPOS - 9,
190                VY + VIDEO_PLAY_SYMBOL_YPOS);
191   }
192
193   if (state & VIDEO_STATE_PBEND_ON)
194   {
195     int cx = DOOR_GFX_PAGEX6, cy = DOOR_GFX_PAGEY1;
196
197     BlitBitmap(pix[PIX_DOOR], drawto,
198                cx + VIDEO_PBEND_LABEL_XPOS,
199                cy + VIDEO_PBEND_LABEL_YPOS,
200                VIDEO_PBEND_LABEL_XSIZE,
201                VIDEO_PBEND_LABEL_YSIZE,
202                VX + VIDEO_REC_LABEL_XPOS,
203                VY + VIDEO_REC_LABEL_YPOS);
204   }
205
206   if (state & VIDEO_STATE_DATE_ON)
207   {
208     int tag = value % 100;
209     int monat = (value/100) % 100;
210     int jahr = (value/10000);
211
212     DrawText(VX+VIDEO_DATE_XPOS,VY+VIDEO_DATE_YPOS,
213              int2str(tag,2),FS_SMALL,FC_SPECIAL1);
214     DrawText(VX+VIDEO_DATE_XPOS+27,VY+VIDEO_DATE_YPOS,
215              monatsname[monat],FS_SMALL,FC_SPECIAL1);
216     DrawText(VX+VIDEO_DATE_XPOS+64,VY+VIDEO_DATE_YPOS,
217              int2str(jahr,2),FS_SMALL,FC_SPECIAL1);
218   }
219
220   if (state & VIDEO_STATE_TIME_ON)
221   {
222     int min = value / 60;
223     int sec = value % 60;
224
225     DrawText(VX+VIDEO_TIME_XPOS,VY+VIDEO_TIME_YPOS,
226              int2str(min,2),FS_SMALL,FC_SPECIAL1);
227     DrawText(VX+VIDEO_TIME_XPOS+27,VY+VIDEO_TIME_YPOS,
228              int2str(sec,2),FS_SMALL,FC_SPECIAL1);
229   }
230
231   if (state & VIDEO_STATE_DATE)
232     redraw_mask |= REDRAW_VIDEO_1;
233   if ((state & ~VIDEO_STATE_DATE) & VIDEO_STATE)
234     redraw_mask |= REDRAW_VIDEO_2;
235   if (state & VIDEO_PRESS)
236     redraw_mask |= REDRAW_VIDEO_3;
237 }
238
239 void DrawCompleteVideoDisplay()
240 {
241   BlitBitmap(pix[PIX_DOOR], drawto,
242              DOOR_GFX_PAGEX3,DOOR_GFX_PAGEY2, VXSIZE,VYSIZE, VX,VY);
243   BlitBitmap(pix[PIX_DOOR], drawto,
244              DOOR_GFX_PAGEX4+VIDEO_CONTROL_XPOS,
245              DOOR_GFX_PAGEY2+VIDEO_CONTROL_YPOS,
246              VIDEO_CONTROL_XSIZE,VIDEO_CONTROL_YSIZE,
247              VX+VIDEO_CONTROL_XPOS,VY+VIDEO_CONTROL_YPOS);
248
249   DrawVideoDisplay(VIDEO_ALL_OFF,0);
250   if (tape.date && tape.length)
251   {
252     DrawVideoDisplay(VIDEO_STATE_DATE_ON,tape.date);
253     DrawVideoDisplay(VIDEO_STATE_TIME_ON,tape.length_seconds);
254   }
255
256   BlitBitmap(drawto, pix[PIX_DB_DOOR],
257              VX,VY, VXSIZE,VYSIZE, DOOR_GFX_PAGEX1,DOOR_GFX_PAGEY2);
258 }
259
260
261 /* NEW GADGET STUFF -------------------------------------------------------- */
262
263
264 /* values for DrawGadget() */
265 #define DG_UNPRESSED            0
266 #define DG_PRESSED              1
267 #define DG_BUFFERED             0
268 #define DG_DIRECT               1
269
270 static struct GadgetInfo *gadget_list_first_entry = NULL;
271 static struct GadgetInfo *gadget_list_last_entry = NULL;
272 static int next_free_gadget_id = 1;
273 static boolean gadget_id_wrapped = FALSE;
274
275 static struct GadgetInfo *getGadgetInfoFromGadgetID(int id)
276 {
277   struct GadgetInfo *gi = gadget_list_first_entry;
278
279   while (gi && gi->id != id)
280     gi = gi->next;
281
282   return gi;
283 }
284
285 static int getNewGadgetID()
286 {
287   int id = next_free_gadget_id++;
288
289   if (next_free_gadget_id <= 0)         /* counter overrun */
290   {
291     gadget_id_wrapped = TRUE;           /* now we must check each ID */
292     next_free_gadget_id = 0;
293   }
294
295   if (gadget_id_wrapped)
296   {
297     next_free_gadget_id++;
298     while (getGadgetInfoFromGadgetID(next_free_gadget_id) != NULL)
299       next_free_gadget_id++;
300   }
301
302   if (next_free_gadget_id <= 0)         /* cannot get new gadget id */
303     Error(ERR_EXIT, "too much gadgets -- this should not happen");
304
305   return id;
306 }
307
308 static struct GadgetInfo *getGadgetInfoFromMousePosition(int mx, int my)
309 {
310   struct GadgetInfo *gi = gadget_list_first_entry;
311
312   while (gi)
313   {
314     if (gi->mapped &&
315         mx >= gi->x && mx < gi->x + gi->width &&
316         my >= gi->y && my < gi->y + gi->height)
317         break;
318
319     gi = gi->next;
320   }
321
322   return gi;
323 }
324
325 static void default_callback_info(void *ptr)
326 {
327 #if 0
328   if (game_status == LEVELED)
329     HandleEditorGadgetInfoText(ptr);
330 #endif
331
332   return;
333 }
334
335 static void default_callback_action(void *ptr)
336 {
337   return;
338 }
339
340 static void DrawGadget(struct GadgetInfo *gi, boolean pressed, boolean direct)
341 {
342   int state = (pressed ? 1 : 0);
343   struct GadgetDesign *gd = (gi->checked ?
344                              &gi->alt_design[state] :
345                              &gi->design[state]);
346
347   switch (gi->type)
348   {
349     case GD_TYPE_NORMAL_BUTTON:
350     case GD_TYPE_CHECK_BUTTON:
351     case GD_TYPE_RADIO_BUTTON:
352       BlitBitmap(gd->bitmap, drawto,
353                  gd->x, gd->y, gi->width, gi->height, gi->x, gi->y);
354       if (gi->deco.design.bitmap)
355         BlitBitmap(gi->deco.design.bitmap, drawto,
356                    gi->deco.design.x, gi->deco.design.y,
357                    gi->deco.width, gi->deco.height,
358                    gi->x + gi->deco.x + (pressed ? gi->deco.xshift : 0),
359                    gi->y + gi->deco.y + (pressed ? gi->deco.yshift : 0));
360       break;
361
362     case GD_TYPE_TEXTINPUT_ALPHANUMERIC:
363     case GD_TYPE_TEXTINPUT_NUMERIC:
364       {
365         int i;
366         char cursor_letter;
367         char cursor_string[3];
368         char text[MAX_GADGET_TEXTSIZE + 1];
369         int font_type = gi->text.font_type;
370         int font_width = getFontWidth(FS_SMALL, font_type);
371         int border = gi->border.size;
372         strcpy(text, gi->text.value);
373         strcat(text, " ");
374
375         /* left part of gadget */
376         BlitBitmap(gd->bitmap, drawto,
377                    gd->x, gd->y, border, gi->height, gi->x, gi->y);
378
379         /* middle part of gadget */
380         for (i=0; i<=gi->text.size; i++)
381           BlitBitmap(gd->bitmap, drawto,
382                      gd->x + border, gd->y, font_width, gi->height,
383                      gi->x + border + i * font_width, gi->y);
384
385         /* right part of gadget */
386         BlitBitmap(gd->bitmap, drawto,
387                    gd->x + gi->border.width - border, gd->y,
388                    border, gi->height, gi->x + gi->width - border, gi->y);
389
390         /* gadget text value */
391         DrawText(gi->x + border, gi->y + border, text, FS_SMALL, font_type);
392
393         cursor_letter = gi->text.value[gi->text.cursor_position];
394         cursor_string[0] = '~';
395         cursor_string[1] = (cursor_letter != '\0' ? cursor_letter : ' ');
396         cursor_string[2] = '\0';
397
398         /* draw cursor, if active */
399         if (pressed)
400           DrawText(gi->x + border + gi->text.cursor_position * font_width,
401                    gi->y + border, cursor_string, FS_SMALL, font_type);
402       }
403       break;
404
405     case GD_TYPE_SCROLLBAR_VERTICAL:
406       {
407         int i;
408         int xpos = gi->x;
409         int ypos = gi->y + gi->scrollbar.position;
410         int design_full = gi->width;
411         int design_body = design_full - 2 * gi->border.size;
412         int size_full = gi->scrollbar.size;
413         int size_body = size_full - 2 * gi->border.size;
414         int num_steps = size_body / design_body;
415         int step_size_remain = size_body - num_steps * design_body;
416
417         /* clear scrollbar area */
418         ClearRectangle(backbuffer, gi->x, gi->y, gi->width, gi->height);
419
420         /* upper part of gadget */
421         BlitBitmap(gd->bitmap, drawto,
422                    gd->x, gd->y,
423                    gi->width, gi->border.size,
424                    xpos, ypos);
425
426         /* middle part of gadget */
427         for (i=0; i<num_steps; i++)
428           BlitBitmap(gd->bitmap, drawto,
429                      gd->x, gd->y + gi->border.size,
430                      gi->width, design_body,
431                      xpos, ypos + gi->border.size + i * design_body);
432
433         /* remaining middle part of gadget */
434         if (step_size_remain > 0)
435           BlitBitmap(gd->bitmap, drawto,
436                      gd->x,  gd->y + gi->border.size,
437                      gi->width, step_size_remain,
438                      xpos, ypos + gi->border.size + num_steps * design_body);
439
440         /* lower part of gadget */
441         BlitBitmap(gd->bitmap, drawto,
442                    gd->x, gd->y + design_full - gi->border.size,
443                    gi->width, gi->border.size,
444                    xpos, ypos + size_full - gi->border.size);
445       }
446       break;
447
448     case GD_TYPE_SCROLLBAR_HORIZONTAL:
449       {
450         int i;
451         int xpos = gi->x + gi->scrollbar.position;
452         int ypos = gi->y;
453         int design_full = gi->height;
454         int design_body = design_full - 2 * gi->border.size;
455         int size_full = gi->scrollbar.size;
456         int size_body = size_full - 2 * gi->border.size;
457         int num_steps = size_body / design_body;
458         int step_size_remain = size_body - num_steps * design_body;
459
460         /* clear scrollbar area */
461         ClearRectangle(backbuffer, gi->x, gi->y, gi->width, gi->height);
462
463         /* left part of gadget */
464         BlitBitmap(gd->bitmap, drawto,
465                    gd->x, gd->y,
466                    gi->border.size, gi->height,
467                    xpos, ypos);
468
469         /* middle part of gadget */
470         for (i=0; i<num_steps; i++)
471           BlitBitmap(gd->bitmap, drawto,
472                      gd->x + gi->border.size, gd->y,
473                      design_body, gi->height,
474                      xpos + gi->border.size + i * design_body, ypos);
475
476         /* remaining middle part of gadget */
477         if (step_size_remain > 0)
478           BlitBitmap(gd->bitmap, drawto,
479                      gd->x + gi->border.size, gd->y,
480                      step_size_remain, gi->height,
481                      xpos + gi->border.size + num_steps * design_body, ypos);
482
483         /* right part of gadget */
484         BlitBitmap(gd->bitmap, drawto,
485                    gd->x + design_full - gi->border.size, gd->y,
486                    gi->border.size, gi->height,
487                    xpos + size_full - gi->border.size, ypos);
488       }
489       break;
490
491     default:
492       return;
493   }
494
495   if (direct)
496     BlitBitmap(drawto, window,
497                gi->x, gi->y, gi->width, gi->height, gi->x, gi->y);
498   else
499     redraw_mask |= (gi->x < SX + SXSIZE ? REDRAW_FIELD :
500                     gi->y < DY + DYSIZE ? REDRAW_DOOR_1 :
501                     gi->y > VY ? REDRAW_DOOR_2 : REDRAW_DOOR_3);
502 }
503
504 static void HandleGadgetTags(struct GadgetInfo *gi, int first_tag, va_list ap)
505 {
506   int tag = first_tag;
507
508   while (tag != GDI_END)
509   {
510     switch(tag)
511     {
512       case GDI_CUSTOM_ID:
513         gi->custom_id = va_arg(ap, int);
514         break;
515
516       case GDI_CUSTOM_TYPE_ID:
517         gi->custom_type_id = va_arg(ap, int);
518         break;
519
520       case GDI_INFO_TEXT:
521         {
522           int max_textsize = MAX_INFO_TEXTSIZE - 1;
523
524           strncpy(gi->info_text, va_arg(ap, char *), max_textsize);
525           gi->info_text[max_textsize] = '\0';
526         }
527         break;
528
529       case GDI_X:
530         gi->x = va_arg(ap, int);
531         break;
532
533       case GDI_Y:
534         gi->y = va_arg(ap, int);
535         break;
536
537       case GDI_WIDTH:
538         gi->width = va_arg(ap, int);
539         break;
540
541       case GDI_HEIGHT:
542         gi->height = va_arg(ap, int);
543         break;
544
545       case GDI_TYPE:
546         gi->type = va_arg(ap, unsigned long);
547         break;
548
549       case GDI_STATE:
550         gi->state = va_arg(ap, unsigned long);
551         break;
552
553       case GDI_CHECKED:
554         gi->checked = va_arg(ap, boolean);
555         break;
556
557       case GDI_RADIO_NR:
558         gi->radio_nr = va_arg(ap, unsigned long);
559         break;
560
561       case GDI_NUMBER_VALUE:
562         gi->text.number_value = va_arg(ap, long);
563         sprintf(gi->text.value, "%d", gi->text.number_value);
564         gi->text.cursor_position = strlen(gi->text.value);
565         break;
566
567       case GDI_NUMBER_MIN:
568         gi->text.number_min = va_arg(ap, long);
569         if (gi->text.number_value < gi->text.number_min)
570         {
571           gi->text.number_value = gi->text.number_min;
572           sprintf(gi->text.value, "%d", gi->text.number_value);
573         }
574         break;
575
576       case GDI_NUMBER_MAX:
577         gi->text.number_max = va_arg(ap, long);
578         if (gi->text.number_value > gi->text.number_max)
579         {
580           gi->text.number_value = gi->text.number_max;
581           sprintf(gi->text.value, "%d", gi->text.number_value);
582         }
583         break;
584
585       case GDI_TEXT_VALUE:
586         {
587           int max_textsize = MAX_GADGET_TEXTSIZE;
588
589           if (gi->text.size)
590             max_textsize = MIN(gi->text.size, MAX_GADGET_TEXTSIZE - 1);
591
592           strncpy(gi->text.value, va_arg(ap, char *), max_textsize);
593           gi->text.value[max_textsize] = '\0';
594           gi->text.cursor_position = strlen(gi->text.value);
595         }
596         break;
597
598       case GDI_TEXT_SIZE:
599         {
600           int tag_value = va_arg(ap, int);
601           int max_textsize = MIN(tag_value, MAX_GADGET_TEXTSIZE - 1);
602
603           gi->text.size = max_textsize;
604           gi->text.value[max_textsize] = '\0';
605         }
606         break;
607
608       case GDI_TEXT_FONT:
609         gi->text.font_type = va_arg(ap, int);
610         break;
611
612       case GDI_DESIGN_UNPRESSED:
613         gi->design[GD_BUTTON_UNPRESSED].bitmap = va_arg(ap, Bitmap);
614         gi->design[GD_BUTTON_UNPRESSED].x = va_arg(ap, int);
615         gi->design[GD_BUTTON_UNPRESSED].y = va_arg(ap, int);
616         break;
617
618       case GDI_DESIGN_PRESSED:
619         gi->design[GD_BUTTON_PRESSED].bitmap = va_arg(ap, Bitmap);
620         gi->design[GD_BUTTON_PRESSED].x = va_arg(ap, int);
621         gi->design[GD_BUTTON_PRESSED].y = va_arg(ap, int);
622         break;
623
624       case GDI_ALT_DESIGN_UNPRESSED:
625         gi->alt_design[GD_BUTTON_UNPRESSED].bitmap= va_arg(ap, Bitmap);
626         gi->alt_design[GD_BUTTON_UNPRESSED].x = va_arg(ap, int);
627         gi->alt_design[GD_BUTTON_UNPRESSED].y = va_arg(ap, int);
628         break;
629
630       case GDI_ALT_DESIGN_PRESSED:
631         gi->alt_design[GD_BUTTON_PRESSED].bitmap = va_arg(ap, Bitmap);
632         gi->alt_design[GD_BUTTON_PRESSED].x = va_arg(ap, int);
633         gi->alt_design[GD_BUTTON_PRESSED].y = va_arg(ap, int);
634         break;
635
636       case GDI_BORDER_SIZE:
637         gi->border.size = va_arg(ap, int);
638         break;
639
640       case GDI_TEXTINPUT_DESIGN_WIDTH:
641         gi->border.width = va_arg(ap, int);
642         break;
643
644       case GDI_DECORATION_DESIGN:
645         gi->deco.design.bitmap = va_arg(ap, Bitmap);
646         gi->deco.design.x = va_arg(ap, int);
647         gi->deco.design.y = va_arg(ap, int);
648         break;
649
650       case GDI_DECORATION_POSITION:
651         gi->deco.x = va_arg(ap, int);
652         gi->deco.y = va_arg(ap, int);
653         break;
654
655       case GDI_DECORATION_SIZE:
656         gi->deco.width = va_arg(ap, int);
657         gi->deco.height = va_arg(ap, int);
658         break;
659
660       case GDI_DECORATION_SHIFTING:
661         gi->deco.xshift = va_arg(ap, int);
662         gi->deco.yshift = va_arg(ap, int);
663         break;
664
665       case GDI_EVENT_MASK:
666         gi->event_mask = va_arg(ap, unsigned long);
667         break;
668
669       case GDI_AREA_SIZE:
670         gi->drawing.area_xsize = va_arg(ap, int);
671         gi->drawing.area_ysize = va_arg(ap, int);
672
673         /* determine dependent values for drawing area gadget, if needed */
674         if (gi->width == 0 && gi->height == 0 &&
675             gi->drawing.item_xsize !=0 && gi->drawing.item_ysize !=0)
676         {
677           gi->width = gi->drawing.area_xsize * gi->drawing.item_xsize;
678           gi->height = gi->drawing.area_ysize * gi->drawing.item_ysize;
679         }
680         else if (gi->drawing.item_xsize == 0 && gi->drawing.item_ysize == 0 &&
681                  gi->width != 0 && gi->height != 0)
682         {
683           gi->drawing.item_xsize = gi->width / gi->drawing.area_xsize;
684           gi->drawing.item_ysize = gi->height / gi->drawing.area_ysize;
685         }
686         break;
687
688       case GDI_ITEM_SIZE:
689         gi->drawing.item_xsize = va_arg(ap, int);
690         gi->drawing.item_ysize = va_arg(ap, int);
691
692         /* determine dependent values for drawing area gadget, if needed */
693         if (gi->width == 0 && gi->height == 0 &&
694             gi->drawing.area_xsize !=0 && gi->drawing.area_ysize !=0)
695         {
696           gi->width = gi->drawing.area_xsize * gi->drawing.item_xsize;
697           gi->height = gi->drawing.area_ysize * gi->drawing.item_ysize;
698         }
699         else if (gi->drawing.area_xsize == 0 && gi->drawing.area_ysize == 0 &&
700                  gi->width != 0 && gi->height != 0)
701         {
702           gi->drawing.area_xsize = gi->width / gi->drawing.item_xsize;
703           gi->drawing.area_ysize = gi->height / gi->drawing.item_ysize;
704         }
705         break;
706
707       case GDI_SCROLLBAR_ITEMS_MAX:
708         gi->scrollbar.items_max = va_arg(ap, int);
709         break;
710
711       case GDI_SCROLLBAR_ITEMS_VISIBLE:
712         gi->scrollbar.items_visible = va_arg(ap, int);
713         break;
714
715       case GDI_SCROLLBAR_ITEM_POSITION:
716         gi->scrollbar.item_position = va_arg(ap, int);
717         break;
718
719       case GDI_CALLBACK_INFO:
720         gi->callback_info = va_arg(ap, gadget_function);
721         break;
722
723       case GDI_CALLBACK_ACTION:
724         gi->callback_action = va_arg(ap, gadget_function);
725         break;
726
727       default:
728         Error(ERR_EXIT, "HandleGadgetTags(): unknown tag %d", tag);
729     }
730
731     tag = va_arg(ap, int);      /* read next tag */
732   }
733
734   /* check if gadget complete */
735   if (gi->type != GD_TYPE_DRAWING_AREA &&
736       (!gi->design[GD_BUTTON_UNPRESSED].bitmap ||
737        !gi->design[GD_BUTTON_PRESSED].bitmap))
738     Error(ERR_EXIT, "gadget incomplete (missing Bitmap)");
739
740   /* adjust gadget values in relation to other gadget values */
741
742   if (gi->type & GD_TYPE_TEXTINPUT)
743   {
744     int font_width = getFontWidth(FS_SMALL, gi->text.font_type);
745     int font_height = getFontHeight(FS_SMALL, gi->text.font_type);
746
747     gi->width = 2 * gi->border.size + (gi->text.size + 1) * font_width;
748     gi->height = 2 * gi->border.size + font_height;
749   }
750
751   if (gi->type & GD_TYPE_TEXTINPUT_NUMERIC)
752   {
753     struct GadgetTextInput *text = &gi->text;
754     int value = text->number_value;
755
756     text->number_value = (value < text->number_min ? text->number_min :
757                           value > text->number_max ? text->number_max :
758                           value);
759
760     sprintf(text->value, "%d", text->number_value);
761   }
762
763   if (gi->type & GD_TYPE_SCROLLBAR)
764   {
765     struct GadgetScrollbar *gs = &gi->scrollbar;
766
767     if (gi->width == 0 || gi->height == 0 ||
768         gs->items_max == 0 || gs->items_visible == 0)
769       Error(ERR_EXIT, "scrollbar gadget incomplete (missing tags)");
770
771     /* calculate internal scrollbar values */
772     gs->size_max = (gi->type == GD_TYPE_SCROLLBAR_VERTICAL ?
773                     gi->height : gi->width);
774     gs->size = gs->size_max * gs->items_visible / gs->items_max;
775     gs->position = gs->size_max * gs->item_position / gs->items_max;
776     gs->position_max = gs->size_max - gs->size;
777     gs->correction = gs->size_max / gs->items_max / 2;
778
779     /* finetuning for maximal right/bottom position */
780     if (gs->item_position == gs->items_max - gs->items_visible)
781       gs->position = gs->position_max;
782   }
783 }
784
785 void ModifyGadget(struct GadgetInfo *gi, int first_tag, ...)
786 {
787   va_list ap;
788
789   va_start(ap, first_tag);
790   HandleGadgetTags(gi, first_tag, ap);
791   va_end(ap);
792
793   RedrawGadget(gi);
794 }
795
796 void RedrawGadget(struct GadgetInfo *gi)
797 {
798   if (gi->mapped)
799     DrawGadget(gi, gi->state, DG_DIRECT);
800 }
801
802 struct GadgetInfo *CreateGadget(int first_tag, ...)
803 {
804   struct GadgetInfo *new_gadget = checked_malloc(sizeof(struct GadgetInfo));
805   va_list ap;
806
807   /* always start with reliable default values */
808   memset(new_gadget, 0, sizeof(struct GadgetInfo));     /* zero all fields */
809   new_gadget->id = getNewGadgetID();
810   new_gadget->callback_info = default_callback_info;
811   new_gadget->callback_action = default_callback_action;
812
813   va_start(ap, first_tag);
814   HandleGadgetTags(new_gadget, first_tag, ap);
815   va_end(ap);
816
817   /* insert new gadget into global gadget list */
818   if (gadget_list_last_entry)
819   {
820     gadget_list_last_entry->next = new_gadget;
821     gadget_list_last_entry = gadget_list_last_entry->next;
822   }
823   else
824     gadget_list_first_entry = gadget_list_last_entry = new_gadget;
825
826   return new_gadget;
827 }
828
829 void FreeGadget(struct GadgetInfo *gi)
830 {
831   struct GadgetInfo *gi_previous = gadget_list_first_entry;
832
833   while (gi_previous && gi_previous->next != gi)
834     gi_previous = gi_previous->next;
835
836   if (gi == gadget_list_first_entry)
837     gadget_list_first_entry = gi->next;
838
839   if (gi == gadget_list_last_entry)
840     gadget_list_last_entry = gi_previous;
841
842   gi_previous->next = gi->next;
843   free(gi);
844 }
845
846 static void CheckRangeOfNumericInputGadget(struct GadgetInfo *gi)
847 {
848   if (gi->type != GD_TYPE_TEXTINPUT_NUMERIC)
849     return;
850
851   gi->text.number_value = atoi(gi->text.value);
852
853   if (gi->text.number_value < gi->text.number_min)
854     gi->text.number_value = gi->text.number_min;
855   if (gi->text.number_value > gi->text.number_max)
856     gi->text.number_value = gi->text.number_max;
857
858   sprintf(gi->text.value, "%d", gi->text.number_value);
859
860   if (gi->text.cursor_position < 0)
861     gi->text.cursor_position = 0;
862   else if (gi->text.cursor_position > strlen(gi->text.value))
863     gi->text.cursor_position = strlen(gi->text.value);
864 }
865
866 /* global pointer to gadget actually in use (when mouse button pressed) */
867 static struct GadgetInfo *last_gi = NULL;
868
869 static void MapGadgetExt(struct GadgetInfo *gi, boolean redraw)
870 {
871   if (gi == NULL || gi->mapped)
872     return;
873
874   gi->mapped = TRUE;
875
876   if (redraw)
877     DrawGadget(gi, DG_UNPRESSED, DG_BUFFERED);
878 }
879
880 void MapGadget(struct GadgetInfo *gi)
881 {
882   MapGadgetExt(gi, TRUE);
883 }
884
885 void UnmapGadget(struct GadgetInfo *gi)
886 {
887   if (gi == NULL || !gi->mapped)
888     return;
889
890   gi->mapped = FALSE;
891
892   if (gi == last_gi)
893     last_gi = NULL;
894 }
895
896 #define MAX_NUM_GADGETS         1024
897 #define MULTIMAP_UNMAP          (1 << 0)
898 #define MULTIMAP_REMAP          (1 << 1)
899 #define MULTIMAP_REDRAW         (1 << 2)
900 #define MULTIMAP_PLAYFIELD      (1 << 3)
901 #define MULTIMAP_DOOR_1         (1 << 4)
902 #define MULTIMAP_DOOR_2         (1 << 5)
903 #define MULTIMAP_ALL            (MULTIMAP_PLAYFIELD | \
904                                  MULTIMAP_DOOR_1 | \
905                                  MULTIMAP_DOOR_2)
906
907 static void MultiMapGadgets(int mode)
908 {
909   struct GadgetInfo *gi = gadget_list_first_entry;
910   static boolean map_state[MAX_NUM_GADGETS];
911   int map_count = 0;
912
913   while (gi)
914   {
915     if ((mode & MULTIMAP_PLAYFIELD && gi->x < SX + SXSIZE) ||
916         (mode & MULTIMAP_DOOR_1 && gi->x >= DX && gi->y < DY + DYSIZE) ||
917         (mode & MULTIMAP_DOOR_2 && gi->x >= DX && gi->y > DY + DYSIZE) ||
918         (mode & MULTIMAP_ALL) == MULTIMAP_ALL)
919     {
920       if (mode & MULTIMAP_UNMAP)
921       {
922         map_state[map_count++ % MAX_NUM_GADGETS] = gi->mapped;
923         UnmapGadget(gi);
924       }
925       else
926       {
927         if (map_state[map_count++ % MAX_NUM_GADGETS])
928           MapGadgetExt(gi, (mode & MULTIMAP_REDRAW));
929       }
930     }
931
932     gi = gi->next;
933   }
934 }
935
936 void UnmapAllGadgets()
937 {
938   MultiMapGadgets(MULTIMAP_ALL | MULTIMAP_UNMAP);
939 }
940
941 void RemapAllGadgets()
942 {
943   MultiMapGadgets(MULTIMAP_ALL | MULTIMAP_REMAP);
944 }
945
946 boolean anyTextGadgetActive()
947 {
948   return (last_gi && last_gi->type & GD_TYPE_TEXTINPUT && last_gi->mapped);
949 }
950
951 void ClickOnGadget(struct GadgetInfo *gi, int button)
952 {
953   /* simulate releasing mouse button over last gadget, if still pressed */
954   if (button_status)
955     HandleGadgets(-1, -1, 0);
956
957   /* simulate pressing mouse button over specified gadget */
958   HandleGadgets(gi->x, gi->y, button);
959
960   /* simulate releasing mouse button over specified gadget */
961   HandleGadgets(gi->x, gi->y, 0);
962 }
963
964 void HandleGadgets(int mx, int my, int button)
965 {
966   static struct GadgetInfo *last_info_gi = NULL;
967   static unsigned long pressed_delay = 0;
968   static int last_button = 0;
969   static int last_mx = 0, last_my = 0;
970   int scrollbar_mouse_pos = 0;
971   struct GadgetInfo *new_gi, *gi;
972   boolean press_event;
973   boolean release_event;
974   boolean mouse_moving;
975   boolean gadget_pressed;
976   boolean gadget_pressed_repeated;
977   boolean gadget_moving;
978   boolean gadget_moving_inside;
979   boolean gadget_moving_off_borders;
980   boolean gadget_released;
981   boolean gadget_released_inside;
982   boolean gadget_released_off_borders;
983   boolean changed_position = FALSE;
984
985   /* check if there are any gadgets defined */
986   if (gadget_list_first_entry == NULL)
987     return;
988
989   /* check which gadget is under the mouse pointer */
990   new_gi = getGadgetInfoFromMousePosition(mx, my);
991
992   /* check if button state has changed since last invocation */
993   press_event = (button != 0 && last_button == 0);
994   release_event = (button == 0 && last_button != 0);
995   last_button = button;
996
997   /* check if mouse has been moved since last invocation */
998   mouse_moving = ((mx != last_mx || my != last_my) && motion_status);
999   last_mx = mx;
1000   last_my = my;
1001
1002   /* special treatment for text and number input gadgets */
1003   if (anyTextGadgetActive() && button != 0 && !motion_status)
1004   {
1005     struct GadgetInfo *gi = last_gi;
1006
1007     if (new_gi == last_gi)
1008     {
1009       /* if mouse button pressed inside activated text gadget, set cursor */
1010       gi->text.cursor_position =
1011         (mx - gi->x - gi->border.size) /
1012         getFontWidth(FS_SMALL, gi->text.font_type);
1013
1014       if (gi->text.cursor_position < 0)
1015         gi->text.cursor_position = 0;
1016       else if (gi->text.cursor_position > strlen(gi->text.value))
1017         gi->text.cursor_position = strlen(gi->text.value);
1018
1019       DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1020     }
1021     else
1022     {
1023       /* if mouse button pressed outside text input gadget, deactivate it */
1024       CheckRangeOfNumericInputGadget(gi);
1025       DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1026
1027       gi->event.type = GD_EVENT_TEXT_LEAVING;
1028
1029       if (gi->event_mask & GD_EVENT_TEXT_LEAVING)
1030         gi->callback_action(gi);
1031
1032       last_gi = NULL;
1033     }
1034   }
1035
1036   gadget_pressed =
1037     (button != 0 && last_gi == NULL && new_gi != NULL && press_event);
1038   gadget_pressed_repeated =
1039     (button != 0 && last_gi != NULL && new_gi == last_gi);
1040
1041   gadget_released =             (release_event && last_gi != NULL);
1042   gadget_released_inside =      (gadget_released && new_gi == last_gi);
1043   gadget_released_off_borders = (gadget_released && new_gi != last_gi);
1044
1045   gadget_moving =             (button != 0 && last_gi != NULL && mouse_moving);
1046   gadget_moving_inside =      (gadget_moving && new_gi == last_gi);
1047   gadget_moving_off_borders = (gadget_moving && new_gi != last_gi);
1048
1049   /* if new gadget pressed, store this gadget  */
1050   if (gadget_pressed)
1051     last_gi = new_gi;
1052
1053   /* 'gi' is actually handled gadget */
1054   gi = last_gi;
1055
1056   /* if gadget is scrollbar, choose mouse position value */
1057   if (gi && gi->type & GD_TYPE_SCROLLBAR)
1058     scrollbar_mouse_pos =
1059       (gi->type == GD_TYPE_SCROLLBAR_HORIZONTAL ? mx - gi->x : my - gi->y);
1060
1061   /* if mouse button released, no gadget needs to be handled anymore */
1062   if (button == 0 && last_gi && !(last_gi->type & GD_TYPE_TEXTINPUT))
1063     last_gi = NULL;
1064
1065   /* modify event position values even if no gadget is pressed */
1066   if (button == 0 && !release_event)
1067     gi = new_gi;
1068
1069   if (gi)
1070   {
1071     int last_x = gi->event.x;
1072     int last_y = gi->event.y;
1073
1074     gi->event.x = mx - gi->x;
1075     gi->event.y = my - gi->y;
1076
1077     if (gi->type == GD_TYPE_DRAWING_AREA)
1078     {
1079       gi->event.x /= gi->drawing.item_xsize;
1080       gi->event.y /= gi->drawing.item_ysize;
1081
1082       if (last_x != gi->event.x || last_y != gi->event.y)
1083         changed_position = TRUE;
1084     }
1085   }
1086
1087   /* handle gadget popup info text */
1088   if (last_info_gi != new_gi ||
1089       (new_gi && new_gi->type == GD_TYPE_DRAWING_AREA && changed_position))
1090   {
1091     last_info_gi = new_gi;
1092
1093     if (new_gi != NULL && (button == 0 || new_gi == last_gi))
1094     {
1095       new_gi->event.type = 0;
1096       new_gi->callback_info(new_gi);
1097     }
1098     else
1099       default_callback_info(NULL);
1100   }
1101
1102   if (gadget_pressed)
1103   {
1104     if (gi->type == GD_TYPE_CHECK_BUTTON)
1105     {
1106       gi->checked = !gi->checked;
1107     }
1108     else if (gi->type == GD_TYPE_RADIO_BUTTON)
1109     {
1110       struct GadgetInfo *rgi = gadget_list_first_entry;
1111
1112       while (rgi)
1113       {
1114         if (rgi->mapped &&
1115             rgi->type == GD_TYPE_RADIO_BUTTON &&
1116             rgi->radio_nr == gi->radio_nr &&
1117             rgi != gi)
1118         {
1119           rgi->checked = FALSE;
1120           DrawGadget(rgi, DG_UNPRESSED, DG_DIRECT);
1121         }
1122
1123         rgi = rgi->next;
1124       }
1125
1126       gi->checked = TRUE;
1127     }
1128     else if (gi->type & GD_TYPE_SCROLLBAR)
1129     {
1130       int mpos, gpos;
1131
1132       if (gi->type == GD_TYPE_SCROLLBAR_HORIZONTAL)
1133       {
1134         mpos = mx;
1135         gpos = gi->x;
1136       }
1137       else
1138       {
1139         mpos = my;
1140         gpos = gi->y;
1141       }
1142
1143       if (mpos >= gpos + gi->scrollbar.position &&
1144           mpos < gpos + gi->scrollbar.position + gi->scrollbar.size)
1145       {
1146         /* drag scrollbar */
1147         gi->scrollbar.drag_position =
1148           scrollbar_mouse_pos - gi->scrollbar.position;
1149       }
1150       else
1151       {
1152         /* click scrollbar one scrollbar length up/left or down/right */
1153
1154         struct GadgetScrollbar *gs = &gi->scrollbar;
1155         int old_item_position = gs->item_position;
1156
1157         changed_position = FALSE;
1158
1159         gs->item_position +=
1160           gs->items_visible * (mpos < gpos + gi->scrollbar.position ? -1 : +1);
1161
1162         if (gs->item_position < 0)
1163           gs->item_position = 0;
1164         if (gs->item_position > gs->items_max - gs->items_visible)
1165           gs->item_position = gs->items_max - gs->items_visible;
1166
1167         if (old_item_position != gs->item_position)
1168         {
1169           gi->event.item_position = gs->item_position;
1170           changed_position = TRUE;
1171         }
1172
1173         ModifyGadget(gi, GDI_SCROLLBAR_ITEM_POSITION, gs->item_position,
1174                      GDI_END);
1175
1176         gi->state = GD_BUTTON_UNPRESSED;
1177         gi->event.type = GD_EVENT_MOVING;
1178         gi->event.off_borders = FALSE;
1179
1180         if (gi->event_mask & GD_EVENT_MOVING && changed_position)
1181           gi->callback_action(gi);
1182
1183         /* don't handle this scrollbar anymore while mouse button pressed */
1184         last_gi = NULL;
1185
1186         return;
1187       }
1188     }
1189
1190     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1191
1192     gi->state = GD_BUTTON_PRESSED;
1193     gi->event.type = GD_EVENT_PRESSED;
1194     gi->event.button = button;
1195     gi->event.off_borders = FALSE;
1196
1197     /* initialize delay counter */
1198     DelayReached(&pressed_delay, 0);
1199
1200     if (gi->event_mask & GD_EVENT_PRESSED)
1201       gi->callback_action(gi);
1202   }
1203
1204   if (gadget_pressed_repeated)
1205   {
1206     gi->event.type = GD_EVENT_PRESSED;
1207
1208     if (gi->event_mask & GD_EVENT_REPEATED &&
1209         DelayReached(&pressed_delay, GADGET_FRAME_DELAY))
1210       gi->callback_action(gi);
1211   }
1212
1213   if (gadget_moving)
1214   {
1215     if (gi->type & GD_TYPE_BUTTON)
1216     {
1217       if (gadget_moving_inside && gi->state == GD_BUTTON_UNPRESSED)
1218         DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1219       else if (gadget_moving_off_borders && gi->state == GD_BUTTON_PRESSED)
1220         DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1221     }
1222
1223     if (gi->type & GD_TYPE_SCROLLBAR)
1224     {
1225       struct GadgetScrollbar *gs = &gi->scrollbar;
1226       int old_item_position = gs->item_position;
1227
1228       gs->position = scrollbar_mouse_pos - gs->drag_position;
1229
1230       if (gs->position < 0)
1231         gs->position = 0;
1232       if (gs->position > gs->position_max)
1233         gs->position = gs->position_max;
1234
1235       gs->item_position =
1236         gs->items_max * (gs->position + gs->correction) / gs->size_max;
1237
1238       if (gs->item_position < 0)
1239         gs->item_position = 0;
1240       if (gs->item_position > gs->items_max - 1)
1241         gs->item_position = gs->items_max - 1;
1242
1243       if (old_item_position != gs->item_position)
1244       {
1245         gi->event.item_position = gs->item_position;
1246         changed_position = TRUE;
1247       }
1248
1249       DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1250     }
1251
1252     gi->state = (gadget_moving_inside || gi->type & GD_TYPE_SCROLLBAR ?
1253                  GD_BUTTON_PRESSED : GD_BUTTON_UNPRESSED);
1254     gi->event.type = GD_EVENT_MOVING;
1255     gi->event.off_borders = gadget_moving_off_borders;
1256
1257     if (gi->event_mask & GD_EVENT_MOVING && changed_position &&
1258         (gadget_moving_inside || gi->event_mask & GD_EVENT_OFF_BORDERS))
1259       gi->callback_action(gi);
1260   }
1261
1262   if (gadget_released_inside)
1263   {
1264     if (!(gi->type & GD_TYPE_TEXTINPUT))
1265       DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1266
1267     gi->state = GD_BUTTON_UNPRESSED;
1268     gi->event.type = GD_EVENT_RELEASED;
1269
1270     if (gi->event_mask & GD_EVENT_RELEASED)
1271       gi->callback_action(gi);
1272   }
1273
1274   if (gadget_released_off_borders)
1275   {
1276     if (gi->type & GD_TYPE_SCROLLBAR)
1277       DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1278
1279     gi->event.type = GD_EVENT_RELEASED;
1280
1281     if (gi->event_mask & GD_EVENT_RELEASED &&
1282         gi->event_mask & GD_EVENT_OFF_BORDERS)
1283       gi->callback_action(gi);
1284   }
1285 }
1286
1287 void HandleGadgetsKeyInput(Key key)
1288 {
1289   struct GadgetInfo *gi = last_gi;
1290   char text[MAX_GADGET_TEXTSIZE];
1291   int text_length;
1292   int cursor_pos;
1293   char letter;
1294   boolean legal_letter;
1295
1296   if (gi == NULL || !(gi->type & GD_TYPE_TEXTINPUT) || !gi->mapped)
1297     return;
1298
1299   text_length = strlen(gi->text.value);
1300   cursor_pos = gi->text.cursor_position;
1301   letter = getCharFromKey(key);
1302   legal_letter = (gi->type == GD_TYPE_TEXTINPUT_NUMERIC ?
1303                   letter >= '0' && letter <= '9' :
1304                   letter != 0);
1305
1306   if (legal_letter && text_length < gi->text.size)
1307   {
1308     strcpy(text, gi->text.value);
1309     strcpy(&gi->text.value[cursor_pos + 1], &text[cursor_pos]);
1310     gi->text.value[cursor_pos] = letter;
1311     gi->text.cursor_position++;
1312     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1313   }
1314   else if (key == KSYM_Left && cursor_pos > 0)
1315   {
1316     gi->text.cursor_position--;
1317     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1318   }
1319   else if (key == KSYM_Right && cursor_pos < text_length)
1320   {
1321     gi->text.cursor_position++;
1322     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1323   }
1324   else if (key == KSYM_BackSpace && cursor_pos > 0)
1325   {
1326     strcpy(text, gi->text.value);
1327     strcpy(&gi->text.value[cursor_pos - 1], &text[cursor_pos]);
1328     gi->text.cursor_position--;
1329     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1330   }
1331   else if (key == KSYM_Delete && cursor_pos < text_length)
1332   {
1333     strcpy(text, gi->text.value);
1334     strcpy(&gi->text.value[cursor_pos], &text[cursor_pos + 1]);
1335     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1336   }
1337   else if (key == KSYM_Return)
1338   {
1339     CheckRangeOfNumericInputGadget(gi);
1340     DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1341
1342     gi->event.type = GD_EVENT_TEXT_RETURN;
1343
1344     if (gi->event_mask & GD_EVENT_TEXT_RETURN)
1345       gi->callback_action(gi);
1346
1347     last_gi = NULL;
1348   }
1349 }