/*----------------------------------------------------------------------------*
 | This file is part of DEU (Doom Editing Utilities), created by the DEU team:|
 | Raphael Quinet, Brendon Wyber, Ted Vessenes and others.  See README.1ST or |
 | the "about" dialog box for full credits.                                   |
 |                                                                            |
 | DEU is an open project: if you think that you can contribute, please join  |
 | the DEU team.  You will be credited for any code (or ideas) included in    |
 | the next version of the program.                                           |
 |                                                                            |
 | If you want to make any modifications and re-distribute them on your own,  |
 | you must follow the conditions of the DEU license.  Read the file LICENSE  |
 | in this directory or README.1ST in the top directory.  If do not have a    |
 | copy of these files, you can request them from any member of the DEU team, |
 | or by mail: Raphael Quinet, Rue des Martyrs 9, B-4550 Nandrin (Belgium).   |
 |                                                                            |
 | This program comes with absolutely no warranty.  Use it at your own risks! |
 *----------------------------------------------------------------------------*

 I_DIALOG.C - dialog boxes

 Created by Ted Vessenes (tedv@geom.umn.edu)
*/

#include "deu.h"
#ifdef DEU_UNIX
#include <sys/types.h>
#include <sys/time.h>
#else
#include <time.h>
#endif
#include "d_config.h"
#include "d_misc.h"
#include "g_mouse.h"
#include "g_colcnv.h"
#include "g_gfx.h"
#include "w_names.h"
#include "w_things.h"
#include "w_lindef.h"
#include "w_sector.h"
#include "i_menus.h"
#include "i_textur.h"
#include "i_dialog.h"

/* Special Cookie input format:
   Int16 type, Int16 x, y (local to window), char key, extra args:

   Note that UInt32 chbit is only included if chvar != NULL.

   Button: Int16 button type (see dialog.h), char *name, Int16 color,
           Bool active,
     Extra button arguments (based on button type)
       OK, CANCEL: *none*
       DIALOG:
         Int16 width, length, Special Cookie count,
           SC 1, SC 2, SC 3, ...
       UPDATE:
         void *var, int repaint value,
         void (*hookfunc)(Int16, va_list *, DBSC *, Bool)
                     arg count^  ^-- Args   ^-- DBSC ptr ^-- Init?
         Function Argument 1
         Function Argument 2
         ...
       FUNCTION:
         int repaint value,
         void (*hookfunc)(Int16, va_list *, DBSC *, Bool)
                     arg count^  ^-- Args   ^-- DBSC ptr ^-- Init?
         Function Argument 1
         Function Argument 2
         ...

     Dialog and function buttons will be more verbose once they are
     implemented.

   Text Field: char *message, Int16 wrap length, Int16 color
   CheckBox: Bool *var, [UInt32 chbit], Bool bits, Bool value
   Labelled Checkbox: Bool *var, [UInt32 chbit], Bool bits, Bool value,
                      char *label
   Input Box: Int16 *var, [UInt32 chbit], Int16 min, max
   Scroll Box: Int16 listtype, listlen, void *var, [UInt32 chbit],
               Int16 height, wid,
               (if list type is LT_NONE)
                 Int16 entry [1],  char *name[1],
                       entry [2],       *name[2], ...
                       entry [listlen], *name[listlen]
               (if list type is LT_STR)
                 char *name[listlen]
               (if list type is LT_THINGS)
                 Int16 *entrys
               (if list type is LT_L_EFFECTS, LT_S_EFFECTS)
                 Int16 entry[0], entry[1], ..., entry[listlen-1]
   Bitmap: Int16 pictype, Int16 *var, char *str
   Dialog Button: Int16 dx, dy, char *name, DB *start
*/

/* global variables */
DB     *dbtop;
DB     *dbcur;
UInt32 *changes;      /* 32 bits of which variables have been changed */



/*
   This function returns TRUE if the mouse is inside of the specified DBSC
*/
static Bool MouseInDbBounds(Int16 x0, Int16 y0, Int16 x1, Int16 y1)
{
  return PointerX >= dbcur->x + x0 && PointerX <= dbcur->x + x1
         && PointerY >= dbcur->y + y0 && PointerY <= dbcur->y + y1;
}



/*
   This function calls a button's callback function
*/
static void CallButtonFunction(DBSC *ptr, va_list *ap, Bool status)
{
  DB *bktop, *bkcur; /* In case the function opens another dialog box */
  va_list tmp[1];

  /* Note from RQ:
     When this function is called, the mouse pointer is already hidden.
     The callback function may NOT hide the mouse pointer again.  If this
     function has to call other functions which hide the mouse pointer,
     these functions must be preceded with ShowMousePointer() and
     followed with HideMousePointer().  This should be done, for example,
     if the callback function opens a new dialog box.
  */
  bktop = dbtop;
  bkcur = dbcur;

  if (ptr->i.button.hookfunc != NULL)
    if (status == INIT)
      ptr->i.button.hookfunc(ap, ptr, status);
    else
      {
        *tmp = *ap;
        ptr->i.button.hookfunc(tmp, ptr, status);
      }

  dbtop = bktop;
  dbcur = bkcur;
}



/*
   This function handles the X centering of a DBSC
*/
static void DialogCenterX(DBSC *ptr, Int16 dx)
{
  Int16 width, height;
  
  ptr->x *= -1;
  GetScDim(ptr, &width, &height);
  
  /* First byte is for number of breaks, second byte is for
     placement on breaks.  Eg: 0x0102 is a "normal" center, 
     0x0104 is a center at 1/4th, 0x0304 is a center at 3/4ths. */

  ptr->x = (ptr->x >> 8) * dx / (ptr->x & 0xFF) - width/2;
}



/*
   This function loads one DBSC into memory and returns the next "last"
*/
static DBSC **LoadDbsc(va_list *ap, DBSC *ptr, DBSC **last, DB *cur,
                       UInt32 *chvar, Int16 i)
{
  Int16 j, newx, newy, newdx, newdy, newargc;

  *last = ptr;
  
  ptr->type = (Int16) va_arg(*ap, int);
  ptr->x = (Int16) va_arg(*ap, int);
  ptr->y = (Int16) va_arg(*ap, int);
  ptr->key = tolower(va_arg(*ap, char));
  
  DisplayDebugMessage(-50, 70 + 10*i, "Parsed DBSC Type %i", ptr->type);

  switch(ptr->type)
    { /* Init remaining info based on type */
    case DBSC_CANCEL:
      ptr->type = DBSC_BUTTON;
      ptr->i.button.type = BUTTON_CANCEL;
      ptr->i.button.name = "Cancel";
      ptr->i.button.color = RED;
      ptr->i.button.var = NULL;
      break;
      
    case DBSC_OK:
      ptr->type = DBSC_BUTTON;
      ptr->i.button.type = BUTTON_OK;
      ptr->i.button.name = "Ok";
      ptr->i.button.color = BLUE;
      ptr->i.button.var = NULL;
      break;

    case DBSC_UPDATE:
      ptr->type = DBSC_BUTTON;
      ptr->i.button.type = BUTTON_UPDATE;
      ptr->i.button.name = "Update";
      ptr->i.button.var = va_arg(*ap, void *);
      ptr->i.button.repaint = va_arg(*ap, int);
      ptr->i.button.hookfunc = (void (*)(va_list *, DBSC *, Bool))va_arg(*ap, void *);
      *(ptr->i.button.argv) = *ap;

      CallButtonFunction(ptr, ap, INIT);

      break;

    case DBSC_BUTTON:
      ptr->i.button.type = (Int16) va_arg(*ap, int);
      ptr->i.button.name = va_arg(*ap, char *);
      ptr->i.button.color = (Int16) va_arg(*ap, int);

      DisplayDebugMessage(-210, 100 + 10*i, "Start of button parse");
      switch (ptr->i.button.type)
        {
        case BUTTON_CANCEL:
        case BUTTON_OK:
          ptr->i.button.var = NULL;
          break;

        case BUTTON_DIALOG:
          ptr->i.button.var = va_arg(*ap, void *);
          ptr->i.button.repaint = (Int16) va_arg(*ap, int);
          newx = (Int16) va_arg(*ap, int);
          newy = (Int16) va_arg(*ap, int);
          newdx = (Int16) va_arg(*ap, int);
          newdy = (Int16) va_arg(*ap, int);

          newargc = (Int16) va_arg(*ap, int);
          ParseDialogBoxArgs(newx, newy, newdx, newdy, chvar, newargc,
                             ap, &(ptr->i.button.start));
          break;

        case BUTTON_PP_DIALOG:
          ptr->i.button.type = BUTTON_DIALOG;
          ptr->i.button.var = va_arg(*ap, void *);
          ptr->i.button.repaint = (Int16) va_arg(*ap, int);
          ptr->i.button.start = va_arg(*ap, DB *);
          break;

        case BUTTON_UPDATE:
        case BUTTON_FUNCTION:
          ptr->i.button.var = va_arg(*ap, void *);
          ptr->i.button.repaint = (Bool) va_arg(*ap, int);
          ptr->i.button.hookfunc = (void (*)(va_list *, DBSC *, Bool))va_arg(*ap, void *);
          *(ptr->i.button.argv) = *ap;

          CallButtonFunction(ptr, ap, INIT);
          break;

        default:
          ProgError("Unknown button type: %i", ptr->i.button.type);
        }
      DisplayDebugMessage(-390, 100 + 10 * i, "End of button parse");
      break;

    case DBSC_TEXT:
      ptr->i.text.string = va_arg(*ap, char *);
      ptr->i.text.wraplen = (Int16) va_arg(*ap, int);
      ptr->i.text.color = (Int16) va_arg(*ap, int);
      break;

    case DBSC_CHECKBOX:
      ptr->i.checkbox.var = va_arg(*ap, Int16 *);
      /* Note from RQ:
         UInt32 is not a standard type, and thus it is dangerous to use it
         in va_arg.  But it could also be dangerous to replace it by
         "unsigned long", because the DEC Alpha has 64 bits for "long"s.
         So it is probably better to keep UInt32 (which will be redefined
         for the Alpha).
      */
      if (chvar != NULL)
        ptr->i.checkbox.chbit = va_arg(*ap, UInt32);
      ptr->i.checkbox.bits = (Bool) va_arg(*ap, int);
      ptr->i.checkbox.value = (Int16) va_arg(*ap, int);
      break;

    case DBSC_L_CHECKBOX:
      ptr->i.l_checkbox.var = va_arg(*ap, Int16 *);
      if (chvar != NULL)
        ptr->i.l_checkbox.chbit = va_arg(*ap, UInt32);
      ptr->i.l_checkbox.bits = (Bool) va_arg(*ap, int);
      ptr->i.l_checkbox.value = (Int16) va_arg(*ap, int);
      ptr->i.l_checkbox.string = va_arg(*ap, char *);
      break;

    case DBSC_INPUTBOX:
      ptr->i.inputbox.var = va_arg(*ap, Int16 *);
      if (chvar != NULL)
        ptr->i.inputbox.chbit = va_arg(*ap, UInt32);
      ptr->i.inputbox.min = (Int16) va_arg(*ap, int);
      ptr->i.inputbox.max = (Int16) va_arg(*ap, int);
      break;

    case DBSC_SCROLLBOX:
      ptr->i.scrollbox.listtype = (Int16) va_arg(*ap, int);
      ptr->i.scrollbox.listlen = (Int16) va_arg(*ap, int);
      if (ptr->i.scrollbox.listtype == LT_STR)
        ptr->i.scrollbox.str = va_arg(*ap, char *);
      else
        ptr->i.scrollbox.var = va_arg(*ap, Int16 *);
      if (chvar != NULL)
        ptr->i.scrollbox.chbit = va_arg(*ap, UInt32);
      ptr->i.scrollbox.height = (Int16) va_arg(*ap, int);
      ptr->i.scrollbox.wid = (Int16) va_arg(*ap, int);

      if (ptr->i.scrollbox.listtype == LT_STR)
        ptr->i.scrollbox.namest = va_arg(*ap, char **);
      else
        {
          /*!TV This will be redone when there are structs for linedefs types
                and sector types.  For now, though, it will have to be messy */
          if (ptr->i.scrollbox.listtype == LT_THINGS)
            ptr->i.scrollbox.numst = va_arg(*ap, Int16 *);
          else
            ptr->i.scrollbox.numst =
              (Int16 *) GetMemory(ptr->i.scrollbox.listlen * sizeof(Int16));

          ptr->i.scrollbox.namest =
            (char **) GetMemory(ptr->i.scrollbox.listlen * sizeof(char *));

          if (ptr->i.scrollbox.listtype == LT_NONE)
            for (j = 0; j < ptr->i.scrollbox.listlen; j++)
              {
                ptr->i.scrollbox.numst[j] = (Int16) va_arg(*ap, int);
                ptr->i.scrollbox.namest[j] = va_arg(*ap, char *);
              }
          else if (ptr->i.scrollbox.listtype != LT_THINGS)
            for (j = 0; j < ptr->i.scrollbox.listlen; j++)
              ptr->i.scrollbox.numst[j] = (Int16) va_arg(*ap, int);
        }
      break;

    case DBSC_BITMAP:
      ptr->i.bitmap.pictype = (Int16) va_arg(*ap, int);
      ptr->i.bitmap.var = va_arg(*ap, Int16 *);
      ptr->i.bitmap.str = va_arg(*ap, char *);
      break;

    case DBSC_BOX:
      ptr->i.box.type = (Int16) va_arg(*ap, int);
      ptr->i.box.dx = (Int16) va_arg(*ap, int);
      ptr->i.box.dy = (Int16) va_arg(*ap, int);
      break;

    default:
      va_end(*ap);
      ProgError("Unknown Special Cookie type: %i", ptr->type);
    }

  if (ptr->type == DBSC_BUTTON && ptr->i.button.type == BUTTON_UPDATE)
    ptr->active = TRUE;
  else
    {
      ptr->active = (ptr->key != '\0');
      
      /* Negative values mean center */
      if (ptr->x < 0)
        DialogCenterX(ptr, cur->dx);
    }
  
  return &(ptr->next);
}



