/////////////////////////////////////////////////////////////////////////
// $Id$
/////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2009-2021  Volker Ruppert
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA

// Define BX_PLUGGABLE in files that can be compiled into plugins.  For
// platforms that require a special tag on exported symbols, BX_PLUGGABLE
// is used to know when we are exporting symbols and when we are importing.
#define BX_PLUGGABLE

#include "win32dialog.h"
#include "bochs.h"
#include "siminterface.h"
#include "win32res.h"

#if BX_USE_WIN32CONFIG

#include "scrollwin.h"

#define ID_LABEL 1000
#define ID_PARAM 1500
#define ID_BROWSE 2000
#define ID_UPDOWN 2500

// helper function

char *backslashes(char *s)
{
  if (s != NULL) {
    while (*s != 0) {
       if (*s == '/') *s = '\\';
       s++;
    }
  }
  return s;
}

// dialog item list code

typedef struct _dlg_list_t {
  bx_list_c *list;
  UINT dlg_list_id;
  UINT dlg_base_id;
  struct _dlg_list_t *next;
} dlg_list_t;

HFONT DlgFont;
WNDPROC DefEditWndProc;
UINT  nextDlgID;
dlg_list_t *dlg_lists = NULL;


UINT registerDlgList(UINT lid, bx_list_c *list)
{
  dlg_list_t *dlg_list = new dlg_list_t;
  dlg_list->list = list;
  dlg_list->dlg_list_id = lid;
  dlg_list->dlg_base_id = nextDlgID;
  int items = list->get_size();
  nextDlgID += items;
  dlg_list->next = NULL;

  if (dlg_lists == NULL) {
    dlg_lists = dlg_list;
  } else {
    dlg_list_t *temp = dlg_lists;

    while (temp->next) {
      if (temp->list == dlg_list->list) {
        delete dlg_list;
        return 0;
      }
      temp = temp->next;
    }
    temp->next = dlg_list;
  }
  return dlg_list->dlg_base_id;
}

UINT findDlgListBaseID(bx_list_c *list)
{
  dlg_list_t *dlg_list;

  for (dlg_list = dlg_lists; dlg_list; dlg_list = dlg_list->next) {
    if (list == dlg_list->list) {
      return dlg_list->dlg_base_id;
    }
  }
  return 0;
}

bx_param_c *findParamFromDlgID(UINT cid)
{
  dlg_list_t *dlg_list;
  int i;

  for (dlg_list = dlg_lists; dlg_list; dlg_list = dlg_list->next) {
    if ((cid >= dlg_list->dlg_base_id) && (cid < (dlg_list->dlg_base_id + dlg_list->list->get_size()))) {
      i = cid - dlg_list->dlg_base_id;
      return dlg_list->list->get(i);
    }
    if (cid == dlg_list->dlg_list_id) {
      return dlg_list->list;
    }
  }
  return NULL;
}

UINT findDlgIDFromParam(bx_param_c *param)
{
  dlg_list_t *dlg_list;
  bx_list_c *list;
  UINT cid;
  int i;

  list = (bx_list_c*)param->get_parent();
  for (dlg_list = dlg_lists; dlg_list; dlg_list = dlg_list->next) {
    if (list == dlg_list->list) {
      cid = dlg_list->dlg_base_id;
      for (i = 0; i < list->get_size(); i++) {
        if (param == list->get(i)) {
          return (cid + i);
        }
      }
    }
  }
  // second try: browse all lists (for runtime-only dialogs)
  for (dlg_list = dlg_lists; dlg_list; dlg_list = dlg_list->next) {
    list = dlg_list->list;
    cid = dlg_list->dlg_base_id;
    for (i = 0; i < list->get_size(); i++) {
      if (param == list->get(i)) {
        return (cid + i);
      }
    }
  }
  return 0;
}

void cleanupDlgLists()
{
  dlg_list_t *d, *next;

  if (dlg_lists) {
    d = dlg_lists;
    while (d != NULL) {
      next = d->next;
      delete d;
      d = next;
    }
    dlg_lists = NULL;
  }
}

// tooltips code

HWND hwndTT, tt_hwndDlg;
HHOOK tt_hhk;
const char *tt_text;

BOOL CALLBACK EnumChildProc(HWND hwndCtrl, LPARAM lParam);
LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam);

BOOL CreateParamDlgTooltip(HWND hwndDlg)
{
  InitCommonControls();
  tt_hwndDlg = hwndDlg;
  hwndTT = CreateWindowEx(0, TOOLTIPS_CLASS, (LPSTR) NULL,
    TTS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
    CW_USEDEFAULT, tt_hwndDlg, (HMENU) NULL, NULL, NULL);

  if (hwndTT == NULL)
    return FALSE;

  if (!EnumChildWindows(tt_hwndDlg, (WNDENUMPROC) EnumChildProc, 0))
    return FALSE;

  tt_hhk = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc,
    (HINSTANCE) NULL, GetCurrentThreadId());

  if (tt_hhk == (HHOOK) NULL)
    return FALSE;

  return TRUE;
}

BOOL CALLBACK EnumChildProc(HWND hwndCtrl, LPARAM lParam)
{
  TOOLINFO ti;
  char szClass[64];

  GetClassName(hwndCtrl, szClass, sizeof(szClass));
  if (lstrcmp(szClass, "STATIC")) {
    ti.cbSize = sizeof(TOOLINFO);
    ti.uFlags = TTF_IDISHWND;

    ti.hwnd = tt_hwndDlg;
    ti.uId = (UINT_PTR) hwndCtrl;
    ti.hinst = 0;
    ti.lpszText = LPSTR_TEXTCALLBACK;
    SendMessage(hwndTT, TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &ti);
  }
  return TRUE;
}

LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam)
{
  MSG *lpmsg;

  lpmsg = (MSG *) lParam;
  if (nCode < 0 || !(IsChild(tt_hwndDlg, lpmsg->hwnd)))
    return (CallNextHookEx(tt_hhk, nCode, wParam, lParam));

  switch (lpmsg->message) {
    case WM_MOUSEMOVE:
    case WM_LBUTTONDOWN:
    case WM_LBUTTONUP:
    case WM_RBUTTONDOWN:
    case WM_RBUTTONUP:
      if (hwndTT != NULL) {
        MSG msg;

        msg.lParam = lpmsg->lParam;
        msg.wParam = lpmsg->wParam;
        msg.message = lpmsg->message;
        msg.hwnd = lpmsg->hwnd;
        SendMessage(hwndTT, TTM_RELAYEVENT, 0, (LPARAM) (LPMSG) &msg);
      }
      break;
    default:
      break;
  }
  return (CallNextHookEx(tt_hhk, nCode, wParam, lParam));
}

VOID OnWMNotify(LPARAM lParam)
{
  LPTOOLTIPTEXT lpttt;
  int idCtrl;

  if ((((LPNMHDR) lParam)->code) == TTN_NEEDTEXT) {
    idCtrl = GetDlgCtrlID((HWND) ((LPNMHDR) lParam)->idFrom);
    lpttt = (LPTOOLTIPTEXT) lParam;

    if ((idCtrl >= ID_PARAM) && (idCtrl < ID_BROWSE)) {
      bx_param_c *param = findParamFromDlgID(idCtrl - ID_PARAM);
      if (param != NULL) {
        tt_text = param->get_description();
        if (lstrlen(tt_text) > 0) {
          lpttt->lpszText = (LPSTR)tt_text;
        }
      }
    }
  }
}

// gui functions

int AskFilename(HWND hwnd, bx_param_filename_c *param, char *buffer)
{
  OPENFILENAME ofn;
  int ret;
  DWORD errcode;
  char filename[BX_PATHNAME_LEN];
  const char *title, *ext;
  char errtext[80];

  if (buffer != NULL) {
    lstrcpyn(filename, buffer, BX_PATHNAME_LEN);
  } else {
    param->get(filename, BX_PATHNAME_LEN);
  }
  // common file dialogs don't accept raw device names
  if ((isalpha(filename[0])) && (filename[1] == ':') && (strlen(filename) == 2)) {
    filename[0] = 0;
  }
  title = param->get_label();
  ext = param->get_extension();
  if (!title) title = param->get_name();
  memset(&ofn, 0, sizeof(OPENFILENAME));
  ofn.lStructSize = sizeof(OPENFILENAME);
  ofn.hwndOwner = hwnd;
  ofn.lpstrFile   = filename;
  ofn.nMaxFile    = MAX_PATH;
  ofn.lpstrInitialDir = bx_startup_flags.initial_dir;
  ofn.lpstrTitle = title;
  ofn.Flags = OFN_EXPLORER | OFN_HIDEREADONLY;
  ofn.lpstrDefExt = ext;
  if (!lstrcmp(ext, "bxrc")) {
    ofn.lpstrFilter = "Bochs config files (*.bxrc)\0*.bxrc\0All files (*.*)\0*.*\0";
  } else if (!lstrcmp(ext, "img")) {
    ofn.lpstrFilter = "Disk image files (*.img)\0*.img\0All files (*.*)\0*.*\0";
  } else if (!lstrcmp(ext, "iso")) {
    ofn.lpstrFilter = "CD-ROM image files (*.iso)\0*.iso\0All files (*.*)\0*.*\0";
  } else if (!lstrcmp(ext, "log")) {
    ofn.lpstrFilter = "Log files (*.log)\0*.log\0All files (*.*)\0*.*\0";
  } else if (!lstrcmp(ext, "map")) {
    ofn.lpstrFilter = "Keymap files (*.map)\0*.map\0All files (*.*)\0*.*\0";
  } else if (!lstrcmp(ext, "txt")) {
    ofn.lpstrFilter = "Text files (*.txt)\0*.txt\0All files (*.*)\0*.*\0";
  } else if (!lstrcmp(ext, "bmp")) {
    ofn.lpstrFilter = "Windows bitmap files (*.bmp)\0*.bmp\0All files (*.*)\0*.*\0";
  } else {
    ofn.lpstrFilter = "All files (*.*)\0*.*\0";
  }
  if (param->get_options() & param->SAVE_FILE_DIALOG) {
    ofn.Flags |= OFN_OVERWRITEPROMPT;
    ret = GetSaveFileName(&ofn);
  } else {
    ofn.Flags |= OFN_FILEMUSTEXIST;
    ret = GetOpenFileName(&ofn);
  }
  if (buffer != NULL) {
    lstrcpyn(buffer, filename, BX_PATHNAME_LEN);
  } else {
    param->set(filename);
  }
  if (ret == 0) {
    errcode = CommDlgExtendedError();
    if (errcode == 0) {
      ret = -1;
    } else {
      if (errcode == 0x3002) {
        wsprintf(errtext, "CommDlgExtendedError: invalid filename");
      } else {
        wsprintf(errtext, "CommDlgExtendedError returns 0x%04x", errcode);
      }
      MessageBox(hwnd, errtext, "Error", MB_ICONERROR);
    }
  }
  return ret;
}

void InitDlgFont(void)
{
  LOGFONT LFont;

  LFont.lfHeight         = 8;                // Default logical height of font
  LFont.lfWidth          = 0;                // Default logical average character width
  LFont.lfEscapement     = 0;                // angle of escapement
  LFont.lfOrientation    = 0;                // base-line orientation angle
  LFont.lfWeight         = FW_NORMAL;        // font weight
  LFont.lfItalic         = 0;                // italic attribute flag
  LFont.lfUnderline      = 0;                // underline attribute flag
  LFont.lfStrikeOut      = 0;                // strikeout attribute flag
  LFont.lfCharSet        = DEFAULT_CHARSET;  // character set identifier
  LFont.lfOutPrecision   = OUT_DEFAULT_PRECIS;  // output precision
  LFont.lfClipPrecision  = CLIP_DEFAULT_PRECIS; // clipping precision
  LFont.lfQuality        = DEFAULT_QUALITY;     // output quality
  LFont.lfPitchAndFamily = DEFAULT_PITCH;     // pitch and family
  lstrcpy(LFont.lfFaceName, "Helv");          // pointer to typeface name string
  DlgFont  = CreateFontIndirect(&LFont);
}

