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