/*
   This function loads a list of DBSCs to a specified dialog box pointer
*/
static void LoadDbscList(va_list *ap, DB *cur, Int16 argc, UInt32 *chvar)
{
  Int16 i;
  DBSC *ptr, **last;
  
  DisplayDebugMessage(-50, 50, "Start of argument parse");
  
  for (last = &(cur->start); *last != NULL; last = &((*last)->next))
    ;

  for (i = 0; i < argc; i++)
    {
      ptr = (DBSC *) GetMemory(sizeof(DBSC));
      last = LoadDbsc(ap, ptr, last, cur, chvar, i);
    }
  *last = NULL;
  
  DisplayDebugMessage(-50, 80 + 10*i, "End of argument parse");
  i = 0;
  for (ptr = cur->start; ptr; ptr = ptr->next)
    i++;
  DisplayDebugMessage(-50, 90 + 10*i, "DBSC Count: %i", i);
}



/*
   This function appends some DBSCs to a specified dialog box
*/
void AppendDbscList(DB *cur, UInt32 *chvar, Int16 argc, ... )
{
  va_list ap;
  
  va_start(ap, argc);
  LoadDbscList(&ap, cur, argc, chvar);
  va_end(ap); /* Consider the effects on BUTTON FUNCTIONs that might need
                  ap still open. */ /*F*/
}



/*
   This function returns the relative importance of a DBSC
*/
Int16 GetFocusRank(DBSC *ptr)
{
  if (ptr == NULL)
    return 0;
  
  switch (ptr->type)
    {
    case DBSC_BUTTON:
      switch (ptr->i.button.type)
        {
        case BUTTON_OK:
          return 6;
        case BUTTON_CANCEL:
          return 5;
        case BUTTON_FUNCTION:
          return 4;
        case BUTTON_UPDATE:
        default:
          return -1;
        }
    case DBSC_CHECKBOX:
    case DBSC_L_CHECKBOX:
      return 1;
    case DBSC_INPUTBOX:
      return 2;
    case DBSC_SCROLLBOX:
      return 3;
    default:
      return -1;
    }
}



/*
   This function returns true if the specified reference matches the chosen
   reference
*/
static Bool ScrollMatch(scrollbox_info *scroll, Int16 ref)
{
  if (scroll->listtype == LT_STR)
    return !strnicmp(scroll->str, scroll->namest [ref], 8);
  else
    return (*(scroll->var) == scroll->numst [ref]);
}



/*
   This function reinitializes a scrollbox's reference value (selected text)
*/
static Bool ReinitRef(DBSC *ptr, DB *cur)
{
  Int16 i;

  for (i = 0; i < ptr->i.scrollbox.listlen; i++)
    if (ScrollMatch(&(ptr->i.scrollbox), i))
      {
        ptr->i.scrollbox.ref = i;
        if (!cur->focus)
          cur->focus = ptr;
        return TRUE;
      }
  return FALSE;
}



/*
   This function returns the offset of the scroll bar slider
*/
static Int16 GetSliderOffset(scrollbox_info *ptr)
{
  return BOUND(0, ptr->barlen - ptr->slider_l,
               (ptr->barlen * ptr->top + ptr->listlen/2) / ptr->listlen);
}



/*
   This function resets the top of a scroll box and its slider offset
*/
static void ResetScrollTop(scrollbox_info *scroll)
{
  scroll->top = BOUND(0, scroll->listlen - scroll->height,
                      scroll->ref - (scroll->height - 1) / 2);
  scroll->slider_o = GetSliderOffset(scroll);
}



/*
   This function initializes various DBSC_SCROLLBOX information fields
*/
static void InitScrollbox(DBSC *ptr, DB *cur)
{
  Int16 i;
  scrollbox_info *scroll;
  
  scroll = &(ptr->i.scrollbox);

  if (ptr->i.scrollbox.listtype == LT_STR)
    {
      scroll->str[8] = '\0';
      scroll->bkstr[8] = '\0';
      strncpy(scroll->bkstr, scroll->str, 8);
    }
  else
    {
      scroll->backup = *(scroll->var);

      switch (scroll->listtype)
        {
        case LT_THINGS:
          for (i = 0; i < scroll->listlen; i++)
            scroll->namest[i] =
              GetThingName(scroll->numst[i]);
          break;
        case LT_L_EFFECTS:
          for (i = 0; i < scroll->listlen; i++)
            scroll->namest[i] =
              GetLineDefTypeLongName(scroll->numst[i]);
          break;
        case LT_S_EFFECTS:
          for (i = 0; i < scroll->listlen; i++)
            scroll->namest[i] =
              GetSectorTypeLongName(scroll->numst[i]);
        }
    }
  
  if (ReinitRef(ptr, cur) == FALSE)
    scroll->ref = 0;
  
  scroll->bar = scroll->height < scroll->listlen;

  if (scroll->bar)
    {
      scroll->barlen = scroll->height*TEXT_H + SCROLL_V_TAB - 2*ARROW_H + 1;
      scroll->slider_l = (scroll->barlen * scroll->height + scroll->listlen/2)
                         / scroll->listlen;
    }

  ResetScrollTop(scroll);
}



/*
   This function initializes the information in a dialog box
*/
static void InitDb(DB *cur)
{
  DBSC *ptr, *focus;

  focus = NULL;

  for (ptr = cur->start; ptr; ptr = ptr->next)
    {
      if (GetFocusRank(ptr) > GetFocusRank(focus))
        focus = ptr;
      switch (ptr->type)
        {
        case DBSC_CHECKBOX:
          ptr->i.checkbox.backup = *(ptr->i.checkbox.var);
          if (ptr->i.checkbox.bits != BIT)
            if (*(ptr->i.checkbox.var) == ptr->i.checkbox.value)
              ptr->i.checkbox.last = 0;
            else
              ptr->i.checkbox.last = *(ptr->i.checkbox.var);
          break;
        case DBSC_L_CHECKBOX:
          ptr->i.l_checkbox.backup = *(ptr->i.l_checkbox.var);
          if (ptr->i.l_checkbox.bits != BIT)
            if (*(ptr->i.l_checkbox.var) == ptr->i.l_checkbox.value)
              ptr->i.l_checkbox.last = 0;
            else
              ptr->i.l_checkbox.last = *(ptr->i.l_checkbox.var);
          break;
        case DBSC_INPUTBOX:
          ptr->i.inputbox.backup = *(ptr->i.inputbox.var);
          break;
        case DBSC_SCROLLBOX:
          InitScrollbox(ptr, cur);
          break;
        case DBSC_BITMAP:
          if (strcmp(ptr->i.bitmap.str, ""))
            ptr->i.bitmap.str[8] = '\0';
          else
            strcpy(ptr->i.bitmap.str, "XXXXXXXX");
          break;
        case DBSC_BUTTON:
          /* Darkgray is reserved for inactive buttons */
          if (ptr->i.button.color == DARKGRAY)
            ptr->i.button.color = WHITE;
          break;
        }
    }
  
  cur->focus = focus;
}



/*
   This function sets the color of the selection text of a scroll box
*/
static void SetScrollColor(scrollbox_info *scroll, Int16 ref, Bool active)
{
  if (!active)
    SetColor(DARKGRAY);
  else if (ScrollMatch(scroll, ref))
    {
      if (&(dbcur->focus->i.scrollbox) == scroll)
        SetColor(CYAN);
      else
        SetColor(LIGHTGREEN);
    }
  else
    SetColor(WHITE);
}



