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