rocksndiamonds-3.1.2
[rocksndiamonds.git] / src / libgame / gadgets.c
index a97fe3bd2b636e04b45a711f87c3a6b07f36518f..5ac104c8a254688839d0f1111ce3f17efa951fc0 100644 (file)
@@ -681,6 +681,22 @@ static void DrawGadget(struct GadgetInfo *gi, boolean pressed, boolean direct)
                    gi->y > gfx.vy ? REDRAW_DOOR_2 : REDRAW_DOOR_3);
 }
 
+static int get_minimal_size_for_numeric_input(int minmax_value)
+{
+  int min_size = 1;    /* value needs at least one digit */
+  int i;
+
+  /* add number of digits needed for absolute value */
+  for (i = 10; i <= ABS(minmax_value); i *= 10)
+    min_size++;
+
+  /* if min/max value is negative, add one digit for minus sign */
+  if (minmax_value < 0)
+    min_size++;
+
+  return min_size;
+}
+
 static void HandleGadgetTags(struct GadgetInfo *gi, int first_tag, va_list ap)
 {
   int tag = first_tag;
@@ -728,11 +744,11 @@ static void HandleGadgetTags(struct GadgetInfo *gi, int first_tag, va_list ap)
        break;
 
       case GDI_TYPE:
-       gi->type = va_arg(ap, unsigned long);
+       gi->type = va_arg(ap, unsigned int);
        break;
 
       case GDI_STATE:
-       gi->state = va_arg(ap, unsigned long);
+       gi->state = va_arg(ap, unsigned int);
        break;
 
       case GDI_ACTIVE:
@@ -754,30 +770,33 @@ static void HandleGadgetTags(struct GadgetInfo *gi, int first_tag, va_list ap)
        break;
 
       case GDI_RADIO_NR:
-       gi->radio_nr = va_arg(ap, unsigned long);
+       gi->radio_nr = va_arg(ap, unsigned int);
        break;
 
       case GDI_NUMBER_VALUE:
-       gi->textinput.number_value = va_arg(ap, long);
+       gi->textinput.number_value = va_arg(ap, int);
        sprintf(gi->textinput.value, "%d", gi->textinput.number_value);
+       strcpy(gi->textinput.last_value, gi->textinput.value);
        gi->textinput.cursor_position = strlen(gi->textinput.value);
        break;
 
       case GDI_NUMBER_MIN:
-       gi->textinput.number_min = va_arg(ap, long);
+       gi->textinput.number_min = va_arg(ap, int);
        if (gi->textinput.number_value < gi->textinput.number_min)
        {
          gi->textinput.number_value = gi->textinput.number_min;
          sprintf(gi->textinput.value, "%d", gi->textinput.number_value);
+         strcpy(gi->textinput.last_value, gi->textinput.value);
        }
        break;
 
       case GDI_NUMBER_MAX:
-       gi->textinput.number_max = va_arg(ap, long);
+       gi->textinput.number_max = va_arg(ap, int);
        if (gi->textinput.number_value > gi->textinput.number_max)
        {
          gi->textinput.number_value = gi->textinput.number_max;
          sprintf(gi->textinput.value, "%d", gi->textinput.number_value);
+         strcpy(gi->textinput.last_value, gi->textinput.value);
        }
        break;
 
@@ -789,12 +808,15 @@ static void HandleGadgetTags(struct GadgetInfo *gi, int first_tag, va_list ap)
            max_textsize = MIN(gi->textinput.size, MAX_GADGET_TEXTSIZE - 1);
 
          strncpy(gi->textinput.value, va_arg(ap, char *), max_textsize);
+         strcpy(gi->textinput.last_value, gi->textinput.value);
+
          gi->textinput.value[max_textsize] = '\0';
          gi->textinput.cursor_position = strlen(gi->textinput.value);
 
          /* same tag also used for other gadget definitions */
          strcpy(gi->textbutton.value, gi->textinput.value);
          strcpy(gi->textarea.value, gi->textinput.value);
+         strcpy(gi->textarea.last_value, gi->textinput.value);
        }
        break;
 
@@ -805,10 +827,17 @@ static void HandleGadgetTags(struct GadgetInfo *gi, int first_tag, va_list ap)
 
          gi->textinput.size = max_textsize;
          gi->textinput.value[max_textsize] = '\0';