/*
   This function re-hilights the scrollbox selections
*/
static void PartialUpdateScroll(Int16 x, Int16 y, scrollbox_info *ptr,
                                Bool active)
{
  Int16 i;

  for (i = ptr->top; i < MIN(ptr->top + ptr->height, ptr->listlen); i++)
    {
      SetScrollColor(ptr, i, active);

      if (strlen(ptr->namest[i]) > ptr->wid)
        {
          char *tmp_str;

          tmp_str = (char *)GetMemory(ptr->wid + 1);
	  strncpy(tmp_str, ptr->namest[i], ptr->wid);
	  tmp_str[ptr->wid] = '\0';
          DrawScreenText(dbcur->x + x+4, dbcur->y + y+4 + (i - ptr->top)*TEXT_H,
                         "%s", tmp_str);
          FreeMemory(tmp_str);
        }
      else
        DrawScreenText(dbcur->x + x+4, dbcur->y + y+4 + (i - ptr->top)*TEXT_H,
                       "%s", ptr->namest[i]);
    }
}



/*
   This function hilights the current DBSC focus
*/
static void HiliteFocus(DBSC *ptr)
{
  Int16 x0, y0, x1, y1;
  if (!ptr)
    return;

  x0 = dbcur->x + ptr->x - 2; /* two pixels buffer to the left */
  y0 = dbcur->y + ptr->y - 2;
  GetScDim(ptr, &x1, &y1);
  x1 += x0 + 4; /* two pixels buffer to the right */
  y1 += y0 + 4;

  if (dbcur->focus == ptr)
    {
      if (ptr->type == DBSC_SCROLLBOX)
        PartialUpdateScroll(ptr->x, ptr->y, &(ptr->i.scrollbox), ptr->active);
      SetColor(YELLOW); /* bright colors are easier to see */
    }
  else
    SetColor(LIGHTGRAY);

  DrawScreenRectangle(x0, y0, x1, y1);
}



/*
   This function draws a string on screen and underlines a character if
   key != NUL.  It returns TRUE if key was in the string.
*/
static Bool PutDialogText(Int16 x, Int16 y, char *string, char key)
{
  Int16 i = 0;
  
  DrawScreenText(x, y, string);
  
  if (key != '\0') /* Save some time */
    {
      while (string[i] != toupper(key) && string[i] != '\0')
        i++;
      
      if (!string[i]) {
        i = 0;
        while (tolower(string[i]) != key && string[i] != '\0')
          i++;
      }

      if (tolower(string[i]) == key) {
        DrawScreenLine(x + i*TEXT_W - 1, y + TEXT_H - 1,
                       x + i*TEXT_W + 6, y + TEXT_H - 1);
        return TRUE;
      }
    }
  return FALSE;
}



/*
   This function draws a box on screen with a given direction
*/
static void DrawDialogBox3D(Int16 x, Int16 y, Int16 dx, Int16 dy, Bool dir)
{
  x += dbcur->x;
  y += dbcur->y;

  if (dir == DOWN_BLACK)
    {
      SetColor(BLACK);
      dir = DOWN;
    } 
  else
    SetColor(LIGHTGRAY);
  
  if (dir == DOWN)
    {
      DrawScreenBox(x+2, y+2, x + dx-1, y + dy-1);
      x += dx;
      dx *= -1;
      y += dy;
      dy *= -1;
    }
  else
    DrawScreenBox(x+1, y+1, x + dx-2, y + dy-2);
  
  SetColor(WHITE);
  DrawScreenLine(x, y, x + dx-dir, y);
  DrawScreenLine(x, y+dir, x, y + dy-dir);
  
  SetColor(DARKGRAY);
  DrawScreenLine(x, y + dy, x + dx, y + dy);
  DrawScreenLine(x+dir, y + dy-dir, x + dx, y + dy-dir);
  DrawScreenLine(x + dx, y, x + dx, y + dy-2*dir);
  DrawScreenLine(x + dx-dir, y+dir, x + dx-dir, y + dy-2*dir);
}



/*
   This function draws a dialog button onscreen
*/
static void DrawButton(Int16 x, Int16 y, DBSC *ptr, Bool dir)
{
  Int16 width, height;
  
  /* Never draw update buttons; instead call them. */
  if (ptr->i.button.type == BUTTON_UPDATE)
    {
      CallButtonFunction(ptr, ptr->i.button.argv, RUN);
      return;
    }
  
  width = MAX(BUTTON_W, (strlen(ptr->i.button.name)+1)*TEXT_W - 1);
  height = BUTTON_H;

  DrawDialogBox3D(x, y, width, height, dir);
  
  /* x and y now refer to the text offsets of the button */
  x += dbcur->x + (width - strlen(ptr->i.button.name)*TEXT_W) / 2 + 1;
  y += dbcur->y + 5;

  if (dir == DOWN)
    x++, y++;

  if (ptr->active)
    SetColor(ptr->i.button.color);
  else
    SetColor(DARKGRAY);
  PutDialogText(x, y, ptr->i.button.name, ptr->key);
}



/*
   This function draws the check box X
*/
static void DrawCheckBoxX(Int16 x, Int16 y, Int16 color)
{
  x += dbcur->x;
  y += dbcur->y;

  SetColor(color);

#ifdef OLD_CHECKBOXES
  /* The \ part of the X */
  DrawScreenLine(x,     y, x+CHECKBOX_H,   y+CHECKBOX_H  );
  DrawScreenLine(x+1,   y, x+CHECKBOX_H,   y+CHECKBOX_H-1);
  DrawScreenLine(x,   y+1, x+CHECKBOX_H-1, y+CHECKBOX_H  );

  /* The / part of the X */
  DrawScreenLine(x,   y+CHECKBOX_H,   x+CHECKBOX_H,   y  );
  DrawScreenLine(x+1, y+CHECKBOX_H,   x+CHECKBOX_H,   y+1);
  DrawScreenLine(x,   y+CHECKBOX_H-1, x+CHECKBOX_H-1, y  );
#else
  DrawScreenBoxHollow(x, y, x + CHECKBOX_H, y + CHECKBOX_H);
  if (color != BLACK)
    {
      SetColor(color);
      DrawScreenBox(x + 3, y + 3, x + CHECKBOX_H - 3, y + CHECKBOX_H - 3);
    }
#endif
}



/*
   This function returns the color the check box X should be drawn
*/
static Int16 GetCheckBoxColor(DBSC *ptr)
{
  if (ptr->type == DBSC_CHECKBOX)
    if ((ptr->i.checkbox.bits == BIT
         && (*(ptr->i.checkbox.var) & ptr->i.checkbox.value))
        || (ptr->i.checkbox.bits != BIT
            && (*(ptr->i.checkbox.var) == ptr->i.checkbox.value)))
      return LIGHTGREEN;
    else
      return BLACK;
  else
    if ((ptr->i.l_checkbox.bits == BIT
         && (*(ptr->i.l_checkbox.var) & ptr->i.l_checkbox.value))
        || (ptr->i.l_checkbox.bits != BIT
            && (*(ptr->i.l_checkbox.var) == ptr->i.l_checkbox.value)))
      return LIGHTGREEN;
    else
      return BLACK;
}



/*
   This function draws a DBSC Bitmap in the dialog box
*/
static void DrawDialogBitmap(DBSC *ptr)
{
  SetColor(BLACK);
  switch (ptr->i.bitmap.pictype)
    {
    case BMP_THING:
      DrawScreenBox(dbcur->x + ptr->x + 2,   dbcur->y + ptr->y + 2,
                    dbcur->x + ptr->x + 257, dbcur->y + ptr->y + 129);
      DisplayThingPic(dbcur->x + ptr->x +   2, dbcur->y + ptr->y +   2,
                      dbcur->x + ptr->x + 257, dbcur->y + ptr->y + 129,
                      GetThingPicName(*(ptr->i.bitmap.var)));
      break;
    case BMP_WALL:
      DrawScreenBox(dbcur->x + ptr->x + 2,   dbcur->y + ptr->y + 2,
                    dbcur->x + ptr->x + 257, dbcur->y + ptr->y + 129);
      DisplayWallTexture(dbcur->x + ptr->x +   2, dbcur->y + ptr->y +   2,
                         dbcur->x + ptr->x + 257, dbcur->y + ptr->y + 129,
                         ptr->i.bitmap.str);
      break;
    case BMP_FLAT:
      DisplayFloorTexture(dbcur->x + ptr->x +   2, dbcur->y + ptr->y +   2,
                          dbcur->x + ptr->x +  65, dbcur->y + ptr->y +  65,
                          ptr->i.bitmap.str);
      break;
    }
}



/*
   This function draws a DBSC_INPUTBOX
*/
static void DrawDialogInput(DBSC *ptr, Bool dir)
{
  Int16 color;
  char str[8];
  
  if (*(ptr->i.inputbox.var) >= ptr->i.inputbox.min &&
      *(ptr->i.inputbox.var) <= ptr->i.inputbox.max)
    color = WHITE;
  else
    color = LIGHTRED;
  
  DrawDialogBox3D(ptr->x, ptr->y, INPUTBOX_W, INPUTBOX_H, dir);
  
  SetColor(color);
  sprintf(str, "%d", *(ptr->i.inputbox.var));

  if (dir == UP)
    DrawScreenText(dbcur->x + ptr->x + (INPUTBOX_W - TEXT_W*strlen(str))/2,
                   dbcur->y + ptr->y+3, str);
  else
    DrawScreenText(dbcur->x + ptr->x+1 + (INPUTBOX_W - TEXT_W*strlen(str))/2,
                   dbcur->y + ptr->y+4, str);
}



/*
   This function does the graphics calls that draws the scrollbar arrow
*/
static void DrawArrow(Int16 x, Int16 y, int dir)
{
/* Arrow graphic:
   x
   |
   v
y->   X
     XXX
    XXXXX
   XXXXXXX
     XXX
     XXX
     XXX
*/

  if (dir == DOWN)
    y += 6; /* Start at the bottom of an arrow pointing down */
  
  SetColor(BLACK);
  DrawScreenBox(x + 2, y + 4*dir, x + 4, y + 6*dir);
  DrawScreenPixel(x + 3, y);
  y += dir;
  DrawScreenLine(x + 2, y, x + 4, y);
  y += dir;
  DrawScreenLine(x + 1, y, x + 5, y);
  y += dir;
  DrawScreenLine(x, y, x + 6, y);
}



