/* ** This file contains routines required to display a standard open
      dialog box.  Apps can directly link to object file or modify this
      source for slightly different dialog box.

      Note - in order to use these routines, the application must
      export DlgfnOpen().  Also, an app that uses these routines must
      be running ss=ds, since they use near pointers into stack.
*/

#include "cal.h"

#define ATTRDIRLIST	0xC010	/* include directories and drives in listbox */
#define ATTRFILELIST	0x0000
#define ID_LISTBOX	10
#define ID_EDIT 	11
#define CBEXTMAX	6  /* Number of bytes in "\*.txt" */

CHAR szLastDir[120];    /* Dir where the last open occurred */
                        /* useful if file is picked up from other than current dir e.g path */
INT idEditSave;
INT idListboxSave;
INT idPathSave;
CHAR *	 szExtSave;
CHAR *	 szFileNameSave;
INT	*pfpSave;
OFSTRUCT *	rgbOpenSave;
INT	cbRootNameMax;
#define CCHNG	    15
CHAR	rgchNg[CCHNG] =  {'"', '\\', '/', '[', ']', ':', '|',
			  '<', '>', '+', '=', ';', ',', ' ', 0};


/*
 *  Function prototypes
 */

VOID cDlgAddCorrectExtension(CHAR *szEdit, WORD fSearching);


/*
 *  Functions
 */

#if 0
/************************* commented out unused code . L.Raman 12/12/90 */

/* int far DlgfnOpen(); */
/* void far cDlgCheckOkEnable(); */
/* BOOL far cDlgCheckFileName();*/

/* ** Display dialog box for opening files.  Allow user to interact with
      dialogbox, change directories as necessary, and try to open file if user
      selects one.  Automatically append extension to filename if necessary.
      The open dialog box contains an edit field, listbox, static field,
      and OK and CANCEL buttons.

      This routine correctly parses filenames containing KANJI characters.

      Input -	hInstance if app module instance handle.
		hwndParent is window handle of parent window
		idDlgIn is dialog id
		idEditIn is id of edit field
		idListboxIn is id of listbox
		idPathIn is id of static field which gets path name
		szExtIn is pointer to zero terminated string containing
			default extension to be added to filenames.
		cbFileNameMaxIn is number of bytes in edit field buffer.

      Output - *pfp gets value of file handle if file is opened.
		    or -1 if file could not be opened.
	       *rgbOpenIn is initialized with file info by OpenFile()
	       *szFileNameIn gets name of selected file (fully qualified)
	       Any leading blanks are removed from sszFileName.
	       Trailing blanks are replaced with a 0 terminator.

      Returns -     -1 if dialogbox() fails (out of memory).
		     0 if user presses cancel
		     1 if user enters legal filename and presses ok
                     2 if user enters illegal file name and presses ok
*/
INT APIENTRY cDlgOpen(
    HANDLE	hInstance,
    HWND	hwndParent,
    INT		idDlgIn,
    INT		idEditIn,
    INT		idListboxIn,
    INT		idPathIn,
    CHAR	*szExtIn,
    INT		cbFileNameMaxIn,
    CHAR	*szFileNameIn,
    OFSTRUCT *	 rgbOpenIn,
    INT		*pfp)
{
    BOOL    fResult;

    idEditSave = idEditIn;
    idListboxSave = idListboxIn;
    idPathSave = idPathIn;
    szExtSave = szExtIn;
    /* Limit for bytes in filename is max bytes in filename less
       space for extension and 0 terminator. */
    cbRootNameMax = cbFileNameMaxIn - CBEXTMAX - 1;
    szFileNameSave = szFileNameIn;
    rgbOpenSave = rgbOpenIn;
    pfpSave = pfp;
    fResult = (BOOL)DialogBox(hInstance, MAKEINTRESOURCE(idDlgIn), hwndParent, cDlgfnOpen );
    return fResult;
}


