/* Cockos SWELL (Simple/Small Win32 Emulation Layer for Linux/OSX)
   Copyright (C) 2006 and later, Cockos, Inc.

    This software is provided 'as-is', without any express or implied
    warranty.  In no event will the authors be held liable for any damages
    arising from the use of this software.

    Permission is granted to anyone to use this software for any purpose,
    including commercial applications, and to alter it and redistribute it
    freely, subject to the following restrictions:

    1. The origin of this software must not be misrepresented; you must not
       claim that you wrote the original software. If you use this software
       in a product, an acknowledgment in the product documentation would be
       appreciated but is not required.
    2. Altered source versions must be plainly marked as such, and must not be
       misrepresented as being the original software.
    3. This notice may not be removed or altered from any source distribution.
  

    This file provides basic APIs for browsing for files, directories, and messageboxes.

    These APIs don't all match the Windows equivelents, but are close enough to make it not too much trouble.

  */


#ifndef SWELL_PROVIDED_BY_APP

#include "swell.h"
#include "swell-internal.h"
#include "swell-dlggen.h"

#include "../wdlcstring.h"
#include "../assocarray.h"
#include "../ptrlist.h"
#include <dirent.h>
#include <time.h>

#include "../lineparse.h"
#define WDL_HASSTRINGS_EXPORT static
#include "../has_strings.h"


#ifndef SWELL_BROWSE_RECENT_SIZE
#define SWELL_BROWSE_RECENT_SIZE 12
#endif
static WDL_PtrList<char> s_browse_rcu, s_browse_rcu_tmp;
static int recent_size() { return s_browse_rcu.GetSize() + s_browse_rcu_tmp.GetSize(); }

static void recent_addtocb(HWND hwnd)
{
  int x;
  for (x=0;x<s_browse_rcu_tmp.GetSize();x++) 
    SendMessage(hwnd,CB_ADDSTRING,0,(LPARAM)s_browse_rcu_tmp.Get(x));
  for (x=0;x<s_browse_rcu.GetSize();x++) 
    SendMessage(hwnd,CB_ADDSTRING,0,(LPARAM)s_browse_rcu.Get(x));
}
static void recent_write(const char *path)
{
  if (!path || !path[0]) return;
  int x;
  for (x=0;x<s_browse_rcu.GetSize() && strcmp(s_browse_rcu.Get(x),path); x++);
  if (x<s_browse_rcu.GetSize())
  {
    if (!x) return; // already at top of flist

    char *ps = s_browse_rcu.Get(x);
    s_browse_rcu.Delete(x,false);
    s_browse_rcu.Insert(0,ps);
  }
  else
  {
    if (s_browse_rcu.GetSize()>=SWELL_BROWSE_RECENT_SIZE)
      s_browse_rcu.Delete(SWELL_BROWSE_RECENT_SIZE,true,free);
    s_browse_rcu.Insert(0,strdup(path));
  }

  for (x=0;x<=s_browse_rcu.GetSize();x++)
  {
    char tmp[64];
    snprintf(tmp,sizeof(tmp),"path%d",x);
    WritePrivateProfileString(".swell_recent_path",tmp, s_browse_rcu.Get(x),"");
  }
}
static void recent_read()
{
  s_browse_rcu_tmp.Empty(true,free);
  if (s_browse_rcu.GetSize()) return;
  int x;
  for (x=0;x<SWELL_BROWSE_RECENT_SIZE;x++)
  {
    char tmp[64], path[2048];
    snprintf(tmp,sizeof(tmp),"path%d",x);
    GetPrivateProfileString(".swell_recent_path",tmp, "", path,sizeof(path),"");
    if (!path[0]) break;
    s_browse_rcu.Add(strdup(path));
  }
}
static void recent_add_tmp(const char *path)
{
  if (!path || !*path) return;

  int x;
  for (x=0;x<s_browse_rcu_tmp.GetSize();x++) 
    if (!strcmp(s_browse_rcu_tmp.Get(x),path)) return;
  for (x=0;x<s_browse_rcu.GetSize();x++) 
    if (!strcmp(s_browse_rcu.Get(x),path)) return;

  s_browse_rcu_tmp.Add(strdup(path));
}

static const char *BFSF_Templ_dlgid;
static DLGPROC BFSF_Templ_dlgproc;
static struct SWELL_DialogResourceIndex *BFSF_Templ_reshead;
void BrowseFile_SetTemplate(const char *dlgid, DLGPROC dlgProc, struct SWELL_DialogResourceIndex *reshead)
{
  BFSF_Templ_reshead=reshead;
  BFSF_Templ_dlgid=dlgid;
  BFSF_Templ_dlgproc=dlgProc;
}

extern struct stat stat_chk;
typedef char assert_failed_stat_not_64[sizeof(stat_chk.st_size)!=8 ? -1 : 1];

class BrowseFile_State
{
public:
  static char s_sortrev;
  enum modeEnum { SAVE=0,OPEN, OPENMULTI, OPENDIR };

  BrowseFile_State(const char *_cap, const char *_idir, const char *_ifile, const char *_el, modeEnum _mode, char *_fnout, int _fnout_sz) :
    caption(_cap), initialdir(_idir), initialfile(_ifile), extlist(_el), mode(_mode), 
    sortcol(0), sortrev(0),
    fnout(_fnout), fnout_sz(_fnout_sz), viewlist_store(16384), viewlist(4096), show_hidden(false)
  {
  }
  ~BrowseFile_State()
  {
    viewlist_clear();
  }

  const char *caption;
  const char *initialdir;
  const char *initialfile;
  const char *extlist;

  modeEnum mode;
  char sortcol, sortrev;

  char *fnout; // if NULL this will be malloced by the window
  int fnout_sz;

  struct rec {
    WDL_INT64 size;
    time_t date;
    char *name;
    int type; // 1 = directory, 2 = file

    void format_date(char *buf, int bufsz)
    {
      *buf=0;
      const WDL_INT64 date64 = (WDL_INT64) date;
      if (date64 > 0 && date64 < WDL_INT64_CONST(0x793406fff))
      {
        struct tm *a=localtime(&date);
        if (a) strftime(buf,bufsz,"%c",a);
      }
    }

    void format_size(char *buf, int bufsz)
    {
      if (type == 1)
      {
        lstrcpyn_safe(buf,"<DIR>",bufsz);
      }
      else
      {
        static const char *tab[]={ "bytes","KB","MB","GB" };
        int lf=0;
        WDL_INT64 s=size;
        if (s<1024)
        {
          snprintf(buf,bufsz,"%d %s",(int)s,tab[0]);
        }
        else
        {
          int w = 1;
          do {  w++; lf = (int)(s&1023); s/=1024; } while (s >= 1024 && w<4);
          snprintf(buf,bufsz,"%d.%d %s",(int)s,(int)((lf*10.0)/1024.0+0.5),tab[w-1]);
        }
      }
    }

    char *format_all(char *buf, int bufsz)
    {
      char dstr[128],sstr[128];
      format_date(dstr,sizeof(dstr));
      format_size(sstr,sizeof(sstr));
      snprintf(buf,bufsz,"%s\t%s\t%s",WDL_get_filepart(name),dstr,sstr);
      return buf;
    }

  };

  void viewlist_clear()
  {
    rec *l = viewlist_store.Get();
    const int n = viewlist_store.GetSize();
    for (int x = 0; x < n; x ++) free(l[x].name);
    viewlist_store.Resize(0);
    viewlist.Empty();
  }
  WDL_TypedBuf<rec> viewlist_store;
  WDL_PtrList<rec> viewlist;

  bool show_hidden;

  void viewlist_sort(const char *filter)
  {
    if (filter)
    {
      viewlist.Empty();
      LineParser lp;
      const bool no_filter = !*filter || !WDL_makeSearchFilter(filter,&lp);
      for (int x=0;x<viewlist_store.GetSize();x++) 
      {
        rec *r = viewlist_store.Get()+x;
        char tmp[512];
        if (no_filter || WDL_hasStrings(r->format_all(tmp,sizeof(tmp)),&lp))
          viewlist.Add(r);
      }
    }
    s_sortrev = sortrev;
    if (viewlist.GetSize()>1)
      qsort(viewlist.GetList(), viewlist.GetSize(),sizeof(rec*), 
        sortcol == 1 ? sortFunc_sz :
        sortcol == 2 ? sortFunc_date : 
        sortFunc_fn);
  }
  static int sortFunc_fn(const void *_a, const void *_b)
  {
    const rec *a = *(const rec * const*)_a, *b = *(const rec * const*)_b;
    int d = a->type - b->type;
    if (d) return d;
    d = stricmp(a->name,b->name);
    return s_sortrev ? -d : d;
  }
  static int sortFunc_date(const void *_a, const void *_b)
  {
    const rec *a = *(const rec * const*)_a, *b = *(const rec * const*)_b;
    if (a->date != b->date) return s_sortrev ? (a->date>b->date?-1:1) : (a->date>b->date?1:-1);
    return stricmp(a->name,b->name);
  }
  static int sortFunc_sz(const void *_a, const void *_b)
  {
    const rec *a = *(const rec * const *)_a, *b = *(const rec * const *)_b;
    int d = a->type - b->type;
    if (d) return s_sortrev ? -d : d;
    if (a->size != b->size) return s_sortrev ? (a->size>b->size?-1:1) : (a->size>b->size?1:-1);
    return stricmp(a->name,b->name);
  }