LRESULT CALLBACK EditHexWndProc(HWND Window, UINT msg, WPARAM wParam, LPARAM lParam)
{
  if (msg == WM_CHAR) {
    switch (wParam) {
      case 0x08:
        break;
      case 'x':
        break;
      default:
        if ((wParam < '0') || ((wParam > '9') && (wParam < 'A')) ||
            ((wParam > 'F') && (wParam < 'a')) || (wParam > 'f')) {
          MessageBeep(MB_OK);
          return 0;
        }
    }
  }
  return CallWindowProc(DefEditWndProc, Window, msg, wParam, lParam);
}

BOOL IsScrollWindow(HWND hwnd)
{
  char classname[80];

  GetClassName(hwnd, classname, 80);
  return (!lstrcmp(classname, "ScrollWin"));
}

HWND CreateLabel(HWND hDlg, UINT cid, UINT xpos, UINT ypos, UINT width, BOOL hide, const char *text)
{
  HWND Label;
  RECT r;
  int code;

  code = ID_LABEL + cid;
  r.left = xpos;
  r.top = ypos + 2;
  r.right = r.left + width;
  r.bottom = r.top + 16;
  if (IsScrollWindow(hDlg)) {
    MapDialogRect(GetParent(hDlg), &r);
  } else {
    MapDialogRect(hDlg, &r);
  }
  Label = CreateWindow("STATIC", text, WS_CHILD, r.left, r.top, r.right-r.left+1, r.bottom-r.top+1, hDlg, (HMENU)code, NULL, NULL);
  SendMessage(Label, WM_SETFONT, (WPARAM)DlgFont, TRUE);
  ShowWindow(Label, hide ? SW_HIDE : SW_SHOW);
  return Label;
}

HWND CreateGroupbox(HWND hDlg, UINT cid, UINT xpos, UINT ypos, SIZE size, BOOL hide, bx_list_c *list)
{
  HWND Groupbox;
  RECT r;
  int code;
  const char *title = NULL;

  code = ID_PARAM + cid;
  r.left = xpos;
  r.top = ypos;
  r.right = r.left + size.cx;
  r.bottom = r.top + size.cy;
  MapDialogRect(hDlg, &r);
  if (list->get_options() & list->USE_BOX_TITLE) {
    title = list->get_title();
  }
  Groupbox = CreateWindow("BUTTON", title, BS_GROUPBOX | WS_CHILD, r.left, r.top,
                          r.right-r.left+1, r.bottom-r.top+1, hDlg, (HMENU)code, NULL, NULL);
  SendMessage(Groupbox, WM_SETFONT, (WPARAM)DlgFont, TRUE);
  ShowWindow(Groupbox, hide ? SW_HIDE : SW_SHOW);
  return Groupbox;
}

HWND CreateTabControl(HWND hDlg, UINT cid, UINT xpos, UINT ypos, SIZE size, BOOL hide, bx_list_c *list)
{
  HWND TabControl;
  TC_ITEM tie;
  RECT r;
  int code, i;
  bx_param_c *item;

  code = ID_PARAM + cid;
  r.left = xpos;
  r.top = ypos;
  r.right = r.left + size.cx;
  r.bottom = r.top + size.cy;
  MapDialogRect(hDlg, &r);
  TabControl = CreateWindow(WC_TABCONTROL, "", WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE,
                            r.left, r.top, r.right-r.left+1, r.bottom-r.top+1, hDlg, (HMENU)code, NULL, NULL);
  for (i = 0; i < list->get_size(); i++) {
    item = list->get(i);
    if (item->get_type() == BXT_LIST) {
      tie.mask = TCIF_TEXT;
      tie.pszText = ((bx_list_c*)item)->get_title();
      TabCtrl_InsertItem(TabControl, i, &tie);
    }
  }
  TabCtrl_SetCurSel(TabControl, 0);
  SendMessage(TabControl, WM_SETFONT, (WPARAM)DlgFont, TRUE);
  ShowWindow(TabControl, hide ? SW_HIDE : SW_SHOW);
  return TabControl;
}


HWND CreateButton(HWND hDlg, UINT id, UINT xpos, UINT ypos, BOOL hide, const char *text)
{
  HWND Button;
  RECT r;

  r.left = xpos;
  r.top = ypos;
  r.right = r.left + 50;
  r.bottom = r.top + 14;
  if (IsScrollWindow(hDlg)) {
    MapDialogRect(GetParent(hDlg), &r);
  } else {
    MapDialogRect(hDlg, &r);
  }
  Button = CreateWindow("BUTTON", text, WS_CHILD, r.left, r.top, r.right-r.left+1, r.bottom-r.top+1, hDlg, (HMENU)id, NULL, NULL);
  SendMessage(Button, WM_SETFONT, (WPARAM)DlgFont, TRUE);
  ShowWindow(Button, hide ? SW_HIDE : SW_SHOW);
  return Button;
}

HWND CreateBrowseButton(HWND hDlg, UINT cid, UINT xpos, UINT ypos, BOOL hide)
{
  return CreateButton(hDlg, ID_BROWSE + cid, xpos, ypos, hide, "Browse...");
}

HWND CreateCheckbox(HWND hDlg, UINT cid, UINT xpos, UINT ypos, BOOL hide, bx_param_bool_c *bparam)
{
  HWND Checkbox;
  RECT r;
  int code, val;

  code = ID_PARAM + cid;
  r.left = xpos;
  r.top = ypos;
  r.right = r.left + 20;
  r.bottom = r.top + 14;
  if (IsScrollWindow(hDlg)) {
    MapDialogRect(GetParent(hDlg), &r);
  } else {
    MapDialogRect(hDlg, &r);
  }
  Checkbox = CreateWindow("BUTTON", "", BS_AUTOCHECKBOX | WS_CHILD | WS_TABSTOP,
                          r.left, r.top, r.right-r.left+1, r.bottom-r.top+1,
                          hDlg, (HMENU)code, NULL, NULL);
  val = bparam->get();
  SendMessage(Checkbox, BM_SETCHECK, val ? BST_CHECKED : BST_UNCHECKED, 0);
  SendMessage(Checkbox, WM_SETFONT, (WPARAM)DlgFont, TRUE);
  ShowWindow(Checkbox, hide ? SW_HIDE : SW_SHOW);
  return Checkbox;
}

