rnd-20001130-1-src
[rocksndiamonds.git] / src / buttons.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 *  buttons.c                                               *
12 ***********************************************************/
13
14 #include <stdarg.h>
15
16 #include "buttons.h"
17 #include "tools.h"
18 #include "misc.h"
19 #include "editor.h"
20 #include "tape.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 (game_status == LEVELED)
328     HandleEditorGadgetInfoText(ptr);
329 }
330
331 static void default_callback_action(void *ptr)
332 {
333   return;
334 }
335
336 static void DrawGadget(struct GadgetInfo *gi, boolean pressed, boolean direct)
337 {
338   int state = (pressed ? 1 : 0);
339   struct GadgetDesign *gd = (gi->checked ?
340                              &gi->alt_design[state] :
341                              &gi->design[state]);
342
343   switch (gi->type)
344   {
345     case GD_TYPE_NORMAL_BUTTON:
346     case GD_TYPE_CHECK_BUTTON:
347     case GD_TYPE_RADIO_BUTTON:
348       BlitBitmap(gd->bitmap, drawto,
349                  gd->x, gd->y, gi->width, gi->height, gi->x, gi->y);
350       if (gi->deco.design.bitmap)
351         BlitBitmap(gi->deco.design.bitmap, drawto,
352                    gi->deco.design.x, gi->deco.design.y,
353                    gi->deco.width, gi->deco.height,
354                    gi->x + gi->deco.x + (pressed ? gi->deco.xshift : 0),
355                    gi->y + gi->deco.y + (pressed ? gi->deco.yshift : 0));
356       break;
357
358     case GD_TYPE_TEXTINPUT_ALPHANUMERIC:
359     case GD_TYPE_TEXTINPUT_NUMERIC:
360       {
361         int i;
362         char cursor_letter;
363         char cursor_string[3];
364         char text[MAX_GADGET_TEXTSIZE + 1];
365         int font_type = gi->text.font_type;
366         int font_width = getFontWidth(FS_SMALL, font_type);
367         int border = gi->border.size;
368         strcpy(text, gi->text.value);
369         strcat(text, " ");
370
371         /* left part of gadget */
372         BlitBitmap(gd->bitmap, drawto,
373                    gd->x, gd->y, border, gi->height, gi->x, gi->y);
374
375         /* middle part of gadget */
376         for (i=0; i<=gi->text.size; i++)
377           BlitBitmap(gd->bitmap, drawto,
378                      gd->x + border, gd->y, font_width, gi->height,
379                      gi->x + border + i * font_width, gi->y);
380
381         /* right part of gadget */
382         BlitBitmap(gd->bitmap, drawto,
383                    gd->x + gi->border.width - border, gd->y,
384                    border, gi->height, gi->x + gi->width - border, gi->y);
385
386         /* gadget text value */
387         DrawText(gi->x + border, gi->y + border, text, FS_SMALL, font_type);
388
389         cursor_letter = gi->text.value[gi->text.cursor_position];
390         cursor_string[0] = '~';
391         cursor_string[1] = (cursor_letter != '\0' ? cursor_letter : ' ');
392         cursor_string[2] = '\0';
393
394         /* draw cursor, if active */
395         if (pressed)
396           DrawText(gi->x + border + gi->text.cursor_position * font_width,
397                    gi->y + border, cursor_string, FS_SMALL, font_type);
398       }
399       break;
400
401     case GD_TYPE_SCROLLBAR_VERTICAL:
402       {
403         int i;
404         int xpos = gi->x;
405         int ypos = gi->y + gi->scrollbar.position;
406         int design_full = gi->width;
407         int design_body = design_full - 2 * gi->border.size;
408         int size_full = gi->scrollbar.size;
409         int size_body = size_full - 2 * gi->border.size;
410         int num_steps = size_body / design_body;
411         int step_size_remain = size_body - num_steps * design_body;
412
413         /* clear scrollbar area */
414         ClearRectangle(backbuffer, gi->x, gi->y, gi->width, gi->height);
415
416         /* upper part of gadget */
417         BlitBitmap(gd->bitmap, drawto,
418                    gd->x, gd->y,
419                    gi->width, gi->border.size,
420                    xpos, ypos);
421
422         /* middle part of gadget */
423         for (i=0; i<num_steps; i++)
424           BlitBitmap(gd->bitmap, drawto,
425                      gd->x, gd->y + gi->border.size,
426                      gi->width, design_body,
427                      xpos, ypos + gi->border.size + i * design_body);
428
429         /* remaining middle part of gadget */
430         if (step_size_remain > 0)
431           BlitBitmap(gd->bitmap, drawto,
432                      gd->x,  gd->y + gi->border.size,
433                      gi->width, step_size_remain,
434                      xpos, ypos + gi->border.size + num_steps * design_body);
435
436         /* lower part of gadget */
437         BlitBitmap(gd->bitmap, drawto,
438                    gd->x, gd->y + design_full - gi->border.size,
439                    gi->width, gi->border.size,
440                    xpos, ypos + size_full - gi->border.size);
441       }
442       break;
443
444     case GD_TYPE_SCROLLBAR_HORIZONTAL:
445       {
446         int i;
447         int xpos = gi->x + gi->scrollbar.position;
448         int ypos = gi->y;
449         int design_full = gi->height;
450         int design_body = design_full - 2 * gi->border.size;
451         int size_full = gi->scrollbar.size;
452         int size_body = size_full - 2 * gi->border.size;
453         int num_steps = size_body / design_body;
454         int step_size_remain = size_body - num_steps * design_body;
455
456         /* clear scrollbar area */
457         ClearRectangle(backbuffer, gi->x, gi->y, gi->width, gi->height);
458
459         /* left part of gadget */
460         BlitBitmap(gd->bitmap, drawto,
461                    gd->x, gd->y,
462                    gi->border.size, gi->height,
463                    xpos, ypos);
464
465         /* middle part of gadget */
466         for (i=0; i<num_steps; i++)
467           BlitBitmap(gd->bitmap, drawto,
468                      gd->x + gi->border.size, gd->y,
469                      design_body, gi->height,
470                      xpos + gi->border.size + i * design_body, ypos);
471
472         /* remaining middle part of gadget */
473         if (step_size_remain > 0)
474           BlitBitmap(gd->bitmap, drawto,
475                      gd->x + gi->border.size, gd->y,
476                      step_size_remain, gi->height,
477                      xpos + gi->border.size + num_steps * design_body, ypos);
478
479         /* right part of gadget */
480         BlitBitmap(gd->bitmap, drawto,
481                    gd->x + design_full - gi->border.size, gd->y,
482                    gi->border.size, gi->height,
483                    xpos + size_full - gi->border.size, ypos);
484       }
485       break;
486
487     default:
488       return;
489   }
490
491   if (direct)
492     BlitBitmap(drawto, window,
493                gi->x, gi->y, gi->width, gi->height, gi->x, gi->y);
494   else
495     redraw_mask |= (gi->x < SX + SXSIZE ? REDRAW_FIELD :
496                     gi->y < DY + DYSIZE ? REDRAW_DOOR_1 :
497                     gi->y > VY ? REDRAW_DOOR_2 : REDRAW_DOOR_3);
498 }
499
500 static void HandleGadgetTags(struct GadgetInfo *gi, int first_tag, va_list ap)
501 {
502   int tag = first_tag;
503
504   while (tag != GDI_END)
505   {
506     switch(tag)
507     {
508       case GDI_CUSTOM_ID:
509         gi->custom_id = va_arg(ap, int);
510         break;
511
512       case GDI_CUSTOM_TYPE_ID:
513         gi->custom_type_id = va_arg(ap, int);
514         break;
515
516       case GDI_INFO_TEXT:
517         {
518           int max_textsize = MAX_INFO_TEXTSIZE - 1;
519
520           strncpy(gi->info_text, va_arg(ap, char *), max_textsize);
521           gi->info_text[max_textsize] = '\0';
522         }
523         break;
524
525       case GDI_X:
526         gi->x = va_arg(ap, int);
527         break;
528
529       case GDI_Y:
530         gi->y = va_arg(ap, int);
531         break;
532
533       case GDI_WIDTH:
534         gi->width = va_arg(ap, int);
535         break;
536
537       case GDI_HEIGHT:
538         gi->height = va_arg(ap, int);
539         break;
540
541       case GDI_TYPE:
542         gi->type = va_arg(ap, unsigned long);
543         break;
544
545       case GDI_STATE:
546         gi->state = va_arg(ap, unsigned long);
547         break;
548
549       case GDI_CHECKED:
550         gi->checked = va_arg(ap, boolean);
551         break;
552
553       case GDI_RADIO_NR:
554         gi->radio_nr = va_arg(ap, unsigned long);
555         break;
556
557       case GDI_NUMBER_VALUE:
558         gi->text.number_value = va_arg(ap, long);
559         sprintf(gi->text.value, "%d", gi->text.number_value);
560         gi->text.cursor_position = strlen(gi->text.value);
561         break;
562
563       case GDI_NUMBER_MIN:
564         gi->text.number_min = va_arg(ap, long);
565         if (gi->text.number_value < gi->text.number_min)
566         {
567           gi->text.number_value = gi->text.number_min;
568           sprintf(gi->text.value, "%d", gi->text.number_value);
569         }
570         break;
571
572       case GDI_NUMBER_MAX:
573         gi->text.number_max = va_arg(ap, long);
574         if (gi->text.number_value > gi->text.number_max)
575         {
576           gi->text.number_value = gi->text.number_max;
577           sprintf(gi->text.value, "%d", gi->text.number_value);
578         }
579         break;
580
581       case GDI_TEXT_VALUE:
582         {
583           int max_textsize = MAX_GADGET_TEXTSIZE;
584
585           if (gi->text.size)
586             max_textsize = MIN(gi->text.size, MAX_GADGET_TEXTSIZE - 1);
587
588           strncpy(gi->text.value, va_arg(ap, char *), max_textsize);
589           gi->text.value[max_textsize] = '\0';
590           gi->text.cursor_position = strlen(gi->text.value);
591         }
592         break;
593
594       case GDI_TEXT_SIZE:
595         {
596           int tag_value = va_arg(ap, int);
597           int max_textsize = MIN(tag_value, MAX_GADGET_TEXTSIZE - 1);
598
599           gi->text.size = max_textsize;
600           gi->text.value[max_textsize] = '\0';
601         }
602         break;
603
604       case GDI_TEXT_FONT:
605         gi->text.font_type = va_arg(ap, int);
606         break;
607
608       case GDI_DESIGN_UNPRESSED:
609         gi->design[GD_BUTTON_UNPRESSED].bitmap = va_arg(ap, Bitmap);
610         gi->design[GD_BUTTON_UNPRESSED].x = va_arg(ap, int);
611         gi->design[GD_BUTTON_UNPRESSED].y = va_arg(ap, int);
612         break;
613
614       case GDI_DESIGN_PRESSED:
615         gi->design[GD_BUTTON_PRESSED].bitmap = va_arg(ap, Bitmap);
616         gi->design[GD_BUTTON_PRESSED].x = va_arg(ap, int);
617         gi->design[GD_BUTTON_PRESSED].y = va_arg(ap, int);
618         break;
619
620       case GDI_ALT_DESIGN_UNPRESSED:
621         gi->alt_design[GD_BUTTON_UNPRESSED].bitmap= va_arg(ap, Bitmap);
622         gi->alt_design[GD_BUTTON_UNPRESSED].x = va_arg(ap, int);
623         gi->alt_design[GD_BUTTON_UNPRESSED].y = va_arg(ap, int);
624         break;
625
626       case GDI_ALT_DESIGN_PRESSED:
627         gi->alt_design[GD_BUTTON_PRESSED].bitmap = va_arg(ap, Bitmap);
628         gi->alt_design[GD_BUTTON_PRESSED].x = va_arg(ap, int);
629         gi->alt_design[GD_BUTTON_PRESSED].y = va_arg(ap, int);
630         break;
631
632       case GDI_BORDER_SIZE:
633         gi->border.size = va_arg(ap, int);
634         break;
635
636       case GDI_TEXTINPUT_DESIGN_WIDTH:
637         gi->border.width = va_arg(ap, int);
638         break;
639
640       case GDI_DECORATION_DESIGN:
641         gi->deco.design.bitmap = va_arg(ap, Bitmap);
642         gi->deco.design.x = va_arg(ap, int);
643         gi->deco.design.y = va_arg(ap, int);
644         break;
645
646       case GDI_DECORATION_POSITION:
647         gi->deco.x = va_arg(ap, int);
648         gi->deco.y = va_arg(ap, int);
649         break;
650
651       case GDI_DECORATION_SIZE:
652         gi->deco.width = va_arg(ap, int);
653         gi->deco.height = va_arg(ap, int);
654         break;
655
656       case GDI_DECORATION_SHIFTING:
657         gi->deco.xshift = va_arg(ap, int);
658         gi->deco.yshift = va_arg(ap, int);
659         break;
660
661       case GDI_EVENT_MASK:
662         gi->event_mask = va_arg(ap, unsigned long);
663         break;
664
665       case GDI_AREA_SIZE:
666         gi->drawing.area_xsize = va_arg(ap, int);
667         gi->drawing.area_ysize = va_arg(ap, int);
668
669         /* determine dependent values for drawing area gadget, if needed */
670         if (gi->width == 0 && gi->height == 0 &&
671             gi->drawing.item_xsize !=0 && gi->drawing.item_ysize !=0)
672         {
673           gi->width = gi->drawing.area_xsize * gi->drawing.item_xsize;
674           gi->height = gi->drawing.area_ysize * gi->drawing.item_ysize;
675         }
676         else if (gi->drawing.item_xsize == 0 && gi->drawing.item_ysize == 0 &&
677                  gi->width != 0 && gi->height != 0)
678         {
679           gi->drawing.item_xsize = gi->width / gi->drawing.area_xsize;
680           gi->drawing.item_ysize = gi->height / gi->drawing.area_ysize;
681         }
682         break;
683
684       case GDI_ITEM_SIZE:
685         gi->drawing.item_xsize = va_arg(ap, int);
686         gi->drawing.item_ysize = va_arg(ap, int);
687
688         /* determine dependent values for drawing area gadget, if needed */
689         if (gi->width == 0 && gi->height == 0 &&
690             gi->drawing.area_xsize !=0 && gi->drawing.area_ysize !=0)
691         {
692           gi->width = gi->drawing.area_xsize * gi->drawing.item_xsize;
693           gi->height = gi->drawing.area_ysize * gi->drawing.item_ysize;
694         }
695         else if (gi->drawing.area_xsize == 0 && gi->drawing.area_ysize == 0 &&
696                  gi->width != 0 && gi->height != 0)
697         {
698           gi->drawing.area_xsize = gi->width / gi->drawing.item_xsize;
699           gi->drawing.area_ysize = gi->height / gi->drawing.item_ysize;
700         }
701         break;
702
703       case GDI_SCROLLBAR_ITEMS_MAX:
704         gi->scrollbar.items_max = va_arg(ap, int);
705         break;
706
707       case GDI_SCROLLBAR_ITEMS_VISIBLE:
708         gi->scrollbar.items_visible = va_arg(ap, int);
709         break;
710
711       case GDI_SCROLLBAR_ITEM_POSITION:
712         gi->scrollbar.item_position = va_arg(ap, int);
713         break;
714
715       case GDI_CALLBACK_INFO:
716         gi->callback_info = va_arg(ap, gadget_function);
717         break;
718
719       case GDI_CALLBACK_ACTION:
720         gi->callback_action = va_arg(ap, gadget_function);
721         break;
722
723       default:
724         Error(ERR_EXIT, "HandleGadgetTags(): unknown tag %d", tag);
725     }
726
727     tag = va_arg(ap, int);      /* read next tag */
728   }
729
730   /* check if gadget complete */
731   if (gi->type != GD_TYPE_DRAWING_AREA &&
732       (!gi->design[GD_BUTTON_UNPRESSED].bitmap ||
733        !gi->design[GD_BUTTON_PRESSED].bitmap))
734     Error(ERR_EXIT, "gadget incomplete (missing Bitmap)");
735
736   /* adjust gadget values in relation to other gadget values */
737
738   if (gi->type & GD_TYPE_TEXTINPUT)
739   {
740     int font_width = getFontWidth(FS_SMALL, gi->text.font_type);
741     int font_height = getFontHeight(FS_SMALL, gi->text.font_type);
742
743     gi->width = 2 * gi->border.size + (gi->text.size + 1) * font_width;
744     gi->height = 2 * gi->border.size + font_height;
745   }
746
747   if (gi->type & GD_TYPE_TEXTINPUT_NUMERIC)
748   {
749     struct GadgetTextInput *text = &gi->text;
750     int value = text->number_value;
751
752     text->number_value = (value < text->number_min ? text->number_min :
753                           value > text->number_max ? text->number_max :
754                           value);
755
756     sprintf(text->value, "%d", text->number_value);
757   }
758
759   if (gi->type & GD_TYPE_SCROLLBAR)
760   {
761     struct GadgetScrollbar *gs = &gi->scrollbar;
762
763     if (gi->width == 0 || gi->height == 0 ||
764         gs->items_max == 0 || gs->items_visible == 0)
765       Error(ERR_EXIT, "scrollbar gadget incomplete (missing tags)");
766
767     /* calculate internal scrollbar values */
768     gs->size_max = (gi->type == GD_TYPE_SCROLLBAR_VERTICAL ?
769                     gi->height : gi->width);
770     gs->size = gs->size_max * gs->items_visible / gs->items_max;
771     gs->position = gs->size_max * gs->item_position / gs->items_max;
772     gs->position_max = gs->size_max - gs->size;
773     gs->correction = gs->size_max / gs->items_max / 2;
774
775     /* finetuning for maximal right/bottom position */
776     if (gs->item_position == gs->items_max - gs->items_visible)
777       gs->position = gs->position_max;
778   }
779 }
780
781 void ModifyGadget(struct GadgetInfo *gi, int first_tag, ...)
782 {
783   va_list ap;
784
785   va_start(ap, first_tag);
786   HandleGadgetTags(gi, first_tag, ap);
787   va_end(ap);
788
789   RedrawGadget(gi);
790 }
791
792 void RedrawGadget(struct GadgetInfo *gi)
793 {
794   if (gi->mapped)
795     DrawGadget(gi, gi->state, DG_DIRECT);
796 }
797
798 struct GadgetInfo *CreateGadget(int first_tag, ...)
799 {
800   struct GadgetInfo *new_gadget = checked_malloc(sizeof(struct GadgetInfo));
801   va_list ap;
802
803   /* always start with reliable default values */
804   memset(new_gadget, 0, sizeof(struct GadgetInfo));     /* zero all fields */
805   new_gadget->id = getNewGadgetID();
806   new_gadget->callback_info = default_callback_info;
807   new_gadget->callback_action = default_callback_action;
808
809   va_start(ap, first_tag);
810   HandleGadgetTags(new_gadget, first_tag, ap);
811   va_end(ap);
812
813   /* insert new gadget into global gadget list */
814   if (gadget_list_last_entry)
815   {
816     gadget_list_last_entry->next = new_gadget;
817     gadget_list_last_entry = gadget_list_last_entry->next;
818   }
819   else
820     gadget_list_first_entry = gadget_list_last_entry = new_gadget;
821
822   return new_gadget;
823 }
824
825 void FreeGadget(struct GadgetInfo *gi)
826 {
827   struct GadgetInfo *gi_previous = gadget_list_first_entry;
828
829   while (gi_previous && gi_previous->next != gi)
830     gi_previous = gi_previous->next;
831
832   if (gi == gadget_list_first_entry)
833     gadget_list_first_entry = gi->next;
834
835   if (gi == gadget_list_last_entry)
836     gadget_list_last_entry = gi_previous;
837
838   gi_previous->next = gi->next;
839   free(gi);
840 }
841
842 static void CheckRangeOfNumericInputGadget(struct GadgetInfo *gi)
843 {
844   if (gi->type != GD_TYPE_TEXTINPUT_NUMERIC)
845     return;
846
847   gi->text.number_value = atoi(gi->text.value);
848
849   if (gi->text.number_value < gi->text.number_min)
850     gi->text.number_value = gi->text.number_min;
851   if (gi->text.number_value > gi->text.number_max)
852     gi->text.number_value = gi->text.number_max;
853
854   sprintf(gi->text.value, "%d", gi->text.number_value);
855
856   if (gi->text.cursor_position < 0)
857     gi->text.cursor_position = 0;
858   else if (gi->text.cursor_position > strlen(gi->text.value))
859     gi->text.cursor_position = strlen(gi->text.value);
860 }
861
862 /* global pointer to gadget actually in use (when mouse button pressed) */
863 static struct GadgetInfo *last_gi = NULL;
864
865 static void MapGadgetExt(struct GadgetInfo *gi, boolean redraw)
866 {
867   if (gi == NULL || gi->mapped)
868     return;
869
870   gi->mapped = TRUE;
871
872   if (redraw)
873     DrawGadget(gi, DG_UNPRESSED, DG_BUFFERED);
874 }
875
876 void MapGadget(struct GadgetInfo *gi)
877 {
878   MapGadgetExt(gi, TRUE);
879 }
880
881 void UnmapGadget(struct GadgetInfo *gi)
882 {
883   if (gi == NULL || !gi->mapped)
884     return;
885
886   gi->mapped = FALSE;
887
888   if (gi == last_gi)
889     last_gi = NULL;
890 }
891
892 #define MAX_NUM_GADGETS         1024
893 #define MULTIMAP_UNMAP          (1 << 0)
894 #define MULTIMAP_REMAP          (1 << 1)
895 #define MULTIMAP_REDRAW         (1 << 2)
896 #define MULTIMAP_PLAYFIELD      (1 << 3)
897 #define MULTIMAP_DOOR_1         (1 << 4)
898 #define MULTIMAP_DOOR_2         (1 << 5)
899 #define MULTIMAP_ALL            (MULTIMAP_PLAYFIELD | \
900                                  MULTIMAP_DOOR_1 | \
901                                  MULTIMAP_DOOR_2)
902
903 static void MultiMapGadgets(int mode)
904 {
905   struct GadgetInfo *gi = gadget_list_first_entry;
906   static boolean map_state[MAX_NUM_GADGETS];
907   int map_count = 0;
908
909   while (gi)
910   {
911     if ((mode & MULTIMAP_PLAYFIELD && gi->x < SX + SXSIZE) ||
912         (mode & MULTIMAP_DOOR_1 && gi->x >= DX && gi->y < DY + DYSIZE) ||
913         (mode & MULTIMAP_DOOR_2 && gi->x >= DX && gi->y > DY + DYSIZE) ||
914         (mode & MULTIMAP_ALL) == MULTIMAP_ALL)
915     {
916       if (mode & MULTIMAP_UNMAP)
917       {
918         map_state[map_count++ % MAX_NUM_GADGETS] = gi->mapped;
919         UnmapGadget(gi);
920       }
921       else
922       {
923         if (map_state[map_count++ % MAX_NUM_GADGETS])
924           MapGadgetExt(gi, (mode & MULTIMAP_REDRAW));
925       }
926     }
927
928     gi = gi->next;
929   }
930 }
931
932 void UnmapAllGadgets()
933 {
934   MultiMapGadgets(MULTIMAP_ALL | MULTIMAP_UNMAP);
935 }
936
937 void RemapAllGadgets()
938 {
939   MultiMapGadgets(MULTIMAP_ALL | MULTIMAP_REMAP);
940 }
941
942 boolean anyTextGadgetActive()
943 {
944   return (last_gi && last_gi->type & GD_TYPE_TEXTINPUT && last_gi->mapped);
945 }
946
947 void ClickOnGadget(struct GadgetInfo *gi, int button)
948 {
949   /* simulate releasing mouse button over last gadget, if still pressed */
950   if (button_status)
951     HandleGadgets(-1, -1, 0);
952
953   /* simulate pressing mouse button over specified gadget */
954   HandleGadgets(gi->x, gi->y, button);
955
956   /* simulate releasing mouse button over specified gadget */
957   HandleGadgets(gi->x, gi->y, 0);
958 }
959
960 void HandleGadgets(int mx, int my, int button)
961 {
962   static struct GadgetInfo *last_info_gi = NULL;
963   static unsigned long pressed_delay = 0;
964   static int last_button = 0;
965   static int last_mx = 0, last_my = 0;
966   int scrollbar_mouse_pos = 0;
967   struct GadgetInfo *new_gi, *gi;
968   boolean press_event;
969   boolean release_event;
970   boolean mouse_moving;
971   boolean gadget_pressed;
972   boolean gadget_pressed_repeated;
973   boolean gadget_moving;
974   boolean gadget_moving_inside;
975   boolean gadget_moving_off_borders;
976   boolean gadget_released;
977   boolean gadget_released_inside;
978   boolean gadget_released_off_borders;
979   boolean changed_position = FALSE;
980
981   /* check if there are any gadgets defined */
982   if (gadget_list_first_entry == NULL)
983     return;
984
985   /* check which gadget is under the mouse pointer */
986   new_gi = getGadgetInfoFromMousePosition(mx, my);
987
988   /* check if button state has changed since last invocation */
989   press_event = (button != 0 && last_button == 0);
990   release_event = (button == 0 && last_button != 0);
991   last_button = button;
992
993   /* check if mouse has been moved since last invocation */
994   mouse_moving = ((mx != last_mx || my != last_my) && motion_status);
995   last_mx = mx;
996   last_my = my;
997
998   /* special treatment for text and number input gadgets */
999   if (anyTextGadgetActive() && button != 0 && !motion_status)
1000   {
1001     struct GadgetInfo *gi = last_gi;
1002
1003     if (new_gi == last_gi)
1004     {
1005       /* if mouse button pressed inside activated text gadget, set cursor */
1006       gi->text.cursor_position =
1007         (mx - gi->x - gi->border.size) /
1008         getFontWidth(FS_SMALL, gi->text.font_type);
1009
1010       if (gi->text.cursor_position < 0)
1011         gi->text.cursor_position = 0;
1012       else if (gi->text.cursor_position > strlen(gi->text.value))
1013         gi->text.cursor_position = strlen(gi->text.value);
1014
1015       DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1016     }
1017     else
1018     {
1019       /* if mouse button pressed outside text input gadget, deactivate it */
1020       CheckRangeOfNumericInputGadget(gi);
1021       DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1022
1023       gi->event.type = GD_EVENT_TEXT_LEAVING;
1024
1025       if (gi->event_mask & GD_EVENT_TEXT_LEAVING)
1026         gi->callback_action(gi);
1027
1028       last_gi = NULL;
1029     }
1030   }
1031
1032   gadget_pressed =
1033     (button != 0 && last_gi == NULL && new_gi != NULL && press_event);
1034   gadget_pressed_repeated =
1035     (button != 0 && last_gi != NULL && new_gi == last_gi);
1036
1037   gadget_released =             (release_event && last_gi != NULL);
1038   gadget_released_inside =      (gadget_released && new_gi == last_gi);
1039   gadget_released_off_borders = (gadget_released && new_gi != last_gi);
1040
1041   gadget_moving =             (button != 0 && last_gi != NULL && mouse_moving);
1042   gadget_moving_inside =      (gadget_moving && new_gi == last_gi);
1043   gadget_moving_off_borders = (gadget_moving && new_gi != last_gi);
1044
1045   /* if new gadget pressed, store this gadget  */
1046   if (gadget_pressed)
1047     last_gi = new_gi;
1048
1049   /* 'gi' is actually handled gadget */
1050   gi = last_gi;
1051
1052   /* if gadget is scrollbar, choose mouse position value */
1053   if (gi && gi->type & GD_TYPE_SCROLLBAR)
1054     scrollbar_mouse_pos =
1055       (gi->type == GD_TYPE_SCROLLBAR_HORIZONTAL ? mx - gi->x : my - gi->y);
1056
1057   /* if mouse button released, no gadget needs to be handled anymore */
1058   if (button == 0 && last_gi && !(last_gi->type & GD_TYPE_TEXTINPUT))
1059     last_gi = NULL;
1060
1061   /* modify event position values even if no gadget is pressed */
1062   if (button == 0 && !release_event)
1063     gi = new_gi;
1064
1065   if (gi)
1066   {
1067     int last_x = gi->event.x;
1068     int last_y = gi->event.y;
1069
1070     gi->event.x = mx - gi->x;
1071     gi->event.y = my - gi->y;
1072
1073     if (gi->type == GD_TYPE_DRAWING_AREA)
1074     {
1075       gi->event.x /= gi->drawing.item_xsize;
1076       gi->event.y /= gi->drawing.item_ysize;
1077
1078       if (last_x != gi->event.x || last_y != gi->event.y)
1079         changed_position = TRUE;
1080     }
1081   }
1082
1083   /* handle gadget popup info text */
1084   if (last_info_gi != new_gi ||
1085       (new_gi && new_gi->type == GD_TYPE_DRAWING_AREA && changed_position))
1086   {
1087     last_info_gi = new_gi;
1088
1089     if (new_gi != NULL && (button == 0 || new_gi == last_gi))
1090     {
1091       new_gi->event.type = 0;
1092       new_gi->callback_info(new_gi);
1093     }
1094     else
1095       default_callback_info(NULL);
1096   }
1097
1098   if (gadget_pressed)
1099   {
1100     if (gi->type == GD_TYPE_CHECK_BUTTON)
1101     {
1102       gi->checked = !gi->checked;
1103     }
1104     else if (gi->type == GD_TYPE_RADIO_BUTTON)
1105     {
1106       struct GadgetInfo *rgi = gadget_list_first_entry;
1107
1108       while (rgi)
1109       {
1110         if (rgi->mapped &&
1111             rgi->type == GD_TYPE_RADIO_BUTTON &&
1112             rgi->radio_nr == gi->radio_nr &&
1113             rgi != gi)
1114         {
1115           rgi->checked = FALSE;
1116           DrawGadget(rgi, DG_UNPRESSED, DG_DIRECT);
1117         }
1118
1119         rgi = rgi->next;
1120       }
1121
1122       gi->checked = TRUE;
1123     }
1124     else if (gi->type & GD_TYPE_SCROLLBAR)
1125     {
1126       int mpos, gpos;
1127
1128       if (gi->type == GD_TYPE_SCROLLBAR_HORIZONTAL)
1129       {
1130         mpos = mx;
1131         gpos = gi->x;
1132       }
1133       else
1134       {
1135         mpos = my;
1136         gpos = gi->y;
1137       }
1138
1139       if (mpos >= gpos + gi->scrollbar.position &&
1140           mpos < gpos + gi->scrollbar.position + gi->scrollbar.size)
1141       {
1142         /* drag scrollbar */
1143         gi->scrollbar.drag_position =
1144           scrollbar_mouse_pos - gi->scrollbar.position;
1145       }
1146       else
1147       {
1148         /* click scrollbar one scrollbar length up/left or down/right */
1149
1150         struct GadgetScrollbar *gs = &gi->scrollbar;
1151         int old_item_position = gs->item_position;
1152
1153         changed_position = FALSE;
1154
1155         gs->item_position +=
1156           gs->items_visible * (mpos < gpos + gi->scrollbar.position ? -1 : +1);
1157
1158         if (gs->item_position < 0)
1159           gs->item_position = 0;
1160         if (gs->item_position > gs->items_max - gs->items_visible)
1161           gs->item_position = gs->items_max - gs->items_visible;
1162
1163         if (old_item_position != gs->item_position)
1164         {
1165           gi->event.item_position = gs->item_position;
1166           changed_position = TRUE;
1167         }
1168
1169         ModifyGadget(gi, GDI_SCROLLBAR_ITEM_POSITION, gs->item_position,
1170                      GDI_END);
1171
1172         gi->state = GD_BUTTON_UNPRESSED;
1173         gi->event.type = GD_EVENT_MOVING;
1174         gi->event.off_borders = FALSE;
1175
1176         if (gi->event_mask & GD_EVENT_MOVING && changed_position)
1177           gi->callback_action(gi);
1178
1179         /* don't handle this scrollbar anymore while mouse button pressed */
1180         last_gi = NULL;
1181
1182         return;
1183       }
1184     }
1185
1186     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1187
1188     gi->state = GD_BUTTON_PRESSED;
1189     gi->event.type = GD_EVENT_PRESSED;
1190     gi->event.button = button;
1191     gi->event.off_borders = FALSE;
1192
1193     /* initialize delay counter */
1194     DelayReached(&pressed_delay, 0);
1195
1196     if (gi->event_mask & GD_EVENT_PRESSED)
1197       gi->callback_action(gi);
1198   }
1199
1200   if (gadget_pressed_repeated)
1201   {
1202     gi->event.type = GD_EVENT_PRESSED;
1203
1204     if (gi->event_mask & GD_EVENT_REPEATED &&
1205         DelayReached(&pressed_delay, GADGET_FRAME_DELAY))
1206       gi->callback_action(gi);
1207   }
1208
1209   if (gadget_moving)
1210   {
1211     if (gi->type & GD_TYPE_BUTTON)
1212     {
1213       if (gadget_moving_inside && gi->state == GD_BUTTON_UNPRESSED)
1214         DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1215       else if (gadget_moving_off_borders && gi->state == GD_BUTTON_PRESSED)
1216         DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1217     }
1218
1219     if (gi->type & GD_TYPE_SCROLLBAR)
1220     {
1221       struct GadgetScrollbar *gs = &gi->scrollbar;
1222       int old_item_position = gs->item_position;
1223
1224       gs->position = scrollbar_mouse_pos - gs->drag_position;
1225
1226       if (gs->position < 0)
1227         gs->position = 0;
1228       if (gs->position > gs->position_max)
1229         gs->position = gs->position_max;
1230
1231       gs->item_position =
1232         gs->items_max * (gs->position + gs->correction) / gs->size_max;
1233
1234       if (gs->item_position < 0)
1235         gs->item_position = 0;
1236       if (gs->item_position > gs->items_max - 1)
1237         gs->item_position = gs->items_max - 1;
1238
1239       if (old_item_position != gs->item_position)
1240       {
1241         gi->event.item_position = gs->item_position;
1242         changed_position = TRUE;
1243       }
1244
1245       DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1246     }
1247
1248     gi->state = (gadget_moving_inside || gi->type & GD_TYPE_SCROLLBAR ?
1249                  GD_BUTTON_PRESSED : GD_BUTTON_UNPRESSED);
1250     gi->event.type = GD_EVENT_MOVING;
1251     gi->event.off_borders = gadget_moving_off_borders;
1252
1253     if (gi->event_mask & GD_EVENT_MOVING && changed_position &&
1254         (gadget_moving_inside || gi->event_mask & GD_EVENT_OFF_BORDERS))
1255       gi->callback_action(gi);
1256   }
1257
1258   if (gadget_released_inside)
1259   {
1260     if (!(gi->type & GD_TYPE_TEXTINPUT))
1261       DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1262
1263     gi->state = GD_BUTTON_UNPRESSED;
1264     gi->event.type = GD_EVENT_RELEASED;
1265
1266     if (gi->event_mask & GD_EVENT_RELEASED)
1267       gi->callback_action(gi);
1268   }
1269
1270   if (gadget_released_off_borders)
1271   {
1272     if (gi->type & GD_TYPE_SCROLLBAR)
1273       DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1274
1275     gi->event.type = GD_EVENT_RELEASED;
1276
1277     if (gi->event_mask & GD_EVENT_RELEASED &&
1278         gi->event_mask & GD_EVENT_OFF_BORDERS)
1279       gi->callback_action(gi);
1280   }
1281 }
1282
1283 void HandleGadgetsKeyInput(Key key)
1284 {
1285   struct GadgetInfo *gi = last_gi;
1286   char text[MAX_GADGET_TEXTSIZE];
1287   int text_length;
1288   int cursor_pos;
1289   char letter;
1290   boolean legal_letter;
1291
1292   if (gi == NULL || !(gi->type & GD_TYPE_TEXTINPUT) || !gi->mapped)
1293     return;
1294
1295   text_length = strlen(gi->text.value);
1296   cursor_pos = gi->text.cursor_position;
1297   letter = getCharFromKey(key);
1298   legal_letter = (gi->type == GD_TYPE_TEXTINPUT_NUMERIC ?
1299                   letter >= '0' && letter <= '9' :
1300                   letter != 0);
1301
1302   if (legal_letter && text_length < gi->text.size)
1303   {
1304     strcpy(text, gi->text.value);
1305     strcpy(&gi->text.value[cursor_pos + 1], &text[cursor_pos]);
1306     gi->text.value[cursor_pos] = letter;
1307     gi->text.cursor_position++;
1308     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1309   }
1310   else if (key == KSYM_Left && cursor_pos > 0)
1311   {
1312     gi->text.cursor_position--;
1313     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1314   }
1315   else if (key == KSYM_Right && cursor_pos < text_length)
1316   {
1317     gi->text.cursor_position++;
1318     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1319   }
1320   else if (key == KSYM_BackSpace && cursor_pos > 0)
1321   {
1322     strcpy(text, gi->text.value);
1323     strcpy(&gi->text.value[cursor_pos - 1], &text[cursor_pos]);
1324     gi->text.cursor_position--;
1325     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1326   }
1327   else if (key == KSYM_Delete && cursor_pos < text_length)
1328   {
1329     strcpy(text, gi->text.value);
1330     strcpy(&gi->text.value[cursor_pos], &text[cursor_pos + 1]);
1331     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1332   }
1333   else if (key == KSYM_Return)
1334   {
1335     CheckRangeOfNumericInputGadget(gi);
1336     DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1337
1338     gi->event.type = GD_EVENT_TEXT_RETURN;
1339
1340     if (gi->event_mask & GD_EVENT_TEXT_RETURN)
1341       gi->callback_action(gi);
1342
1343     last_gi = NULL;
1344   }
1345 }