/*
   This function draws a scrollbar arrow in a specified direction
*/
static void DrawScrollArrow(Int16 x, Int16 y, int dir, int status)
{
  /* dir == UP if the arrow points up,
     status == UP if the arrow isn't pressed. */
  
  DrawDialogBox3D(x - dbcur->x, y - dbcur->y, ARROW_H-1, ARROW_H-1, status);
  if (status == UP)
    DrawArrow(x+ARROW_TAB, y+ARROW_TAB, dir);
  else
    DrawArrow(x+ARROW_TAB+1, y+ARROW_TAB+1, dir);
}



/*
   This function updates the scrollbar slider
*/
static void UpdateScrollBar(Int16 x, Int16 y, scrollbox_info *ptr, Int16 prev)
{
  Int16 x1, y1; /* Scrollbar point values: left, top, bot */

  /* Initialize xoff and yoff */
  x1 = x + 2 + TEXT_W*ptr->wid + SCROLL_H_TAB;
  y1 = y + ARROW_H;

  SetColor(BLACK);
  DrawScreenBox(dbcur->x + x1, dbcur->y + y1 + prev,
   dbcur->x + x1 + SCROLLBAR_W-1, dbcur->y + y1 + prev + ptr->slider_l-1);

  DrawScreenBox3D(dbcur->x + x1, dbcur->y + y1+ptr->slider_o,
   dbcur->x + x1+SCROLLBAR_W-1,
   dbcur->y + y1+ptr->slider_o+ptr->slider_l-1);
}



/*
   This function completely redraws the scrollbox slider and selections
*/
static void FullUpdateScroll(Int16 x, Int16 y, scrollbox_info *ptr,
                             Int16 prev, Bool active)
{
  /* Note that prev is the previous value of ptr->slider_o, NOT the previous
     value of *(ptr->var) */
  Int16 i;

  if (ptr->bar)
    {
      if (prev == ptr->slider_o)
        ptr->slider_o = GetSliderOffset(ptr);

      UpdateScrollBar(x, y, ptr, prev);
    }

  SetColor(BLACK);

  DrawScreenBox(dbcur->x + x + 2, dbcur->y + y + 2,
   dbcur->x + x + TEXT_W*ptr->wid    + SCROLL_H_TAB - 2,
   dbcur->y + y + TEXT_H*ptr->height + SCROLL_V_TAB - 2);

  for (i = ptr->top; i < MIN(ptr->top + ptr->height, ptr->listlen); i++)
    {
      SetScrollColor(ptr, i, active);

      if (strlen(ptr->namest[i]) > ptr->wid)
        {
          char *tmp_str;

          tmp_str = (char *)GetMemory(ptr->wid + 1);
	  strncpy(tmp_str, ptr->namest[i], ptr->wid);
	  tmp_str[ptr->wid] = '\0';
          DrawScreenText(dbcur->x + x+4, dbcur->y + y+4 + (i - ptr->top)*TEXT_H,
                         "%s", tmp_str);
          FreeMemory(tmp_str);
        }
      else
        DrawScreenText(dbcur->x + x+4, dbcur->y + y+4 + (i - ptr->top)*TEXT_H,
                       "%s", ptr->namest[i]);
    }
}



/*
   This function draws a scroll box and scroll bar on screen
*/
static void DrawDialogScroll(Int16 x, Int16 y, scrollbox_info *scroll,
                             Bool active)
{
/*
        x1---.  .----x2
  x          v  v
y .----------..-.
  |          ||-|
  |          || |
  |          ||-|
y2`----------'`-'
*/

  Int16 x1, x2, y2;
  
  x2 = x1 = x + SCROLL_H_TAB + TEXT_W*scroll->wid;
  y2 =      y + SCROLL_V_TAB + TEXT_H*scroll->height;
  
  if (scroll->bar)
    x2 += 1 + ARROW_H;
  
  DrawDialogBox3D(x, y, x1-x, y2-y, DOWN_BLACK);

  x  += dbcur->x;
  x1 += dbcur->x;
  x2 += dbcur->x;
  y  += dbcur->y;
  y2 += dbcur->y;
  
  if (scroll->bar)
    {
      /* Draw the dividing bars */
      SetColor(WHITE);
      DrawScreenLine(x1, y+1, x1, y2-1);
      
      /* Draw the arrows */
      DrawScrollArrow(x1+2, y, UP, UP);
      DrawScrollArrow(x1+2, y2-ARROW_H+1, DOWN, UP);
      
      SetColor(BLACK);
      DrawScreenBox(x1+2, y+ARROW_H, x2, y2-ARROW_H);
    }
  
  FullUpdateScroll(x - dbcur->x, y - dbcur->y, scroll, scroll->slider_o, active);
}



/*
   This function redraws a specific DBSC
*/
static void UpdateDialogSC(DBSC *ptr, void *var)
{
  switch (ptr->type)
    {
    case DBSC_BUTTON:
      if (ptr->i.button.var == var || !var
          || (ptr->i.button.type == BUTTON_UPDATE && !ptr->i.button.var))
        {
          if (ptr->i.button.type == BUTTON_UPDATE)
            CallButtonFunction(ptr, ptr->i.button.argv, RUN);
          else
            DrawButton(ptr->x, ptr->y, ptr, UP);
        }
      break;
    case DBSC_CHECKBOX:
      if (ptr->i.checkbox.var == var || !var)
        DrawCheckBoxX(ptr->x, ptr->y, GetCheckBoxColor(ptr));
      break;
    case DBSC_L_CHECKBOX:
      if (ptr->i.l_checkbox.var == var || !var)
        DrawCheckBoxX(ptr->x, ptr->y, GetCheckBoxColor(ptr));
      break;
    case DBSC_INPUTBOX:
      if (ptr->i.inputbox.var == var || !var)
        DrawDialogInput(ptr, UP);
      break;
    case DBSC_SCROLLBOX:
      if (ptr->i.scrollbox.var == var || !var)
        PartialUpdateScroll(ptr->x, ptr->y, &(ptr->i.scrollbox), ptr->active);
      break;
    case DBSC_BITMAP:
      if (ptr->i.bitmap.var == var || ptr->i.bitmap.str == var
          || !var)
        {
          if ((ptr->i.bitmap.last != *(ptr->i.bitmap.var))
              || (ptr->i.bitmap.last == -1)
              || strnicmp(ptr->i.bitmap.laststr, ptr->i.bitmap.str, 8))
            {
              ptr->i.bitmap.last = *(ptr->i.bitmap.var);
              strncpy(ptr->i.bitmap.laststr, ptr->i.bitmap.str, 8);
              DrawDialogBitmap(ptr);
            }
        }
      break;
    }
}



/*
   This function updates specific DBSCs after variable changes
*/
static void UpdateDialogBox(void *var)
{
  /* var points to the value that was changed, or NULL for update all */
  DBSC *ptr;

  for (ptr = dbcur->start; ptr; ptr = ptr->next)
    UpdateDialogSC(ptr, var);
}



/*
   This function draws and underlines a given text string in a dialog box
   with line wrapping
*/
static Int16 DrawDialogText(Int16 x, Int16 y, char *string, char key,
                            Int16 color, Int16 wraplen)
{
  char  tmpstr[200];
  Int16 i, lines = 0;
  Bool  key_used = FALSE;

  x += dbcur->x;
  y += dbcur->y;

  SetColor(color);

  /* Don't bother checking for the key if none is specified */
  key_used = (key == '\0');

  for (;;)
    {
      if (strlen(string) <= wraplen || !wraplen)
        {
          if (key_used)
            DrawScreenText(x, y, string);
          else
            PutDialogText(x, y, string, key);
          return (lines);
        }
      
      i = wraplen;
      while (i > 0 && string[i-1] != ' ')
        i--;
      
      if (i)
        i--;
      else
        i = wraplen;
      
      strncpy(tmpstr, string, i);
      tmpstr[i] = '\0';

      if (key_used)
        DrawScreenText(x, y, tmpstr);
      else
        key_used = PutDialogText(x, y, tmpstr, key);
      
      y += TEXT_H;
      lines++;

      string += i;
      if (*string == ' ')
        string++;
    }
}



/*
   This function draws the background dialog box and all of its DBSCs
*/
static void RepaintDialogBox(void)
{
  DBSC *ptr;

  if (UseMouse)
    HideMousePointer();

  /* Draw the background window */
  DrawScreenBox3D(dbcur->x,             dbcur->y,
                  dbcur->x + dbcur->dx, dbcur->y + dbcur->dy);

  /* Then draw each special cookie */
  for (ptr = dbcur->start; ptr; ptr = ptr->next)
    {
      switch (ptr->type) {
      case DBSC_BUTTON:
        if (ptr->i.button.type == BUTTON_UPDATE)
          CallButtonFunction(ptr, ptr->i.button.argv, RUN);
        break;
      case DBSC_TEXT:
        ptr->i.text.lines = DrawDialogText(ptr->x, ptr->y,
                                           ptr->i.text.string, ptr->key,
                                           ptr->i.text.color,
                                           ptr->i.text.wraplen);
        break;
      case DBSC_L_CHECKBOX:
        DrawDialogText(ptr->x + CHECKBOX_W+TEXT_W, ptr->y + 1,
                       ptr->i.l_checkbox.string, ptr->key,
                       ptr->active ? WHITE : DARKGRAY, '\0');
        break;
      case DBSC_SCROLLBOX:
        if (!ScrollMatch(&(ptr->i.scrollbox), ptr->i.scrollbox.ref))
          {
            ReinitRef(ptr, dbcur);
            ResetScrollTop(&(ptr->i.scrollbox));
          }
        DrawDialogScroll(ptr->x, ptr->y, &(ptr->i.scrollbox), ptr->active);
        break;
      case DBSC_BITMAP:
        ptr->i.bitmap.last = -1;
        ptr->i.bitmap.laststr[0] = '\0';
        if (ptr->i.bitmap.pictype == BMP_FLAT)
          DrawScreenBoxHollow(dbcur->x + ptr->x,       dbcur->y + ptr->y,
                              dbcur->x + ptr->x + 67,  dbcur->y + ptr->y + 67);
        else
          DrawScreenBoxHollow(dbcur->x + ptr->x,       dbcur->y + ptr->y,
                              dbcur->x + ptr->x + 256, dbcur->y + ptr->y + 131);
        break;
      case DBSC_BOX:
        switch(ptr->i.box.type)
          {
          case BOX_OUTLINE:
            SetColor(BLACK);
            DrawScreenRectangle(dbcur->x + ptr->x, dbcur->y + ptr->y,
                                dbcur->x + ptr->x + ptr->i.box.dx,
                                dbcur->y + ptr->y + ptr->i.box.dy);
            break;
          case BOX_RAISED:
            DrawScreenBox3D(dbcur->x + ptr->x, dbcur->y + ptr->y,
                            dbcur->x + ptr->x + ptr->i.box.dx,
                            dbcur->y + ptr->y + ptr->i.box.dy);
            break;
          case BOX_SUNKEN:
            DrawScreenBoxSunken(dbcur->x + ptr->x, dbcur->y + ptr->y,
                                dbcur->x + ptr->x + ptr->i.box.dx,
                                dbcur->y + ptr->y + ptr->i.box.dy);
            break;
          case BOX_HOLLOW:
            DrawScreenBoxHollow(dbcur->x + ptr->x, dbcur->y + ptr->y,
                                dbcur->x + ptr->x + ptr->i.box.dx,
                                dbcur->y + ptr->y + ptr->i.box.dy);
            break;
          case BOX_GRAY:
            SetColor(LIGHTGRAY);
            DrawScreenBox(dbcur->x + ptr->x, dbcur->y + ptr->y,
                          dbcur->x + ptr->x + ptr->i.box.dx,
                          dbcur->y + ptr->y + ptr->i.box.dy);
            break;
        }
        break;
      }
    }

  /* Hilite the current focus */
  HiliteFocus(dbcur->focus);

  /* Update all SC vars */
  UpdateDialogBox(NULL);

  if (UseMouse)
    ShowMousePointer();
}