HWND CreateInput(HWND hDlg, UINT cid, UINT xpos, UINT ypos, BOOL hide, bx_param_c *param)
{
  HWND Input, Updown;
  RECT r;
  int code, style;
  bx_param_num_c *nparam = NULL;
  bx_param_string_c *sparam;
  char buffer[512];
  BOOL spinctrl = FALSE, hexedit = FALSE;

  code = ID_PARAM + cid;
  style = WS_CHILD | WS_TABSTOP;
  if (param->get_type() == BXT_PARAM_STRING || param->get_type() == BXT_PARAM_BYTESTRING) {
    sparam = (bx_param_string_c*)param;
    sparam->dump_param(buffer, 512);
    if (param->get_type() != BXT_PARAM_BYTESTRING) {
      style |= ES_AUTOHSCROLL;
    }
  } else {
    nparam = (bx_param_num_c*)param;
    if (nparam->get_base() == BASE_HEX) {
      wsprintf(buffer, "0x%x", nparam->get());
      hexedit = TRUE;
    } else {
      wsprintf(buffer, "%I64d", nparam->get64());
      style |= ES_NUMBER;
    }
    if (nparam->get_options() & nparam->USE_SPIN_CONTROL) {
      spinctrl = TRUE;
    }
  }
  r.left = xpos;
  r.top = ypos;
  r.right = r.left + 100;
  r.bottom = r.top + 14;
  if (IsScrollWindow(hDlg)) {
    MapDialogRect(GetParent(hDlg), &r);
  } else {
    MapDialogRect(hDlg, &r);
  }
  Input = CreateWindowEx(WS_EX_CLIENTEDGE | WS_EX_NOPARENTNOTIFY, "EDIT", buffer,
                         style, r.left, r.top, r.right-r.left+1,
                         r.bottom-r.top+1, hDlg, (HMENU)code, NULL, NULL);
  if (hexedit) {
    DefEditWndProc = (WNDPROC)SetWindowLongPtr(Input, GWLP_WNDPROC, (LONG_PTR)EditHexWndProc);
  }
  if (spinctrl) {
    style = WS_CHILD | WS_BORDER | WS_VISIBLE | UDS_NOTHOUSANDS | UDS_ARROWKEYS |
            UDS_ALIGNRIGHT | UDS_SETBUDDYINT;
    Updown = CreateUpDownControl(style, 0, 0, 0, 0, hDlg, ID_UPDOWN + cid, NULL, Input,
                                 (int)nparam->get_max(), (int)nparam->get_min(), (int)nparam->get());
    ShowWindow(Updown, hide ? SW_HIDE : SW_SHOW);
  }
  SendMessage(Input, WM_SETFONT, (WPARAM)DlgFont, TRUE);
  ShowWindow(Input, hide ? SW_HIDE : SW_SHOW);
  return Input;
}

HWND CreateCombobox(HWND hDlg, UINT cid, UINT xpos, UINT ypos, BOOL hide, bx_param_enum_c *eparam)
{
  HWND Combo;
  RECT r;
  int code, j;
  const char *choice;

  code = ID_PARAM + cid;
  r.left = xpos;
  r.top = ypos;
  r.right = r.left + 100;
  r.bottom = r.top + 14 * ((int)(eparam->get_max() - eparam->get_min()) + 1);
  if (IsScrollWindow(hDlg)) {
    MapDialogRect(GetParent(hDlg), &r);
  } else {
    MapDialogRect(hDlg, &r);
  }
  Combo = CreateWindow("COMBOBOX", "", WS_CHILD | WS_TABSTOP | WS_VSCROLL | CBS_DROPDOWNLIST,
                       r.left, r.top, r.right-r.left+1, r.bottom-r.top+1, hDlg, (HMENU)code, NULL, NULL);
  j = 0;
  while(1) {
    choice = eparam->get_choice(j);
    if (choice == NULL) break;
    SendMessage(Combo, CB_ADDSTRING, 0, (LPARAM)choice);
    j++;
  }
  SendMessage(Combo, CB_SETCURSEL, (WPARAM)(eparam->get()-eparam->get_min()), 0);
  SendMessage(Combo, WM_SETFONT, (WPARAM)DlgFont, TRUE);
  ShowWindow(Combo, hide ? SW_HIDE : SW_SHOW);
  return Combo;
}


HWND CreateScrollWindow(HWND hDlg, int xpos, int ypos, int width, int height)
{
  RECT r;
  HWND scrollwin;

  r.left = xpos;
  r.top = ypos;
  r.right = r.left + width;
  r.bottom = r.top + height;
  MapDialogRect(hDlg, &r);
  scrollwin = CreateWindowEx(WS_EX_CLIENTEDGE | WS_EX_CONTROLPARENT, "ScrollWin",
                             "scrollwin", WS_CHILD | WS_CLIPCHILDREN | WS_TABSTOP | WS_VSCROLL,
                             r.left, r.top, r.right-r.left+1, r.bottom-r.top+1,
                             hDlg, NULL, NULL, NULL);
  ShowWindow(scrollwin, SW_SHOW);
  return scrollwin;
}

void EnableParam(HWND hDlg, UINT cid, bx_param_c *param, BOOL val)
{
  HWND Button, Updown;

  if (cid == 0) {
    cid = findDlgIDFromParam(param);
  }
  if (param->get_type() != BXT_LIST) {
    bx_list_c *list = (bx_list_c*)param->get_parent();
    if (list->get_options() & list->USE_SCROLL_WINDOW) {
      hDlg = FindWindowEx(hDlg, NULL, "ScrollWin", NULL);
    }
    EnableWindow(GetDlgItem(hDlg, ID_LABEL + cid), val);
    EnableWindow(GetDlgItem(hDlg, ID_PARAM + cid), val);
    Button = GetDlgItem(hDlg, ID_BROWSE + cid);
    if (Button != NULL) {
      EnableWindow(Button, val);
    }
    Updown = GetDlgItem(hDlg, ID_UPDOWN + cid);
    if (Updown != NULL) {
      EnableWindow(Updown, val);
    }
  }
}