  void scan_path(const char *path, const char *filterlist, bool dir_only)
  {
    viewlist_clear();
    DIR *dir = opendir(path);
    if (!dir) return;
    char tmp[2048];
    struct dirent *ent;
    while (NULL != (ent = readdir(dir)))
    {
      if (ent->d_name[0] == '.') 
      {
        if (ent->d_name[1] == 0 || ent->d_name[1] == '.' || !show_hidden) continue;
      }
      bool is_dir = (ent->d_type == DT_DIR);
      if (ent->d_type == DT_UNKNOWN)
      {
        snprintf(tmp,sizeof(tmp),"%s/%s",path,ent->d_name);
        DIR *d = opendir(tmp);
        if (d) { is_dir = true; closedir(d); }
      }
      else if (ent->d_type == DT_LNK)
      {
        snprintf(tmp,sizeof(tmp),"%s/%s",path,ent->d_name);
        char *rp = realpath(tmp,NULL);
        if (rp)
        {
          DIR *d = opendir(rp);
          if (d) { is_dir = true; closedir(d); }
          free(rp);
        }
      }
      if (!dir_only || is_dir)
      {
        if (filterlist && *filterlist && !is_dir)
        {
          const char *f = filterlist;
          while (*f)
          {
            const char *nf = f;
            while (*nf && *nf != ';') nf++;
            if (*f != '*')
            {
              const char *nw = f;
              while (nw < nf && *nw != '*') nw++;

              if ((nw!=nf || f+strlen(ent->d_name) == nw) && !strncasecmp(ent->d_name,f,nw-f)) 
              {
                // matched leading text
                if (nw == nf) break;
                f = nw;
              }
            }

            if (*f == '*')
            {
              f++;
              if (!*f || *f == ';' || (*f == '.' && f[1] == '*')) break;
              size_t l = strlen(ent->d_name);
              if (f+l > nf && !strncasecmp(ent->d_name + l - (nf-f), f,nf-f)) break;
            }
            f = nf;
            while (*f == ';') f++;
          }
          if (!*f) continue; // did not match
        }
        snprintf(tmp,sizeof(tmp),"%s/%s",path,ent->d_name);

        struct stat st={0,};
        stat(tmp,&st);

        rec r = { st.st_size, st.st_mtime, strdup(ent->d_name), is_dir?1:2 } ;
        viewlist_store.Add(&r,1);
      }
    }
    // sort viewlist

    closedir(dir);
  }
};

char BrowseFile_State::s_sortrev;

static void preprocess_user_path(char *buf, int bufsz)
{
  if (buf[0] == '~')
  {
    char *tmp = strdup(buf+1);
    if (buf[1] == '/' || !buf[1])
    {
      char *p = getenv("HOME");
      if (p && *p) snprintf(buf,bufsz,"%s%s",p,tmp);
    }
    else
    {
      snprintf(buf,bufsz,"/home/%s",tmp); // if someone wants to write code to lookup homedirs, please, go right ahead!
    }
    free(tmp);
  }
}