/*
   This function sets the current DBSC focus
*/
static void SetFocus(DBSC *ptr)
{
  DBSC *old;

  if (ptr == dbcur->focus)
    return;

  old = dbcur->focus;
  dbcur->focus = ptr;

  HiliteFocus(old);
  HiliteFocus(ptr);

  if (old->type == DBSC_SCROLLBOX)
    PartialUpdateScroll(old->x, old->y, &(old->i.scrollbox), old->active);
}


/*! This has to be changed!  Under BC, CLOCKS_PER_SEC is a float (18.2),
    so it cannot be used with "%".  And there is no such symbol under Unix.
*/
#ifdef CLOCKS_PER_SEC
#undef CLOCKS_PER_SEC
#endif
#define CLOCKS_PER_SEC 18


/*
   This function returns true if the mouse should be processed again
*/
static Bool MouseDelay(Bool status)
{
  static clock_t oldtimer = 0, next = -1;
  clock_t timer;
  static Bool repeat = FALSE;
#ifdef DEU_UNIX
  struct timeval tv;
#endif

  if (status == RESET)
    {
      repeat = FALSE;
      next = -1;
    }
  else if (status == REPEAT)
    {
#ifdef DEU_UNIX
      (void) gettimeofday(&tv);
      timer = tv.tv_usec;
#else
      timer = clock();
#endif
      if ((timer - oldtimer + CLOCKS_PER_SEC) % CLOCKS_PER_SEC
          >= CLOCKS_PER_SEC / Config.mouseRepeatRate)
        {
          oldtimer = timer;
          return TRUE;
        }
      else
        return FALSE;
    }
  else if (status == WAIT)
    {
#ifdef DEU_UNIX
      (void) gettimeofday(&tv);
      timer = tv.tv_usec;
#else
      timer = clock();
#endif
      if ((timer - oldtimer + CLOCKS_PER_SEC) % CLOCKS_PER_SEC
          >= next)
        {
          if (next >= 0)
            {
              repeat = TRUE;
              next = CLOCKS_PER_SEC / Config.mouseRepeatRate;
            }
          else if (next == -1)
            next = (CLOCKS_PER_SEC / 1000) * Config.mouseRepeatDelay;

          oldtimer = timer;
          return TRUE;
        }
      else
        return FALSE;
    }
  return TRUE;
}



/*
   This function moves the top of the scrollbox window by a specified amount
*/
static void MoveScrollTop(scrollbox_info *scroll, Int16 dist)
{
  /* use - dist instead of + dist so UP moves up, not down */
  scroll->top = BOUND(0, scroll->listlen - scroll->height,
                      scroll->top - dist);
  scroll->slider_o = GetSliderOffset(scroll);
}



/*
   This function marks that a change has taken place in the global change var
*/
void SetChangeBits(UInt32 bits)
{
  if (changes != NULL)
    *changes |= bits;
}



/*
   This function updates a scrollbox selection
*/
static void UpdateScrollboxVar(scrollbox_info *scroll)
{
  if (scroll->listtype == LT_STR)
    strncpy(scroll->str, scroll->namest [scroll->ref], 8);
  else
    *(scroll->var) = scroll->numst [scroll->ref];
  SetChangeBits(scroll->chbit);
}



/*
   This function sets the reference choice of a scrollbox
*/
static void SetScrollRef(scrollbox_info *scroll, Int16 ref)
{
  scroll->ref = BOUND(0, scroll->listlen - 1, ref);
  UpdateScrollboxVar(scroll);
}



/*
   The function updates (redraws selections and sliders) all scrollboxes in
   the current dialog box
*/
static void ScrollUpdateAll(Int16 x, Int16 y, scrollbox_info *scroll,
                            Int16 prev, Bool update_type, Bool active)
{
  DBSC *ptr;

  for (ptr = dbcur->start; ptr; ptr = ptr->next)
    if (ptr->type == DBSC_SCROLLBOX && &(ptr->i.scrollbox) == scroll)
      if (update_type == FULL)
        FullUpdateScroll(x, y, scroll, prev, active);
      else
        PartialUpdateScroll(x, y, scroll, active);
    else
      if (scroll->listtype == LT_STR)
        UpdateDialogSC(ptr, scroll->str);
      else
        UpdateDialogSC(ptr, scroll->var);
}



/*
   This function processes the click of a mouse in a scroll box
*/
static void ProcessScrollClick(DBSC *ptr, UInt16 buttons)
{
  Int16  oldslider, oldtop, oldvar = -1;
  char   oldstr[9];
  Int16  x1, x2, y1, y2, y3;
  Int16  oldx, oldy;
  UInt16 oldbuttons;
  Int16  dist;
  Bool   overfield, test;
  scrollbox_info *scroll;

  scroll = &(ptr->i.scrollbox);

  if (MouseInDbBounds(ptr->x, ptr->y,
                      ptr->x + 2 + scroll->wid   *TEXT_W,
                      ptr->y + 2 + scroll->height*TEXT_H))
    {
      test = 0;
      oldstr[8] = '\0';
      while (buttons)
        {
          oldslider = scroll->slider_o;
          oldtop = scroll->top;
          if (scroll->listtype == LT_STR)
            strncpy(oldstr, scroll->str, 8);
          else
            oldvar = *(scroll->var);

          if (PointerY < dbcur->y + ptr->y + 3)
            {
              if (test != 1)
                {
                  MouseDelay(RESET);
                  test = 1;
                }

              if (MouseDelay(REPEAT) && scroll->top > 0)
                MoveScrollTop(scroll, UP);
              SetScrollRef(scroll, scroll->top);
            }
          else if (PointerY > dbcur->y + ptr->y + 3 + scroll->height*TEXT_H)
            {
              if (test != 2)
                {
                  MouseDelay(RESET);
                  test = 2;
                }

              if (MouseDelay(REPEAT)
                  && scroll->top <= scroll->listlen - scroll->height)
                MoveScrollTop(scroll, DOWN);
              SetScrollRef(scroll, scroll->top + scroll->height - 1);
            }
          else
            {
              test = 0;
              SetScrollRef(scroll, scroll->top
                           + (PointerY - (dbcur->y + ptr->y + 3)) / TEXT_H);
            }

          if (oldslider != scroll->slider_o || oldtop != scroll->top)
            ScrollUpdateAll(ptr->x, ptr->y, scroll, oldslider, FULL,
                            ptr->active);
          else if (scroll->listtype == LT_STR)
            {
              if (strnicmp(oldstr, scroll->str, 8))
                ScrollUpdateAll(ptr->x, ptr->y, scroll, -1, PARTIAL, ptr->active);
            }
          else if (oldvar != *(scroll->var))
            ScrollUpdateAll(ptr->x, ptr->y, scroll, -1, PARTIAL, ptr->active);

          GetMouseCoords(&PointerX, &PointerY, &buttons);
        }
    }
  else if (scroll->bar)
    {
      x1 = ptr->x + scroll->wid*TEXT_W + SCROLL_H_TAB + 2;
      x2 = x1 + ARROW_H;
      y1 = ptr->y + scroll->height*TEXT_H + SCROLL_V_TAB;
      y2 = ptr->y + ARROW_H;
      y3 = y1 - ARROW_H;

      if (!MouseInDbBounds(x1, ptr->y, x2, y1))
        return;

      if (MouseInDbBounds(x1, ptr->y, x2, y2-1)
          || MouseInDbBounds(x1, y3 + 1, x2, y1))
        {
          /* Mouse is over the arrow buttons */
          if (PointerY - dbcur->y < y2)
            {
              dist = UP;
              y1 = ptr->y;
              y2--; /* y1 and y2 are now the arrow bounds */
            }
          else 
            {
              dist = DOWN;
              y2 = y1;
              y1 = y3 + 1;
            }

          overfield = FALSE;
          oldx = -1;
          oldy = -1;
          oldbuttons = buttons;

          do
            {
              GetMouseCoords(&PointerX, &PointerY, &buttons);

              if (oldx != PointerX || oldy != PointerY
                  || (oldbuttons & 0x07) != (buttons & 0x07))
                {
                  if (overfield != (test = MouseInDbBounds(x1, y1, x2, y2)))
                    {
                      overfield = test;

                      DrawScrollArrow(dbcur->x + x1, dbcur->y + y1, dist,
                                      (overfield ? DOWN : UP));
                    }
                }

              if (overfield)
                if (MouseDelay(WAIT))
                  {
                    oldslider = scroll->slider_o;
                    oldtop = scroll->top;
                    MoveScrollTop(scroll, dist);
                    if (oldslider != scroll->slider_o ||
                        oldtop    != scroll->top)
                      ScrollUpdateAll(ptr->x, ptr->y, scroll, oldslider, FULL,
                                      ptr->active);
                }
              else
                MouseDelay(RESET);

              oldx = PointerX;
              oldy = PointerY;
              oldbuttons = buttons;
            } while (buttons & 0x07);

          MouseDelay(RESET);
          DrawScrollArrow(dbcur->x + x1, dbcur->y + y1, dist, UP);
        }
      else if (MouseInDbBounds(x1, y2 + scroll->slider_o,
                               x2, y2 + scroll->slider_o
                               + scroll->slider_l - 1))
        {
          oldy = PointerY;
          dist = PointerY - (y2 + scroll->slider_o);

          do
            {
              GetMouseCoords(&PointerX, &PointerY, &buttons);

              if (oldy != BOUND(y2 + dist, y3 + dist - scroll->slider_l + 1,
                                PointerY))
                {
                  oldslider = scroll->slider_o; /* oldx now holds old_slider_offset */

                  scroll->slider_o = BOUND(0, scroll->barlen - scroll->slider_l,
                                           scroll->slider_o + PointerY - oldy); /*! always false? */
                  oldtop = scroll->top;
                  scroll->top = (scroll->slider_o * scroll->listlen
                                 + scroll->barlen/2) / scroll->barlen;
                  if (oldtop != scroll->top)
                    ScrollUpdateAll(ptr->x, ptr->y, scroll, oldslider, FULL,
                                    ptr->active);
                  else
                    UpdateScrollBar(ptr->x, ptr->y, scroll, oldslider);
                }

              oldy = BOUND(y2 + dist, y3 + dist - (scroll->slider_l) + 1,
                           PointerY);
            } while (buttons & 0x07);
        }
      else
        {
          if (PointerY - dbcur->y < y2 + scroll->slider_o)
            dist = UP * scroll->height;
          else
            dist = DOWN * scroll->height;

          do
            {
              GetMouseCoords(&PointerX, &PointerY, &buttons);

              if (dist < 0)
                overfield = PointerY - dbcur->y <= y3
                  && PointerY - dbcur->y >= y2 + scroll->slider_o
                                            + scroll->slider_l;
              else
                overfield = PointerY - dbcur->y >= y2
                  && PointerY - dbcur->y <  y2 + scroll->slider_o;

              if (overfield)
                {
                  if (MouseDelay(WAIT))
                    {
                      oldslider = scroll->slider_o;
                      MoveScrollTop(scroll, dist);
                      if (oldslider != scroll->slider_o)
                        ScrollUpdateAll(ptr->x, ptr->y, scroll, oldslider, FULL,
                                        ptr->active);
                    }
                }
              else
                MouseDelay(RESET);

            } while (buttons & 0x07);
          MouseDelay(RESET);
        }
    }
}