UINT GetLabelText(bx_param_c *param, bx_list_c *list, char *buffer)
{
  const char *label, *tmpstr;
  char tmpbuf[512];
  int len;

  label = param->get_label();
  if (label == NULL) {
    label = param->get_name();
  }
  if ((list->get_options() & list->SHOW_GROUP_NAME) && (param->get_group() != NULL)) {
    wsprintf(tmpbuf, "%s %s", param->get_group(), label);
  } else {
    lstrcpyn(tmpbuf, label, 512);
  }
  if (buffer != NULL) {
    lstrcpy(buffer, tmpbuf);
  }
  len = lstrlen(tmpbuf);
  tmpstr = strchr(tmpbuf, 10);
  if (tmpstr != NULL) {
    if (lstrlen(tmpstr) > (len / 2)) {
      len = lstrlen(tmpstr) - 1;
    } else {
      len -= lstrlen(tmpstr);
    }
  }
  return (len * 4);
}

SIZE CreateParamList(HWND hDlg, UINT lid, UINT xpos, UINT ypos, BOOL hide, bx_list_c *list)
{
  SIZE size, lsize;
  bx_param_c *param;
  bx_param_string_c *sparam;
  char buffer[512];
  int options;
  UINT cid, i, items, lw, w1, x0, x1, x2, y;
  BOOL ihide;
  HWND scrollwin = NULL, hParent;
  RECT vrect;

  items = list->get_size();
  options = list->get_options();
  cid = registerDlgList(lid, list);
  x0 = xpos + 5;
  size.cx = 195;
  if (options & list->USE_TAB_WINDOW) {
    y = ypos + 15;
    size.cy = 18;
  } else if (options & list->USE_SCROLL_WINDOW) {
    x0 = 5;
    y = 5;
    size.cy = 3;
  } else {
    y = ypos + 10;
    size.cy = 13;
  }
  // find out longest label text
  w1 = 78;
  for (i = 0; i < items; i++) {
    param = list->get(i);
    if (!SIM->get_init_done() || param->get_runtime_param()) {
      if (param->get_type() != BXT_LIST) {
        lw = GetLabelText(param, list, NULL);
        if (lw > w1) {
          w1 = lw;
        }
      }
    }
  }
  // set columns and width
  x1 = x0 + w1 + 2;
  x2 = x1 + 110;
  if (size.cx < (int)(x2 + 5)) {
    size.cx = x2 + 5;
  }
  if ((items > 16) && (options & list->USE_SCROLL_WINDOW)) {
    size.cx += 20;
    scrollwin = CreateScrollWindow(hDlg, xpos, ypos, size.cx, 325);
    vrect.left = vrect.top = 0;
    vrect.right = size.cx;
    vrect.bottom = size.cy;
    hParent = scrollwin;
  } else {
    hParent = hDlg;
  }
  // create controls
  for (i = 0; i < items; i++) {
    param = list->get(i);
    if (!SIM->get_init_done() || param->get_runtime_param()) {
      ihide = hide || ((i != 0) && (options & list->USE_TAB_WINDOW));
      if (param->get_type() == BXT_LIST) {
        lsize = CreateParamList(hDlg, cid, x0 + 4, y + 1, ihide, (bx_list_c*)param);
        if ((lsize.cx + 18) > size.cx) {
          size.cx = lsize.cx + 18;
        }
        if (!(options & list->USE_TAB_WINDOW)) {
          y += (lsize.cy + 6);
          size.cy += (lsize.cy + 6);
        } else {
          if ((lsize.cy + 24) > size.cy) {
            size.cy = lsize.cy + 24;
          }
        }
      } else {
        lw = GetLabelText(param, list, buffer);
        CreateLabel(hParent, cid, x0, y, w1, hide, buffer);
        if (param->get_type() == BXT_PARAM_BOOL) {
          CreateCheckbox(hParent, cid, x1, y, hide, (bx_param_bool_c*)param);
        } else if (param->get_type() == BXT_PARAM_ENUM) {
          CreateCombobox(hParent, cid, x1, y, hide, (bx_param_enum_c*)param);
        } else if (param->get_type() == BXT_PARAM_NUM) {
          CreateInput(hParent, cid, x1, y, hide, param);
        } else if (param->get_type() == BXT_PARAM_STRING || param->get_type() == BXT_PARAM_BYTESTRING) {
          CreateInput(hParent, cid, x1, y, hide, param);
          sparam = (bx_param_string_c*)param;
          if (sparam->get_options() & sparam->IS_FILENAME) {
            CreateBrowseButton(hParent, cid, x2, y, hide);
            if (size.cx < (int)(x2 + 60)) {
              size.cx = x2 + 60;
            }
          }
        }
        if (!param->get_enabled()) {
          EnableParam(hDlg, cid, param, FALSE);
        }
        y += 20;
        if ((scrollwin == NULL) || (i < 16)) {
          size.cy += 20;
        }
        if (scrollwin != NULL) {
          vrect.bottom += 20;
        }
      }
    }
    cid++;
  }
  if (options & list->USE_TAB_WINDOW) {
    CreateTabControl(hDlg, lid, xpos, ypos, size, hide, list);
  } else if (scrollwin != NULL) {
    MapDialogRect(hDlg, &vrect);
    SendMessage(scrollwin, WM_USER, 0x1234, vrect.bottom);
  } else {
    CreateGroupbox(hDlg, lid, xpos, ypos, size, hide, list);
  }
  return size;
}