static LRESULT WINAPI swellFileSelectProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  enum { IDC_EDIT=0x100, IDC_LABEL, IDC_CHILD, IDC_DIR, IDC_LIST, IDC_EXT, IDC_PARENTBUTTON, IDC_FILTER, ID_SHOW_HIDDEN };
  enum { WM_UPD=WM_USER+100 };
  const int maxPathLen = 2048;
  const char *multiple_files = "(multiple files)";
  switch (uMsg)
  {
    case WM_CREATE:
      if (lParam)  // swell-specific
      {
        SetWindowLong(hwnd,GWL_WNDPROC,(LPARAM)SwellDialogDefaultWindowProc);
        SetWindowLong(hwnd,DWL_DLGPROC,(LPARAM)swellFileSelectProc);

        SetWindowLong(hwnd,GWL_STYLE, GetWindowLong(hwnd,GWL_STYLE)|WS_THICKFRAME);

        SetWindowLongPtr(hwnd,GWLP_USERDATA,lParam);
        BrowseFile_State *parms = (BrowseFile_State *)lParam;

        char tmp[1024];
        recent_read();

        recent_add_tmp(parms->initialdir);

        if (parms->initialfile && *parms->initialfile != '.')
        {
          lstrcpyn_safe(tmp,parms->initialfile,sizeof(tmp));
          WDL_remove_filepart(tmp);
          recent_add_tmp(tmp);
        }

        if (parms->caption) SetWindowText(hwnd,parms->caption);

        SWELL_MakeSetCurParms(1,1,0,0,hwnd,false,false);

        HWND edit = SWELL_MakeEditField(IDC_EDIT, 0,0,0,0,  0);
        SWELL_MakeButton(0,
              parms->mode == BrowseFile_State::OPENDIR ? "Choose directory" :
              parms->mode == BrowseFile_State::SAVE ? "Save" : "Open",
              IDOK,0,0,0,0, 0);

        SWELL_MakeButton(0, "Cancel", IDCANCEL,0,0,0,0, 0);
        HWND dir = SWELL_MakeCombo(IDC_DIR, 0,0,0,0, 0);
        SWELL_MakeButton(0, "..", IDC_PARENTBUTTON, 0,0,0,0, 0);
        SWELL_MakeEditField(IDC_FILTER, 0,0,0,0,  0);

        const char *ent = parms->mode == BrowseFile_State::OPENDIR ? "dir_browser" : "file_browser";
        GetPrivateProfileString(".swell",ent,"", tmp,sizeof(tmp),"");
        int x=0,y=0,w=0,h=0, c1=0,c2=0,c3=0,extraflag=0;
        int flag = SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER;
        if (tmp[0] && 
            sscanf(tmp,"%d %d %d %d %d %d %d %d",&x,&y,&w,&h,&c1,&c2,&c3,&extraflag) >= 4) 
          flag &= ~SWP_NOMOVE;
        if (w < 100) w=SWELL_UI_SCALE(600);
        if (h < 100) h=SWELL_UI_SCALE(400);
        if (extraflag&1)
          parms->show_hidden=true;

        if (c1 + c2 + c3 < w/2)
        {
          c1=SWELL_UI_SCALE(280);
          c2=SWELL_UI_SCALE(120);
          c3=SWELL_UI_SCALE(140);
        }

        HWND list = SWELL_MakeControl("",IDC_LIST,"SysListView32",LVS_REPORT|LVS_SHOWSELALWAYS|
              (parms->mode == BrowseFile_State::OPENMULTI ? 0 : LVS_SINGLESEL)|
              LVS_OWNERDATA|WS_BORDER|WS_TABSTOP,0,0,0,0,0);
        if (list)
        {
          LVCOLUMN c={LVCF_TEXT|LVCF_WIDTH, 0, c1, (char*)"Filename" };
          ListView_InsertColumn(list,0,&c);
          c.cx = c2;
          c.pszText = (char*) "Size";
          ListView_InsertColumn(list,1,&c);
          c.cx = c3;
          c.pszText = (char*) "Date";
          ListView_InsertColumn(list,2,&c);
          HWND hdr = ListView_GetHeader(list);
          HDITEM hi;
          memset(&hi,0,sizeof(hi));
          hi.mask = HDI_FORMAT;
          hi.fmt = parms->sortrev ? HDF_SORTDOWN : HDF_SORTUP;
          Header_SetItem(hdr,parms->sortcol,&hi);
        }
        HWND extlist = (parms->extlist && *parms->extlist) ? SWELL_MakeCombo(IDC_EXT, 0,0,0,0, CBS_DROPDOWNLIST) : NULL;
        if (extlist)
        {
          const char *p = parms->extlist;
          while (*p)
          {
            const char *rd=p;
            p += strlen(p)+1;
            if (!*p) break;
            int a = SendMessage(extlist,CB_ADDSTRING,0,(LPARAM)rd);
            SendMessage(extlist,CB_SETITEMDATA,a,(LPARAM)p);
            p += strlen(p)+1;
          }

          int sel = 0;
          if (parms->initialfile && *parms->initialfile)
          {
            sel = ext_valid_for_extlist(WDL_get_fileext(parms->initialfile), parms->extlist);
            if (sel<0) sel=0;
          }
          SendMessage(extlist,CB_SETCURSEL,sel,0);
        }

        SWELL_MakeLabel(-1,parms->mode == BrowseFile_State::OPENDIR ? "Directory: " : "File:",IDC_LABEL, 0,0,0,0, 0); 
        
        if (BFSF_Templ_dlgid && BFSF_Templ_dlgproc)
        {
          HWND dlg = SWELL_CreateDialog(BFSF_Templ_reshead, BFSF_Templ_dlgid, hwnd, BFSF_Templ_dlgproc, 0);
          if (dlg) SetWindowLong(dlg,GWL_ID,IDC_CHILD);
          BFSF_Templ_dlgproc=0;
          BFSF_Templ_dlgid=0;
        }

        SWELL_MakeSetCurParms(1,1,0,0,NULL,false,false);

        if (edit && dir)
        {
          char buf[maxPathLen];
          const char *filepart = "";
          if (parms->initialfile && *parms->initialfile && *parms->initialfile != '.')
          { 
            lstrcpyn_safe(buf,parms->initialfile,sizeof(buf));
            char *p = (char *)WDL_get_filepart(buf);
            if (p > buf) 
            { 
              p[-1]=0; 
              filepart = p; 
            }
            else
            {
              filepart = parms->initialfile;
              goto get_dir;
            }
          }
          else 
          {
get_dir:
            if (parms->initialdir && *parms->initialdir && strcmp(parms->initialdir,".")) 
            {
              lstrcpyn_safe(buf,parms->initialdir,sizeof(buf));
            }
            else if (!getcwd(buf,sizeof(buf)))
              buf[0]=0;
          }

          SetWindowText(edit,filepart);
          SendMessage(hwnd, WM_UPD, IDC_DIR, (LPARAM)buf);
        }

        if (list) SetWindowPos(list,HWND_BOTTOM,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE);
        SetWindowPos(hwnd,NULL,x,y, w,h, flag);
        SendMessage(hwnd,WM_UPD,1,0);
        SendMessage(edit,EM_SETSEL,0,(LPARAM)-1);
        SetFocus(edit);
      }
    break;
    case WM_DESTROY:
      {
        BrowseFile_State *parms = (BrowseFile_State *)GetWindowLongPtr(hwnd,GWLP_USERDATA);
        if (parms)
        {
          RECT r;
          GetWindowRect(hwnd,&r);
          HWND list = GetDlgItem(hwnd,IDC_LIST);
          const int c1 = ListView_GetColumnWidth(list,0);
          const int c2 = ListView_GetColumnWidth(list,1);
          const int c3 = ListView_GetColumnWidth(list,2);
          char tmp[128];
          int extraflag=0;
          if (parms->show_hidden) extraflag|=1;
          snprintf(tmp,sizeof(tmp),"%d %d %d %d %d %d %d %d",r.left,r.top,r.right-r.left,r.bottom-r.top,c1,c2,c3,extraflag);
          const char *ent = parms->mode == BrowseFile_State::OPENDIR ? "dir_browser" : "file_browser";
          WritePrivateProfileString(".swell",ent, tmp, "");
        }
      }
    break;
    case WM_UPD:
      switch (wParam)
      {
        case IDC_DIR: // update directory combo box -- destroys buffer pointed to by lParam
          if (lParam)
          {
            char *path = (char*)lParam;
            HWND combo=GetDlgItem(hwnd,IDC_DIR);
            SendMessage(combo,CB_RESETCONTENT,0,0);
            WDL_remove_trailing_dirchars(path);
            while (path[0]) 
            {
              SendMessage(combo,CB_ADDSTRING,0,(LPARAM)path);
              WDL_remove_filepart(path);
              WDL_remove_trailing_dirchars(path);
            }
            SendMessage(combo,CB_ADDSTRING,0,(LPARAM)"/");
            recent_addtocb(combo);
            SendMessage(combo,CB_SETCURSEL,0,0);
          } 
        break;
        case IDC_EXT:
        case 1:
        {
          BrowseFile_State *parms = (BrowseFile_State *)GetWindowLongPtr(hwnd,GWLP_USERDATA);
          if (parms)
          {
            SetDlgItemText(hwnd,IDC_FILTER,"");
            KillTimer(hwnd,1);

            char buf[maxPathLen];
            const char *filt = NULL;
            HWND ext = GetDlgItem(hwnd,IDC_EXT);
            if (ext)
            {
              LRESULT aidx = SendMessage(ext,CB_GETCURSEL,0,0);
              if (aidx != CB_ERR)
              {
                filt = (const char *)SendMessage(ext,CB_GETITEMDATA,aidx,0);
                if (wParam == IDC_EXT && parms->extlist && *parms->extlist)
                {
                  GetDlgItemText(hwnd,IDC_EDIT,buf,sizeof(buf));

                  if (buf[0])
                  {
                    const char *erd = filt;
                    if (*erd == '*' && erd[1] == '.' && erd[2] && erd[2] != '*')
                    {
                      const char *a = (erd+=1);
                      while (*erd && *erd != ';') erd++;
                      if (erd > a+1)
                      {
                        if (ext_valid_for_extlist(WDL_get_fileext(buf),parms->extlist)>=0)
                        {
                          WDL_remove_fileext(buf);
                        }
                        else
                        {
                          char *p = buf;
                          while (*p) p++;
                          while (p > buf && (p[-1] == ' ' || p[-1] == '.' || p[-1] == '/' || p[-1] == '\\')) p--;
                          *p=0;
                        }
                        if (buf[0])
                        {
                          snprintf_append(buf,sizeof(buf),"%.*s",(int)(erd-a),a);
                          SetDlgItemText(hwnd,IDC_EDIT,buf);
                        }
                      }
                    }
                  }
                }
              }
            }

            GetDlgItemText(hwnd,IDC_DIR,buf,sizeof(buf));
            preprocess_user_path(buf,sizeof(buf));

            if (buf[0]) parms->scan_path(buf, filt, parms->mode == BrowseFile_State::OPENDIR);
            else parms->viewlist_clear();
            HWND list = GetDlgItem(hwnd,IDC_LIST);
            ListView_SetItemCount(list, 0); // clear selection

            parms->viewlist_sort("");
            ListView_SetItemCount(list, parms->viewlist.GetSize());
            ListView_RedrawItems(list,0, parms->viewlist.GetSize());
          }
        }
        break;
      }
    break;
    case WM_GETMINMAXINFO:
      {
        LPMINMAXINFO p=(LPMINMAXINFO)lParam;
        p->ptMinTrackSize.x = 300;
        p->ptMinTrackSize.y = 300;
      }
    break;
    case WM_SIZE:
      {
        BrowseFile_State *parms = (BrowseFile_State *)GetWindowLongPtr(hwnd,GWLP_USERDATA);
        // reposition controls
        RECT r;
        GetClientRect(hwnd,&r);
        const int buth = SWELL_UI_SCALE(24), cancelbutw = SWELL_UI_SCALE(50), okbutw = SWELL_UI_SCALE(parms->mode == BrowseFile_State::OPENDIR ? 120 : 50);
        const int xborder = SWELL_UI_SCALE(4), yborder=SWELL_UI_SCALE(8);
        const int fnh = SWELL_UI_SCALE(20), fnlblw = SWELL_UI_SCALE(parms->mode == BrowseFile_State::OPENDIR ? 70 : 50);
        const int ypad = SWELL_UI_SCALE(4);

        int ypos = r.bottom - ypad - buth;
        int xpos = r.right;
        SetWindowPos(GetDlgItem(hwnd,IDCANCEL), NULL, xpos -= cancelbutw + xborder, ypos, cancelbutw,buth, SWP_NOZORDER|SWP_NOACTIVATE);
        SetWindowPos(GetDlgItem(hwnd,IDOK), NULL, xpos -= okbutw + xborder, ypos, okbutw,buth, SWP_NOZORDER|SWP_NOACTIVATE);

        HWND emb = GetDlgItem(hwnd,IDC_CHILD);
        if (emb)
        {
          RECT sr;
          GetClientRect(emb,&sr);
          if (ypos > r.bottom-ypad-sr.bottom) ypos = r.bottom-ypad-sr.bottom;
          SetWindowPos(emb,NULL, xborder,ypos, xpos - xborder*2, sr.bottom, SWP_NOZORDER|SWP_NOACTIVATE);
          ShowWindow(emb,SW_SHOWNA);
        }

        HWND filt = GetDlgItem(hwnd,IDC_EXT);
        if (filt)
        {
          SetWindowPos(filt, NULL, xborder*2 + fnlblw, ypos -= fnh + yborder, r.right-fnlblw-xborder*3, fnh, SWP_NOZORDER|SWP_NOACTIVATE);
        }

        SetWindowPos(GetDlgItem(hwnd,IDC_EDIT), NULL, xborder*2 + fnlblw, ypos -= fnh + yborder, r.right-fnlblw-xborder*3, fnh, SWP_NOZORDER|SWP_NOACTIVATE);
        SetWindowPos(GetDlgItem(hwnd,IDC_LABEL), NULL, xborder, ypos, fnlblw, fnh, SWP_NOZORDER|SWP_NOACTIVATE);
        const int comboh = g_swell_ctheme.combo_height;
        const int filterw = wdl_max(r.right/8, SWELL_UI_SCALE(50));
        SetWindowPos(GetDlgItem(hwnd,IDC_DIR), NULL, xborder, yborder/2, 
            r.right-xborder*4 - comboh - filterw, comboh, SWP_NOZORDER|SWP_NOACTIVATE);

        SetWindowPos(GetDlgItem(hwnd,IDC_PARENTBUTTON),NULL,
            r.right-xborder*2-comboh - filterw,yborder/2,
            comboh,comboh,SWP_NOZORDER|SWP_NOACTIVATE);

        SetWindowPos(GetDlgItem(hwnd,IDC_FILTER),NULL,
            r.right-xborder-filterw,yborder/2 + (comboh-fnh)/2,
            filterw,fnh,SWP_NOZORDER|SWP_NOACTIVATE);

        SetWindowPos(GetDlgItem(hwnd,IDC_LIST), NULL, xborder, g_swell_ctheme.combo_height+yborder, r.right-xborder*2, ypos - (g_swell_ctheme.combo_height+yborder) - yborder, SWP_NOZORDER|SWP_NOACTIVATE);
      }
    break;
    case WM_TIMER:
      if (wParam == 1)
      {
        KillTimer(hwnd,1);
        BrowseFile_State *parms = (BrowseFile_State *)GetWindowLongPtr(hwnd,GWLP_USERDATA);
        if (parms)
        {
          char buf[128];
          GetDlgItemText(hwnd,IDC_FILTER,buf,sizeof(buf));
          parms->viewlist_sort(buf);
          HWND list = GetDlgItem(hwnd,IDC_LIST);
          ListView_SetItemCount(list, parms->viewlist.GetSize());
          ListView_RedrawItems(list,0, parms->viewlist.GetSize());
        }
      }
    break;
    case WM_COMMAND:
      switch (LOWORD(wParam))
      {
        case IDC_FILTER:
          if (HIWORD(wParam) == EN_CHANGE)
          {
            KillTimer(hwnd,1);
            SetTimer(hwnd,1,250,NULL);
          }
        return 0;
        case IDC_EXT:
          if (HIWORD(wParam) == CBN_SELCHANGE)
          {
            SendMessage(hwnd,WM_UPD,IDC_EXT,0);
          }
        return 0;
        case IDC_PARENTBUTTON:
          {
            int a = (int) SendDlgItemMessage(hwnd,IDC_DIR,CB_GETCURSEL,0,0);
            int cbcnt = (int) SendDlgItemMessage(hwnd,IDC_DIR,CB_GETCOUNT,0,0);
            if (a>=0 && a < cbcnt - recent_size())
            {
              SendDlgItemMessage(hwnd,IDC_DIR,CB_SETCURSEL,a+1,0);
            }
            else
            {
              char buf[maxPathLen];
              GetDlgItemText(hwnd,IDC_DIR,buf,sizeof(buf));
              preprocess_user_path(buf,sizeof(buf));
              WDL_remove_filepart(buf);
              if (a>=0)
                SendMessage(hwnd,WM_UPD,IDC_DIR,(LPARAM)buf);
              else
                SetDlgItemText(hwnd,IDC_DIR,buf);
            }
            SendMessage(hwnd,WM_UPD,1,0);
          }
        return 0;
        case IDC_DIR:
          if (HIWORD(wParam) == CBN_SELCHANGE)
          {
            int a = (int) SendDlgItemMessage(hwnd,IDC_DIR,CB_GETCURSEL,0,0);
            int cbcnt = (int) SendDlgItemMessage(hwnd,IDC_DIR,CB_GETCOUNT,0,0);
            if (a>=cbcnt - recent_size())
            {
              char buf[maxPathLen];
              GetDlgItemText(hwnd,IDC_DIR,buf,sizeof(buf));
              preprocess_user_path(buf,sizeof(buf));
              SendMessage(hwnd,WM_UPD,IDC_DIR,(LPARAM)buf);
            }
            SendMessage(hwnd,WM_UPD,1,0);
          }
        return 0;
        case IDCANCEL: EndDialog(hwnd,0); return 0;
        case IDOK: 
          {
            char buf[maxPathLen], msg[2048];
            GetDlgItemText(hwnd,IDC_DIR,buf,sizeof(buf));
            preprocess_user_path(buf,sizeof(buf));

            if (GetFocus() == GetDlgItem(hwnd,IDC_DIR))
            {
              DIR *dir = opendir(buf);
              if (!dir)
              {
                //snprintf(msg,sizeof(msg),"Path does not exist:\r\n\r\n%s",buf);
                //MessageBox(hwnd,msg,"Path not found",MB_OK);
                return 0;
              }
              closedir(dir);

              SendMessage(hwnd,WM_UPD,1,0);
              SendMessage(hwnd, WM_UPD, IDC_DIR, (LPARAM)buf);
              HWND e = GetDlgItem(hwnd,IDC_EDIT);
              SendMessage(e,EM_SETSEL,0,(LPARAM)-1);
              SetFocus(e);
              return 0;
            }

            size_t buflen = strlen(buf);
            if (!buflen) strcpy(buf,"/");
            else
            {
              if (buflen > sizeof(buf)-2) buflen = sizeof(buf)-2;
              if (buf[buflen-1]!='/') { buf[buflen++] = '/'; buf[buflen]=0; }
            }
            GetDlgItemText(hwnd,IDC_EDIT,msg,sizeof(msg));
            preprocess_user_path(msg,sizeof(msg));

            BrowseFile_State *parms = (BrowseFile_State *)GetWindowLongPtr(hwnd,GWLP_USERDATA);
            int cnt;
            if (parms->mode == BrowseFile_State::OPENMULTI && (cnt=ListView_GetSelectedCount(GetDlgItem(hwnd,IDC_LIST)))>1 && (!*msg || !strcmp(msg,multiple_files)))
            {
              recent_write(buf);
              HWND list = GetDlgItem(hwnd,IDC_LIST);
              WDL_TypedBuf<char> fs;
              fs.Set(buf,strlen(buf)+1);
              int a = ListView_GetNextItem(list,-1,LVNI_SELECTED);
              while (a != -1 && fs.GetSize() < 4096*4096 && cnt--)
              {
                if (a < 0 || a >= parms->viewlist.GetSize()) break;
                const struct BrowseFile_State::rec *rec = parms->viewlist.Get(a);
                if (!rec) break;

                fs.Add(rec->name,strlen(rec->name)+1);
                a = ListView_GetNextItem(list,a,LVNI_SELECTED);
              }
              fs.Add("",1);

              parms->fnout = (char*)malloc(fs.GetSize());
              if (parms->fnout) memcpy(parms->fnout,fs.Get(),fs.GetSize());

              EndDialog(hwnd,1);
              return 0;
            }
            else 
            {
              if (msg[0] == '.' && (msg[1] == '.' || msg[1] == 0))
              {
                if (msg[1] == '.') 
                {
                  int a = (int) SendDlgItemMessage(hwnd,IDC_DIR,CB_GETCURSEL,0,0);
                  if (a>=0) SendDlgItemMessage(hwnd,IDC_DIR,CB_SETCURSEL,a+1,0);
                }
                SetDlgItemText(hwnd,IDC_EDIT,"");
                SendMessage(hwnd,WM_UPD,1,0);
                return 0;
              }
              else if (msg[0] == '/') lstrcpyn_safe(buf,msg,sizeof(buf));
              else lstrcatn(buf,msg,sizeof(buf));
            }

            switch (parms->mode)
            {
              case BrowseFile_State::OPENDIR:
                 if (!buf[0]) return 0;
                 else if (msg[0])
                 {
                   // navigate to directory if filepart set
treatAsDir:
                   DIR *dir = opendir(buf);
                   if (!dir) 
                   {
                     snprintf(msg,sizeof(msg),"Error opening directory:\r\n\r\n%.1000s\r\n\r\nCreate?",buf);
                     if (MessageBox(hwnd,msg,"Create directory?",MB_OKCANCEL)==IDCANCEL) return 0;
                     CreateDirectory(buf,NULL);
                     dir=opendir(buf);
                   }
                   if (!dir) { MessageBox(hwnd,"Error creating directory","Error",MB_OK); return 0; }
                   closedir(dir);
                   SendMessage(hwnd, WM_UPD, IDC_DIR, (LPARAM)buf);
                   SetDlgItemText(hwnd,IDC_EDIT,"");
                   SendMessage(hwnd,WM_UPD,1,0);

                   return 0;
                 }
                 else
                 {
                   DIR *dir = opendir(buf);
                   if (!dir) return 0;
                   closedir(dir);
                 }
              break;
              case BrowseFile_State::SAVE:
                 if (!buf[0]) return 0;
                 else  
                 {
                   struct stat st={0,};
                   DIR *dir = opendir(buf);
                   if (dir)
                   {
                     closedir(dir);
                     SendMessage(hwnd, WM_UPD, IDC_DIR, (LPARAM)buf);
                     SetDlgItemText(hwnd,IDC_EDIT,"");
                     SendMessage(hwnd,WM_UPD,1,0);
                     return 0;
                   }
                   if (buf[strlen(buf)-1] == '/') goto treatAsDir;

                   const char *extlist = parms->extlist;
                   if (extlist && *extlist && ext_valid_for_extlist(WDL_get_fileext(buf),extlist) < 0)
                   {
                     const char *erd = extlist+strlen(extlist)+1;
                     LRESULT combo_sel;
                     HWND combo = GetDlgItem(hwnd, IDC_EXT);
                     if (combo && (combo_sel=SendMessage(combo,CB_GETCURSEL,0,0)) != CB_ERR)
                     {
                       const char *filt = (const char *)SendMessage(combo,CB_GETITEMDATA,combo_sel,0);
                       if (filt && *filt == '*' && filt[1] == '.' && filt[2] && filt[2] != '*') erd = filt;
                     }

                     if (*erd == '*' && erd[1] == '.' && erd[2] && erd[2] != '*') // add default extension
                     {
                       const char *a = (erd+=1);
                       while (*erd && *erd != ';') erd++;
                       if (erd > a+1) snprintf_append(buf,sizeof(buf),"%.*s",(int)(erd-a),a);
                     }
                   }
                   if (!stat(buf,&st))
                   {
                     snprintf(msg,sizeof(msg),"File exists:\r\n\r\n%.1000s\r\n\r\nOverwrite?",buf);
                     if (MessageBox(hwnd,msg,"Overwrite file?",MB_OKCANCEL)==IDCANCEL) return 0;
                   }
                 }
              break;
              default:
                 if (!buf[0]) return 0;
                 else  
                 {
                   struct stat st={0,};
                   DIR *dir = opendir(buf);
                   if (dir)
                   {
                     closedir(dir);
                     SendMessage(hwnd, WM_UPD, IDC_DIR, (LPARAM)buf);
                     SetDlgItemText(hwnd,IDC_EDIT,"");
                     SendMessage(hwnd,WM_UPD,1,0);
                     return 0;
                   }
                   if (stat(buf,&st))
                   {
                     //snprintf(msg,sizeof(msg),"File does not exist:\r\n\r\n%s",buf);
                     //MessageBox(hwnd,msg,"File not found",MB_OK);
                     return 0;
                   }
                 }
              break;
            }
            if (parms->fnout) 
            {
              lstrcpyn_safe(parms->fnout,buf,parms->fnout_sz);
            }
            else
            {
              size_t l = strlen(buf);
              parms->fnout = (char*)calloc(l+2,1);
              memcpy(parms->fnout,buf,l);
            }
            if (parms->mode != BrowseFile_State::OPENDIR)
              WDL_remove_filepart(buf);
            recent_write(buf);
          }
          EndDialog(hwnd,1);
        return 0;
        case ID_SHOW_HIDDEN:
          {
            BrowseFile_State *parms = (BrowseFile_State *)GetWindowLongPtr(hwnd,GWLP_USERDATA);
            parms->show_hidden = !parms->show_hidden;
            SendMessage(hwnd,WM_UPD,1,0);
          }
        return 0;
      }
    break;
    case WM_NOTIFY:
      {
        LPNMHDR l=(LPNMHDR)lParam;
        if (l->code == LVN_GETDISPINFO)
        {
          BrowseFile_State *parms = (BrowseFile_State *)GetWindowLongPtr(hwnd,GWLP_USERDATA);
          NMLVDISPINFO *lpdi = (NMLVDISPINFO*) lParam;
          const int idx=lpdi->item.iItem;
          if (l->idFrom == IDC_LIST && parms)
          {
            struct BrowseFile_State::rec *rec = parms->viewlist.Get(idx);
            if (rec && rec->name)
            {
              if (lpdi->item.mask&LVIF_TEXT) 
              {
                switch (lpdi->item.iSubItem)
                {
                  case 0:
                    lpdi->item.pszText = rec->name;
                  break;
                  case 1:
                    rec->format_size(lpdi->item.pszText,lpdi->item.cchTextMax);
                  break;
                  case 2:
                    rec->format_date(lpdi->item.pszText,lpdi->item.cchTextMax);
                  break;
                }
              }
            }
          }
        }
        else if (l->code == LVN_ODFINDITEM)
        {
        }
        else if (l->code == LVN_ITEMCHANGED)
        {
          const int selidx = ListView_GetNextItem(l->hwndFrom, -1, LVNI_SELECTED);
          BrowseFile_State *parms = (BrowseFile_State *)GetWindowLongPtr(hwnd,GWLP_USERDATA);
          if (selidx>=0 && parms)
          {
            if (parms->mode == BrowseFile_State::OPENMULTI && ListView_GetSelectedCount(l->hwndFrom)>1)
            {
              SetDlgItemText(hwnd,IDC_EDIT,multiple_files);
            }
            else
            {
              struct BrowseFile_State::rec *rec = parms->viewlist.Get(selidx);
              if (rec)
              {
                SetDlgItemText(hwnd,IDC_EDIT,rec->name);
              }
            }
          }
        }
        else if (l->code == NM_DBLCLK)
        {
          SendMessage(hwnd,WM_COMMAND,IDOK,0);
        }
        else if (l->code == LVN_COLUMNCLICK)
        {
          NMLISTVIEW* lv = (NMLISTVIEW*) l;
          BrowseFile_State *parms = (BrowseFile_State *)GetWindowLongPtr(hwnd,GWLP_USERDATA);
          parms->sortrev=!parms->sortrev;
          char lcol = parms->sortcol;
          if ((int)parms->sortcol != lv->iSubItem)
          { 
            parms->sortcol = (char)lv->iSubItem; 
            parms->sortrev=0; 
          }

          HWND hdr = ListView_GetHeader(l->hwndFrom);
          HDITEM hi;
          memset(&hi,0,sizeof(hi));
          hi.mask = HDI_FORMAT;
          Header_SetItem(hdr,lcol,&hi);
          hi.fmt = parms->sortrev ? HDF_SORTDOWN : HDF_SORTUP;
          Header_SetItem(hdr,parms->sortcol,&hi);

          parms->viewlist_sort(NULL);
          ListView_RedrawItems(l->hwndFrom,0, parms->viewlist.GetSize());
        }
      }
    break;
    case WM_KEYDOWN:
      if (lParam == FVIRTKEY && wParam == VK_F5)
      {
        SendMessage(hwnd,WM_UPD,1,0);
        return 1;
      }
      else if (lParam == (FVIRTKEY|FCONTROL) && wParam == 'H')
      {
        SendMessage(hwnd,WM_COMMAND,ID_SHOW_HIDDEN,0);
        return 1;
      }
      else if (lParam == FVIRTKEY && wParam == VK_BACK && 
               GetFocus() == GetDlgItem(hwnd,IDC_LIST))
      {
        SendMessage(hwnd,WM_COMMAND,IDC_PARENTBUTTON,0);
        return 1;
      }
      else if ((lParam&0xff) == FVIRTKEY && wParam == VK_RETURN &&
               GetFocus() == GetDlgItem(hwnd,IDC_LIST))
      {
        SendMessage(hwnd,WM_COMMAND,IDOK,0);
        return 1;
      }
    return 0;
    case WM_CONTEXTMENU:
      {
        BrowseFile_State *parms = (BrowseFile_State *)GetWindowLongPtr(hwnd,GWLP_USERDATA);
        HMENU menu = CreatePopupMenu();
        SWELL_InsertMenu(menu,0,MF_BYPOSITION|(parms->show_hidden ? MF_CHECKED:MF_UNCHECKED), ID_SHOW_HIDDEN, "Show files/directories beginning with .");
        POINT p;
        GetCursorPos(&p);
        TrackPopupMenu(menu,0,p.x,p.y,0,hwnd,NULL);
        DestroyMenu(menu);
      }
    return 1;
  }
  return 0;
}