+         strcpy(gi->textinput.last_value, gi->textinput.value);
 
          /* same tag also used for other gadget definitions */
-         strcpy(gi->textbutton.value, gi->textinput.value);
-         gi->textbutton.size = gi->textinput.size;
+
+         gi->textarea.size = max_textsize;
+         gi->textarea.value[max_textsize] = '\0';
+         strcpy(gi->textarea.last_value, gi->textinput.value);
+
+         gi->textbutton.size = max_textsize;
+         gi->textbutton.value[max_textsize] = '\0';
+
          gi->selectbox.size = gi->textinput.size;
        }
        break;
@@ -890,7 +919,7 @@ static void HandleGadgetTags(struct GadgetInfo *gi, int first_tag, va_list ap)
        break;
 
       case GDI_EVENT_MASK:
-       gi->event_mask = va_arg(ap, unsigned long);
+       gi->event_mask = va_arg(ap, unsigned int);
        break;
 
       case GDI_AREA_SIZE:
@@ -985,6 +1014,19 @@ static void HandleGadgetTags(struct GadgetInfo *gi, int first_tag, va_list ap)
     int border_xsize = gi->border.xsize;
     int border_ysize = gi->border.ysize;
 
+    if (gi->type == GD_TYPE_TEXT_INPUT_NUMERIC)
+    {
+      int number_min = gi->textinput.number_min;
+      int number_max = gi->textinput.number_max;
+      int min_size_min = get_minimal_size_for_numeric_input(number_min);
+      int min_size_max = get_minimal_size_for_numeric_input(number_max);
+      int min_size = MAX(min_size_min, min_size_max);
+
+      /* expand gadget text input size, if maximal value is too large */
+      if (gi->textinput.size < min_size)
+       gi->textinput.size = min_size;
+    }
+
     gi->width  = 2 * border_xsize + (gi->textinput.size + 1) * font_width;
     gi->height = 2 * border_ysize + font_height;
   }
@@ -1026,7 +1068,11 @@ static void HandleGadgetTags(struct GadgetInfo *gi, int first_tag, va_list ap)
     getFontCharSource(font_nr, FONT_ASCII_CURSOR, &src_bitmap, &src_x, &src_y);
     src_x += font_width / 2;
     src_y += font_height / 2;
-    gi->selectbox.inverse_color = GetPixel(src_bitmap, src_x, src_y);
+
+    /* there may be esoteric cases with missing or too small font bitmap */
+    if (src_bitmap != NULL &&
+       src_x < src_bitmap->width && src_y < src_bitmap->height)
+      gi->selectbox.inverse_color = GetPixel(src_bitmap, src_x, src_y);
 
     /* always start with closed selectbox */
     gi->selectbox.open = FALSE;
@@ -1059,16 +1105,23 @@ static void HandleGadgetTags(struct GadgetInfo *gi, int first_tag, va_list ap)
   if (gi->type & GD_TYPE_SCROLLBAR)
   {
     struct GadgetScrollbar *gs = &gi->scrollbar;
+    int scrollbar_size_cmp;
 
     if (gi->width == 0 || gi->height == 0 ||
        gs->items_max == 0 || gs->items_visible == 0)
       Error(ERR_EXIT, "scrollbar gadget incomplete (missing tags)");
 
     /* calculate internal scrollbar values */
+    gs->size_min = (gi->type == GD_TYPE_SCROLLBAR_VERTICAL ?
+                   gi->width : gi->height);
     gs->size_max = (gi->type == GD_TYPE_SCROLLBAR_VERTICAL ?
                    gi->height : gi->width);
-    gs->size = gs->size_max * gs->items_visible / gs->items_max;
-    gs->position = gs->size_max * gs->item_position / gs->items_max;
+
+    scrollbar_size_cmp = gs->size_max * gs->items_visible / gs->items_max;
+    gs->size = MAX(scrollbar_size_cmp, gs->size_min);
+    gs->size_max_cmp = (gs->size_max - (gs->size - scrollbar_size_cmp));
+
+    gs->position = gs->size_max_cmp * gs->item_position / gs->items_max;
     gs->position_max = gs->size_max - gs->size;
     gs->correction = gs->size_max / gs->items_max / 2;
 
@@ -1267,21 +1320,33 @@ void RemapAllGadgets()
   MultiMapGadgets(MULTIMAP_ALL | MULTIMAP_REMAP);
 }
 