void SetParamList(HWND hDlg, bx_list_c *list)
{
  bx_param_c *param;
  Bit64s val;
  const char *src;
  char buffer[512];
  UINT cid, i, items, lid;

  lid = findDlgListBaseID(list);
  items = list->get_size();
  if (list->get_options() & list->USE_SCROLL_WINDOW) {
    hDlg = FindWindowEx(hDlg, NULL, "ScrollWin", NULL);
  }
  for (i = 0; i < items; i++) {
    cid = lid + i;
    param = list->get(i);
    if (param->get_enabled() && (!SIM->get_init_done() || (SIM->get_init_done() && param->get_runtime_param()))) {
      if (param->get_type() == BXT_LIST) {
        SetParamList(hDlg, (bx_list_c*)param);
      } else if (param->get_type() == BXT_PARAM_BOOL) {
        val = (SendMessage(GetDlgItem(hDlg, ID_PARAM + cid), BM_GETCHECK, 0, 0) == BST_CHECKED);
        bx_param_bool_c *bparam = (bx_param_bool_c*)param;
        if (val != bparam->get()) {
          bparam->set(val);
        }
      } else if (param->get_type() == BXT_PARAM_ENUM) {
        bx_param_enum_c *eparam = (bx_param_enum_c*)param;
        val = (LRESULT)(SendMessage(GetDlgItem(hDlg, ID_PARAM + cid), CB_GETCURSEL, 0, 0) + eparam->get_min());
        if (val != eparam->get()) {
          eparam->set(val);
        }
      } else {
        if (SendMessage(GetDlgItem(hDlg, ID_PARAM + cid), EM_GETMODIFY, 0, 0)) {
          if (param->get_type() == BXT_PARAM_NUM) {
            bx_param_num_c *nparam = (bx_param_num_c*)param;
            if (nparam->get_base() == BASE_HEX) {
              GetWindowText(GetDlgItem(hDlg, ID_PARAM + cid), buffer, 511);
              sscanf(buffer, "%llx", &val);
            } else {
              GetWindowText(GetDlgItem(hDlg, ID_PARAM + cid), buffer, 511);
              val = strtoll(buffer, NULL, 10);
            }
            nparam->set(val);
          } else if (param->get_type() == BXT_PARAM_STRING) {
            GetWindowText(GetDlgItem(hDlg, ID_PARAM + cid), buffer, 511);
            bx_param_string_c *sparam = (bx_param_string_c*)param;
            sparam->set(buffer);
          } else if (param->get_type() == BXT_PARAM_BYTESTRING) {
            GetWindowText(GetDlgItem(hDlg, ID_PARAM + cid), buffer, 511);
            bx_param_bytestring_c *sparam = (bx_param_bytestring_c*)param;
            src = &buffer[0];
            sparam->parse_param(src);
          }
        }
      }
    }
    if ((i + 1) >= (UINT)list->get_size()) break;
  }
}

void ShowParamList(HWND hDlg, UINT lid, BOOL show, bx_list_c *list)
{
  UINT cid;
  int i;
  HWND Button, Updown;
  BOOL ishow;

  ShowWindow(GetDlgItem(hDlg, ID_PARAM + lid), show ? SW_SHOW : SW_HIDE);
  cid = findDlgListBaseID(list);
  for (i = 0; i < list->get_size(); i++) {
    if (list->get(i)->get_type() == BXT_LIST) {
      ishow = show;
      if (list->get_options() & list->USE_TAB_WINDOW) {
        ishow &= (i == 0);
      }
      ShowParamList(hDlg, cid + i, ishow, (bx_list_c*)list->get(i));
    } else {
      ShowWindow(GetDlgItem(hDlg, ID_LABEL + cid + i), show ? SW_SHOW : SW_HIDE);
      ShowWindow(GetDlgItem(hDlg, ID_PARAM + cid + i), show ? SW_SHOW : SW_HIDE);
      Button = GetDlgItem(hDlg, ID_BROWSE + cid + i);
      if (Button != NULL) {
        ShowWindow(Button, show ? SW_SHOW : SW_HIDE);
      }
      Updown = GetDlgItem(hDlg, ID_UPDOWN + cid + i);
      if (Updown != NULL) {
        ShowWindow(Updown, show ? SW_SHOW : SW_HIDE);
      }
    }
  }
}

void ProcessDependentList(HWND hDlg, bx_param_c *param, BOOL enabled)
{
  UINT cid;
  bx_list_c *deplist;
  bx_param_c *dparam;
  bx_param_string_c *sparam;
  param_enable_handler enable_handler;
  bx_param_enum_c *eparam;
  Bit64s value;
  Bit64u enable_bitmap, mask;
  char buffer[BX_PATHNAME_LEN];
  int i;
  BOOL en;

  deplist = param->get_dependent_list();
  if (deplist != NULL) {
    cid = findDlgIDFromParam(param);
    if (param->get_type() == BXT_PARAM_ENUM) {
      eparam = (bx_param_enum_c*)param;
      value = SendMessage(GetDlgItem(hDlg, ID_PARAM + cid), CB_GETCURSEL, 0, 0);
      enable_bitmap = eparam->get_dependent_bitmap(value + eparam->get_min());
      mask = 0x1;
      for (i = 0; i < deplist->get_size(); i++) {
        dparam = deplist->get(i);
        if (dparam != param) {
          en = (enable_bitmap & mask) && enabled;
          if (dparam->get_type() == BXT_PARAM_STRING) {
            sparam = (bx_param_string_c*)dparam;
            enable_handler = sparam->get_enable_handler();
            if (enable_handler) {
              en = enable_handler(sparam, en);
            }
          }
          cid = findDlgIDFromParam(dparam);
          if (cid != 0) {
            if (en != IsWindowEnabled(GetDlgItem(hDlg, ID_PARAM + cid))) {
              ProcessDependentList(hDlg, dparam, en);
              EnableParam(hDlg, 0, dparam, en);
            }
          }
        }
        mask <<= 1;
      }
    } else if ((param->get_type() == BXT_PARAM_BOOL) ||
               (param->get_type() == BXT_PARAM_NUM) ||
               (param->get_type() == BXT_PARAM_STRING) ||
               (param->get_type() == BXT_PARAM_BYTESTRING)) {
      if (param->get_type() == BXT_PARAM_BOOL) {
        value = SendMessage(GetDlgItem(hDlg, ID_PARAM + cid), BM_GETCHECK, 0, 0);
      } else if (param->get_type() == BXT_PARAM_NUM) {
        value = GetDlgItemInt(hDlg, ID_PARAM + cid, NULL, FALSE);
      } else {
        GetWindowText(GetDlgItem(hDlg, ID_PARAM + cid), buffer, BX_PATHNAME_LEN);
        value = (lstrlen(buffer) > 0) && (strcmp(buffer, "none"));
      }
      for (i = 0; i < deplist->get_size(); i++) {
        dparam = deplist->get(i);
        if (dparam != param) {
          en = (value && enabled);
          cid = findDlgIDFromParam(dparam);
          if (cid != 0) {
            if (en != IsWindowEnabled(GetDlgItem(hDlg, ID_PARAM + cid))) {
              ProcessDependentList(hDlg, dparam, en);
              EnableParam(hDlg, 0, dparam, en);
            }
          }
        }
      }
    }
  }
}