// return true
bool BrowseForSaveFile(const char *text, const char *initialdir, const char *initialfile, const char *extlist,
                       char *fn, int fnsize)
{
  BrowseFile_State state( text, initialdir, initialfile, extlist, BrowseFile_State::SAVE, fn, fnsize );
  if (!DialogBoxParam(NULL,NULL,GetForegroundWindow(),swellFileSelectProc,(LPARAM)&state)) return false;

  return true;
}

bool BrowseForDirectory(const char *text, const char *initialdir, char *fn, int fnsize)
{
  BrowseFile_State state( text, initialdir, initialdir, NULL, BrowseFile_State::OPENDIR, fn, fnsize );
  return !!DialogBoxParam(NULL,NULL,GetForegroundWindow(),swellFileSelectProc,(LPARAM)&state);
}


char *BrowseForFiles(const char *text, const char *initialdir, 
                     const char *initialfile, bool allowmul, const char *extlist)
{
  BrowseFile_State state( text, initialdir, initialfile, extlist, 
           allowmul ? BrowseFile_State::OPENMULTI : BrowseFile_State::OPEN, NULL, 0 );
  return DialogBoxParam(NULL,NULL,GetForegroundWindow(),swellFileSelectProc,(LPARAM)&state) ? state.fnout : NULL;
}