-static boolean anyTextInputGadgetActive()
+boolean anyTextInputGadgetActive()
 {
   return (last_gi && (last_gi->type & GD_TYPE_TEXT_INPUT) && last_gi->mapped);
 }
 
-static boolean anyTextAreaGadgetActive()
+boolean anyTextAreaGadgetActive()
 {
   return (last_gi && (last_gi->type & GD_TYPE_TEXT_AREA) && last_gi->mapped);
 }
 
-static boolean anySelectboxGadgetActive()
+boolean anySelectboxGadgetActive()
 {
   return (last_gi && (last_gi->type & GD_TYPE_SELECTBOX) && last_gi->mapped);
 }
 
+boolean anyScrollbarGadgetActive()
+{
+  return (last_gi && (last_gi->type & GD_TYPE_SCROLLBAR) && last_gi->mapped);
+}
+
+boolean anyTextGadgetActive()
+{
+  return (anyTextInputGadgetActive() ||
+         anyTextAreaGadgetActive() ||
+         anySelectboxGadgetActive());
+}
+
 static boolean insideSelectboxLine(struct GadgetInfo *gi, int mx, int my)
 {
   return(gi != NULL &&
@@ -1298,15 +1363,11 @@ static boolean insideSelectboxArea(struct GadgetInfo *gi, int mx, int my)
         my >= gi->selectbox.y && my < gi->selectbox.y + gi->selectbox.height);
 }
 