void SetDefaultButtons(HWND Window, UINT xpos, UINT ypos)
{
  RECT r;

  r.left = xpos;
  r.top = ypos;
  r.right = r.left + 50;
  r.bottom = r.top + 14;
  MapDialogRect(Window, &r);
  MoveWindow(GetDlgItem(Window, IDOK), r.left, r.top, r.right-r.left+1, r.bottom-r.top+1, FALSE);
  r.left = xpos + 60;
  r.top = ypos;
  r.right = r.left + 50;
  r.bottom = r.top + 14;
  MapDialogRect(Window, &r);
  MoveWindow(GetDlgItem(Window, IDCANCEL), r.left, r.top, r.right-r.left+1, r.bottom-r.top+1, FALSE);
}

void HandleOkCancel(HWND hDlg, UINT_PTR id, bx_list_c *list)
{
  if (id == IDOK) {
    SetParamList(hDlg, list);
  }
  cleanupDlgLists();
  DestroyWindow(hwndTT);
  EndDialog(hDlg, (id == IDOK) ? 1 : -1);
}

#define BXPD_SET_PATH    1
#define BXPD_UPDATE_DEPS 2

UINT HandleChildWindowEvents(HWND hDlg, UINT_PTR id, UINT_PTR nc)
{
  UINT i, ret = 0;
  char fname[BX_PATHNAME_LEN];

  if ((id >= ID_BROWSE) && (id < (ID_BROWSE + nextDlgID))) {
    i = (UINT)(id - ID_BROWSE);
    bx_param_string_c *sparam = (bx_param_string_c *)findParamFromDlgID(i);
    if (sparam != NULL) {
      GetDlgItemText(hDlg, ID_PARAM + i, fname, BX_PATHNAME_LEN);
      if (AskFilename(hDlg, (bx_param_filename_c *)sparam, fname) > 0) {
        SetWindowText(GetDlgItem(hDlg, ID_PARAM + i), fname);
        SendMessage(GetDlgItem(hDlg, ID_PARAM + i), EM_SETMODIFY, 1, 0);
        SetFocus(GetDlgItem(hDlg, ID_PARAM + i));
        ret = BXPD_SET_PATH;
      }
    }
  } else if ((id >= ID_PARAM) && (id < (ID_PARAM + nextDlgID))) {
    if ((nc == EN_CHANGE) || ((nc == CBN_SELCHANGE)) || (nc == BN_CLICKED)) {
      i = (UINT)(id - ID_PARAM);
      bx_param_c *param = findParamFromDlgID(i);
      if (param != NULL) {
        ProcessDependentList(hDlg, param, TRUE);
        ret = BXPD_UPDATE_DEPS;
      }
    }
  }
  return ret;
}

static INT_PTR CALLBACK ParamDlgProc(HWND Window, UINT AMessage, WPARAM wParam, LPARAM lParam)
{
  static bx_list_c *list = NULL;
  bx_list_c *tmplist;
  int cid;
  UINT_PTR code, id, noticode;
  UINT i, j, k;
  RECT r, r2;
  SIZE size;
  NMHDR nmhdr;

  switch (AMessage) {
    case WM_CLOSE:
      HandleOkCancel(Window, IDCANCEL, list);
      break;
    case WM_INITDIALOG:
      list = (bx_list_c*)SIM->get_param((const char*)lParam);
      SetWindowText(Window, list->get_title());
      nextDlgID = 1;
      size = CreateParamList(Window, 0, 6, 6, FALSE, list);
      SetDefaultButtons(Window, size.cx / 2 - 50, size.cy + 12);
      GetWindowRect(Window, &r2);
      r.left = 0;
      r.top = 0;
      r.right = size.cx + 18;
      r.bottom = size.cy + 52;
      MapDialogRect(Window, &r);
      MoveWindow(Window, r2.left, r2.top, r.right, r.bottom, TRUE);
      CreateParamDlgTooltip(Window);
      return TRUE;
    case WM_COMMAND:
      code = LOWORD(wParam);
      noticode = HIWORD(wParam);
      switch (code) {
        case IDOK:
        case IDCANCEL:
          HandleOkCancel(Window, code, list);
          break;
        default:
          HandleChildWindowEvents(Window, code, noticode);
      }
      break;
    case WM_NOTIFY:
      nmhdr = *(LPNMHDR)lParam;
      code = nmhdr.code;
      id = nmhdr.idFrom;
      if (code == (UINT)TCN_SELCHANGE) {
        j = (UINT)(id - ID_PARAM);
        tmplist = (bx_list_c *)findParamFromDlgID(j);
        if (tmplist != NULL) {
          k = (UINT)TabCtrl_GetCurSel(GetDlgItem(Window, id));
          cid = findDlgListBaseID(tmplist);
          for (i = 0; i < (UINT)tmplist->get_size(); i++) {
            ShowParamList(Window, cid + i, (i == k), (bx_list_c*)tmplist->get(i));
          }
        }
      } else if (code == (UINT)UDN_DELTAPOS) {
        if (id >= ID_UPDOWN) {
          PostMessage(GetDlgItem(Window, ID_PARAM + (id - ID_UPDOWN)), EM_SETMODIFY, TRUE, 0);
        }
      } else {
        OnWMNotify(lParam);
      }
      break;
    case WM_CTLCOLOREDIT:
      SetTextColor((HDC)wParam, GetSysColor(COLOR_WINDOWTEXT));
      return (INT_PTR)GetSysColorBrush(COLOR_WINDOW);
      break;
  }
  return 0;
}