/* ** Dialog function for Open File */
INT_PTR CALLBACK cDlgfnOpen(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    INT item;
    CHAR rgch[256];
    INT cchFile, cchDir;
    CHAR *pchFile;
    BOOL    fWild;
    static BOOL bRO=FALSE;
    INT     result = 2;    /* Assume illegal filename */
    INT len, nx;

    switch (msg) {
    case WM_INITDIALOG:
        /* Save the global read-only status. */
        bRO=vfOpenFileReadOnly;

	/* Set edit field with default search spec */
        SetDlgItemText(hwnd, idEditSave, g(szExtSave+1));
        SendDlgItemMessage(hwnd, idEditSave, +++EM_SETSEL(use macros)+++, 0, MAKELONG(6,0));

	/* Don't let user type more than cbRootNameMax bytes in edit ctl. */
        SendDlgItemMessage(hwnd, idEditSave, EM_LIMITTEXT, cbRootNameMax-1, 0L);

	/* fill list box with filenames that match spec, and fill static
	   field with path name */
	if (!DlgDirList(hwnd, g(szExtSave+1), IDCN_LISTBOXDIR, idPathSave, ATTRDIRLIST))
	    EndDialog(hwnd, 0);
	if (!DlgDirList(hwnd, g(szExtSave+1), IDCN_LISTBOX, idPathSave, ATTRFILELIST))
	    EndDialog(hwnd, 0);
	break;

      case WM_COMMAND:
	wParam = GET_WM_COMMAND_ID(wParam, lParam);
#ifdef NEVER
/*  The following lines are commented out because they introduce this bug:
    When the edit field becomes empty, OK button is not greyed
    SANKAR 06-21-89.
*/
	if (wParam == idEditSave)
	    wParam = ID_EDIT;
#endif
	switch (wParam) {
        case IDOK:
LoadIt:
            vfOpenFileReadOnly=IsDlgButtonChecked(hwnd, IDCN_READONLY);

            if (IsWindowEnabled(GetDlgItem(hwnd, IDOK)))
                {
		/* Get contents of edit field */
		/* Add search spec if it does not contain one. */
		len = 7 + GetWindowTextLength (GetDlgItem(hwnd, IDCN_EDIT));
		GetDlgItemText(hwnd, idEditSave, gszFileNameSave, len);
		lstrcpy(grgch, szFileNameSave);

		/* Append appropriate extension to user's entry */
		cDlgAddCorrectExtension(rgch, TRUE);

		/* Try to open directory.  If successful, fill listbox with
		   contents of new directory.  Otherwise, open datafile. */
                if (cFSearchSpec(rgch))
                    {
                    if (DlgDirList(hwnd, grgch, IDCN_LISTBOXDIR, idPathSave, ATTRDIRLIST))
                        {
                        lstrcpy(gszFileNameSave, rgch);
                        DlgDirList(hwnd, grgch, IDCN_LISTBOX, idPathSave, ATTRFILELIST);
			SetDlgItemText(hwnd, idEditSave, gszFileNameSave);
			break;
                        }
                    }


		cDlgAddCorrectExtension(szFileNameSave, FALSE);
		/* If no directory list and filename contained search spec,
		   honk and don't try to open. */
		if (cFSearchSpec(szFileNameSave)) {
		    MessageBeep(0);
		    break;
		}

		/* Make filename upper case and if it's a legal dos
		   name, try to open the file. */
		AnsiUpper(gszFileNameSave);
		if (cDlgCheckFileName(szFileNameSave)) {
		    result = 1;
		    *pfpSave = MOpenFile(gszFileNameSave, (LPOFSTRUCT)rgbOpenSave, OF_PROMPT+OF_CANCEL);
		    if ((*pfpSave == -1) &&
			(((LPOFSTRUCT)rgbOpenSave)->nErrCode == 0))
                            result = 2;
            else    {        /* successful file open */
                strcpy(szLastDir, ((LPOFSTRUCT)rgbOpenSave)->szPathName);
                szLastDir[strlen(szLastDir)-strlen(szFileNameSave)] = 0;
            }
        }
		EndDialog(hwnd, result);
	    }
	    break;

	case IDCANCEL:
            /* User pressed cancel.  Just take down dialog box. */
            vfOpenFileReadOnly=bRO; /* And restore this. */
	    EndDialog(hwnd, 0);
	    break;

	/* User single clicked or doubled clicked in listbox -
	   Single click means fill edit box with selection.
	   Double click means go ahead and open the selection. */
	case IDCN_LISTBOX:
	case IDCN_LISTBOXDIR:
	    switch (GET_WM_COMMAND_ID(wParam, lParam)) {

	    /* Single click case */
	    case 1:
                GetDlgItemText(hwnd, idEditSave, grgch, cbRootNameMax+1);

		/* Get selection, which may be either a prefix to a new search
		   path or a filename. DlgDirSelectEx parses selection, and
                   appends a backslash if selection is a prefix */

                if (wParam==IDCN_LISTBOXDIR)
                    SendDlgItemMessage(hwnd, IDCN_LISTBOX, LB_SETCURSEL, -1, 0L);
                else
                    SendDlgItemMessage(hwnd, IDCN_LISTBOXDIR, LB_SETCURSEL, -1, 0L);

                nx=DLGDIRSELECT(hwnd, szFileNameSave, +++nLen+++, wParam);

                if (nx)
                    {
		    cchDir = lstrlen(gszFileNameSave);
		    cchFile = lstrlen(grgch);
		    pchFile = rgch+cchFile;

                    /* Now see if there are any wild characters (* or ?) in
		       edit field.  If so, append to prefix. If edit field
		       contains no wild cards append default search spec
		       which is  "*.TXT" for notepad. */
		    fWild = (*pchFile == '*' || *pchFile == ':');
		    while (pchFile > rgch) {
			pchFile = (CHAR *)LOWORD((LONG)AnsiPrev(g(rgch), pchFile));
			if (*pchFile == '*' || *pchFile == '?')
			    fWild = TRUE;
			if (*pchFile == '\\' || *pchFile == ':') {
			    pchFile = (CHAR *)LOWORD((LONG)AnsiNext(gpchFile));
			    break;
			}
		    }
		    if (fWild)
			lstrcpy(gszFileNameSave + cchDir, pchFile);
		    else
			lstrcpy(gszFileNameSave + cchDir, (szExtSave+1));
		}

		/* Set edit field to entire file/path name. */
		SetDlgItemText(hwnd, idEditSave, gszFileNameSave);

		break;

	    /* Double click case - first click has already been processed
	       as single click */
	    case 2:
		/* Basically the same as ok.  If new selection is directory,
                   open it and list it.  Otherwise, open file. */
#if NEVER
/* None of this code is necessary.  A double click is more than basically the
   same as pressing OK, it is EXACTLY the same as pressing OK.  No point in
   duplicating all this code, especially since it will bring up 2 consecutive
   System-Modal Dialogs if the path investigated references drive A with the
   door open.   Clark Cyr, 14 August 1989                                     */

                DlgDirList(hwnd, szFileNameSave, IDCN_LISTBOX, idPathSave,ATTRFILELIST);

                if (DlgDirList (hwnd, szFileNameSave, IDCN_LISTBOXDIR,IDCN_PATH, ATTRDIRLIST))
                    {
		    SetDlgItemText(hwnd, idEditSave, gszFileNameSave);
		    break;
                    }
#endif
		goto LoadIt;	/* go load it up */
	    }
	    break;

	case IDCN_EDIT:
	    cDlgCheckOkEnable(hwnd, idEditSave, GET_WM_COMMAND_ID(wParam, lParam));
		
	    break;

	default:
	    return(FALSE);
	}
    default:
	return FALSE;
    }
    return(TRUE);
}