-boolean anyTextGadgetActive()
-{
-  return (anyTextInputGadgetActive() ||
-         anyTextAreaGadgetActive() ||
-         anySelectboxGadgetActive());
-}
-
 void ClickOnGadget(struct GadgetInfo *gi, int button)
 {
+  if (!gi->mapped)
+    return;
+
   /* simulate releasing mouse button over last gadget, if still pressed */
   if (button_status)
     HandleGadgets(-1, -1, 0);
@@ -1400,14 +1461,35 @@ boolean HandleGadgets(int mx, int my, int button)
       button != 0 && !motion_status && new_gi != last_gi)
 #endif
   {
-    CheckRangeOfNumericInputGadget(last_gi);   /* in case of numeric gadget */
+    struct GadgetInfo *gi = last_gi;
+    boolean gadget_changed = (gi->event_mask & GD_EVENT_TEXT_LEAVING);
 
-    DrawGadget(last_gi, DG_UNPRESSED, last_gi->direct_draw);
+    /* check if text gadget has changed its value */
+    if (gi->type & GD_TYPE_TEXT_INPUT)
+    {
+      CheckRangeOfNumericInputGadget(gi);
 
-    last_gi->event.type = GD_EVENT_TEXT_LEAVING;
+      if (strcmp(gi->textinput.value, gi->textinput.last_value) != 0)
+       strcpy(gi->textinput.last_value, gi->textinput.value);
+      else
+       gadget_changed = FALSE;
+    }
 
-    if (last_gi->event_mask & GD_EVENT_TEXT_LEAVING)
-      last_gi->callback_action(last_gi);
+    /* selectbox does not change its value when closed by clicking outside */
+    if (gi->type & GD_TYPE_SELECTBOX)
+      gadget_changed = FALSE;
+
+    DrawGadget(gi, DG_UNPRESSED, gi->direct_draw);
+
+    gi->event.type = GD_EVENT_TEXT_LEAVING;
+
+#if 1
+    if (gadget_changed && !(gi->type & GD_TYPE_SELECTBOX))
+      gi->callback_action(gi);
+#else
+    if (gi->event_mask & GD_EVENT_TEXT_LEAVING)
+      gi->callback_action(gi);
+#endif
 
     last_gi = NULL;
 
@@ -1635,7 +1717,7 @@ boolean HandleGadgets(int mx, int my, int button)
 
        if (gs->item_position < 0)
          gs->item_position = 0;
-       if (gs->item_position > gs->items_max - gs->items_visible)
+       else if (gs->item_position > gs->items_max - gs->items_visible)
          gs->item_position = gs->items_max - gs->items_visible;
 
        if (old_item_position != gs->item_position)
@@ -1717,13 +1799,22 @@ boolean HandleGadgets(int mx, int my, int button)
 
       gs->position = scrollbar_mouse_pos - gs->drag_position;
 
-      if (gs->position < 0)
+      /* make sure to always precisely reach end positions when dragging */
+      if (gs->position <= 0)
+      {
        gs->position = 0;
-      if (gs->position > gs->position_max)
+       gs->item_position = 0;
+      }
+      else if (gs->position >= gs->position_max)
+      {
        gs->position = gs->position_max;
-
-      gs->item_position =
-       gs->items_max * (gs->position + gs->correction) / gs->size_max;
+       gs->item_position = gs->items_max - gs->items_visible;
+      }
+      else
+      {
+       gs->item_position =
+         gs->items_max * (gs->position + gs->correction) / gs->size_max_cmp;
+      }
 
       if (gs->item_position < 0)
        gs->item_position = 0;
@@ -1752,20 +1843,29 @@ boolean HandleGadgets(int mx, int my, int button)
   if (gadget_released_inside)
   {
     boolean deactivate_gadget = TRUE;
+    boolean gadget_changed = TRUE;
 
     if (gi->type & GD_TYPE_SELECTBOX)
     {
 #if 1
       if (mouse_released_where_pressed ||
          !gadget_released_inside_select_area)       /* selectbox stays open */
+      {
        deactivate_gadget = FALSE;
+       gadget_changed = FALSE;
+      }
 #else
       if (gadget_released_inside_select_line ||
          gadget_released_off_borders)               /* selectbox stays open */
+      {
        deactivate_gadget = FALSE;
+       gadget_changed = FALSE;
+      }
 #endif
-      else
+      else if (gi->selectbox.index != gi->selectbox.current_index)
        gi->selectbox.index = gi->selectbox.current_index;
+      else
+       gadget_changed = FALSE;
     }
 
     if (deactivate_gadget &&
@@ -1776,8 +1876,15 @@ boolean HandleGadgets(int mx, int my, int button)
     gi->state = GD_BUTTON_UNPRESSED;
     gi->event.type = GD_EVENT_RELEASED;
 
+#if 1
+    if ((gi->event_mask & GD_EVENT_RELEASED) && gadget_changed)
+    {
+      gi->callback_action(gi);
+    }
+#else
     if ((gi->event_mask & GD_EVENT_RELEASED) && deactivate_gadget)
       gi->callback_action(gi);
+#endif
   }
 
   if (gadget_released_off_borders)
@@ -1827,10 +1934,24 @@ boolean HandleGadgetsKeyInput(Key key)
 
   if (key == KSYM_Return)      /* valid for both text input and selectbox */
   {
+    boolean gadget_changed = (gi->event_mask & GD_EVENT_TEXT_RETURN);
+
     if (gi->type & GD_TYPE_TEXT_INPUT)
+    {
       CheckRangeOfNumericInputGadget(gi);
+
+      if (strcmp(gi->textinput.value, gi->textinput.last_value) != 0)
+       strcpy(gi->textinput.last_value, gi->textinput.value);
+      else
+       gadget_changed = FALSE;
+    }
     else if (gi->type & GD_TYPE_SELECTBOX)
-      gi->selectbox.index = gi->selectbox.current_index;
+    {
+      if (gi->selectbox.index != gi->selectbox.current_index)
+       gi->selectbox.index = gi->selectbox.current_index;
+      else
+       gadget_changed = FALSE;
+    }
 
     if (gi->type & GD_TYPE_TEXT_AREA)
     {
@@ -1847,8 +1968,13 @@ boolean HandleGadgetsKeyInput(Key key)
       last_gi = NULL;
     }
 
+#if 1
+    if (gadget_changed)
+      gi->callback_action(gi);
+#else
     if (gi->event_mask & GD_EVENT_TEXT_RETURN)
       gi->callback_action(gi);
+#endif
   }
   else if (gi->type & GD_TYPE_TEXT_INPUT)      /* only valid for text input */
   {