BOOL CreateImage(HWND hDlg, int sectors, const char *filename)
{
  if (sectors < 1) {
    MessageBox(hDlg, "The disk size is invalid.", "Invalid size", MB_ICONERROR);
    return FALSE;
  }
  if (lstrlen(filename) < 1) {
    MessageBox(hDlg, "You must type a file name for the new disk image.", "Bad filename", MB_ICONERROR);
    return FALSE;
  }
  int ret = SIM->create_disk_image (filename, sectors, 0);
  if (ret == -1) {  // already exists
    int answer = MessageBox(hDlg, "File exists.  Do you want to overwrite it?",
                            "File exists", MB_YESNO);
    if (answer == IDYES)
      ret = SIM->create_disk_image (filename, sectors, 1);
    else
      return FALSE;
  }
  if (ret == -2) {
    MessageBox(hDlg, "I could not create the disk image. Check for permission problems or available disk space.", "Failed", MB_ICONERROR);
    return FALSE;
  }
  return TRUE;
}

static INT_PTR CALLBACK FloppyParamDlgProc(HWND Window, UINT AMessage, WPARAM wParam, LPARAM lParam)
{
  static bx_list_c *list = NULL;
  static UINT path_id, type_id, status_id;
  bx_param_enum_c *status;
  UINT_PTR code, noticode;
  UINT ret;
  int i;
  RECT r, r2;
  SIZE size;
  char mesg[MAX_PATH];
  char path[MAX_PATH];

  switch (AMessage) {
    case WM_INITDIALOG:
      list = (bx_list_c*)SIM->get_param((const char*)lParam);
      SetWindowText(Window, list->get_title());
      nextDlgID = 1;
      size = CreateParamList(Window, 0, 6, 6, FALSE, list);
      CreateLabel(Window, 99, 60, size.cy + 12, 160, FALSE,
                  "Clicking OK signals a media change for this drive.");
      CreateButton(Window, IDCREATE, size.cx / 2 - 85, size.cy + 30, FALSE, "Create Image");
      SetDefaultButtons(Window, size.cx / 2 - 25, size.cy + 30);
      GetWindowRect(Window, &r2);
      r.left = 0;
      r.top = 0;
      r.right = size.cx + 18;
      r.bottom = size.cy + 70;
      MapDialogRect(Window, &r);
      MoveWindow(Window, r2.left, r2.top, r.right, r.bottom, TRUE);
      CreateParamDlgTooltip(Window);
      path_id = findDlgIDFromParam(list->get_by_name("path")) + ID_PARAM;
      type_id = findDlgIDFromParam(list->get_by_name("type")) + ID_PARAM;
      status_id = findDlgIDFromParam(list->get_by_name("status")) + ID_PARAM;
      return TRUE;
    case WM_COMMAND:
      code = LOWORD(wParam);
      noticode = HIWORD(wParam);
      switch (code) {
        case IDOK:
          // force a media change
          status = (bx_param_enum_c*)list->get_by_name("status");
          status->set(BX_EJECTED);
        case IDCANCEL:
          HandleOkCancel(Window, code, list);
          break;
        case IDCREATE:
          GetDlgItemText(Window, path_id, path, MAX_PATH);
          backslashes(path);
          i = SendMessage(GetDlgItem(Window, type_id), CB_GETCURSEL, 0, 0);
          if (CreateImage(Window, floppy_type_n_sectors[i], path)) {
            wsprintf(mesg, "Created a %s disk image called %s", floppy_type_names[i], path);
            MessageBox(Window, mesg, "Image created", MB_OK);
          }
          return TRUE;
          break;
        default:
          ret = HandleChildWindowEvents(Window, code, noticode);
          if (ret == BXPD_SET_PATH) {
            SendMessage(GetDlgItem(Window, status_id), BM_SETCHECK, BST_CHECKED, 0);
            SendMessage(GetDlgItem(Window, type_id), CB_SELECTSTRING, (WPARAM)-1, (LPARAM)"auto");
            EnableWindow(GetDlgItem(Window, IDCREATE), FALSE);
          } else if (ret == BXPD_UPDATE_DEPS) {
            if (code == path_id) {
              EnableWindow(GetDlgItem(Window, IDCREATE), IsWindowEnabled(GetDlgItem(Window, type_id)));
            } else if (code == type_id) {
              i = SendMessage(GetDlgItem(Window, type_id), CB_GETCURSEL, 0, 0);
              EnableWindow(GetDlgItem(Window, IDCREATE), (i != 0));
            }
          }
      }
      break;
    case WM_CLOSE:
    case WM_NOTIFY:
    case WM_CTLCOLOREDIT:
      return ParamDlgProc(Window, AMessage, wParam, lParam);
      break;
  }
  return 0;
}

INT_PTR win32ParamDialog(HWND parent, const char *menu)
{
  InitDlgFont();
  RegisterScrollWindow(NULL);
  INT_PTR ret = DialogBoxParam(NULL, MAKEINTRESOURCE(PARAM_DLG), parent, (DLGPROC)ParamDlgProc, (LPARAM)menu);
  DeleteObject(DlgFont);
  return ret;
}

INT_PTR win32FloppyParamDialog(HWND parent, const char *menu)
{
  InitDlgFont();
  RegisterScrollWindow(NULL);
  INT_PTR ret = DialogBoxParam(NULL, MAKEINTRESOURCE(PARAM_DLG), parent, (DLGPROC)FloppyParamDlgProc, (LPARAM)menu);
  DeleteObject(DlgFont);
  return ret;
}

#endif // BX_USE_WIN32CONFIG