/*
   This function returns TRUE if the mouse is over the given DBSC
*/
static Bool ScInBounds(DBSC *check)
{
  Int16 width, height;
  
  if (!DbscActive(check))
    return FALSE;

  GetScDim(check, &width, &height);

  if (MouseInDbBounds(check->x, check->y,
                      check->x + width, check->y + height))
    return TRUE;
  else
    return FALSE;
}



/*
   This function returns the first DBSC the mouse is over, or NULL
*/
static DBSC *CheckAllScBounds(void)
{
  DBSC *ptr;
  
  for (ptr = dbcur->start; ptr && !ScInBounds(ptr); ptr = ptr->next)
    ;
  
  return ptr;
}



/*
   This function either toggles or sets the state of a check box
*/
static void ToggleCheckBox(DBSC *ptr)
{
  if (ptr->type == DBSC_CHECKBOX)
    {
      if (ptr->i.checkbox.bits == BIT)
        *(ptr->i.checkbox.var) ^= ptr->i.checkbox.value;
      else
        if (*(ptr->i.checkbox.var) == ptr->i.checkbox.value &&
            ptr->i.checkbox.bits == TVAL)
          *(ptr->i.checkbox.var) = ptr->i.checkbox.last;
        else
          {
            ptr->i.checkbox.last = *(ptr->i.checkbox.var);
            *(ptr->i.checkbox.var) = ptr->i.checkbox.value;
          }
    }
  else 
    {
      if (ptr->i.l_checkbox.bits == BIT)
        *(ptr->i.l_checkbox.var) ^= ptr->i.l_checkbox.value;
      else
        if (*(ptr->i.l_checkbox.var) == ptr->i.l_checkbox.value
            && ptr->i.l_checkbox.bits == TVAL)
          *(ptr->i.l_checkbox.var) = ptr->i.l_checkbox.last;
        else
          {
            ptr->i.l_checkbox.last = *(ptr->i.l_checkbox.var);
            *(ptr->i.l_checkbox.var) = ptr->i.l_checkbox.value;
          }
    }
}



/*
   This function checks if a button is depressed over a DBSC
*/
static Bool WaitForButtonDepress(DBSC *ptr, UInt16 buttons)
{
  Bool   oversc, test;
  Int16  backup = 0;
  UInt16 oldx, oldy, oldbuttons;

  oldx = PointerX;
  oldy = PointerY;
  oldbuttons = 0;
  oversc = FALSE;

  if (ptr->type == DBSC_CHECKBOX)
    backup = *(ptr->i.checkbox.var);
  else if (ptr->type == DBSC_L_CHECKBOX)
    backup = *(ptr->i.l_checkbox.var);

  do
    {
      if ((oldx != PointerX || oldy != PointerY ||
           (oldbuttons & 0x07) != (buttons & 0x07)) &&
          (oversc != (test = ScInBounds(ptr))))
        {
          SetFocus(ptr);
          oversc = test;

          HideMousePointer();
          switch (ptr->type)
            {
            case DBSC_BUTTON:
              DrawButton(ptr->x, ptr->y, ptr, oversc ? DOWN : UP);
              break;

            case DBSC_CHECKBOX:
              if (*(ptr->i.checkbox.var) == backup)
                ToggleCheckBox(ptr);
              else
                *(ptr->i.checkbox.var) = backup;
              UpdateDialogBox(ptr->i.checkbox.var);
              break;

            case DBSC_L_CHECKBOX:
              if (*(ptr->i.l_checkbox.var) == backup)
                ToggleCheckBox(ptr);
              else
                *(ptr->i.l_checkbox.var) = backup;
              UpdateDialogBox(ptr->i.l_checkbox.var);
              break;

            case DBSC_INPUTBOX:
              DrawDialogInput(ptr, oversc ? DOWN : UP);
              break;
            }
          ShowMousePointer();
        }

      oldx = PointerX;
      oldy = PointerY;
      oldbuttons = buttons;
      GetMouseCoords(&PointerX, &PointerY, &buttons);
    } while (buttons & 0x07);

  if (ptr->type == DBSC_CHECKBOX)
    *(ptr->i.checkbox.var) = backup;
  else if (ptr->type == DBSC_L_CHECKBOX)
    *(ptr->i.l_checkbox.var) = backup;

  return (oversc ? DOWN : UP);
}



/*!*/
static Int16 GetDialogBoxInput(void)
{
  Int16          key;
  static UInt16  buttons;
  DBSC          *ptr;

  if (UseMouse)
    {
      key = '\0';
      GetMouseCoords(&PointerX, &PointerY, &buttons);

      /* If a mouse button has been hit */
      if (buttons & 0x07)
        {
          ptr = CheckAllScBounds();
          if (ptr != NULL && DbscActive(ptr))
            {
              if (ptr->type == DBSC_SCROLLBOX)
                {
                  HideMousePointer();
                  SetFocus(ptr);
                  ProcessScrollClick(ptr, buttons);
                  UpdateDialogBox(ptr->i.scrollbox.var);
                  ShowMousePointer();
                }
              else if (WaitForButtonDepress(ptr, buttons) == DOWN)
                {
                  HideMousePointer();
                  key = ptr->key;
                  if (ptr->type == DBSC_BUTTON)
                    DrawButton(ptr->x, ptr->y, ptr, UP);
                  ShowMousePointer();
                }
            }
        }
      else
        {
          if (IsKeyPressed())
            key = WaitForKey();
        }
    }
  else
    key = WaitForKey();

  return key;
}



/*
   This function matches a pressed key with a DBSC
*/
static DBSC *MatchKeyToSC(Int16 key)
{
  DBSC *ptr, *last;

  if ((key & 0x00FF) == KEY_TAB)
    {
      ptr = dbcur->focus;
      for (ptr = ptr->next; ptr != dbcur->focus; ptr = ptr->next)
        {
          if (ptr == NULL)
            ptr = dbcur->start;
          if (!(ptr->type == DBSC_BUTTON && ptr->i.button.type == BUTTON_UPDATE)
              && DbscActive(ptr))
            break;
        }
      SetFocus(ptr);
      return NULL;
    }

  if ((key & 0xFF00) == KEY_SHIFTTAB)
    {
      last = ptr = dbcur->focus;
      for (ptr = ptr->next; ptr != dbcur->focus; ptr = ptr->next)
        {
          if (ptr == NULL)
            ptr = dbcur->start;
          if (!(ptr->type == DBSC_BUTTON && ptr->i.button.type == BUTTON_UPDATE)
              && DbscActive(ptr))
            last = ptr;
        }
      SetFocus(last);
      return NULL;
    }

  key &= 0x00FF;

  if (key == KEY_ESC)
    {
      for (ptr = dbcur->start; ptr; ptr = ptr->next)
        if (ptr->type == DBSC_BUTTON && ptr->i.button.type == BUTTON_CANCEL)
          return ptr;

      return NULL;
    }

  if (key == KEY_ENTER || key == ' ')
    return dbcur->focus;

  key = tolower(key);

  for (ptr = dbcur->start; ptr; ptr = ptr->next)
    if (ptr->key == key && DbscActive(ptr))
      return ptr;

  return NULL;
}



/*
   This function moves the reference choice by a specified amount
*/
static void MoveScrollRef(DBSC *ptr, Int16 dist)
{
  Bool ResetTop = FALSE;
  Int16 oldslider, oldtop;
  scrollbox_info *scroll;

  scroll = &(ptr->i.scrollbox);
  oldslider = scroll->slider_o;
  oldtop = scroll->top;
  
  if (scroll->ref <  scroll->top
      || scroll->ref >= scroll->top + scroll->listlen)
    ResetTop = TRUE;
  
  SetScrollRef(scroll, scroll->ref - dist);
  if (ResetTop)
    ResetScrollTop(scroll);
  else if (scroll->ref <  scroll->top
           || scroll->ref >= scroll->top + scroll->height)
    MoveScrollTop(scroll, dist);
  
  if (oldslider == scroll->slider_o && oldtop == scroll->top)
    ScrollUpdateAll(ptr->x, ptr->y, scroll, -1, PARTIAL, ptr->active);
  else
    ScrollUpdateAll(ptr->x, ptr->y, scroll, oldslider, FULL, ptr->active);
}



