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