static const char *mbidtostr(int idx)
{
  switch (idx)
  {
    case IDOK: return "OK";
    case IDCANCEL: return "Cancel";
    case IDYES: return "Yes";
    case IDNO: return "No";
    case IDRETRY: return "Retry";
    case IDABORT: return "Abort";
    case IDIGNORE: return "Ignore";
    default:
      WDL_ASSERT(idx == 0);
    return "";
  }
}

static LRESULT WINAPI swellMessageBoxProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  enum { IDC_LABEL=0x100 };
  const int button_spacing = 8;
  switch (uMsg)
  {
    case WM_CREATE:
      if (lParam)  // swell-specific
      {
        SetWindowLong(hwnd,GWL_WNDPROC,(LPARAM)SwellDialogDefaultWindowProc);
        SetWindowLong(hwnd,DWL_DLGPROC,(LPARAM)swellMessageBoxProc);
        void **parms = (void **)lParam;
        if (parms[1]) SetWindowText(hwnd,(const char*)parms[1]);


        int b1=IDOK, b2=0,b3=0;

        const int fmode = (int)(INT_PTR)parms[2];
        switch (fmode & 0xf)
        {
          case MB_ABORTRETRYIGNORE:
            b1 = IDABORT;
            b2 = IDRETRY;
            b3 = IDIGNORE;
          break;
          case MB_RETRYCANCEL:
            b1 = IDRETRY;
            // fallthrough
          case MB_OKCANCEL:
            b2 = IDCANCEL;
          break;
          case MB_YESNOCANCEL:
            b3 = IDCANCEL;
            // fallthrough
          case MB_YESNO:
            b1 = IDYES;
            b2 = IDNO;
          break;
        }

        SWELL_MakeSetCurParms(1,1,0,0,hwnd,false,false);
        RECT labsize = {0,0,300,20};
        HWND lab = SWELL_MakeLabel(-1,parms[0] ? (const char *)parms[0] : "", IDC_LABEL, 0,0,10,10,SS_CENTER|SS_NOPREFIX); //we'll resize this manually
        HDC dc=GetDC(lab); 
        if (lab && parms[0])
        {
          DrawText(dc,(const char *)parms[0],-1,&labsize,DT_CALCRECT|DT_NOPREFIX);// if dc isnt valid yet, try anyway
        }

        const int sc10 = SWELL_UI_SCALE(10);
        const int sc8 = SWELL_UI_SCALE(8);
        labsize.top += sc10;
        labsize.bottom += sc10 + sc8;

        RECT vp;
        SWELL_GetViewPort(&vp,NULL,true);
        vp.bottom -= vp.top;
        if (labsize.bottom > vp.bottom*7/8)
          labsize.bottom = vp.bottom*7/8;


        int x;
        int button_height=0, button_total_w=0;;
        const int bspace = SWELL_UI_SCALE(button_spacing);
        int button_sizes[3];
        const int nbuttons = b3 ? 3 : b2 ? 2 : 1;
        for (x = 0; x < nbuttons; x ++)
        {
          const int idx = x==0?b1:x==1?b2:b3;
          RECT r={0,0,35,12};
          DrawText(dc,mbidtostr(idx),-1,&r,DT_CALCRECT|DT_NOPREFIX|DT_SINGLELINE);
          button_sizes[x] = r.right-r.left + sc10;
          button_total_w += button_sizes[x] + (x ? bspace : 0);
          if (r.bottom-r.top+sc10 > button_height) button_height = r.bottom-r.top+sc10;
        }

        if (labsize.right < button_total_w+sc8*2) labsize.right = button_total_w+sc8*2;

        int xpos = labsize.right/2 - button_total_w/2;
        const int def_id = (fmode & MB_DEFBUTTON3)&&b3 ? b3 : (fmode & MB_DEFBUTTON2)&&b2 ? b2 : b1;

        for (x = 0; x < nbuttons; x ++)
        {
          const int idx = x==0?b1:x==1?b2:b3;
          SWELL_MakeButton(idx==def_id,mbidtostr(idx),idx,xpos,labsize.bottom,button_sizes[x],button_height,0);
          xpos += button_sizes[x] + bspace;
        }

        if (dc) ReleaseDC(lab,dc);
        SWELL_MakeSetCurParms(1,1,0,0,NULL,false,false);
        SetWindowPos(hwnd,NULL,0,0,
              labsize.right + sc8*2,labsize.bottom + button_height + sc8,SWP_NOACTIVATE|SWP_NOZORDER|SWP_NOMOVE);
        if (lab) SetWindowPos(lab,NULL,sc8,0,labsize.right,labsize.bottom,SWP_NOACTIVATE|SWP_NOZORDER);
        SetFocus(GetDlgItem(hwnd,def_id));
      }
    break;
    case WM_SIZE:
      {
        RECT r;
        GetClientRect(hwnd,&r);
        HWND h = GetWindow(hwnd,GW_CHILD);
        int n = 10, w[8];
        HWND tab[8],lbl=NULL;
        int tabsz=0, bxwid=0, button_height=0;
        while (h && n-- && tabsz<8) 
        {
          int idx = GetWindowLong(h,GWL_ID);
          if (idx == IDCANCEL || idx == IDOK || idx == IDNO || idx == IDYES || idx == IDRETRY) 
          { 
            RECT tr;
            GetClientRect(h,&tr);
            tab[tabsz] = h;
            w[tabsz++] = tr.right - tr.left;
            button_height = tr.bottom-tr.top;
            bxwid += tr.right-tr.left;
          } else if (idx==IDC_LABEL) lbl=h;
          h = GetWindow(h,GW_HWNDNEXT);
        }
        const int bspace = SWELL_UI_SCALE(button_spacing), sc8 = SWELL_UI_SCALE(8);
        if (lbl) SetWindowPos(lbl,NULL,sc8,0,r.right,r.bottom - sc8 - button_height,  SWP_NOZORDER|SWP_NOACTIVATE);
        int xo = r.right/2 - (bxwid + (tabsz-1)*bspace)/2;
        for (int x=0; x<tabsz; x++)
        {
          SetWindowPos(tab[x],NULL,xo,r.bottom - button_height - sc8, 0,0, SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
          xo += w[x] + bspace;
        }
      }
    break;
    case WM_COMMAND:
      if (LOWORD(wParam) && HIWORD(wParam) == BN_CLICKED) 
      {
        EndDialog(hwnd,LOWORD(wParam));
      }
    break;
    case WM_CLOSE:
      if (GetDlgItem(hwnd,IDCANCEL)) EndDialog(hwnd,IDCANCEL);
      else if (GetDlgItem(hwnd,IDNO)) EndDialog(hwnd,IDNO);
      else EndDialog(hwnd,IDOK);
    break;
  }
  return 0;
}