/*
   This function resets the backup values after a cancel
*/
static void ResetBackupVals(DB *target)
{
  DBSC *ptr;
  
  for (ptr = target->start; ptr; ptr = ptr->next)
    switch (ptr->type)
      {
      case DBSC_CHECKBOX:
        *(ptr->i.checkbox.var) = ptr->i.checkbox.backup;
        break;
      case DBSC_L_CHECKBOX:
        *(ptr->i.l_checkbox.var) = ptr->i.l_checkbox.backup;
        break;
      case DBSC_INPUTBOX:
        *(ptr->i.inputbox.var) = ptr->i.inputbox.backup;
        break;
      case DBSC_SCROLLBOX:
        if (ptr->i.scrollbox.listtype == LT_STR)
          strncpy(ptr->i.scrollbox.str, ptr->i.scrollbox.bkstr, 8);
        else
          *(ptr->i.scrollbox.var) = ptr->i.scrollbox.backup;
        break;
      case DBSC_BUTTON:
        if (ptr->i.button.type == BUTTON_UPDATE
            || ptr->i.button.type == BUTTON_FUNCTION)
          CallButtonFunction(ptr, ptr->i.button.argv, UNDO);
      }
}



/*
   This function repaints differing amounts of information after a return
   from a button call
*/
static void ButtonRepaint(int RepaintVal)
{
  DB *oldcur;

  switch (RepaintVal)
    {
    case DB_ALL: /* Repaint screen and all dialog boxes */
      if (UseMouse)
        HideMousePointer();
      ClearScreen();  /*! temporary */
      if (UseMouse)
        ShowMousePointer();
      oldcur = dbcur;
      for (dbcur = dbtop; dbcur; dbcur = dbcur->next)
        RepaintDialogBox();
      dbcur = oldcur;
      break;

    case DB_CUR: /* Repaint current dialog box */
      RepaintDialogBox();
      break;

    case DB_NONE: /* Update nothing */
      break;

    case DB_UPDATE: /* Update current dialog box */
    default:
      if (UseMouse)
        HideMousePointer();
      UpdateDialogBox(NULL);
      if (UseMouse)
        ShowMousePointer();
      break;
    }
}



/*
   This function handles the input of a value into a DBSC_INPUTBOX
*/
static void InputDialogInt(DBSC *ptr, Int16 *valp, Int16 minv, Int16 maxv)
{
  Int16  key, val;
  Bool neg, ok, firstkey = TRUE;
  char str[8];
  
  neg = (*valp < 0);
  val = neg ? -(*valp) : *valp;
  for (;;)
    {
      if (UseMouse)
        HideMousePointer();

      DrawDialogBox3D(ptr->x, ptr->y, INPUTBOX_W, INPUTBOX_H, DOWN_BLACK);

      ok = (neg ? -val : val) >= minv && (neg ? -val : val) <= maxv;
      if (ok)
        SetColor(WHITE);
      else
        SetColor(LIGHTRED);

      if (neg)
        sprintf(str, "-%d", val);
      else
        sprintf(str, "%d", val);

      DrawScreenText(dbcur->x + ptr->x+1 + (INPUTBOX_W - TEXT_W*strlen(str))/2,
                     dbcur->y + ptr->y+4, str);
      if (UseMouse)
        ShowMousePointer();

      key = WaitForKey();
      if (firstkey && (key & 0x00FF) > ' ')
        {
          val = 0;
          neg = FALSE;
        }
      firstkey = FALSE;
      if (val < 3275 && (key & 0x00FF) >= '0' && (key & 0x00FF) <= '9')
        val = val * 10 + (key & 0x00FF) - '0';
      else if (val > 0 && (key & 0x00FF) == 0x0008)
        val /= 10;
      else if (neg && (key & 0x00FF) == 0x0008)
        neg = FALSE;
      else if ((key & 0x00FF) == '-')
        neg = !neg;
      else if (ok && (key & 0x00FF) == 0x000D)
        break; /* return "val" */
      else if ((key & 0xFF00) == 0x4800 || (key & 0xFF00) == 0x5000
               || (key & 0xFF00) == 0x4B00 || (key & 0xFF00) == 0x4D00
               || (key & 0x00FF) == 0x0009 || (key & 0xFF00) == 0x0F00)
        break; /* return "val", even if not valid */
      else if ((key & 0x00FF) == KEY_ESC)
        return; /* Don't change anything */
      else
        Beep();
    }
  if (neg)
    *valp = -val;
  else
    *valp = val;
  
  SetChangeBits(ptr->i.inputbox.chbit);
}



/*
   This function parses and executes a key press
*/
static void ParseDialogBoxKey(DBSC *ptr)
{
  DB *bkcur;

  SetFocus(ptr);
  switch (ptr->type)
    {
    case DBSC_BUTTON:
      switch (ptr->i.button.type)
        {
        case BUTTON_DIALOG:
          bkcur = dbcur;
          dbcur->next = ptr->i.button.start;
          dbcur = dbcur->next;
          OpenDialogBox(bkcur->x, bkcur->y, bkcur->dx, bkcur->dy);
          dbcur = bkcur;
          dbcur->next = NULL;
          break;
          
        case BUTTON_FUNCTION:
          if (UseMouse)
            HideMousePointer();
          CallButtonFunction(ptr, ptr->i.button.argv, RUN);
          UpdateDialogBox(ptr->i.button.var);
          if (UseMouse)
            ShowMousePointer();
          break;
        }
      ButtonRepaint(ptr->i.button.repaint);
      break;
      
    case DBSC_CHECKBOX:
    case DBSC_L_CHECKBOX:
      if (UseMouse)
        HideMousePointer();
      ToggleCheckBox(ptr);
      if (ptr->type == DBSC_CHECKBOX)
        {
          SetChangeBits(ptr->i.checkbox.chbit);
          UpdateDialogBox(ptr->i.checkbox.var);
        }
      else
        {
          SetChangeBits(ptr->i.l_checkbox.chbit);
          UpdateDialogBox(ptr->i.l_checkbox.var);
        }
      if (UseMouse)
        ShowMousePointer();
      break;

    case DBSC_INPUTBOX:
      InputDialogInt(ptr, ptr->i.inputbox.var,
                     ptr->i.inputbox.min,
                     ptr->i.inputbox.max);
      if (UseMouse)
        HideMousePointer();
      UpdateDialogBox(ptr->i.inputbox.var);
      if (UseMouse)
        ShowMousePointer();
      break;

    case DBSC_SCROLLBOX:
      UpdateScrollboxVar(&(ptr->i.scrollbox));
      if (UseMouse)
        HideMousePointer();
      UpdateDialogBox(ptr->i.scrollbox.var);
      if (UseMouse)
        ShowMousePointer();
      break;
    }
}



/*
   This function processes does the high level input processing
*/
static Bool DialogBoxInputParse(void)  
{
  Bool    done = FALSE, cancel = FALSE;
  Int16   key, dist;
  UInt16  buttons;
  DBSC   *ptr;
  
  /* If you're already clicking the mouse button, wait 'till you stop */
  if (UseMouse)
    {
      do
        GetMouseCoords(&PointerX, &PointerY, &buttons);
      while (buttons);
    }

  while (!done)
    {
      key = GetDialogBoxInput();

      if (key == KEY_UP   || key == KEY_DOWN ||
          key == KEY_P_UP || key == KEY_P_DOWN)
        {
          ptr = dbcur->focus;
          switch (ptr->type)
            {
            case DBSC_INPUTBOX:
              switch (key)
                {
                case KEY_UP:
                  dist = UP;
                  break;
                case KEY_DOWN:
                  dist = DOWN;
                  break;
                case KEY_P_UP:
                  dist = UP * 0x08;
                  break;
                case KEY_P_DOWN:
                  dist = DOWN * 0x08;
                  break;
                default:
                  dist = 0;
                  break;
                }
              *(ptr->i.inputbox.var) = BOUND(ptr->i.inputbox.min,
                                             ptr->i.inputbox.max,
                                             *(ptr->i.inputbox.var) + dist);
              if (UseMouse)
                HideMousePointer();
              UpdateDialogBox(ptr->i.inputbox.var);
              if (UseMouse)
                ShowMousePointer();
              break;

            case DBSC_SCROLLBOX:
              switch (key)
                {
                case KEY_UP:
                  dist = UP;
                  break;
                case KEY_DOWN:
                  dist = DOWN;
                  break;
                case KEY_P_UP:
                  dist = UP * ptr->i.scrollbox.height;
                  break;
                case KEY_P_DOWN:
                  dist = DOWN * ptr->i.scrollbox.height;
                  break;
                default:
                  dist = 0;
                  break;
                }
              MoveScrollRef(ptr, dist);
              break;
            }
        }
      else if (key)
        {
          if ((ptr = MatchKeyToSC(key)) != NULL)
            if (ptr->type == DBSC_BUTTON &&
                (ptr->i.button.type == BUTTON_CANCEL ||
                 ptr->i.button.type == BUTTON_OK))
              {
                done = TRUE;
                if (ptr->i.button.type == BUTTON_CANCEL)
                  cancel = TRUE;
              }
            else
              ParseDialogBoxKey(ptr);
        }
    }

  return cancel;
}



/*
   This function calls the highlevel draw routines and handles backup
   variables.  It returns the cancel value.
*/
Bool OpenDialogBox(Int16 x, Int16 y, Int16 dx, Int16 dy)
{
  Int16 oldx, oldy;
  Bool cancel;

  InitDb(dbcur);

  oldx = dbcur->x;
  oldy = dbcur->y;

  if (dbcur->x < 0)
    dbcur->x = x + (dx - dbcur->dx) / 2;
  else
    dbcur->x = x + dbcur->x;

  if (dbcur->y < 0)
    dbcur->y = y + (dy - dbcur->dy) / 2;
  else
    dbcur->y = y + dbcur->y;

  RepaintDialogBox();
  
  cancel = DialogBoxInputParse();

  if (cancel)
    ResetBackupVals(dbcur);

  dbcur->x = oldx;
  dbcur->y = oldy;

  return cancel;
}



/*
   This function frees the memory in a dialog box
*/
static void FreeDbList(DB *target)
{
  void *tmp;
  DBSC *ptr;
  
  ptr = target->start;
  while (ptr)
    {
      switch (ptr->type)
        {
        case DBSC_BUTTON:
          if (ptr->i.button.type == BUTTON_DIALOG)
            FreeDbList(ptr->i.button.start);
          break;
        case DBSC_SCROLLBOX:
          if (ptr->i.scrollbox.listtype != LT_STR)
            {
              if (ptr->i.scrollbox.listtype != LT_THINGS)
                free(ptr->i.scrollbox.numst);
              free(ptr->i.scrollbox.namest);
            }
          break;
        }
      tmp = ptr->next;
      free(ptr);
      ptr = (DBSC *)tmp;
    }

  free(target);
}