/* ** Enable ok button in a dialog box if and only if edit item
      contains text.  Edit item must have id of idEditSave */
VOID APIENTRY cDlgCheckOkEnable(
    HWND	hwnd,
    INT	idEdit,
    WORD message)
{
    if (message == EN_CHANGE) {
	EnableWindow(GetDlgItem(hwnd, IDOK), (SendMessage(GetDlgItem(hwnd, idEdit), WM_GETTEXTLENGTH, 0, 0L)));
    }
}

/* ** Given filename or partial filename or search spec or partial
      search spec, add appropriate extension. */
VOID cDlgAddCorrectExtension(CHAR *szEdit, WORD fSearching)
{
    register CHAR    *pchLast;
    register CHAR    *pchT;
    INT ichExt;
    BOOL    fDone = FALSE;
    INT     cchEdit;

    pchT = pchLast = (CHAR *)LOWORD((LONG)AnsiPrev(gszEdit, (szEdit + (cchEdit = lstrlen(szEdit)))));

    if ((*pchLast == '.' && *(AnsiPrev(gszEdit, pchLast)) == '.') && cchEdit == 2)
	ichExt = 0;
    else if (*pchLast == '\\' || *pchLast == ':')
	ichExt = 1;
    else {
	ichExt = fSearching ? 0 : 2;
	for (; pchT > szEdit; pchT = (CHAR *)LOWORD((LONG)AnsiPrev(gszEdit, pchT))) {
	    /* If we're not searching and we encounter a period, don't add
	       any extension.  If we are searching, period is assumed to be
	       part of directory name, so go ahead and add extension. However,
	       if we are searching and find a search spec, do not add any
	       extension. */
	    if (fSearching) {
		if (*pchT == '*' || *pchT == '?')
		    return;
	    } else if (*pchT == '.'){
		return;
	    }
	    /* Quit when we get to beginning of last node. */
	    if (*pchT == '\\')
		break;
	}
	/* Special case hack fix since AnsiPrev can not return value less than
	   szEdit. If first char is wild card, return without appending. */
	if (fSearching && (*pchT == '*' || *pchT == '?'))
	    return;
    }
#ifdef DBCS
    lstrcpy(gAnsiNext(pchLast), (szExtSave+ichExt));
#else
    lstrcpy(g(pchLast+1), (szExtSave+ichExt));
#endif
}