int MessageBox(HWND hwndParent, const char *text, const char *caption, int type)
{
#ifndef SWELL_LICE_GDI
  printf("MessageBox: %s %s\n",text,caption);
#endif
  const void *parms[4]= {text,caption,(void*)(INT_PTR)type} ;
  return DialogBoxParam(NULL,NULL,hwndParent,swellMessageBoxProc,(LPARAM)parms);

#if 0
  int ret=0;
  
  if (type == MB_OK)
  {
    // todo
    ret=IDOK;
  }	
  else if (type == MB_OKCANCEL)
  {
    ret = 1; // todo
    if (ret) ret=IDOK;
    else ret=IDCANCEL;
  }
  else if (type == MB_YESNO)
  {
    ret = 1 ; // todo
    if (ret) ret=IDYES;
    else ret=IDNO;
  }
  else if (type == MB_RETRYCANCEL)
  {
    ret = 1; // todo

    if (ret) ret=IDRETRY;
    else ret=IDCANCEL;
  }
  else if (type == MB_YESNOCANCEL)
  {
    ret = 1; // todo

    if (ret == 1) ret=IDYES;
    else if (ret==-1) ret=IDNO;
    else ret=IDCANCEL;
  }
  
  return ret; 
#endif
}

#ifdef SWELL_LICE_GDI
struct ChooseColor_State {
  int ncustom;
  COLORREF *custom;

  double h,s,v;

  LICE_IBitmap *bm;
};

static double h6s2i(double h)
{
  h -= ((int)(h*1.0/6.0))*6.0;
  if (h < 3)
  {
    if (h < 1.0) return 1.0 - h;
    return 0.0;
  }
  if (h < 4.0) return h - 3.0;
  return 1.0;
};

static void _HSV2RGB(double h, double s, double v, double *r, double *g, double *b)
{
  h *= 1.0 / 60.0; 
  s *= v * 1.0 / 255.0;
  *r = v-h6s2i(h+2)*s;
  *g = v-h6s2i(h)*s;
  *b = v-h6s2i(h+4)*s;
}
static int _HSV2RGBV(double h, double s, double v)
{
  double r,g,b;
  _HSV2RGB(h,s,v,&r,&g,&b);
  int ir = (int) (r+0.5);
  int ig = (int) (g+0.5);
  int ib = (int) (b+0.5);
  if (ir<0) ir=0; else if (ir>255) ir=255;
  if (ig<0) ig=0; else if (ig>255) ig=255;
  if (ib<0) ib=0; else if (ib>255) ib=255;
  return RGB(ir,ig,ib);
}


static void _RGB2HSV(double r, double g, double b, double *h, double *s, double *v)
{
  const double maxrgb=wdl_max(wdl_max(r,g),b);
  const double df = maxrgb - wdl_min(wdl_min(r,g),b);
  double d=r-g, degoffs = 240.0;

  if (g > r)
  {
    if (g > b)
    {
      degoffs=120;
      d=b-r;
    }
  }
  else if (r > b)
  {
    degoffs=0.0;
    d=g-b;
  }
  
  *v = maxrgb;
  if (df)
  {
    degoffs += (d*60)/df;
    if (degoffs<0.0) degoffs+=360.0;
    else if (degoffs >= 360.0) degoffs-=360.0;
    *h = degoffs;
    *s = (df*256)/(maxrgb+1);
  }
  else
    *h = *s = 0;
}

static LRESULT WINAPI swellColorSelectProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  static int s_reent,s_vmode;
  static int wndw, custsz, buth, border, butw, edh, edlw, edew, vsize, psize, yt;
  if (!wndw)
  {
    wndw = SWELL_UI_SCALE(400);
    custsz = SWELL_UI_SCALE(20);
    butw = SWELL_UI_SCALE(50);
    buth = SWELL_UI_SCALE(24);
    border = SWELL_UI_SCALE(4);
    edh = SWELL_UI_SCALE(20);
    edlw = SWELL_UI_SCALE(16);
    edew = SWELL_UI_SCALE(40);
    vsize = SWELL_UI_SCALE(40);
    psize = border+edlw + edew;
    yt = border + psize + border + (edh + border)*6;
  }

  const int customperrow = (wndw-border)/(custsz+border);
  switch (uMsg)
  {
    case WM_CREATE:
      if (lParam)  // swell-specific
      {
        SetWindowLong(hwnd,GWL_WNDPROC,(LPARAM)SwellDialogDefaultWindowProc);
        SetWindowLong(hwnd,DWL_DLGPROC,(LPARAM)swellColorSelectProc);
        SetWindowLongPtr(hwnd,GWLP_USERDATA,lParam);

        SetWindowText(hwnd,"Choose Color");

        SWELL_MakeSetCurParms(1,1,0,0,hwnd,false,false);

        SWELL_MakeButton(0, "OK", IDOK,0,0,0,0, 0);
        SWELL_MakeButton(0, "Cancel", IDCANCEL,0,0,0,0, 0);
        SWELL_MakeLabel(0, "(right click a custom color to save)", 0x500, 0,0,0,0, 0); 

        static const char * const lbl[] = { "R","G","B","H","S","V"};
        for (int x=0;x<6;x++)
        {
          SWELL_MakeLabel(0,lbl[x], 0x100+x, 0,0,0,0, 0); 
          SWELL_MakeEditField(0x200+x, 0,0,0,0,  0);
        }

        ChooseColor_State *cs = (ChooseColor_State*)GetWindowLongPtr(hwnd,GWLP_USERDATA);
        SWELL_MakeSetCurParms(1,1,0,0,NULL,false,false);
        int nrows = ((cs?cs->ncustom : 0 ) + customperrow-1)/wdl_max(customperrow,1);
        SetWindowPos(hwnd,NULL,0,0, wndw, 
            yt + buth + border + nrows * (custsz+border), 
            SWP_NOZORDER|SWP_NOMOVE);
        SendMessage(hwnd,WM_USER+100,0,3);
      }
    break;
    case WM_LBUTTONDOWN:
    case WM_RBUTTONDOWN:
      {
        ChooseColor_State *cs = (ChooseColor_State*)GetWindowLongPtr(hwnd,GWLP_USERDATA);
        if (!cs) break;
        RECT r;
        GetClientRect(hwnd,&r);
        const int xt = r.right - edew - edlw - border*3;

        const int y = GET_Y_LPARAM(lParam);
        const int x = GET_X_LPARAM(lParam);
        if (x < xt && y < yt)
        {
          s_vmode = x >= xt-vsize;
          SetCapture(hwnd);
          // fall through
        }
        else 
        {
          if (cs->custom && cs->ncustom && y >= yt && y < r.bottom - buth - border)
          {
            int row = (y-yt) / (custsz+border), rowoffs = (y-yt) % (custsz+border);
            if (rowoffs < custsz)
            {
              int col = (x-border) / (custsz+border), coloffs = (x-border) % (custsz+border);
              if (coloffs < custsz)
              {
                col += customperrow*row;
                if (col >= 0 && col < cs->ncustom)
                {
                  if (uMsg == WM_LBUTTONDOWN)
                  {
                    _RGB2HSV(GetRValue(cs->custom[col]),GetGValue(cs->custom[col]),GetBValue(cs->custom[col]),&cs->h,&cs->s,&cs->v);
                    SendMessage(hwnd,WM_USER+100,0,3);
                  }
                  else
                  {
                    cs->custom[col] = _HSV2RGBV(cs->h,cs->s,cs->v);
                    InvalidateRect(hwnd,NULL,FALSE);
                  }
                }
              }
            }
          }
          break;
        }
        // fall through
      }
    case WM_MOUSEMOVE:
      if (GetCapture()==hwnd)
      {
        RECT r;
        GetClientRect(hwnd,&r);
        const int xt = r.right - edew - edlw - border*3;
        ChooseColor_State *cs = (ChooseColor_State*)GetWindowLongPtr(hwnd,GWLP_USERDATA);
        if (!cs) break;
        int var = 255 - (GET_Y_LPARAM(lParam) - border)*256 / (yt-border*2);
        if (var<0)var=0;
        else if (var>255)var=255;
        if (s_vmode)
        {
          if (var != cs->v)
          {
            cs->v=var;
            SendMessage(hwnd,WM_USER+100,0,3);
          }
        }
        else
        {
          int hue = (GET_X_LPARAM(lParam) - border)*360 / (xt-border - vsize);
          if (hue<0) hue=0;
          else if (hue>359) hue=359;
          if (cs->h != hue || cs->s != var)
          {
            cs->h=hue;
            cs->s=var;
            SendMessage(hwnd,WM_USER+100,0,3);
          }
        }
      }
    break;
    case WM_LBUTTONUP:
    case WM_RBUTTONUP:
      ReleaseCapture();
    break;
    case WM_PAINT:
      {
        PAINTSTRUCT ps;
        ChooseColor_State *cs = (ChooseColor_State*)GetWindowLongPtr(hwnd,GWLP_USERDATA);
        if (cs && BeginPaint(hwnd,&ps))
        {
          RECT r;
          GetClientRect(hwnd,&r);
          const int xt = r.right - edew - edlw - border*3;
          if (cs->custom && cs->ncustom)
          {
            int ypos = yt;
            int xpos = border;
            for (int x = 0; x < cs->ncustom; x ++)
            {
              HBRUSH br = CreateSolidBrush(cs->custom[x]);
              RECT tr={xpos,ypos,xpos+custsz, ypos+custsz };
              FillRect(ps.hdc,&tr,br);
              DeleteObject(br);

              xpos += border+custsz;
              if (xpos+custsz >= r.right)
              {
                xpos=border;
                ypos += border + custsz;
              }
            }
          }

          {
            HBRUSH br = CreateSolidBrush(_HSV2RGBV(cs->h,cs->s,cs->v));
            RECT tr={r.right - border - psize, border, r.right-border, border+psize};
            FillRect(ps.hdc,&tr,br);
            DeleteObject(br);
          }

          if (!cs->bm) cs->bm = new LICE_SysBitmap(xt-border,yt-border);
          else cs->bm->resize(xt-border,yt-border);

          int x1 = xt - border - vsize;
          int var = cs->v;

          const int ysz = yt-border*2;
          const int vary = ysz - 1 - (ysz * cs->v)/256;

          for (int y = 0; y < ysz; y ++)
          {
            LICE_pixel *wr = cs->bm->getBits() + cs->bm->getRowSpan() * y;
            const int sat = 255 - y*256/ysz;
            double xx=0.0, dx=384.0/x1;
            int x;
            for (x = 0; x < x1; x++)
            {
              *wr++ = LICE_HSV2Pix((int)(xx+0.5),sat,var,255);
              xx+=dx;
            }
            LICE_pixel p = LICE_HSV2Pix(cs->h * 384.0/360.0,cs->s,sat ^ (y==vary ? 128 : 0),255);
            for (;x < xt-border;x++) *wr++ = p;
          }
          LICE_pixel p = LICE_HSV2Pix((int)(cs->h+0.5),(int)(cs->s+0.5),((int)(128.5+cs->v))&255,255);
          const int saty = ysz - 1 - (int) (ysz * cs->s + 0.5)/256;
          const int huex = (x1*cs->h)/360;
          LICE_Line(cs->bm,huex,saty-4,huex,saty+4,p,.75f,LICE_BLIT_MODE_COPY,false);
          LICE_Line(cs->bm,huex-4,saty,huex+4,saty,p,.75f,LICE_BLIT_MODE_COPY,false);

          BitBlt(ps.hdc,border,border,xt-border,ysz,cs->bm->getDC(),0,0,SRCCOPY);

          EndPaint(hwnd,&ps);
        }
      }

    break;
    case WM_GETMINMAXINFO:
      {
        LPMINMAXINFO p=(LPMINMAXINFO)lParam;
        p->ptMinTrackSize.x = 300;
        p->ptMinTrackSize.y = 300;
      }
    break;
    case WM_USER+100:
      {
        ChooseColor_State *cs = (ChooseColor_State*)GetWindowLongPtr(hwnd,GWLP_USERDATA);
        if (cs)
        {
          double t[6];
          t[3] = cs->h;
          t[4] = cs->s;
          t[5] = cs->v;
          _HSV2RGB(t[3],t[4],t[5],t,t+1,t+2);
          s_reent++;
          for (int x=0;x<6;x++) if (lParam & ((x<3)?1:2)) SetDlgItemInt(hwnd,0x200+x,(int) (t[x]+0.5),FALSE);
          s_reent--;
          InvalidateRect(hwnd,NULL,FALSE);
        }
      }
    break;
    case WM_SIZE:
      {
        RECT r;
        GetClientRect(hwnd,&r);
        int tx = r.right - edew-edlw-border*2, ty = border*2 + psize;
        for (int x=0;x<6;x++)
        {
          SetWindowPos(GetDlgItem(hwnd,0x100+x),NULL,tx, ty, edlw, edh, SWP_NOZORDER|SWP_NOACTIVATE);
          SetWindowPos(GetDlgItem(hwnd,0x200+x),NULL,tx+edlw+border, ty, edew, edh, SWP_NOZORDER|SWP_NOACTIVATE);
          ty += border+edh;
        }

        r.right -= border + butw;
        r.bottom -= border + buth;
        SetWindowPos(GetDlgItem(hwnd,IDCANCEL), NULL, r.right, r.bottom, butw, buth, SWP_NOZORDER|SWP_NOACTIVATE);
        r.right -= border*2 + butw;
        SetWindowPos(GetDlgItem(hwnd,IDOK), NULL, r.right, r.bottom, butw, buth, SWP_NOZORDER|SWP_NOACTIVATE);

        SetWindowPos(GetDlgItem(hwnd,0x500), NULL, border, r.bottom, r.right-border*2, buth, SWP_NOZORDER|SWP_NOACTIVATE);
      }
    break;
    case WM_COMMAND:
      switch (LOWORD(wParam))
      {
        case IDCANCEL:
          EndDialog(hwnd,0);
        break;
        case IDOK:
          EndDialog(hwnd,1);
        break;
        case 0x200:
        case 0x201:
        case 0x202:
        case 0x203:
        case 0x204:
        case 0x205:
          if (!s_reent)
          {
            const bool ishsv = LOWORD(wParam) >= 0x203;
            int offs = ishsv ? 0x203 : 0x200;
            BOOL t = FALSE;
            double h = GetDlgItemInt(hwnd,offs++,&t,FALSE);
            if (!t) break;
            double s = GetDlgItemInt(hwnd,offs++,&t,FALSE);
            if (!t) break;
            double v = GetDlgItemInt(hwnd,offs++,&t,FALSE);
            if (!t) break;
            if (s<0) s=0; else if (s>255) s=255;
            if (v<0) v=0; else if (v>255) v=255;
            if (h<0) h=0;

            if (!ishsv) 
            {
              if (h>255) h=255;
              _RGB2HSV(h,s,v,&h,&s,&v);
            }
            else
            {
              if (h>360) h=360;
            }

            ChooseColor_State *cs = (ChooseColor_State*)GetWindowLongPtr(hwnd,GWLP_USERDATA);
            if (cs)
            {
              cs->h = h;
              cs->s = s;
              cs->v = v;
            }
            SendMessage(hwnd,WM_USER+100,0,ishsv?1:2);
          }
        break;
      }
    break;

  }
  return 0;
}
#endif //SWELL_LICE_GDI

