1 /***********************************************************
2 * Artsoft Retro-Game Library *
3 *----------------------------------------------------------*
4 * (c) 1994-2006 Artsoft Entertainment *
6 * Detmolder Strasse 189 *
9 * e-mail: info@artsoft.org *
10 *----------------------------------------------------------*
12 ***********************************************************/
22 /* values for DrawGadget() */
23 #define DG_UNPRESSED 0
29 static struct GadgetInfo *gadget_list_first_entry = NULL;
30 static struct GadgetInfo *gadget_list_last_entry = NULL;
31 static struct GadgetInfo *last_info_gi = NULL;
32 static int next_free_gadget_id = 1;
33 static boolean gadget_id_wrapped = FALSE;
35 static void (*PlayGadgetSoundActivating)(void) = NULL;
36 static void (*PlayGadgetSoundSelecting)(void) = NULL;
39 void InitGadgetsSoundCallback(void (*activating_function)(void),
40 void (*selecting_function)(void))
42 PlayGadgetSoundActivating = activating_function;
43 PlayGadgetSoundSelecting = selecting_function;
46 static struct GadgetInfo *getGadgetInfoFromGadgetID(int id)
48 struct GadgetInfo *gi = gadget_list_first_entry;
50 while (gi && gi->id != id)
56 static int getNewGadgetID()
58 int id = next_free_gadget_id++;
60 if (next_free_gadget_id <= 0) /* counter overrun */
62 gadget_id_wrapped = TRUE; /* now we must check each ID */
63 next_free_gadget_id = 0;
66 if (gadget_id_wrapped)
68 next_free_gadget_id++;
69 while (getGadgetInfoFromGadgetID(next_free_gadget_id) != NULL)
70 next_free_gadget_id++;
73 if (next_free_gadget_id <= 0) /* cannot get new gadget id */
74 Error(ERR_EXIT, "too much gadgets -- this should not happen");
79 static struct GadgetInfo *getGadgetInfoFromMousePosition(int mx, int my,
82 struct GadgetInfo *gi;
84 /* first check for scrollbars in case of mouse scroll wheel button events */
85 if (IS_WHEEL_BUTTON(button))
87 /* real horizontal wheel or vertical wheel with modifier key pressed */
88 boolean check_horizontal = (IS_WHEEL_BUTTON_HORIZONTAL(button) ||
89 GetKeyModState() & KMOD_Shift);
91 /* check for the first active scrollbar with matching mouse wheel area */
92 for (gi = gadget_list_first_entry; gi != NULL; gi = gi->next)
94 if (gi->mapped && gi->active &&
95 ((gi->type & GD_TYPE_SCROLLBAR_HORIZONTAL && check_horizontal) ||
96 (gi->type & GD_TYPE_SCROLLBAR_VERTICAL && !check_horizontal)) &&
97 mx >= gi->wheelarea.x && mx < gi->wheelarea.x + gi->wheelarea.width &&
98 my >= gi->wheelarea.y && my < gi->wheelarea.y + gi->wheelarea.height)
102 /* no active scrollbar found -- ignore this scroll wheel button event */
106 /* open selectboxes may overlap other active gadgets, so check them first */
107 for (gi = gadget_list_first_entry; gi != NULL; gi = gi->next)
109 if (gi->mapped && gi->active &&
110 gi->type & GD_TYPE_SELECTBOX && gi->selectbox.open &&
111 mx >= gi->selectbox.x && mx < gi->selectbox.x + gi->selectbox.width &&
112 my >= gi->selectbox.y && my < gi->selectbox.y + gi->selectbox.height)
116 /* check all other gadgets */
117 for (gi = gadget_list_first_entry; gi != NULL; gi = gi->next)
119 if (gi->mapped && gi->active &&
120 mx >= gi->x && mx < gi->x + gi->width &&
121 my >= gi->y && my < gi->y + gi->height)
128 static void setTextAreaCursorExt(struct GadgetInfo *gi, boolean set_cursor_pos)
130 char *text = gi->textarea.value;
131 int area_xsize = gi->textarea.xsize;
132 int area_ysize = gi->textarea.ysize;
133 int cursor_position = gi->textarea.cursor_position;
134 int cursor_x = gi->textarea.cursor_x;
135 int cursor_y = gi->textarea.cursor_y;
142 if (set_cursor_pos) /* x/y => position */
144 if (y == cursor_y && (x == cursor_x || (x < cursor_x && *text == '\n')))
147 else /* position => x/y */
149 if (pos == cursor_position)
153 if (x + 1 >= area_xsize || *text == '\n')
155 if (y + 1 >= area_ysize)
168 gi->textarea.cursor_x = x;
169 gi->textarea.cursor_y = y;
170 gi->textarea.cursor_x_preferred = x;
171 gi->textarea.cursor_position = pos;
174 static void setTextAreaCursorXY(struct GadgetInfo *gi, int x, int y)
176 gi->textarea.cursor_x = x;
177 gi->textarea.cursor_y = y;
179 setTextAreaCursorExt(gi, TRUE);
182 static void setTextAreaCursorPosition(struct GadgetInfo *gi, int pos)
184 gi->textarea.cursor_position = pos;
186 setTextAreaCursorExt(gi, FALSE);
189 static void default_callback_info(void *ptr)
194 static void default_callback_action(void *ptr)
199 static void DrawGadget(struct GadgetInfo *gi, boolean pressed, boolean direct)
201 struct GadgetDesign *gd;
202 int state = (pressed ? GD_BUTTON_PRESSED : GD_BUTTON_UNPRESSED);
203 boolean redraw_selectbox = FALSE;
208 gd = (!gi->active ? &gi->alt_design[state] :
209 gi->checked ? &gi->alt_design[state] : &gi->design[state]);
213 case GD_TYPE_NORMAL_BUTTON:
214 case GD_TYPE_CHECK_BUTTON:
215 case GD_TYPE_RADIO_BUTTON:
216 BlitBitmapOnBackground(gd->bitmap, drawto,
217 gd->x, gd->y, gi->width, gi->height,
219 if (gi->deco.design.bitmap)
220 BlitBitmap(gi->deco.design.bitmap, drawto,
221 gi->deco.design.x, gi->deco.design.y,
222 gi->deco.width, gi->deco.height,
223 gi->x + gi->deco.x + (pressed ? gi->deco.xshift : 0),
224 gi->y + gi->deco.y + (pressed ? gi->deco.yshift : 0));
227 case GD_TYPE_TEXT_BUTTON:
230 int font_nr = (gi->active ? gi->font_active : gi->font);
231 int font_width = getFontWidth(font_nr);
232 int border_x = gi->border.xsize;
233 int border_y = gi->border.ysize;
234 int text_size = strlen(gi->textbutton.value);
235 int text_start = (gi->width - text_size * font_width) / 2;
237 /* left part of gadget */
238 BlitBitmapOnBackground(gd->bitmap, drawto, gd->x, gd->y,
239 border_x, gi->height, gi->x, gi->y);
241 /* middle part of gadget */
242 for (i=0; i < gi->textbutton.size; i++)
243 BlitBitmapOnBackground(gd->bitmap, drawto, gd->x + border_x, gd->y,
244 font_width, gi->height,
245 gi->x + border_x + i * font_width, gi->y);
247 /* right part of gadget */
248 BlitBitmapOnBackground(gd->bitmap, drawto,
249 gd->x + gi->border.width - border_x, gd->y,
250 border_x, gi->height,
251 gi->x + gi->width - border_x, gi->y);
253 /* gadget text value */
255 gi->x + text_start + (pressed ? gi->deco.xshift : 0),
256 gi->y + border_y + (pressed ? gi->deco.yshift : 0),
257 gi->textbutton.value, font_nr, BLIT_MASKED);
261 case GD_TYPE_TEXT_INPUT_ALPHANUMERIC:
262 case GD_TYPE_TEXT_INPUT_NUMERIC:
266 char cursor_string[2];
267 char text[MAX_GADGET_TEXTSIZE + 1];
268 int font_nr = (pressed ? gi->font_active : gi->font);
269 int font_width = getFontWidth(font_nr);
270 int border_x = gi->border.xsize;
271 int border_y = gi->border.ysize;
273 /* left part of gadget */
274 BlitBitmapOnBackground(gd->bitmap, drawto, gd->x, gd->y,
275 border_x, gi->height, gi->x, gi->y);
277 /* middle part of gadget */
278 for (i=0; i < gi->textinput.size + 1; i++)
279 BlitBitmapOnBackground(gd->bitmap, drawto, gd->x + border_x, gd->y,
280 font_width, gi->height,
281 gi->x + border_x + i * font_width, gi->y);
283 /* right part of gadget */
284 BlitBitmapOnBackground(gd->bitmap, drawto,
285 gd->x + gi->border.width - border_x, gd->y,
286 border_x, gi->height,
287 gi->x + gi->width - border_x, gi->y);
290 strcpy(text, gi->textinput.value);
293 /* gadget text value */
295 gi->x + border_x, gi->y + border_y, text,
296 font_nr, BLIT_MASKED);
298 cursor_letter = gi->textinput.value[gi->textinput.cursor_position];
299 cursor_string[0] = (cursor_letter != '\0' ? cursor_letter : ' ');
300 cursor_string[1] = '\0';
302 /* draw cursor, if active */
306 gi->textinput.cursor_position * font_width,
307 gi->y + border_y, cursor_string,
308 font_nr, BLIT_INVERSE);
312 case GD_TYPE_TEXT_AREA:
316 char cursor_string[2];
317 int font_nr = (pressed ? gi->font_active : gi->font);
318 int font_width = getFontWidth(font_nr);
319 int font_height = getFontHeight(font_nr);
320 int border_x = gi->border.xsize;
321 int border_y = gi->border.ysize;
322 int gd_height = 2 * border_y + font_height;
324 /* top left part of gadget border */
325 BlitBitmapOnBackground(gd->bitmap, drawto, gd->x, gd->y,
326 border_x, border_y, gi->x, gi->y);
328 /* top middle part of gadget border */
329 for (i=0; i < gi->textarea.xsize; i++)
330 BlitBitmapOnBackground(gd->bitmap, drawto, gd->x + border_x, gd->y,
331 font_width, border_y,
332 gi->x + border_x + i * font_width, gi->y);
334 /* top right part of gadget border */
335 BlitBitmapOnBackground(gd->bitmap, drawto,
336 gd->x + gi->border.width - border_x, gd->y,
338 gi->x + gi->width - border_x, gi->y);
340 /* left and right part of gadget border for each row */
341 for (i=0; i < gi->textarea.ysize; i++)
343 BlitBitmapOnBackground(gd->bitmap, drawto, gd->x, gd->y + border_y,
344 border_x, font_height,
345 gi->x, gi->y + border_y + i * font_height);
346 BlitBitmapOnBackground(gd->bitmap, drawto,
347 gd->x + gi->border.width - border_x,
349 border_x, font_height,
350 gi->x + gi->width - border_x,
351 gi->y + border_y + i * font_height);
354 /* bottom left part of gadget border */
355 BlitBitmapOnBackground(gd->bitmap, drawto,
356 gd->x, gd->y + gd_height - border_y,
358 gi->x, gi->y + gi->height - border_y);
360 /* bottom middle part of gadget border */
361 for (i=0; i < gi->textarea.xsize; i++)
362 BlitBitmapOnBackground(gd->bitmap, drawto,
364 gd->y + gd_height - border_y,
365 font_width, border_y,
366 gi->x + border_x + i * font_width,
367 gi->y + gi->height - border_y);
369 /* bottom right part of gadget border */
370 BlitBitmapOnBackground(gd->bitmap, drawto,
371 gd->x + gi->border.width - border_x,
372 gd->y + gd_height - border_y,
374 gi->x + gi->width - border_x,
375 gi->y + gi->height - border_y);
377 ClearRectangleOnBackground(drawto,
380 gi->width - 2 * border_x,
381 gi->height - 2 * border_y);
383 /* gadget text value */
384 DrawTextToTextArea(gi->x + border_x, gi->y + border_y,
385 gi->textarea.value, font_nr, gi->textarea.xsize,
386 gi->textarea.xsize, gi->textarea.ysize,
389 cursor_letter = gi->textarea.value[gi->textarea.cursor_position];
390 cursor_string[0] = (cursor_letter != '\0' ? cursor_letter : ' ');
391 cursor_string[1] = '\0';
393 /* draw cursor, if active */
396 gi->x + border_x + gi->textarea.cursor_x * font_width,
397 gi->y + border_y + gi->textarea.cursor_y * font_height,
399 font_nr, BLIT_INVERSE);
403 case GD_TYPE_SELECTBOX:
406 char text[MAX_GADGET_TEXTSIZE + 1];
407 int font_nr = (pressed ? gi->font_active : gi->font);
408 int font_width = getFontWidth(font_nr);
409 int font_height = getFontHeight(font_nr);
410 int border_x = gi->border.xsize;
411 int border_y = gi->border.ysize;
412 int button = gi->border.xsize_selectbutton;
413 int width_inner = gi->border.width - button - 2 * border_x;
414 int box_width = gi->selectbox.width;
415 int box_height = gi->selectbox.height;
417 /* left part of gadget */
418 BlitBitmapOnBackground(gd->bitmap, drawto, gd->x, gd->y,
419 border_x, gi->height, gi->x, gi->y);
421 /* middle part of gadget */
422 for (i=0; i < gi->selectbox.size; i++)
423 BlitBitmapOnBackground(gd->bitmap, drawto, gd->x + border_x, gd->y,
424 font_width, gi->height,
425 gi->x + border_x + i * font_width, gi->y);
427 /* button part of gadget */
428 BlitBitmapOnBackground(gd->bitmap, drawto,
429 gd->x + border_x + width_inner, gd->y,
431 gi->x + gi->width - border_x - button, gi->y);
433 /* right part of gadget */
434 BlitBitmapOnBackground(gd->bitmap, drawto,
435 gd->x + gi->border.width - border_x, gd->y,
436 border_x, gi->height,
437 gi->x + gi->width - border_x, gi->y);
440 strncpy(text, gi->selectbox.options[gi->selectbox.index].text,
442 text[gi->selectbox.size] = '\0';
444 /* gadget text value */
445 DrawTextExt(drawto, gi->x + border_x, gi->y + border_y, text,
446 font_nr, BLIT_MASKED);
450 if (!gi->selectbox.open)
452 gi->selectbox.open = TRUE;
453 gi->selectbox.stay_open = FALSE;
454 gi->selectbox.current_index = gi->selectbox.index;
456 /* save background under selectbox */
457 BlitBitmap(drawto, gfx.field_save_buffer,
458 gi->selectbox.x, gi->selectbox.y,
459 gi->selectbox.width, gi->selectbox.height,
460 gi->selectbox.x, gi->selectbox.y);
463 /* draw open selectbox */
465 /* top left part of gadget border */
466 BlitBitmapOnBackground(gd->bitmap, drawto, gd->x, gd->y,
468 gi->selectbox.x, gi->selectbox.y);
470 /* top middle part of gadget border */
471 for (i=0; i < gi->selectbox.size; i++)
472 BlitBitmapOnBackground(gd->bitmap, drawto, gd->x + border_x, gd->y,
473 font_width, border_y,
474 gi->selectbox.x + border_x + i * font_width,
477 /* top button part of gadget border */
478 BlitBitmapOnBackground(gd->bitmap, drawto,
479 gd->x + border_x + width_inner, gd->y,
481 gi->selectbox.x + box_width -border_x -button,
484 /* top right part of gadget border */
485 BlitBitmapOnBackground(gd->bitmap, drawto,
486 gd->x + gi->border.width - border_x, gd->y,
488 gi->selectbox.x + box_width - border_x,
491 /* left and right part of gadget border for each row */
492 for (i=0; i < gi->selectbox.num_values; i++)
494 BlitBitmapOnBackground(gd->bitmap, drawto, gd->x, gd->y + border_y,
495 border_x, font_height,
497 gi->selectbox.y + border_y + i*font_height);
498 BlitBitmapOnBackground(gd->bitmap, drawto,
499 gd->x + gi->border.width - border_x,
501 border_x, font_height,
502 gi->selectbox.x + box_width - border_x,
503 gi->selectbox.y + border_y + i*font_height);
506 /* bottom left part of gadget border */
507 BlitBitmapOnBackground(gd->bitmap, drawto,
508 gd->x, gd->y + gi->height - border_y,
511 gi->selectbox.y + box_height - border_y);
513 /* bottom middle part of gadget border */
514 for (i=0; i < gi->selectbox.size; i++)
515 BlitBitmapOnBackground(gd->bitmap, drawto,
517 gd->y + gi->height - border_y,
518 font_width, border_y,
519 gi->selectbox.x + border_x + i * font_width,
520 gi->selectbox.y + box_height - border_y);
522 /* bottom button part of gadget border */
523 BlitBitmapOnBackground(gd->bitmap, drawto,
524 gd->x + border_x + width_inner,
525 gd->y + gi->height - border_y,
527 gi->selectbox.x + box_width -border_x -button,
528 gi->selectbox.y + box_height - border_y);
530 /* bottom right part of gadget border */
531 BlitBitmapOnBackground(gd->bitmap, drawto,
532 gd->x + gi->border.width - border_x,
533 gd->y + gi->height - border_y,
535 gi->selectbox.x + box_width - border_x,
536 gi->selectbox.y + box_height - border_y);
538 ClearRectangleOnBackground(drawto,
539 gi->selectbox.x + border_x,
540 gi->selectbox.y + border_y,
541 gi->selectbox.width - 2 * border_x,
542 gi->selectbox.height - 2 * border_y);
544 /* selectbox text values */
545 for (i=0; i < gi->selectbox.num_values; i++)
547 int mask_mode = BLIT_MASKED;
549 strncpy(text, gi->selectbox.options[i].text, gi->selectbox.size);
550 text[gi->selectbox.size] = '\0';
552 if (i == gi->selectbox.current_index)
554 FillRectangle(drawto,
555 gi->selectbox.x + border_x,
556 gi->selectbox.y + border_y + i * font_height,
557 gi->selectbox.width - 2 * border_x, font_height,
558 gi->selectbox.inverse_color);
560 /* prevent use of cursor graphic by drawing at least two chars */
562 text[gi->selectbox.size] = '\0';
564 mask_mode = BLIT_INVERSE;
568 gi->selectbox.x + border_x,
569 gi->selectbox.y + border_y + i * font_height, text,
573 redraw_selectbox = TRUE;
575 else if (gi->selectbox.open)
577 gi->selectbox.open = FALSE;
579 /* restore background under selectbox */
580 BlitBitmap(gfx.field_save_buffer, drawto,
581 gi->selectbox.x, gi->selectbox.y,
582 gi->selectbox.width, gi->selectbox.height,
583 gi->selectbox.x, gi->selectbox.y);
585 /* redraw closed selectbox */
586 DrawGadget(gi, FALSE, FALSE);
588 redraw_selectbox = TRUE;
593 case GD_TYPE_SCROLLBAR_VERTICAL:
597 int ypos = gi->y + gi->scrollbar.position;
598 int design_full = gi->width;
599 int design_body = design_full - 2 * gi->border.ysize;
600 int size_full = gi->scrollbar.size;
601 int size_body = size_full - 2 * gi->border.ysize;
602 int num_steps = size_body / design_body;
603 int step_size_remain = size_body - num_steps * design_body;
605 /* clear scrollbar area */
606 ClearRectangleOnBackground(backbuffer, gi->x, gi->y,
607 gi->width, gi->height);
609 /* upper part of gadget */
610 BlitBitmapOnBackground(gd->bitmap, drawto,
612 gi->width, gi->border.ysize,
615 /* middle part of gadget */
616 for (i=0; i < num_steps; i++)
617 BlitBitmapOnBackground(gd->bitmap, drawto,
618 gd->x, gd->y + gi->border.ysize,
619 gi->width, design_body,
621 ypos + gi->border.ysize + i * design_body);
623 /* remaining middle part of gadget */
624 if (step_size_remain > 0)
625 BlitBitmapOnBackground(gd->bitmap, drawto,
626 gd->x, gd->y + gi->border.ysize,
627 gi->width, step_size_remain,
629 ypos + gi->border.ysize
630 + num_steps * design_body);
632 /* lower part of gadget */
633 BlitBitmapOnBackground(gd->bitmap, drawto,
634 gd->x, gd->y + design_full - gi->border.ysize,
635 gi->width, gi->border.ysize,
636 xpos, ypos + size_full - gi->border.ysize);
640 case GD_TYPE_SCROLLBAR_HORIZONTAL:
643 int xpos = gi->x + gi->scrollbar.position;
645 int design_full = gi->height;
646 int design_body = design_full - 2 * gi->border.xsize;
647 int size_full = gi->scrollbar.size;
648 int size_body = size_full - 2 * gi->border.xsize;
649 int num_steps = size_body / design_body;
650 int step_size_remain = size_body - num_steps * design_body;
652 /* clear scrollbar area */
653 ClearRectangleOnBackground(backbuffer, gi->x, gi->y,
654 gi->width, gi->height);
656 /* left part of gadget */
657 BlitBitmapOnBackground(gd->bitmap, drawto,
659 gi->border.xsize, gi->height,
662 /* middle part of gadget */
663 for (i=0; i < num_steps; i++)
664 BlitBitmapOnBackground(gd->bitmap, drawto,
665 gd->x + gi->border.xsize, gd->y,
666 design_body, gi->height,
667 xpos + gi->border.xsize + i * design_body,
670 /* remaining middle part of gadget */
671 if (step_size_remain > 0)
672 BlitBitmapOnBackground(gd->bitmap, drawto,
673 gd->x + gi->border.xsize, gd->y,
674 step_size_remain, gi->height,
675 xpos + gi->border.xsize
676 + num_steps * design_body,
679 /* right part of gadget */
680 BlitBitmapOnBackground(gd->bitmap, drawto,
681 gd->x + design_full - gi->border.xsize, gd->y,
682 gi->border.xsize, gi->height,
683 xpos + size_full - gi->border.xsize, ypos);
693 BlitBitmap(drawto, window,
694 gi->x, gi->y, gi->width, gi->height, gi->x, gi->y);
696 if (gi->type == GD_TYPE_SELECTBOX && redraw_selectbox)
697 BlitBitmap(drawto, window,
698 gi->selectbox.x, gi->selectbox.y,
699 gi->selectbox.width, gi->selectbox.height,
700 gi->selectbox.x, gi->selectbox.y);
703 redraw_mask |= (gi->x < gfx.sx + gfx.sxsize ? REDRAW_FIELD :
704 gi->y < gfx.dy + gfx.dysize ? REDRAW_DOOR_1 :
705 gi->y > gfx.vy ? REDRAW_DOOR_2 : REDRAW_DOOR_3);
708 static int get_minimal_size_for_numeric_input(int minmax_value)
710 int min_size = 1; /* value needs at least one digit */
713 /* add number of digits needed for absolute value */
714 for (i = 10; i <= ABS(minmax_value); i *= 10)
717 /* if min/max value is negative, add one digit for minus sign */
718 if (minmax_value < 0)
724 static void HandleGadgetTags(struct GadgetInfo *gi, int first_tag, va_list ap)
731 while (tag != GDI_END)
736 gi->custom_id = va_arg(ap, int);
739 case GDI_CUSTOM_TYPE_ID:
740 gi->custom_type_id = va_arg(ap, int);
745 int max_textsize = MAX_INFO_TEXTSIZE;
746 char *text = va_arg(ap, char *);
749 strncpy(gi->info_text, text, max_textsize);
753 gi->info_text[max_textsize] = '\0';
758 gi->x = va_arg(ap, int);
762 gi->y = va_arg(ap, int);
766 gi->width = va_arg(ap, int);
770 gi->height = va_arg(ap, int);
774 gi->type = va_arg(ap, unsigned int);
778 gi->state = va_arg(ap, unsigned int);
782 gi->active = (boolean)va_arg(ap, int);
785 case GDI_DIRECT_DRAW:
786 gi->direct_draw = (boolean)va_arg(ap, int);
790 gi->checked = (boolean)va_arg(ap, int);
794 gi->radio_nr = va_arg(ap, unsigned int);
797 case GDI_NUMBER_VALUE:
798 gi->textinput.number_value = va_arg(ap, int);
799 sprintf(gi->textinput.value, "%d", gi->textinput.number_value);
800 strcpy(gi->textinput.last_value, gi->textinput.value);
801 gi->textinput.cursor_position = strlen(gi->textinput.value);
805 gi->textinput.number_min = va_arg(ap, int);
806 if (gi->textinput.number_value < gi->textinput.number_min)
808 gi->textinput.number_value = gi->textinput.number_min;
809 sprintf(gi->textinput.value, "%d", gi->textinput.number_value);
810 strcpy(gi->textinput.last_value, gi->textinput.value);
815 gi->textinput.number_max = va_arg(ap, int);
816 if (gi->textinput.number_value > gi->textinput.number_max)
818 gi->textinput.number_value = gi->textinput.number_max;
819 sprintf(gi->textinput.value, "%d", gi->textinput.number_value);
820 strcpy(gi->textinput.last_value, gi->textinput.value);
826 int max_textsize = MAX_GADGET_TEXTSIZE;
828 if (gi->textinput.size)
829 max_textsize = MIN(gi->textinput.size, MAX_GADGET_TEXTSIZE);
831 strncpy(gi->textinput.value, va_arg(ap, char *), max_textsize);
832 strcpy(gi->textinput.last_value, gi->textinput.value);
834 gi->textinput.value[max_textsize] = '\0';
835 gi->textinput.cursor_position = strlen(gi->textinput.value);
837 /* same tag also used for other gadget definitions */
838 strcpy(gi->textbutton.value, gi->textinput.value);
839 strcpy(gi->textarea.value, gi->textinput.value);
840 strcpy(gi->textarea.last_value, gi->textinput.value);
846 int tag_value = va_arg(ap, int);
847 int max_textsize = MIN(tag_value, MAX_GADGET_TEXTSIZE);
849 gi->textinput.size = max_textsize;
850 gi->textinput.value[max_textsize] = '\0';
851 strcpy(gi->textinput.last_value, gi->textinput.value);
853 /* same tag also used for other gadget definitions */
855 gi->textarea.size = max_textsize;
856 gi->textarea.value[max_textsize] = '\0';
857 strcpy(gi->textarea.last_value, gi->textinput.value);
859 gi->textbutton.size = max_textsize;
860 gi->textbutton.value[max_textsize] = '\0';
862 gi->selectbox.size = gi->textinput.size;
867 gi->font = va_arg(ap, int);
868 if (gi->font_active == 0)
869 gi->font_active = gi->font;
872 case GDI_TEXT_FONT_ACTIVE:
873 gi->font_active = va_arg(ap, int);
876 case GDI_SELECTBOX_OPTIONS:
877 gi->selectbox.options = va_arg(ap, struct ValueTextInfo *);
880 case GDI_SELECTBOX_INDEX:
881 gi->selectbox.index = va_arg(ap, int);
884 case GDI_DESIGN_UNPRESSED:
885 gi->design[GD_BUTTON_UNPRESSED].bitmap = va_arg(ap, Bitmap *);
886 gi->design[GD_BUTTON_UNPRESSED].x = va_arg(ap, int);
887 gi->design[GD_BUTTON_UNPRESSED].y = va_arg(ap, int);
890 case GDI_DESIGN_PRESSED:
891 gi->design[GD_BUTTON_PRESSED].bitmap = va_arg(ap, Bitmap *);
892 gi->design[GD_BUTTON_PRESSED].x = va_arg(ap, int);
893 gi->design[GD_BUTTON_PRESSED].y = va_arg(ap, int);
896 case GDI_ALT_DESIGN_UNPRESSED:
897 gi->alt_design[GD_BUTTON_UNPRESSED].bitmap= va_arg(ap, Bitmap *);
898 gi->alt_design[GD_BUTTON_UNPRESSED].x = va_arg(ap, int);
899 gi->alt_design[GD_BUTTON_UNPRESSED].y = va_arg(ap, int);
902 case GDI_ALT_DESIGN_PRESSED:
903 gi->alt_design[GD_BUTTON_PRESSED].bitmap = va_arg(ap, Bitmap *);
904 gi->alt_design[GD_BUTTON_PRESSED].x = va_arg(ap, int);
905 gi->alt_design[GD_BUTTON_PRESSED].y = va_arg(ap, int);
908 case GDI_BORDER_SIZE:
909 gi->border.xsize = va_arg(ap, int);
910 gi->border.ysize = va_arg(ap, int);
913 case GDI_BORDER_SIZE_SELECTBUTTON:
914 gi->border.xsize_selectbutton = va_arg(ap, int);
917 case GDI_DESIGN_WIDTH:
918 gi->border.width = va_arg(ap, int);
921 case GDI_DECORATION_DESIGN:
922 gi->deco.design.bitmap = va_arg(ap, Bitmap *);
923 gi->deco.design.x = va_arg(ap, int);
924 gi->deco.design.y = va_arg(ap, int);
927 case GDI_DECORATION_POSITION:
928 gi->deco.x = va_arg(ap, int);
929 gi->deco.y = va_arg(ap, int);
932 case GDI_DECORATION_SIZE:
933 gi->deco.width = va_arg(ap, int);
934 gi->deco.height = va_arg(ap, int);
937 case GDI_DECORATION_SHIFTING:
938 gi->deco.xshift = va_arg(ap, int);
939 gi->deco.yshift = va_arg(ap, int);
943 gi->event_mask = va_arg(ap, unsigned int);
947 gi->drawing.area_xsize = va_arg(ap, int);
948 gi->drawing.area_ysize = va_arg(ap, int);
950 /* determine dependent values for drawing area gadget, if needed */
951 if (gi->width == 0 && gi->height == 0 &&
952 gi->drawing.item_xsize !=0 && gi->drawing.item_ysize !=0)
954 gi->width = gi->drawing.area_xsize * gi->drawing.item_xsize;
955 gi->height = gi->drawing.area_ysize * gi->drawing.item_ysize;
957 else if (gi->drawing.item_xsize == 0 && gi->drawing.item_ysize == 0 &&
958 gi->width != 0 && gi->height != 0)
960 gi->drawing.item_xsize = gi->width / gi->drawing.area_xsize;
961 gi->drawing.item_ysize = gi->height / gi->drawing.area_ysize;
964 /* same tag also used for other gadget definitions */
965 gi->textarea.xsize = gi->drawing.area_xsize;
966 gi->textarea.ysize = gi->drawing.area_ysize;
968 if (gi->type & GD_TYPE_TEXT_AREA) /* force recalculation */
977 gi->drawing.item_xsize = va_arg(ap, int);
978 gi->drawing.item_ysize = va_arg(ap, int);
980 /* determine dependent values for drawing area gadget, if needed */
981 if (gi->width == 0 && gi->height == 0 &&
982 gi->drawing.area_xsize !=0 && gi->drawing.area_ysize !=0)
984 gi->width = gi->drawing.area_xsize * gi->drawing.item_xsize;
985 gi->height = gi->drawing.area_ysize * gi->drawing.item_ysize;
987 else if (gi->drawing.area_xsize == 0 && gi->drawing.area_ysize == 0 &&
988 gi->width != 0 && gi->height != 0)
990 gi->drawing.area_xsize = gi->width / gi->drawing.item_xsize;
991 gi->drawing.area_ysize = gi->height / gi->drawing.item_ysize;
995 case GDI_SCROLLBAR_ITEMS_MAX:
996 gi->scrollbar.items_max = va_arg(ap, int);
999 case GDI_SCROLLBAR_ITEMS_VISIBLE:
1000 gi->scrollbar.items_visible = va_arg(ap, int);
1003 case GDI_SCROLLBAR_ITEM_POSITION:
1004 gi->scrollbar.item_position = va_arg(ap, int);
1007 case GDI_WHEEL_AREA_X:
1008 gi->wheelarea.x = va_arg(ap, int);
1011 case GDI_WHEEL_AREA_Y:
1012 gi->wheelarea.y = va_arg(ap, int);
1015 case GDI_WHEEL_AREA_WIDTH:
1016 gi->wheelarea.width = va_arg(ap, int);
1019 case GDI_WHEEL_AREA_HEIGHT:
1020 gi->wheelarea.height = va_arg(ap, int);
1023 case GDI_CALLBACK_INFO:
1024 gi->callback_info = va_arg(ap, gadget_function);
1027 case GDI_CALLBACK_ACTION:
1028 gi->callback_action = va_arg(ap, gadget_function);
1032 Error(ERR_EXIT, "HandleGadgetTags(): unknown tag %d", tag);
1035 tag = va_arg(ap, int); /* read next tag */
1038 /* check if gadget is complete */
1039 if (gi->type != GD_TYPE_DRAWING_AREA &&
1040 (!gi->design[GD_BUTTON_UNPRESSED].bitmap ||
1041 !gi->design[GD_BUTTON_PRESSED].bitmap))
1042 Error(ERR_EXIT, "gadget incomplete (missing Bitmap)");
1044 /* adjust gadget values in relation to other gadget values */
1046 if (gi->type & GD_TYPE_TEXT_INPUT)
1048 int font_nr = gi->font_active;
1049 int font_width = getFontWidth(font_nr);
1050 int font_height = getFontHeight(font_nr);
1051 int border_xsize = gi->border.xsize;
1052 int border_ysize = gi->border.ysize;
1054 if (gi->type == GD_TYPE_TEXT_INPUT_NUMERIC)
1056 int number_min = gi->textinput.number_min;
1057 int number_max = gi->textinput.number_max;
1058 int min_size_min = get_minimal_size_for_numeric_input(number_min);
1059 int min_size_max = get_minimal_size_for_numeric_input(number_max);
1060 int min_size = MAX(min_size_min, min_size_max);
1062 /* expand gadget text input size, if maximal value is too large */
1063 if (gi->textinput.size < min_size)
1064 gi->textinput.size = min_size;
1067 gi->width = 2 * border_xsize + (gi->textinput.size + 1) * font_width;
1068 gi->height = 2 * border_ysize + font_height;
1071 if (gi->type & GD_TYPE_SELECTBOX)
1073 int font_nr = gi->font_active;
1074 int font_width = getFontWidth(font_nr);
1075 int font_height = getFontHeight(font_nr);
1076 int border_xsize = gi->border.xsize;
1077 int border_ysize = gi->border.ysize;
1078 int button_size = gi->border.xsize_selectbutton;
1079 int bottom_screen_border = gfx.sy + gfx.sysize - font_height;
1083 gi->width = 2 * border_xsize + gi->textinput.size*font_width +button_size;
1084 gi->height = 2 * border_ysize + font_height;
1086 if (gi->selectbox.options == NULL)
1087 Error(ERR_EXIT, "selectbox gadget incomplete (missing options array)");
1089 gi->selectbox.num_values = 0;
1090 while (gi->selectbox.options[gi->selectbox.num_values].text != NULL)
1091 gi->selectbox.num_values++;
1093 /* calculate values for open selectbox */
1094 gi->selectbox.width = gi->width;
1095 gi->selectbox.height =
1096 2 * border_ysize + gi->selectbox.num_values * font_height;
1098 gi->selectbox.x = gi->x;
1099 gi->selectbox.y = gi->y + gi->height;
1100 if (gi->selectbox.y + gi->selectbox.height > bottom_screen_border)
1101 gi->selectbox.y = gi->y - gi->selectbox.height;
1102 if (gi->selectbox.y < 0)
1103 gi->selectbox.y = bottom_screen_border - gi->selectbox.height;
1105 getFontCharSource(font_nr, FONT_ASCII_CURSOR, &src_bitmap, &src_x, &src_y);
1106 src_x += font_width / 2;
1107 src_y += font_height / 2;
1109 /* there may be esoteric cases with missing or too small font bitmap */
1110 if (src_bitmap != NULL &&
1111 src_x < src_bitmap->width && src_y < src_bitmap->height)
1112 gi->selectbox.inverse_color = GetPixel(src_bitmap, src_x, src_y);
1114 /* always start with closed selectbox */
1115 gi->selectbox.open = FALSE;
1118 if (gi->type & GD_TYPE_TEXT_INPUT_NUMERIC)
1120 struct GadgetTextInput *text = &gi->textinput;
1121 int value = text->number_value;
1123 text->number_value = (value < text->number_min ? text->number_min :
1124 value > text->number_max ? text->number_max :
1127 sprintf(text->value, "%d", text->number_value);
1130 if (gi->type & GD_TYPE_TEXT_BUTTON)
1132 int font_nr = gi->font_active;
1133 int font_width = getFontWidth(font_nr);
1134 int font_height = getFontHeight(font_nr);
1135 int border_xsize = gi->border.xsize;
1136 int border_ysize = gi->border.ysize;
1138 gi->width = 2 * border_xsize + gi->textbutton.size * font_width;
1139 gi->height = 2 * border_ysize + font_height;
1142 if (gi->type & GD_TYPE_SCROLLBAR)
1144 struct GadgetScrollbar *gs = &gi->scrollbar;
1145 int scrollbar_size_cmp;
1147 if (gi->width == 0 || gi->height == 0 ||
1148 gs->items_max == 0 || gs->items_visible == 0)
1149 Error(ERR_EXIT, "scrollbar gadget incomplete (missing tags)");
1151 /* calculate internal scrollbar values */
1152 gs->size_min = (gi->type == GD_TYPE_SCROLLBAR_VERTICAL ?
1153 gi->width : gi->height);
1154 gs->size_max = (gi->type == GD_TYPE_SCROLLBAR_VERTICAL ?
1155 gi->height : gi->width);
1157 scrollbar_size_cmp = gs->size_max * gs->items_visible / gs->items_max;
1158 gs->size = MAX(scrollbar_size_cmp, gs->size_min);
1159 gs->size_max_cmp = (gs->size_max - (gs->size - scrollbar_size_cmp));
1161 gs->position = gs->size_max_cmp * gs->item_position / gs->items_max;
1162 gs->position_max = gs->size_max - gs->size;
1163 gs->correction = gs->size_max / gs->items_max / 2;
1165 /* finetuning for maximal right/bottom position */
1166 if (gs->item_position == gs->items_max - gs->items_visible)
1167 gs->position = gs->position_max;
1170 if (gi->type & GD_TYPE_TEXT_AREA)
1172 int font_nr = gi->font_active;
1173 int font_width = getFontWidth(font_nr);
1174 int font_height = getFontHeight(font_nr);
1175 int border_xsize = gi->border.xsize;
1176 int border_ysize = gi->border.ysize;
1178 if (gi->width == 0 || gi->height == 0)
1180 gi->width = 2 * border_xsize + gi->textarea.xsize * font_width;
1181 gi->height = 2 * border_ysize + gi->textarea.ysize * font_height;
1185 gi->textarea.xsize = (gi->width - 2 * border_xsize) / font_width;
1186 gi->textarea.ysize = (gi->height - 2 * border_ysize) / font_height;
1191 void ModifyGadget(struct GadgetInfo *gi, int first_tag, ...)
1195 va_start(ap, first_tag);
1196 HandleGadgetTags(gi, first_tag, ap);
1202 void RedrawGadget(struct GadgetInfo *gi)
1208 DrawGadget(gi, gi->state, gi->direct_draw);
1211 struct GadgetInfo *CreateGadget(int first_tag, ...)
1213 struct GadgetInfo *new_gadget = checked_calloc(sizeof(struct GadgetInfo));
1216 /* always start with reliable default values */
1217 new_gadget->id = getNewGadgetID();
1218 new_gadget->callback_info = default_callback_info;
1219 new_gadget->callback_action = default_callback_action;
1220 new_gadget->active = TRUE;
1221 new_gadget->direct_draw = TRUE;
1223 new_gadget->next = NULL;
1225 va_start(ap, first_tag);
1226 HandleGadgetTags(new_gadget, first_tag, ap);
1229 /* insert new gadget into global gadget list */
1230 if (gadget_list_last_entry)
1232 gadget_list_last_entry->next = new_gadget;
1233 gadget_list_last_entry = gadget_list_last_entry->next;
1236 gadget_list_first_entry = gadget_list_last_entry = new_gadget;
1241 void FreeGadget(struct GadgetInfo *gi)
1243 struct GadgetInfo *gi_previous = gadget_list_first_entry;
1245 /* prevent "last_info_gi" from pointing to memory that will be freed */
1246 if (last_info_gi == gi)
1247 last_info_gi = NULL;
1249 while (gi_previous != NULL && gi_previous->next != gi)
1250 gi_previous = gi_previous->next;
1252 if (gi == gadget_list_first_entry)
1253 gadget_list_first_entry = gi->next;
1255 if (gi == gadget_list_last_entry)
1256 gadget_list_last_entry = gi_previous;
1258 if (gi_previous != NULL)
1259 gi_previous->next = gi->next;
1264 static void CheckRangeOfNumericInputGadget(struct GadgetInfo *gi)
1266 if (gi->type != GD_TYPE_TEXT_INPUT_NUMERIC)
1269 gi->textinput.number_value = atoi(gi->textinput.value);
1271 if (gi->textinput.number_value < gi->textinput.number_min)
1272 gi->textinput.number_value = gi->textinput.number_min;
1273 if (gi->textinput.number_value > gi->textinput.number_max)
1274 gi->textinput.number_value = gi->textinput.number_max;
1276 sprintf(gi->textinput.value, "%d", gi->textinput.number_value);
1278 if (gi->textinput.cursor_position < 0)
1279 gi->textinput.cursor_position = 0;
1280 else if (gi->textinput.cursor_position > strlen(gi->textinput.value))
1281 gi->textinput.cursor_position = strlen(gi->textinput.value);
1284 /* global pointer to gadget actually in use (when mouse button pressed) */
1285 static struct GadgetInfo *last_gi = NULL;
1287 static void MapGadgetExt(struct GadgetInfo *gi, boolean redraw)
1289 if (gi == NULL || gi->mapped)
1295 DrawGadget(gi, DG_UNPRESSED, DG_BUFFERED);
1298 void MapGadget(struct GadgetInfo *gi)
1300 MapGadgetExt(gi, TRUE);
1303 void UnmapGadget(struct GadgetInfo *gi)
1305 if (gi == NULL || !gi->mapped)
1314 #define MAX_NUM_GADGETS 1024
1315 #define MULTIMAP_UNMAP (1 << 0)
1316 #define MULTIMAP_REMAP (1 << 1)
1317 #define MULTIMAP_REDRAW (1 << 2)
1318 #define MULTIMAP_PLAYFIELD (1 << 3)
1319 #define MULTIMAP_DOOR_1 (1 << 4)
1320 #define MULTIMAP_DOOR_2 (1 << 5)
1321 #define MULTIMAP_ALL (MULTIMAP_PLAYFIELD | \
1325 static void MultiMapGadgets(int mode)
1327 struct GadgetInfo *gi = gadget_list_first_entry;
1328 static boolean map_state[MAX_NUM_GADGETS];
1333 if ((mode & MULTIMAP_PLAYFIELD &&
1334 gi->x < gfx.sx + gfx.sxsize) ||
1335 (mode & MULTIMAP_DOOR_1 &&
1336 gi->x >= gfx.dx && gi->y < gfx.dy + gfx.dysize) ||
1337 (mode & MULTIMAP_DOOR_2 &&
1338 gi->x >= gfx.dx && gi->y > gfx.dy + gfx.dysize) ||
1339 (mode & MULTIMAP_ALL) == MULTIMAP_ALL)
1341 if (mode & MULTIMAP_UNMAP)
1343 map_state[map_count++ % MAX_NUM_GADGETS] = gi->mapped;
1348 if (map_state[map_count++ % MAX_NUM_GADGETS])
1349 MapGadgetExt(gi, (mode & MULTIMAP_REDRAW));
1357 void UnmapAllGadgets()
1359 MultiMapGadgets(MULTIMAP_ALL | MULTIMAP_UNMAP);
1362 void RemapAllGadgets()
1364 MultiMapGadgets(MULTIMAP_ALL | MULTIMAP_REMAP);
1367 boolean anyTextInputGadgetActive()
1369 return (last_gi && (last_gi->type & GD_TYPE_TEXT_INPUT) && last_gi->mapped);
1372 boolean anyTextAreaGadgetActive()
1374 return (last_gi && (last_gi->type & GD_TYPE_TEXT_AREA) && last_gi->mapped);
1377 boolean anySelectboxGadgetActive()
1379 return (last_gi && (last_gi->type & GD_TYPE_SELECTBOX) && last_gi->mapped);
1382 boolean anyScrollbarGadgetActive()
1384 return (last_gi && (last_gi->type & GD_TYPE_SCROLLBAR) && last_gi->mapped);
1387 boolean anyTextGadgetActive()
1389 return (anyTextInputGadgetActive() ||
1390 anyTextAreaGadgetActive() ||
1391 anySelectboxGadgetActive());
1394 static boolean insideSelectboxLine(struct GadgetInfo *gi, int mx, int my)
1396 return(gi != NULL &&
1397 gi->type & GD_TYPE_SELECTBOX &&
1398 mx >= gi->x && mx < gi->x + gi->width &&
1399 my >= gi->y && my < gi->y + gi->height);
1402 static boolean insideSelectboxArea(struct GadgetInfo *gi, int mx, int my)
1404 return(gi != NULL &&
1405 gi->type & GD_TYPE_SELECTBOX &&
1406 mx >= gi->selectbox.x && mx < gi->selectbox.x + gi->selectbox.width &&
1407 my >= gi->selectbox.y && my < gi->selectbox.y + gi->selectbox.height);
1410 void ClickOnGadget(struct GadgetInfo *gi, int button)
1415 /* simulate releasing mouse button over last gadget, if still pressed */
1417 HandleGadgets(-1, -1, 0);
1419 /* simulate pressing mouse button over specified gadget */
1420 HandleGadgets(gi->x, gi->y, button);
1422 /* simulate releasing mouse button over specified gadget */
1423 HandleGadgets(gi->x, gi->y, 0);
1426 boolean HandleGadgets(int mx, int my, int button)
1428 static unsigned long pressed_delay = 0;
1429 static unsigned long pressed_delay_value = GADGET_FRAME_DELAY;
1430 static int last_button = 0;
1431 static int last_mx = 0, last_my = 0;
1432 static int pressed_mx = 0, pressed_my = 0;
1433 static boolean keep_selectbox_open = FALSE;
1434 static boolean gadget_stopped = FALSE;
1435 int scrollbar_mouse_pos = 0;
1436 struct GadgetInfo *new_gi, *gi;
1437 boolean press_event;
1438 boolean release_event;
1439 boolean mouse_moving;
1440 boolean mouse_inside_select_line;
1441 boolean mouse_inside_select_area;
1442 boolean mouse_released_where_pressed;
1443 boolean gadget_pressed;
1444 boolean gadget_pressed_repeated;
1445 boolean gadget_pressed_off_borders;
1446 boolean gadget_pressed_inside_select_line;
1447 boolean gadget_pressed_delay_reached;
1448 boolean gadget_moving;
1449 boolean gadget_moving_inside;
1450 boolean gadget_moving_off_borders;
1451 boolean gadget_draggable;
1452 boolean gadget_dragging;
1453 boolean gadget_released;
1454 boolean gadget_released_inside;
1455 boolean gadget_released_inside_select_line;
1456 boolean gadget_released_inside_select_area;
1457 boolean gadget_released_off_borders;
1458 boolean changed_position = FALSE;
1460 /* check if there are any gadgets defined */
1461 if (gadget_list_first_entry == NULL)
1464 /* simulated release of mouse button over last gadget */
1465 if (mx == -1 && my == -1 && button == 0)
1471 /* check which gadget is under the mouse pointer */
1472 new_gi = getGadgetInfoFromMousePosition(mx, my, button);
1474 /* check if button state has changed since last invocation */
1475 press_event = (button != 0 && last_button == 0);
1476 release_event = (button == 0 && last_button != 0);
1477 last_button = button;
1479 /* check if mouse has been moved since last invocation */
1480 mouse_moving = ((mx != last_mx || my != last_my) && motion_status);
1484 if (press_event && new_gi != last_gi)
1490 mouse_released_where_pressed =
1491 (release_event && mx == pressed_mx && my == pressed_my);
1493 mouse_inside_select_line = insideSelectboxLine(new_gi, mx, my);
1494 mouse_inside_select_area = insideSelectboxArea(new_gi, mx, my);
1496 gadget_pressed_off_borders = (press_event && new_gi != last_gi);
1498 gadget_pressed_inside_select_line =
1499 (press_event && new_gi != NULL &&
1500 new_gi->type & GD_TYPE_SELECTBOX && new_gi->selectbox.open &&
1501 insideSelectboxLine(new_gi, mx, my));
1503 /* if mouse button pressed outside text or selectbox gadget, deactivate it */
1504 if (anyTextGadgetActive() &&
1505 (gadget_pressed_off_borders ||
1506 (gadget_pressed_inside_select_line && !mouse_inside_select_area)))
1508 struct GadgetInfo *gi = last_gi;
1509 boolean gadget_changed = ((gi->event_mask & GD_EVENT_TEXT_LEAVING) != 0);
1511 /* check if text gadget has changed its value */
1512 if (gi->type & GD_TYPE_TEXT_INPUT)
1514 CheckRangeOfNumericInputGadget(gi);
1516 if (!strEqual(gi->textinput.last_value, gi->textinput.value))
1517 strcpy(gi->textinput.last_value, gi->textinput.value);
1519 gadget_changed = FALSE;
1522 /* selectbox does not change its value when closed by clicking outside */
1523 if (gi->type & GD_TYPE_SELECTBOX)
1524 gadget_changed = FALSE;
1526 DrawGadget(gi, DG_UNPRESSED, gi->direct_draw);
1528 gi->event.type = GD_EVENT_TEXT_LEAVING;
1530 if (gadget_changed && !(gi->type & GD_TYPE_SELECTBOX))
1531 gi->callback_action(gi);
1535 if (gadget_pressed_inside_select_line)
1540 (button != 0 && last_gi == NULL && new_gi != NULL && press_event);
1541 gadget_pressed_repeated =
1542 (button != 0 && last_gi != NULL && new_gi == last_gi);
1544 gadget_pressed_delay_reached =
1545 DelayReached(&pressed_delay, pressed_delay_value);
1547 gadget_released = (release_event && last_gi != NULL);
1548 gadget_released_inside = (gadget_released && new_gi == last_gi);
1549 gadget_released_off_borders = (gadget_released && new_gi != last_gi);
1551 gadget_moving = (button != 0 && last_gi != NULL && mouse_moving);
1552 gadget_moving_inside = (gadget_moving && new_gi == last_gi);
1553 gadget_moving_off_borders = (gadget_moving && new_gi != last_gi);
1555 /* when handling selectbox, set additional state values */
1556 if (gadget_released_inside && (last_gi->type & GD_TYPE_SELECTBOX))
1558 gadget_released_inside_select_line = insideSelectboxLine(last_gi, mx, my);
1559 gadget_released_inside_select_area = insideSelectboxArea(last_gi, mx, my);
1563 gadget_released_inside_select_line = FALSE;
1564 gadget_released_inside_select_area = FALSE;
1567 /* setting state for handling over-large selectbox */
1568 if (keep_selectbox_open && (press_event || !mouse_inside_select_line))
1569 keep_selectbox_open = FALSE;
1571 /* if new gadget pressed, store this gadget */
1575 /* 'gi' is actually handled gadget */
1578 /* if gadget is scrollbar, choose mouse position value */
1579 if (gi && gi->type & GD_TYPE_SCROLLBAR)
1580 scrollbar_mouse_pos =
1581 (gi->type == GD_TYPE_SCROLLBAR_HORIZONTAL ? mx - gi->x : my - gi->y);
1583 /* if mouse button released, no gadget needs to be handled anymore */
1584 if (gadget_released)
1586 if (gi->type & GD_TYPE_SELECTBOX &&
1587 (keep_selectbox_open ||
1588 mouse_released_where_pressed ||
1589 !gadget_released_inside_select_area)) /* selectbox stays open */
1591 gi->selectbox.stay_open = TRUE;
1595 else if (!(gi->type & GD_TYPE_TEXT_INPUT ||
1596 gi->type & GD_TYPE_TEXT_AREA)) /* text input stays open */
1600 /* modify event position values even if no gadget is pressed */
1601 if (button == 0 && !release_event)
1604 /* if new gadget or if no gadget was pressed, release stopped processing */
1605 if (gadget_pressed || new_gi == NULL)
1606 gadget_stopped = FALSE;
1608 /* if gadget was stopped while being handled, stop gadget processing here */
1614 int last_x = gi->event.x;
1615 int last_y = gi->event.y;
1617 gi->event.x = mx - gi->x;
1618 gi->event.y = my - gi->y;
1620 if (gi->type == GD_TYPE_DRAWING_AREA)
1622 gi->event.x /= gi->drawing.item_xsize;
1623 gi->event.y /= gi->drawing.item_ysize;
1625 if (last_x != gi->event.x || last_y != gi->event.y)
1626 changed_position = TRUE;
1628 else if (gi->type & GD_TYPE_TEXT_INPUT && button != 0 && !motion_status)
1630 int old_cursor_position = gi->textinput.cursor_position;
1632 /* if mouse button pressed inside activated text gadget, set cursor */
1633 gi->textinput.cursor_position =
1634 (mx - gi->x - gi->border.xsize) / getFontWidth(gi->font);
1636 if (gi->textinput.cursor_position < 0)
1637 gi->textinput.cursor_position = 0;
1638 else if (gi->textinput.cursor_position > strlen(gi->textinput.value))
1639 gi->textinput.cursor_position = strlen(gi->textinput.value);
1641 if (gi->textinput.cursor_position != old_cursor_position)
1642 DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1644 else if (gi->type & GD_TYPE_TEXT_AREA && button != 0 && !motion_status)
1646 int old_cursor_position = gi->textarea.cursor_position;
1647 int x = (mx - gi->x - gi->border.xsize) / getFontWidth(gi->font);
1648 int y = (my - gi->y - gi->border.ysize) / getFontHeight(gi->font);
1650 x = (x < 0 ? 0 : x >= gi->textarea.xsize ? gi->textarea.xsize - 1 : x);
1651 y = (y < 0 ? 0 : y >= gi->textarea.ysize ? gi->textarea.ysize - 1 : y);
1653 setTextAreaCursorXY(gi, x, y);
1655 if (gi->textarea.cursor_position != old_cursor_position)
1656 DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1658 else if (gi->type & GD_TYPE_SELECTBOX && gi->selectbox.open &&
1659 !keep_selectbox_open)
1661 int old_index = gi->selectbox.current_index;
1663 /* if mouse moving inside activated selectbox, select value */
1664 if (my >= gi->selectbox.y && my < gi->selectbox.y + gi->selectbox.height)
1665 gi->selectbox.current_index =
1666 (my - gi->selectbox.y - gi->border.ysize) / getFontHeight(gi->font);
1668 if (gi->selectbox.current_index < 0)
1669 gi->selectbox.current_index = 0;
1670 else if (gi->selectbox.current_index > gi->selectbox.num_values - 1)
1671 gi->selectbox.current_index = gi->selectbox.num_values - 1;
1673 if (gi->selectbox.current_index != old_index)
1674 DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1678 /* handle gadget popup info text */
1679 if (last_info_gi != new_gi ||
1680 (new_gi && new_gi->type == GD_TYPE_DRAWING_AREA && changed_position))
1682 if (new_gi != NULL && (button == 0 || new_gi == last_gi))
1684 new_gi->event.type = GD_EVENT_INFO_ENTERING;
1685 new_gi->callback_info(new_gi);
1687 else if (last_info_gi != NULL)
1689 last_info_gi->event.type = GD_EVENT_INFO_LEAVING;
1690 last_info_gi->callback_info(last_info_gi);
1693 last_info_gi = new_gi;
1696 gadget_draggable = (gi && gi->type & GD_TYPE_SCROLLBAR);
1698 /* reset drag position for newly pressed scrollbar to "not dragging" */
1699 if (gadget_pressed && gadget_draggable)
1700 gi->scrollbar.drag_position = -1;
1702 gadget_dragging = (gadget_draggable && gi->scrollbar.drag_position != -1);
1704 /* clicking next to a scrollbar to move it is not considered "moving" */
1705 if (gadget_draggable && !gadget_dragging)
1706 gadget_moving = FALSE;
1708 /* when leaving scrollbar area when jump-scrolling, stop gadget processing */
1709 if (gadget_draggable && !gadget_dragging && gadget_moving_off_borders)
1710 gadget_stopped = TRUE;
1712 if ((gadget_pressed) ||
1713 (gadget_pressed_repeated && gadget_pressed_delay_reached))
1715 if (gadget_pressed) /* gadget pressed the first time */
1717 /* initialize delay counter */
1718 DelayReached(&pressed_delay, 0);
1720 /* start gadget delay with longer delay after first click on gadget */
1721 pressed_delay_value = GADGET_FRAME_DELAY_FIRST;
1723 else /* gadget hold pressed for some time */
1725 /* after first repeated gadget click, continue with shorter delay value */
1726 pressed_delay_value = GADGET_FRAME_DELAY;
1729 if (gi->type & GD_TYPE_SCROLLBAR && !gadget_dragging)
1731 int mpos = (gi->type == GD_TYPE_SCROLLBAR_HORIZONTAL ? mx : my);
1732 int gpos = (gi->type == GD_TYPE_SCROLLBAR_HORIZONTAL ? gi->x : gi->y);
1733 int slider_start = gpos + gi->scrollbar.position;
1734 int slider_end = gpos + gi->scrollbar.position + gi->scrollbar.size - 1;
1735 boolean inside_slider = (mpos >= slider_start && mpos <= slider_end);
1737 if (IS_WHEEL_BUTTON(button) || !inside_slider)
1739 /* click scrollbar one scrollbar length up/left or down/right */
1741 struct GadgetScrollbar *gs = &gi->scrollbar;
1742 int old_item_position = gs->item_position;
1743 int item_steps = gs->items_visible - 1;
1744 int item_direction = (mpos < gpos + gi->scrollbar.position ? -1 : +1);
1746 if (IS_WHEEL_BUTTON(button))
1748 boolean scroll_single_step = ((GetKeyModState() & KMOD_Alt) != 0);
1750 item_steps = (scroll_single_step ? 1 : DEFAULT_WHEEL_STEPS);
1751 item_direction = (button == MB_WHEEL_UP ||
1752 button == MB_WHEEL_LEFT ? -1 : +1);
1755 changed_position = FALSE;
1757 gs->item_position += item_steps * item_direction;
1759 if (gs->item_position < 0)
1760 gs->item_position = 0;
1761 else if (gs->item_position > gs->items_max - gs->items_visible)
1762 gs->item_position = gs->items_max - gs->items_visible;
1764 if (old_item_position != gs->item_position)
1766 gi->event.item_position = gs->item_position;
1767 changed_position = TRUE;
1770 ModifyGadget(gi, GDI_SCROLLBAR_ITEM_POSITION, gs->item_position,
1773 gi->state = GD_BUTTON_UNPRESSED;
1774 gi->event.type = GD_EVENT_MOVING;
1775 gi->event.off_borders = FALSE;
1777 if (gi->event_mask & GD_EVENT_MOVING && changed_position)
1778 gi->callback_action(gi);
1784 /* don't handle this scrollbar anymore when mouse position reached */
1785 if (gadget_pressed_repeated)
1787 gadget_stopped = TRUE;
1797 PlayGadgetSoundActivating();
1799 if (gi->type == GD_TYPE_CHECK_BUTTON)
1801 gi->checked = !gi->checked;
1803 else if (gi->type == GD_TYPE_RADIO_BUTTON)
1805 struct GadgetInfo *rgi = gadget_list_first_entry;
1810 rgi->type == GD_TYPE_RADIO_BUTTON &&
1811 rgi->radio_nr == gi->radio_nr &&
1814 rgi->checked = FALSE;
1815 DrawGadget(rgi, DG_UNPRESSED, rgi->direct_draw);
1823 else if (gi->type & GD_TYPE_SCROLLBAR)
1825 int mpos = (gi->type == GD_TYPE_SCROLLBAR_HORIZONTAL ? mx : my);
1826 int gpos = (gi->type == GD_TYPE_SCROLLBAR_HORIZONTAL ? gi->x : gi->y);
1827 int slider_start = gpos + gi->scrollbar.position;
1828 int slider_end = gpos + gi->scrollbar.position + gi->scrollbar.size - 1;
1829 boolean inside_slider = (mpos >= slider_start && mpos <= slider_end);
1831 if (!IS_WHEEL_BUTTON(button) && inside_slider)
1833 /* start dragging scrollbar */
1834 gi->scrollbar.drag_position =
1835 scrollbar_mouse_pos - gi->scrollbar.position;
1838 else if (gi->type & GD_TYPE_SELECTBOX)
1840 /* keep selectbox open in case of over-large selectbox */
1841 keep_selectbox_open = (mouse_inside_select_line &&
1842 mouse_inside_select_area);
1845 DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1847 gi->state = GD_BUTTON_PRESSED;
1848 gi->event.type = GD_EVENT_PRESSED;
1849 gi->event.button = button;
1850 gi->event.off_borders = FALSE;
1852 if (gi->event_mask & GD_EVENT_PRESSED)
1853 gi->callback_action(gi);
1856 if (gadget_pressed_repeated)
1858 gi->event.type = GD_EVENT_PRESSED;
1860 if (gi->event_mask & GD_EVENT_REPEATED && gadget_pressed_delay_reached)
1861 gi->callback_action(gi);
1866 if (gi->type & GD_TYPE_BUTTON)
1868 if (gadget_moving_inside && gi->state == GD_BUTTON_UNPRESSED)
1869 DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1870 else if (gadget_moving_off_borders && gi->state == GD_BUTTON_PRESSED)
1871 DrawGadget(gi, DG_UNPRESSED, gi->direct_draw);
1873 else if (gi->type & GD_TYPE_SELECTBOX && !keep_selectbox_open)
1875 int old_index = gi->selectbox.current_index;
1877 /* if mouse moving inside activated selectbox, select value */
1878 if (my >= gi->selectbox.y && my < gi->selectbox.y + gi->selectbox.height)
1879 gi->selectbox.current_index =
1880 (my - gi->selectbox.y - gi->border.ysize) / getFontHeight(gi->font);
1882 if (gi->selectbox.current_index < 0)
1883 gi->selectbox.current_index = 0;
1884 else if (gi->selectbox.current_index > gi->selectbox.num_values - 1)
1885 gi->selectbox.current_index = gi->selectbox.num_values - 1;
1887 if (gi->selectbox.current_index != old_index)
1888 DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1890 else if (gi->type & GD_TYPE_SCROLLBAR)
1892 struct GadgetScrollbar *gs = &gi->scrollbar;
1893 int old_item_position = gs->item_position;
1895 gs->position = scrollbar_mouse_pos - gs->drag_position;
1897 /* make sure to always precisely reach end positions when dragging */
1898 if (gs->position <= 0)
1901 gs->item_position = 0;
1903 else if (gs->position >= gs->position_max)
1905 gs->position = gs->position_max;
1906 gs->item_position = gs->items_max - gs->items_visible;
1911 gs->items_max * (gs->position + gs->correction) / gs->size_max_cmp;
1914 if (gs->item_position < 0)
1915 gs->item_position = 0;
1916 if (gs->item_position > gs->items_max - 1)
1917 gs->item_position = gs->items_max - 1;
1919 if (old_item_position != gs->item_position)
1921 gi->event.item_position = gs->item_position;
1922 changed_position = TRUE;
1925 DrawGadget(gi, DG_PRESSED, gi->direct_draw);
1928 gi->state = (gadget_moving_inside || gadget_draggable ?
1929 GD_BUTTON_PRESSED : GD_BUTTON_UNPRESSED);
1930 gi->event.type = GD_EVENT_MOVING;
1931 gi->event.off_borders = gadget_moving_off_borders;
1933 if (gi->event_mask & GD_EVENT_MOVING && changed_position &&
1934 (gadget_moving_inside || gi->event_mask & GD_EVENT_OFF_BORDERS))
1935 gi->callback_action(gi);
1938 if (gadget_released_inside)
1940 boolean deactivate_gadget = TRUE;
1941 boolean gadget_changed = TRUE;
1943 if (gi->type & GD_TYPE_SELECTBOX)
1945 if (keep_selectbox_open ||
1946 mouse_released_where_pressed ||
1947 !gadget_released_inside_select_area) /* selectbox stays open */
1949 deactivate_gadget = FALSE;
1950 gadget_changed = FALSE;
1952 else if (gi->selectbox.index != gi->selectbox.current_index)
1953 gi->selectbox.index = gi->selectbox.current_index;
1955 gadget_changed = FALSE;
1958 if (deactivate_gadget &&
1959 !(gi->type & GD_TYPE_TEXT_INPUT ||
1960 gi->type & GD_TYPE_TEXT_AREA)) /* text input stays open */
1961 DrawGadget(gi, DG_UNPRESSED, gi->direct_draw);
1963 gi->state = GD_BUTTON_UNPRESSED;
1964 gi->event.type = GD_EVENT_RELEASED;
1966 if ((gi->event_mask & GD_EVENT_RELEASED) && gadget_changed)
1968 gi->callback_action(gi);
1972 if (gadget_released_off_borders)
1974 if (gi->type & GD_TYPE_SCROLLBAR)
1975 DrawGadget(gi, DG_UNPRESSED, gi->direct_draw);
1977 gi->state = GD_BUTTON_UNPRESSED;
1978 gi->event.type = GD_EVENT_RELEASED;
1980 if (gi->event_mask & GD_EVENT_RELEASED &&
1981 gi->event_mask & GD_EVENT_OFF_BORDERS)
1982 gi->callback_action(gi);
1985 /* handle gadgets unmapped/mapped between pressing and releasing */
1986 if (release_event && !gadget_released && new_gi)
1987 new_gi->state = GD_BUTTON_UNPRESSED;
1989 return (gadget_pressed || gadget_pressed_repeated ||
1990 gadget_released || gadget_moving);
1993 static void insertCharIntoTextArea(struct GadgetInfo *gi, char c)
1995 char text[MAX_GADGET_TEXTSIZE + 1];
1996 int cursor_position = gi->textarea.cursor_position;
1998 if (strlen(gi->textarea.value) >= MAX_GADGET_TEXTSIZE) /* no space left */
2001 strcpy(text, gi->textarea.value);
2002 strcpy(&gi->textarea.value[cursor_position + 1], &text[cursor_position]);
2003 gi->textarea.value[cursor_position] = c;
2005 setTextAreaCursorPosition(gi, gi->textarea.cursor_position + 1);
2008 boolean HandleGadgetsKeyInput(Key key)
2010 struct GadgetInfo *gi = last_gi;
2012 if (gi == NULL || !gi->mapped ||
2013 !(gi->type & GD_TYPE_TEXT_INPUT ||
2014 gi->type & GD_TYPE_TEXT_AREA ||
2015 gi->type & GD_TYPE_SELECTBOX))
2018 if (key == KSYM_Return) /* valid for both text input and selectbox */
2020 boolean gadget_changed = ((gi->event_mask & GD_EVENT_TEXT_RETURN) != 0);
2022 if (gi->type & GD_TYPE_TEXT_INPUT)
2024 CheckRangeOfNumericInputGadget(gi);
2026 if (!strEqual(gi->textinput.last_value, gi->textinput.value))
2027 strcpy(gi->textinput.last_value, gi->textinput.value);
2029 gadget_changed = FALSE;
2031 else if (gi->type & GD_TYPE_SELECTBOX)
2033 if (gi->selectbox.index != gi->selectbox.current_index)
2034 gi->selectbox.index = gi->selectbox.current_index;
2036 gadget_changed = FALSE;
2039 if (gi->type & GD_TYPE_TEXT_AREA)
2041 insertCharIntoTextArea(gi, '\n');
2043 DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2047 DrawGadget(gi, DG_UNPRESSED, gi->direct_draw);
2049 gi->event.type = GD_EVENT_TEXT_RETURN;
2055 gi->callback_action(gi);
2057 else if (gi->type & GD_TYPE_TEXT_INPUT) /* only valid for text input */
2059 char text[MAX_GADGET_TEXTSIZE + 1];
2060 int text_length = strlen(gi->textinput.value);
2061 int cursor_pos = gi->textinput.cursor_position;
2062 char letter = getCharFromKey(key);
2063 boolean legal_letter = (gi->type == GD_TYPE_TEXT_INPUT_NUMERIC ?
2064 letter >= '0' && letter <= '9' :
2067 if (legal_letter && text_length < gi->textinput.size)
2069 strcpy(text, gi->textinput.value);
2070 strcpy(&gi->textinput.value[cursor_pos + 1], &text[cursor_pos]);
2071 gi->textinput.value[cursor_pos] = letter;
2072 gi->textinput.cursor_position++;
2074 DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2076 else if (key == KSYM_Left && cursor_pos > 0)
2078 gi->textinput.cursor_position--;
2080 DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2082 else if (key == KSYM_Right && cursor_pos < text_length)
2084 gi->textinput.cursor_position++;
2086 DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2088 else if (key == KSYM_BackSpace && cursor_pos > 0)
2090 strcpy(text, gi->textinput.value);
2091 strcpy(&gi->textinput.value[cursor_pos - 1], &text[cursor_pos]);
2092 gi->textinput.cursor_position--;
2094 DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2096 else if (key == KSYM_Delete && cursor_pos < text_length)
2098 strcpy(text, gi->textinput.value);
2099 strcpy(&gi->textinput.value[cursor_pos], &text[cursor_pos + 1]);
2101 DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2104 else if (gi->type & GD_TYPE_TEXT_AREA) /* only valid for text area */
2106 char text[MAX_GADGET_TEXTSIZE + 1];
2107 int text_length = strlen(gi->textarea.value);
2108 int area_ysize = gi->textarea.ysize;
2109 int cursor_x_pref = gi->textarea.cursor_x_preferred;
2110 int cursor_y = gi->textarea.cursor_y;
2111 int cursor_pos = gi->textarea.cursor_position;
2112 char letter = getCharFromKey(key);
2113 boolean legal_letter = (letter != 0);
2117 insertCharIntoTextArea(gi, letter);
2119 DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2121 else if (key == KSYM_Left && cursor_pos > 0)
2123 setTextAreaCursorPosition(gi, gi->textarea.cursor_position - 1);
2125 DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2127 else if (key == KSYM_Right && cursor_pos < text_length)
2129 setTextAreaCursorPosition(gi, gi->textarea.cursor_position + 1);
2131 DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2133 else if (key == KSYM_Up && cursor_y > 0)
2135 setTextAreaCursorXY(gi, cursor_x_pref, cursor_y - 1);
2136 gi->textarea.cursor_x_preferred = cursor_x_pref;
2138 DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2140 else if (key == KSYM_Down && cursor_y < area_ysize - 1)
2142 setTextAreaCursorXY(gi, cursor_x_pref, cursor_y + 1);
2143 gi->textarea.cursor_x_preferred = cursor_x_pref;
2145 DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2147 else if (key == KSYM_BackSpace && cursor_pos > 0)
2149 strcpy(text, gi->textarea.value);
2150 strcpy(&gi->textarea.value[cursor_pos - 1], &text[cursor_pos]);
2152 setTextAreaCursorPosition(gi, gi->textarea.cursor_position - 1);
2154 DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2156 else if (key == KSYM_Delete && cursor_pos < text_length)
2158 strcpy(text, gi->textarea.value);
2159 strcpy(&gi->textarea.value[cursor_pos], &text[cursor_pos + 1]);
2161 DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2164 else if (gi->type & GD_TYPE_SELECTBOX) /* only valid for selectbox */
2166 int index = gi->selectbox.current_index;
2167 int num_values = gi->selectbox.num_values;
2169 if (key == KSYM_Up && index > 0)
2171 gi->selectbox.current_index--;
2173 DrawGadget(gi, DG_PRESSED, gi->direct_draw);
2175 else if (key == KSYM_Down && index < num_values - 1)
2177 gi->selectbox.current_index++;
2179 DrawGadget(gi, DG_PRESSED, gi->direct_draw);