/* ** Check for legal filename. Strip leading blanks and
      0 terminate */
BOOL  APIENTRY cDlgCheckFilename(register CHAR	*pch)
{
#ifndef CRISPY

    OFSTRUCT	ofT;
    return (MOpenFile(gpch, (LPOFSTRUCT)&ofT, OF_PARSE) == 0);
}
#else


    INT     cchFN;
    register INT     cchT;
    CHAR	*pchIn;
    CHAR	*pchFirst;
    CHAR	*pchSave;
    INT     s;
    BOOL	fBackSlash;

    s = 0;
    fBackSlash = FALSE;
    pchIn = pch;
    for (;; pch = AnsiNext(gpch)) {

	switch (s) {

	/* Trim leading blanks */
	case 0:
	    if (*pch == ' ')
		break;

	    if (*pch == '\\') {
		pchFirst = pch;
		cchT = 0;
		s = 2;
	    } else if (*pch == 0 || !cIsChLegal(*pch)) {
		return FALSE;

	    } else {
		pchFirst = pch;
		cchT = 1;
		if (*pch == '.')
		    s = 5;
		else
		    s = 1;
	    }
	    break;

	/* Volume, drive, subdirectory	node or filename */
	case 1:
	    if (*pch == ':' && cchT == 1) {
		if (*(pch-1) < 'A' || *(pch-1) > 'Z')
		    return FALSE;
		s = 2;
		cchT--;
	    } else if (*pch == '\\') {
		if (*(pch+1) == '\\')
		    return FALSE;
		cchT = 0;
	     } else if (*pch == '.') {
		cchT = 0;
		s = 3;
	    } else if (!cIsChLegal(*pch))
		return FALSE;
	    else if (*pch == 0)
		goto RetGood;
	    else if (cchT++) {
		s++;
	    }
	    break;

	/* sub directory node or filename */
	case 2:
	    if (*pch == '\\') {
		if (*(pch+1) == '\\')
		    return FALSE;
		fBackSlash = TRUE;
		cchT = 0;
	    } else if (*pch == '.') {
		if (*pch+1 == '.') {
		    if (fBackSlash || cchT)
			return FALSE;
		    s = 5;
		    cchT = 1;
		} else {
		    s++;
		    cchT = 0;
		}
	    } else if (*pch == 0)
		goto RetGood;
	    else if (cchT++ > 7 || *pch == ' ' || !IsChLegal(*pch))
		return FALSE;
	    break;

	/* up to three characters in extension */
	case 3:
	    if (*pch == 0) {
		goto   RetGood;
	    }

	    if (cchT++ > 2 || *pch == '.' || !IsChLegal(*pch))
		return FALSE;

	    if (*pch == ' ') {
		pchSave = pch;
		s++;
	    }
	    break;

	/* Trim trailing blanks */
	case 4:
	    if (*pch == 0) {
		*pchSave = 0;
		goto RetGood;
	    } else if (*pch != ' ')
		return FALSE;
	    break;

	/* check for ..\ */
	case 5:
	    if (*pch++ != '.' || *pch != '\\')
		return FALSE;
	    cchT = 0;
	    fBackSlash = TRUE;
	    s = 2;
	    break;
	}
    }
RetGood:
    if (pchFirst != pchIn)
	lstrcpy(gpchIn, pchFirst);
    return TRUE;
}

/* ** Check for legal MS-DOS filename characters.
      return TRUE if legal, FALSE otherwise. */
BOOL APIENTRY cIsChLegal(INT	ch)
{
	register CHAR	 *pch = rgchNg;
	register INT ich = 0;

	if (ch < ' ')
	    return FALSE;

	rgchNg[CCHNG-1] = ch;
#ifdef DBCS
        while (ch != *pch){
	    if( IsDBCSLeadByte( *pch ) )
		ich++;
            ich++;
            pch = AnsiNext(pch);
        }
#else
	while (ch != *pch++)
	    ich++;
#endif

	return (ich == CCHNG-1);
}
#endif

/* ** return TRUE iff 0 terminated string contains a '*' or '\' */
BOOL  APIENTRY cFSearchSpec(register CHAR *sz)
{
#ifdef DBCS
    for (; *sz;sz=AnsiNext(sz)){
	if (*sz == '*' || *sz == '?')
	    return TRUE;
    }
#else
    for (; *sz;sz++) {
	if (*sz == '*' || *sz == '?')
	    return TRUE;
    }
#endif
    return FALSE;
}
#endif