bool SWELL_ChooseColor(HWND h, COLORREF *val, int ncustom, COLORREF *custom)
{
#ifdef SWELL_LICE_GDI
  ChooseColor_State state = { ncustom, custom };
  COLORREF c = val ? *val : 0;
  _RGB2HSV(GetRValue(c),GetGValue(c),GetBValue(c),&state.h,&state.s,&state.v);
  bool rv = DialogBoxParam(NULL,NULL,h,swellColorSelectProc,(LPARAM)&state)!=0;
  delete state.bm;
  if (rv && val) 
  {
    *val = _HSV2RGBV(state.h,state.s,state.v);
  }
  return rv;
#else
  return false;
#endif
}

#if defined(SWELL_FREETYPE) && defined(SWELL_LICE_GDI)

struct FontChooser_State
{
  FontChooser_State()
  {
    hFont = 0;
  }
  ~FontChooser_State()
  {
    DeleteObject(hFont);
  }

  LOGFONT font;
  HFONT hFont;
  WDL_FastString lastfn;
};

extern const char *swell_last_font_filename;

const char *swell_enumFontFiles(int x);
int swell_getLineLength(const char *buf, int *post_skip, int wrap_maxwid, HDC hdc);

static LRESULT WINAPI swellFontChooserProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  enum { IDC_LIST=0x100, IDC_FACE, IDC_SIZE, IDC_WEIGHT, IDC_ITALIC };
  enum { preview_h = 90, _border = 4, _buth = 24 };

  switch (uMsg)
  {
    case WM_CREATE:
      if (lParam)  // swell-specific
      {
        SetWindowLong(hwnd,GWL_WNDPROC,(LPARAM)SwellDialogDefaultWindowProc);
        SetWindowLong(hwnd,DWL_DLGPROC,(LPARAM)swellFontChooserProc);
        SetWindowLongPtr(hwnd,GWLP_USERDATA,lParam);

        SetWindowText(hwnd,"Choose Font");

        SWELL_MakeSetCurParms(1,1,0,0,hwnd,false,false);

        SWELL_MakeButton(0, "OK", IDOK,0,0,0,0, 0);
        SWELL_MakeButton(0, "Cancel", IDCANCEL,0,0,0,0, 0);
        SWELL_MakeListBox(IDC_LIST,0,0,0,0, LBS_OWNERDRAWFIXED);
        SWELL_MakeEditField(IDC_FACE, 0,0,0,0,  0);
        SWELL_MakeEditField(IDC_SIZE, 0,0,0,0,  0);
        SWELL_MakeCombo(IDC_WEIGHT, 0,0,0,0, CBS_DROPDOWNLIST);
        SWELL_MakeCheckBox("Italic",IDC_ITALIC,0,0,0,0, 0);

        SendDlgItemMessage(hwnd,IDC_WEIGHT,CB_ADDSTRING,0,(LPARAM)"Normal");
        SendDlgItemMessage(hwnd,IDC_WEIGHT,CB_ADDSTRING,0,(LPARAM)"Bold");
        SendDlgItemMessage(hwnd,IDC_WEIGHT,CB_ADDSTRING,0,(LPARAM)"Light");

        SWELL_MakeSetCurParms(1,1,0,0,NULL,false,false);

        SetWindowPos(hwnd,NULL,0,0, 550,380, SWP_NOZORDER|SWP_NOMOVE);

        WDL_StringKeyedArray<char> list;
        const char *fontfile;
        int x;
        for (x=0; (fontfile=swell_enumFontFiles(x)); x ++)
        {
          char buf[512];
          lstrcpyn_safe(buf,WDL_get_filepart(fontfile),sizeof(buf));
          char *tmp = buf;
          while (*tmp && *tmp != '-' && *tmp != '.') tmp++;
          *tmp=0;
          if (*buf) list.AddUnsorted(buf,true);
        }
        swell_enumFontFiles(-1); // clear cache
        list.Resort();
        FontChooser_State *cs = (FontChooser_State*)lParam;
        bool italics = cs->font.lfItalic!=0;
        int wt = cs->font.lfWeight;
        const char *lp=NULL;
        int cnt=0;
        for (x=0;x<list.GetSize();x++)
        {
          const char *p=NULL;
          if (list.Enumerate(x,&p) && p)
          {
            if (!stricmp(p,cs->font.lfFaceName))
              SendDlgItemMessage(hwnd,IDC_LIST,LB_SETCURSEL,cnt,0);

            size_t ll;
            if (lp && !strncmp(p,lp,ll=strlen(lp)))
            {
              // if this is an extension of the last one, skip
              const char *trail = p+ll;
              if (strlen(trail)<=2)
              {
                for (int y=0;y<2;y++)
                {
                  char c = *trail;
                  if (c>0) c=toupper_safe(c);
                  if (c == 'B' || c == 'I' || c == 'L') trail++;
                }
              }
              else while (*trail)
              {
                if (!strnicmp(trail,"Bold",4)) trail+=4;
                else if (!strnicmp(trail,"Light",5)) trail+=5;
                else if (!strnicmp(trail,"Italic",6)) trail+=6;
                else if (!strnicmp(trail,"Oblique",7)) trail+=7;
                else break;
              }
              if (!*trail) continue;
            }
            cnt++;
            SendDlgItemMessage(hwnd,IDC_LIST,LB_ADDSTRING,0,(LPARAM)p);
            lp=p;
          }
        }
        SetDlgItemText(hwnd,IDC_FACE,cs->font.lfFaceName);
        if (cs->font.lfHeight > 0)
        {
          HFONT font = CreateFontIndirect(&cs->font);
          if (font)
          {
            HDC hdc = GetDC(hwnd);
            if (hdc)
            {
              HGDIOBJ oldfont = SelectObject(hdc,font);
              TEXTMETRIC tm = { 0, };
              GetTextMetrics(hdc,&tm);
              if (tm.tmHeight > 0) cs->font.lfHeight = -tm.tmHeight;
              SelectObject(hdc,oldfont);
              ReleaseDC(hwnd,hdc);
            }
            DeleteObject(font);
          }
        }
        SetDlgItemInt(hwnd,IDC_SIZE,cs->font.lfHeight < 0 ? -cs->font.lfHeight : cs->font.lfHeight,TRUE);
        SendDlgItemMessage(hwnd,IDC_WEIGHT,CB_SETCURSEL,wt<=FW_LIGHT ? 2 : wt < FW_BOLD ? 0 : 1,0);
        if (italics)
          CheckDlgButton(hwnd,IDC_ITALIC,BST_CHECKED);
      }
    break;
    case WM_DRAWITEM:
    {
      DRAWITEMSTRUCT *di=(DRAWITEMSTRUCT *)lParam;
      FontChooser_State *cs = (FontChooser_State*)GetWindowLongPtr(hwnd,GWLP_USERDATA);
      if (cs && di->CtlID == IDC_LIST)
      {
        char buf[512];
        buf[0]=0;
        SendDlgItemMessage(hwnd,IDC_LIST,LB_GETTEXT,di->itemID,(WPARAM)buf);
        if (buf[0])
        {
          HFONT font = CreateFont(-wdl_abs(g_swell_ctheme.default_font_size), 0, 0, 0,
              cs->font.lfWeight, cs->font.lfItalic,
              FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, buf);

          HGDIOBJ oldFont = SelectObject(di->hDC,font);
          DrawText(di->hDC,buf,-1,&di->rcItem,DT_VCENTER|DT_LEFT|DT_SINGLELINE|DT_NOPREFIX);
          wchar_t tmp[] = {'a','A','z','Z'};
          unsigned short ind[4];
          GetGlyphIndicesW(di->hDC,tmp,4,ind,0);
          SelectObject(di->hDC,oldFont);

          int x;
          for (x=0;x<4 && ind[x]==0xffff;x++);
          if (x==4)
          {
            RECT r = di->rcItem;
            r.right-=4;
            DrawText(di->hDC,buf,-1,&r,DT_VCENTER|DT_RIGHT|DT_SINGLELINE|DT_NOPREFIX);
          }
          DeleteObject(font);

        }
      }
    }
    return 0;
    case WM_PAINT:
      {
        PAINTSTRUCT ps;
        FontChooser_State *cs = (FontChooser_State*)GetWindowLongPtr(hwnd,GWLP_USERDATA);
        if (cs && BeginPaint(hwnd,&ps))
        {
          RECT r;
          GetClientRect(hwnd,&r);

          const int border = SWELL_UI_SCALE(_border);
          const int buth = SWELL_UI_SCALE(_buth);
          const int ph = SWELL_UI_SCALE(preview_h);
          r.left += border;
          r.right -= border;
          r.bottom -= border*2 + buth;
          r.top = r.bottom - ph;

          HFONT f = CreateFontIndirect(&cs->font);

          HBRUSH br = CreateSolidBrush(RGB(255,255,255));
          FillRect(ps.hdc,&r,br);
          DeleteObject(br);
          SetTextColor(ps.hdc,RGB(0,0,0));
          SetBkMode(ps.hdc,TRANSPARENT);
          r.right-=4;
          r.left+=4;
          if (swell_last_font_filename)
          {
            r.bottom -= DrawText(ps.hdc,swell_last_font_filename,-1,&r,DT_BOTTOM|DT_NOPREFIX|DT_SINGLELINE|DT_RIGHT);
          }

          HGDIOBJ oldFont = SelectObject(ps.hdc,f);

          extern const char *g_swell_fontpangram;
          const char *str = g_swell_fontpangram;
          //
          // thanks, http://dailypangram.tumblr.com/ :)
          if (!str) str = "Strangely, aerobic exercise doesn’t quite work with improvised free jazz.";

          while (*str)
          {
            int sk=0, lb=swell_getLineLength(str, &sk, r.right-r.left, ps.hdc);
            if (!lb&&!sk) break;
            if (lb>0) r.top += DrawText(ps.hdc,str,lb,&r,DT_TOP|DT_LEFT|DT_NOPREFIX|DT_SINGLELINE);
            str+=lb+sk;
          }


          SelectObject(ps.hdc,oldFont);
          DeleteObject(f);


          EndPaint(hwnd,&ps);
        }
      }

    break;
    case WM_GETMINMAXINFO:
      {
        LPMINMAXINFO p=(LPMINMAXINFO)lParam;
        p->ptMinTrackSize.x = 400;
        p->ptMinTrackSize.y = 300;
      }
    break;
    case WM_SIZE:
      {
        RECT r;
        GetClientRect(hwnd,&r);
        const int border = SWELL_UI_SCALE(_border);
        const int buth = SWELL_UI_SCALE(_buth);
        const int butw = SWELL_UI_SCALE(50);
        const int edh = SWELL_UI_SCALE(20);
        const int size_w = SWELL_UI_SCALE(50);
        const int wt_w = SWELL_UI_SCALE(80);
        const int italic_w = SWELL_UI_SCALE(60);

        r.left += border;
        r.right -= border;

        r.bottom -= border + buth;
        SetWindowPos(GetDlgItem(hwnd,IDCANCEL), NULL, r.right - butw, r.bottom, butw, buth, SWP_NOZORDER|SWP_NOACTIVATE);
        SetWindowPos(GetDlgItem(hwnd,IDOK), NULL, r.right - border - butw*2, r.bottom, butw, buth, SWP_NOZORDER|SWP_NOACTIVATE);
        r.bottom -= SWELL_UI_SCALE(preview_h) + border;
        int psz=wdl_max(g_swell_ctheme.combo_height,edh);
        r.bottom -= psz + border;
        SetWindowPos(GetDlgItem(hwnd,IDC_FACE),NULL,r.left,r.bottom + (psz-edh)/2, 
            r.right-r.left - size_w-wt_w-italic_w - border*3, edh, SWP_NOZORDER|SWP_NOACTIVATE);
        SetWindowPos(GetDlgItem(hwnd,IDC_SIZE),NULL,r.right-size_w-wt_w-italic_w-border*2,r.bottom + (psz-edh)/2, 
            size_w, edh, SWP_NOZORDER|SWP_NOACTIVATE);
        SetWindowPos(GetDlgItem(hwnd,IDC_WEIGHT),NULL,r.right-wt_w-italic_w-border,r.bottom + (psz-g_swell_ctheme.combo_height)/2, 
            wt_w, g_swell_ctheme.combo_height, SWP_NOZORDER|SWP_NOACTIVATE);
        SetWindowPos(GetDlgItem(hwnd,IDC_ITALIC),NULL,r.right-italic_w,r.bottom + (psz-edh)/2, 
            italic_w, edh, SWP_NOZORDER|SWP_NOACTIVATE);

        SetWindowPos(GetDlgItem(hwnd,IDC_LIST), NULL, border, border, r.right, r.bottom - border*2, SWP_NOZORDER|SWP_NOACTIVATE);


      }
    break;
    case WM_COMMAND:
      switch (LOWORD(wParam))
      {
        case IDC_LIST:
          if (HIWORD(wParam) == LBN_SELCHANGE)
          {
            int idx = (int) SendDlgItemMessage(hwnd,IDC_LIST,LB_GETCURSEL,0,0);
            if (idx>=0)
            {
              char buf[512];
              buf[0]=0;
              SendDlgItemMessage(hwnd,IDC_LIST,LB_GETTEXT,idx,(WPARAM)buf);
              if (buf[0]) SetDlgItemText(hwnd,IDC_FACE,buf);
            }
          }
        break;
        case IDC_SIZE:
        case IDC_FACE:
        case IDC_ITALIC:
        case IDC_WEIGHT:
        {
          FontChooser_State *cs = (FontChooser_State*)GetWindowLongPtr(hwnd,GWLP_USERDATA);
          if (cs) 
          {
            if (LOWORD(wParam) == IDC_FACE)
              GetDlgItemText(hwnd,IDC_FACE,cs->font.lfFaceName,sizeof(cs->font.lfFaceName));
            else if (LOWORD(wParam) == IDC_SIZE)
            {
              BOOL t;
              int a = GetDlgItemInt(hwnd,IDC_SIZE,&t,FALSE);
              if (t && a > 0)
                cs->font.lfHeight = -a;
            }
            else if (LOWORD(wParam) == IDC_ITALIC) cs->font.lfItalic = IsDlgButtonChecked(hwnd,IDC_ITALIC) ? 1:0;
            else if (LOWORD(wParam) == IDC_WEIGHT && HIWORD(wParam) == CBN_SELCHANGE)
            {
              int idx = (int) SendDlgItemMessage(hwnd,IDC_WEIGHT,CB_GETCURSEL,0,0);
              if (idx==0) cs->font.lfWeight = FW_NORMAL;
              else if (idx==1) cs->font.lfWeight = FW_BOLD;
              else if (idx==2) cs->font.lfWeight = FW_LIGHT;
            }
            InvalidateRect(hwnd,NULL,FALSE);
          }
        }
        break;
        case IDCANCEL:
          EndDialog(hwnd,0);
        break;
        case IDOK:
          EndDialog(hwnd,1);
        break;
      }
    break;

  }
  return 0;
}

void *swell_MatchFont(const char *lfFaceName, int weight, int italic, const char **fnOut);

#endif

bool SWELL_ChooseFont(HWND h, LOGFONT *lf)
{
#if defined(SWELL_FREETYPE) && defined(SWELL_LICE_GDI)
  FontChooser_State state;
  state.font = *lf;

  bool rv = DialogBoxParam(NULL,NULL,h,swellFontChooserProc,(LPARAM)&state)!=0;
  if (rv)
  {
    *lf = state.font;
  }
  return rv;
#else
  return false;
#endif
}

#endif
