c24981f377d6b9c29492d1ea795b9ea344764559
[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_1 && gi->x >= DX && gi->y > DY + DYSIZE))
903     {
904       if (mode & MULTIMAP_UNMAP)
905       {
906         map_state[map_count++ % MAX_NUM_GADGETS] = gi->mapped;
907         UnmapGadget(gi);
908       }
909       else
910       {
911         if (map_state[map_count++ % MAX_NUM_GADGETS])
912           MapGadgetExt(gi, (mode & MULTIMAP_REDRAW));
913       }
914     }
915
916     gi = gi->next;
917   }
918 }
919
920 void UnmapAllGadgets()
921 {
922   MultiMapGadgets(MULTIMAP_ALL | MULTIMAP_UNMAP);
923 }
924
925 void RemapAllGadgets()
926 {
927   MultiMapGadgets(MULTIMAP_ALL | MULTIMAP_REMAP);
928 }
929
930 boolean anyTextGadgetActive()
931 {
932   return (last_gi && last_gi->type & GD_TYPE_TEXTINPUT && last_gi->mapped);
933 }
934
935 void ClickOnGadget(struct GadgetInfo *gi, int button)
936 {
937   /* simulate releasing mouse button over last gadget, if still pressed */
938   if (button_status)
939     HandleGadgets(-1, -1, 0);
940
941   /* simulate pressing mouse button over specified gadget */
942   HandleGadgets(gi->x, gi->y, button);
943
944   /* simulate releasing mouse button over specified gadget */
945   HandleGadgets(gi->x, gi->y, 0);
946 }
947
948 void HandleGadgets(int mx, int my, int button)
949 {
950   static struct GadgetInfo *last_info_gi = NULL;
951   static unsigned long pressed_delay = 0;
952   static int last_button = 0;
953   static int last_mx = 0, last_my = 0;
954   int scrollbar_mouse_pos = 0;
955   struct GadgetInfo *new_gi, *gi;
956   boolean press_event;
957   boolean release_event;
958   boolean mouse_moving;
959   boolean gadget_pressed;
960   boolean gadget_pressed_repeated;
961   boolean gadget_moving;
962   boolean gadget_moving_inside;
963   boolean gadget_moving_off_borders;
964   boolean gadget_released;
965   boolean gadget_released_inside;
966   boolean gadget_released_off_borders;
967   boolean changed_position = FALSE;
968
969   /* check if there are any gadgets defined */
970   if (gadget_list_first_entry == NULL)
971     return;
972
973   /* check which gadget is under the mouse pointer */
974   new_gi = getGadgetInfoFromMousePosition(mx, my);
975
976   /* check if button state has changed since last invocation */
977   press_event = (button != 0 && last_button == 0);
978   release_event = (button == 0 && last_button != 0);
979   last_button = button;
980
981   /* check if mouse has been moved since last invocation */
982   mouse_moving = ((mx != last_mx || my != last_my) && motion_status);
983   last_mx = mx;
984   last_my = my;
985
986   /* special treatment for text and number input gadgets */
987   if (anyTextGadgetActive() && button != 0 && !motion_status)
988   {
989     struct GadgetInfo *gi = last_gi;
990
991     if (new_gi == last_gi)
992     {
993       /* if mouse button pressed inside activated text gadget, set cursor */
994       gi->text.cursor_position = (mx - gi->x) / FONT2_XSIZE;
995
996       if (gi->text.cursor_position < 0)
997         gi->text.cursor_position = 0;
998       else if (gi->text.cursor_position > strlen(gi->text.value))
999         gi->text.cursor_position = strlen(gi->text.value);
1000
1001       DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1002     }
1003     else
1004     {
1005       /* if mouse button pressed outside text input gadget, deactivate it */
1006       CheckRangeOfNumericInputGadget(gi);
1007       DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1008
1009       if (gi->event_mask & GD_EVENT_TEXT_LEAVING)
1010         gi->callback_action(gi);
1011
1012       last_gi = NULL;
1013     }
1014   }
1015
1016   gadget_pressed =
1017     (button != 0 && last_gi == NULL && new_gi != NULL && press_event);
1018   gadget_pressed_repeated =
1019     (button != 0 && last_gi != NULL && new_gi == last_gi);
1020
1021   gadget_released =             (release_event && last_gi != NULL);
1022   gadget_released_inside =      (gadget_released && new_gi == last_gi);
1023   gadget_released_off_borders = (gadget_released && new_gi != last_gi);
1024
1025   gadget_moving =             (button != 0 && last_gi != NULL && mouse_moving);
1026   gadget_moving_inside =      (gadget_moving && new_gi == last_gi);
1027   gadget_moving_off_borders = (gadget_moving && new_gi != last_gi);
1028
1029   /* if new gadget pressed, store this gadget  */
1030   if (gadget_pressed)
1031     last_gi = new_gi;
1032
1033   /* 'gi' is actually handled gadget */
1034   gi = last_gi;
1035
1036   /* if gadget is scrollbar, choose mouse position value */
1037   if (gi && gi->type & GD_TYPE_SCROLLBAR)
1038     scrollbar_mouse_pos =
1039       (gi->type == GD_TYPE_SCROLLBAR_HORIZONTAL ? mx - gi->x : my - gi->y);
1040
1041   /* if mouse button released, no gadget needs to be handled anymore */
1042   if (button == 0 && last_gi && !(last_gi->type & GD_TYPE_TEXTINPUT))
1043     last_gi = NULL;
1044
1045   /* modify event position values even if no gadget is pressed */
1046   if (button == 0 && !release_event)
1047     gi = new_gi;
1048
1049   if (gi)
1050   {
1051     int last_x = gi->event.x;
1052     int last_y = gi->event.y;
1053
1054     gi->event.x = mx - gi->x;
1055     gi->event.y = my - gi->y;
1056
1057     if (gi->type == GD_TYPE_DRAWING_AREA)
1058     {
1059       gi->event.x /= gi->drawing.item_xsize;
1060       gi->event.y /= gi->drawing.item_ysize;
1061
1062       if (last_x != gi->event.x || last_y != gi->event.y)
1063         changed_position = TRUE;
1064     }
1065   }
1066
1067   /* handle gadget popup info text */
1068   if (last_info_gi != new_gi ||
1069       (new_gi && new_gi->type == GD_TYPE_DRAWING_AREA && changed_position))
1070   {
1071     last_info_gi = new_gi;
1072
1073     if (new_gi != NULL && (button == 0 || new_gi == last_gi))
1074     {
1075       new_gi->event.type = 0;
1076       new_gi->callback_info(new_gi);
1077     }
1078     else
1079       default_callback_info(NULL);
1080   }
1081
1082   if (gadget_pressed)
1083   {
1084     if (gi->type == GD_TYPE_CHECK_BUTTON)
1085     {
1086       gi->checked = !gi->checked;
1087     }
1088     else if (gi->type == GD_TYPE_RADIO_BUTTON)
1089     {
1090       struct GadgetInfo *rgi = gadget_list_first_entry;
1091
1092       while (rgi)
1093       {
1094         if (rgi->mapped &&
1095             rgi->type == GD_TYPE_RADIO_BUTTON &&
1096             rgi->radio_nr == gi->radio_nr &&
1097             rgi != gi)
1098         {
1099           rgi->checked = FALSE;
1100           DrawGadget(rgi, DG_UNPRESSED, DG_DIRECT);
1101         }
1102
1103         rgi = rgi->next;
1104       }
1105
1106       gi->checked = TRUE;
1107     }
1108     else if (gi->type & GD_TYPE_SCROLLBAR)
1109     {
1110       int mpos, gpos;
1111
1112       if (gi->type == GD_TYPE_SCROLLBAR_HORIZONTAL)
1113       {
1114         mpos = mx;
1115         gpos = gi->x;
1116       }
1117       else
1118       {
1119         mpos = my;
1120         gpos = gi->y;
1121       }
1122
1123       if (mpos >= gpos + gi->scrollbar.position &&
1124           mpos < gpos + gi->scrollbar.position + gi->scrollbar.size)
1125       {
1126         /* drag scrollbar */
1127         gi->scrollbar.drag_position =
1128           scrollbar_mouse_pos - gi->scrollbar.position;
1129       }
1130       else
1131       {
1132         /* click scrollbar one scrollbar length up/left or down/right */
1133
1134         struct GadgetScrollbar *gs = &gi->scrollbar;
1135         int old_item_position = gs->item_position;
1136
1137         changed_position = FALSE;
1138
1139         gs->item_position +=
1140           gs->items_visible * (mpos < gpos + gi->scrollbar.position ? -1 : +1);
1141
1142         if (gs->item_position < 0)
1143           gs->item_position = 0;
1144         if (gs->item_position > gs->items_max - gs->items_visible)
1145           gs->item_position = gs->items_max - gs->items_visible;
1146
1147         if (old_item_position != gs->item_position)
1148         {
1149           gi->event.item_position = gs->item_position;
1150           changed_position = TRUE;
1151         }
1152
1153         ModifyGadget(gi, GDI_SCROLLBAR_ITEM_POSITION, gs->item_position,
1154                      GDI_END);
1155
1156         gi->state = GD_BUTTON_UNPRESSED;
1157         gi->event.type = GD_EVENT_MOVING;
1158         gi->event.off_borders = FALSE;
1159
1160         if (gi->event_mask & GD_EVENT_MOVING && changed_position)
1161           gi->callback_action(gi);
1162
1163         /* don't handle this scrollbar anymore while mouse button pressed */
1164         last_gi = NULL;
1165
1166         return;
1167       }
1168     }
1169
1170     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1171
1172     gi->state = GD_BUTTON_PRESSED;
1173     gi->event.type = GD_EVENT_PRESSED;
1174     gi->event.button = button;
1175     gi->event.off_borders = FALSE;
1176
1177     /* initialize delay counter */
1178     DelayReached(&pressed_delay, 0);
1179
1180     if (gi->event_mask & GD_EVENT_PRESSED)
1181       gi->callback_action(gi);
1182   }
1183
1184   if (gadget_pressed_repeated)
1185   {
1186     if (gi->event_mask & GD_EVENT_REPEATED &&
1187         DelayReached(&pressed_delay, GADGET_FRAME_DELAY))
1188       gi->callback_action(gi);
1189   }
1190
1191   if (gadget_moving)
1192   {
1193     if (gi->type & GD_TYPE_BUTTON)
1194     {
1195       if (gadget_moving_inside && gi->state == GD_BUTTON_UNPRESSED)
1196         DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1197       else if (gadget_moving_off_borders && gi->state == GD_BUTTON_PRESSED)
1198         DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1199     }
1200
1201     if (gi->type & GD_TYPE_SCROLLBAR)
1202     {
1203       struct GadgetScrollbar *gs = &gi->scrollbar;
1204       int old_item_position = gs->item_position;
1205
1206       gs->position = scrollbar_mouse_pos - gs->drag_position;
1207
1208       if (gs->position < 0)
1209         gs->position = 0;
1210       if (gs->position > gs->position_max)
1211         gs->position = gs->position_max;
1212
1213       gs->item_position = gs->items_max * gs->position / gs->size_max;
1214
1215       if (old_item_position != gs->item_position)
1216       {
1217         gi->event.item_position = gs->item_position;
1218         changed_position = TRUE;
1219       }
1220
1221       DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1222     }
1223
1224     gi->state = (gadget_moving_inside || gi->type & GD_TYPE_SCROLLBAR ?
1225                  GD_BUTTON_PRESSED : GD_BUTTON_UNPRESSED);
1226     gi->event.type = GD_EVENT_MOVING;
1227     gi->event.off_borders = gadget_moving_off_borders;
1228
1229     if (gi->event_mask & GD_EVENT_MOVING && changed_position &&
1230         (gadget_moving_inside || gi->event_mask & GD_EVENT_OFF_BORDERS))
1231       gi->callback_action(gi);
1232   }
1233
1234   if (gadget_released_inside)
1235   {
1236     if (!(gi->type & GD_TYPE_TEXTINPUT))
1237       DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1238
1239     gi->state = GD_BUTTON_UNPRESSED;
1240     gi->event.type = GD_EVENT_RELEASED;
1241
1242     if (gi->event_mask & GD_EVENT_RELEASED)
1243       gi->callback_action(gi);
1244   }
1245
1246   if (gadget_released_off_borders)
1247   {
1248     if (gi->type & GD_TYPE_SCROLLBAR)
1249       DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1250
1251     gi->event.type = GD_EVENT_RELEASED;
1252
1253     if (gi->event_mask & GD_EVENT_RELEASED &&
1254         gi->event_mask & GD_EVENT_OFF_BORDERS)
1255       gi->callback_action(gi);
1256   }
1257 }
1258
1259 void HandleGadgetsKeyInput(KeySym key)
1260 {
1261   struct GadgetInfo *gi = last_gi;
1262   char text[MAX_GADGET_TEXTSIZE];
1263   int text_length;
1264   int cursor_pos;
1265   char letter;
1266   boolean legal_letter;
1267
1268   if (gi == NULL || !(gi->type & GD_TYPE_TEXTINPUT) || !gi->mapped)
1269     return;
1270
1271   text_length = strlen(gi->text.value);
1272   cursor_pos = gi->text.cursor_position;
1273   letter = getCharFromKeySym(key);
1274   legal_letter = (gi->type == GD_TYPE_TEXTINPUT_NUMERIC ?
1275                   letter >= '0' && letter <= '9' :
1276                   letter != 0);
1277
1278   if (legal_letter && text_length < gi->text.size)
1279   {
1280     strcpy(text, gi->text.value);
1281     strcpy(&gi->text.value[cursor_pos + 1], &text[cursor_pos]);
1282     gi->text.value[cursor_pos] = letter;
1283     gi->text.cursor_position++;
1284     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1285   }
1286   else if (key == XK_Left && cursor_pos > 0)
1287   {
1288     gi->text.cursor_position--;
1289     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1290   }
1291   else if (key == XK_Right && cursor_pos < text_length)
1292   {
1293     gi->text.cursor_position++;
1294     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1295   }
1296   else if (key == XK_BackSpace && cursor_pos > 0)
1297   {
1298     strcpy(text, gi->text.value);
1299     strcpy(&gi->text.value[cursor_pos - 1], &text[cursor_pos]);
1300     gi->text.cursor_position--;
1301     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1302   }
1303   else if (key == XK_Delete && cursor_pos < text_length)
1304   {
1305     strcpy(text, gi->text.value);
1306     strcpy(&gi->text.value[cursor_pos], &text[cursor_pos + 1]);
1307     DrawGadget(gi, DG_PRESSED, DG_DIRECT);
1308   }
1309   else if (key == XK_Return)
1310   {
1311     CheckRangeOfNumericInputGadget(gi);
1312     DrawGadget(gi, DG_UNPRESSED, DG_DIRECT);
1313
1314     if (gi->event_mask & GD_EVENT_TEXT_RETURN)
1315       gi->callback_action(gi);
1316
1317     last_gi = NULL;
1318   }
1319 }