/*
   This function is draws the dialog box and handles the interface
*/
UInt32 DrawDialogBox(Int16 x, Int16 y, Int16 dx, Int16 dy, UInt32 *chvar,
                     Int16 argc, ...)
{
  Bool    cancel;
  va_list ap;

  /* Initialize */
  va_start(ap, argc);
  if (chvar)
    *chvar = 0x00000000L;
  changes = chvar;
  ParseDialogBoxArgs(x, y, dx, dy, chvar, argc, &ap, &dbtop);
  dbcur = dbtop;

  /* Open */
  cancel = OpenDialogBox(0, 0, ScrMaxX, ScrMaxY);

  /* Cleanup */
  FreeDbList(dbcur);
  va_end(ap);

  /* Exit */
  if (cancel)
    return FALSE;
  else
    if (changes == NULL)
      return TRUE;
    else
      return *changes;
}


/*
   This function processes the interface on an already loaded dialog box
*/
UInt32 ProcessDialogBox(DB *top, UInt32 *chvar)
{
  Bool cancel;
  
  /* Initialize */
  if (chvar)
    *chvar = 0x00000000L;
  changes = chvar;
  dbcur = dbtop = top;
  
  /* Open */
  cancel = OpenDialogBox(0, 0, ScrMaxX, ScrMaxY);
  
  /* Cleanup */
  FreeDbList(dbcur);
  va_end(ap);
  
  /* Exit */
  if (cancel)
    return FALSE;
  else
    if (changes == NULL)
      return TRUE;
    else
      return *changes;
}


/*
   This function allocates memory for a dialog box and loads its DBSCs
*/
void ParseDialogBoxArgs(Int16 x, Int16 y, Int16 dx, Int16 dy, UInt32 *chvar,
                        Int16 argc, va_list *ap, DB **dblast)
{
  DB *cur;
  
  *dblast = cur = (DB *) GetMemory(sizeof(DB));
  
  cur->next = NULL;
  cur->start = NULL;
  
  cur->x = x;
  cur->y = y;
  cur->dx = dx;
  cur->dy = dy;

  LoadDbscList(ap, cur, argc, chvar);
}



/*
   This function loads a dialog box into memory from function arguments
*/
DB *LoadDialogBox(Int16 x, Int16 y, Int16 dx, Int16 dy, UInt32 *chvar,
                      Int16 argc, ... )
{
  DB *cur;
  va_list ap;

  va_start(ap, argc);
  ParseDialogBoxArgs(x, y, dx, dy, chvar, argc, &ap, &cur);
  va_end(ap); /* Consider the effects on BUTTON FUNCTIONs that might need
                  ap still open. */ /*F*/
  return cur;
}



/*
   This function returns TRUE if the DBSC is an active button
*/
Bool IsButton(DBSC *ptr)
{
  switch (ptr->type)
    {
    case DBSC_BUTTON:
      return ptr->active;
    default:
      return FALSE;
    }
}



/*
   This function gets the dimensions of a DBSC
*/
void GetScDim(DBSC *ptr, Int16 *width, Int16 *height)
{
  switch (ptr ? ptr->type : DBSC_NONE)
    {
    case DBSC_BUTTON:
      if (ptr->i.button.type == BUTTON_UPDATE)
        *width = *height = -1;
      else
        {
          *width = MAX(BUTTON_W, (strlen(ptr->i.button.name)+1)*TEXT_W - 1);
          *height = BUTTON_H;
        }
      break;
    case DBSC_CHECKBOX:
      *width = *height = CHECKBOX_H;
      break;
    case DBSC_L_CHECKBOX:
      *width = CHECKBOX_H + TEXT_W*(strlen(ptr->i.l_checkbox.string)+1) - 1;
      *height = CHECKBOX_H;
      break;
    case DBSC_INPUTBOX:
      *width = INPUTBOX_W;
      *height = INPUTBOX_H;
      break;
    case DBSC_SCROLLBOX:
      *width = TEXT_W * ptr->i.scrollbox.wid + SCROLL_H_TAB + 1
        + SCROLLBAR_W;
      *height = TEXT_H * ptr->i.scrollbox.height + SCROLL_V_TAB;
      break;
    case DBSC_TEXT:
      if (ptr->i.text.wraplen)
        *width = MIN(strlen(ptr->i.text.string),
                     ptr->i.text.wraplen) * TEXT_W;
      else
        *width = strlen(ptr->i.text.string) * TEXT_W;
      *height = TEXT_H * ptr->i.text.lines - 1;
      break;
    case DBSC_BITMAP:
      switch (ptr->i.bitmap.pictype)
        {
        case BMP_FLAT:
          *width = *height = 0x0044;
          break;
        default:
          *width = 0x0104;
          *height = 0x0084;
        }
      break;
    default:
      *width = *height = -1;
    }
}


/*
   This function gets the variable pointer of a specified DBSC
*/
void *GetVar(DBSC *ptr)
{
  switch (ptr->type)
    {
    case DBSC_BUTTON:
      return ptr->i.button.var;
    case DBSC_CHECKBOX:
      return ptr->i.checkbox.var;
    case DBSC_L_CHECKBOX:
      return ptr->i.l_checkbox.var;
    case DBSC_INPUTBOX:
      return ptr->i.inputbox.var;
    case DBSC_SCROLLBOX:
      if (ptr->i.scrollbox.listtype == LT_STR)
        return ptr->i.scrollbox.str;
      else
        return ptr->i.scrollbox.var;
    case DBSC_TEXT:
    case DBSC_BITMAP:
    default:
      return NULL;
    }
}



/*
   This function returns TRUE if the specified DBSC is active
*/
Bool DbscActive(DBSC *ptr)
{
  switch (ptr->type)
    {
    case DBSC_BUTTON:
    case DBSC_CHECKBOX:
    case DBSC_L_CHECKBOX:
    case DBSC_INPUTBOX:
    case DBSC_SCROLLBOX:
      return ptr->active;
    case DBSC_TEXT:
    case DBSC_BITMAP:
    case DBSC_BOX:
    default:
      return FALSE;
    }
}



/*
   Displays a message on screen and pauses when GDebug is TRUE.
*/
void DisplayDebugMessage(Int16 x, Int16 y, char *str, ...)
{
  char    msg[160];
  va_list args;

  if (!Config.gDebug || x >= 0)
    return;

  x *= -1;

  va_start(args, str);
  vsprintf(msg, str, args);
  va_end(args);

  DrawScreenBox3D(x, y, x + (strlen(msg) + 1) * TEXT_W , y + 10);
  SetColor(WHITE);
  DrawScreenText(x + 2, y + 2, "%s", msg);

  (void) WaitForKey();
}



/*
   View the "playpal" palettes.
*/
/*! Note: this function should moved be in another I_*.C or M_*.C file
    because it doesn't really belong to the set of dialog box routines.
    Maybe a new file E_PALEDI.C should be created?
*/
void ViewPalette(int playpalnum)
{
/* Code disabled until this function is moved somewhere else.  The calls
   to internal graphics functions should also be removed/changed.
*/
#if 0
   Bool done = FALSE;
   int i, j, color;
   int r, g, b;
   UInt8 h, s, v;
   Int16 winx = 0, winy = 0;
   Int16 oldx, oldy;
   UInt16 buttons;

   MDirPtr dir;
   static UInt8 huge *dpal;

   if (playpalnum < 0 && playpalnum > 13)
      playpalnum = 0;

   if (UseMouse)
      HideMousePointer();

   SetDoomPalette(playpalnum);
   dir = FindMasterDir(MasterDir, "PLAYPAL");
   dpal = (UInt8 huge *)GetFarMemory(768 * sizeof(UInt8));
   BasicWadSeek(dir->wadfile, dir->dir.start);
   BasicWadRead(dir->wadfile, dpal, 768L);

   DrawScreenBox3D(winx, winy, winx + 16*7 + 32*16, winy + 16*2 + 8*16);

   for (i = 0; i < 32; i++)
     for (j = 0; j < 8; j++)
      {
        setcolor(TranslateTo16Color(i + 32*j));
        DrawScreenBox(winx + (i+1)*16,      winy + (j+1)*16,
                      winx + (i+1)*16 + 15, winy + (j+1)*16 + 15);
     }

   for (i = 0; i < 0x10; i++)
    {
      color = TranslateToDoomColor(i);
      if (i == BLACK)
        SetColor(WHITE);
      else
        SetColor(BLACK);

      if (i >= 10)
        DrawScreenText(winx + (color % 32)*16 + 17,
                       winy + (color / 32)*16 + 21, "%i", i);
      else
        DrawScreenText(winx + (color % 32)*16 + 21,
                       winy + (color / 32)*16 + 21, "%i", i);
   }

   if (UseMouse)
      ShowMousePointer();

   do
    {
      oldx = oldy = -1;
      GetMouseCoords(&PointerX, &PointerY, &buttons);

      while (buttons)
        {
          if (buttons & 0x02)
            done = TRUE;
          else if ((oldx != (Int16) PointerX || oldy != (Int16) PointerY)
           && (PointerX > winx + 16 && PointerX <= winx + 32*16 + 16
            && PointerY > winy + 16 && PointerY <= winy +  8*16 + 16))
            {
              HideMousePointer();

              color = ((PointerX - winx) / 16 - 1) +
                      ((PointerY - winy) / 16 - 1) * 32;
              setcolor(TranslateTo16Color(color));
              DrawScreenBox(winx + 34*16,      winy + 16,
                            winx + 34*16 + 64, winy + 16 + 64);

              SetColor(LIGHTGRAY);
              DrawScreenBox(winx + 33*16 + 8,  winy + 86,
                            winx + 33*16 + 86, winy + 86 + TEXT_H * 7);
              SetColor(WHITE);
              r = dpal[3*color    ];
              g = dpal[3*color + 1];
              b = dpal[3*color + 2];
              TranslateToHSV(r, g, b, &h, &s, &v);
              DrawScreenText(winx + 33*16 + 8, winy + 86,
                             "%3i (0x%2X)", color, color);
              DrawScreenText(winx + 33*16 + 8, winy + 98,
                             "%3i (0x%2X)", r, r);
              DrawScreenText(winx + 33*16 + 8, winy + 108,
                             "%3i (0x%2X)", g, g);
              DrawScreenText(winx + 33*16 + 8, winy + 118,
                             "%3i (0x%2X)", b, b);
              DrawScreenText(winx + 33*16 + 8, winy + 130,
                             "%3i (0x%2X)", h, h);
              DrawScreenText(winx + 33*16 + 8, winy + 140,
                             "%3i (0x%2X)", s, s);
              DrawScreenText(winx + 33*16 + 8, winy + 150,
                             "%3i (0x%2X)", v, v);

              ShowMousePointer();
            }
          oldx = PointerX;
          oldy = PointerY;
          GetMouseCoords(&PointerX, &PointerY, &buttons);
        }

     if (IsKeyPressed() && (WaitForKey() & 0xFF) == 0x1B) /* Also quit via ESC */
       done = TRUE;
   } while (!done); /* Loop while right button is not pressed */
#endif
}


/* end of file */
