/* START.C  -- Popup Windows Application Starter
	       with thanks and apologies to Charles Petzold & Ray Duncan.
	       Thanks also to William B. McCormick for qsprintf (saves
	       almost 4k in exe size!) and the anonymous author of Exec.

	       START.EXE reads text strings from a data file (START.INI) into
	       a listbox.  Start will EXEC the application indicated in
	       the listbox when it detects a double click or an Enter key-
	       press.  The format for START.INI corresponds to the format
	       for WIN.INI:
      [Start]
      AppName=AppName.exe, OptionalCmdLine, Opt2ndApp.exe, Opt2ndCndLine, etc..

	       Start displays AppName in the listbox, and will EXEC all
	       the parameters after the "=" which include ".EXE", ".COM"
	       or ".PIF".  The optional command lines can be parameters to
	       pass onto the application (specific files to load or command
	       line switches) or drive and directory information which tells
	       START where to EXEC the application. (This corresponds to the
	       "Initial directory" information in a PIF file.)
	       Example:

      Write letterhead memo=spell.pif, d:\docs, write.exe, d:\docs\&lhmemo.wri

	       In the above example, if you double click on
	       "Write letterhead memo" in the listbox, START will
	       EXEC WRITE.EXE from the d:\docs directory, and pass
	       on "d:\docs\&lhmemo.wri" as a command line parameter
	       to WRITE.EXE, and then EXEC SPELL.PIF from the d:\docs directory.

	       Lines in the START.INI file can also take the form

      Next Menu=...

	       If you double-click on this listbox entry, START will
	       reload the listbox with all the entries found under the

      [Next Menu]

	       block in the START.INI file.  Start allows nesting to four
	       levels.	An Esc or Backspace will back you up one menu level.
	       The top menu level will always be the entries found under
	       the [Start] block.

Things to do:

Add awareness of current level....					Done
Add change to new level 						Done
Add keyboard interface accelerators					Done
Add command line parameters						Done
Add chdir								Done
Fix chdir								Done
Add chdrive								Done
Add return to default drive & dir					Done
Add Hourglass cursor when reading new applist				Done
Add search for [MenuName] then for AppData..				Done
Add unique search for AppData						Done
Add Start Min/Max							Done
Add resizing of parent to listbox					Done
Fix Start BATCH mode							Done
Add Configuration file edit						Done
Add Help system  (szHelpVar loads resource text)			Done
Add Start Options dialog						Done
Change all sprintf to qsprintf						Done
Fix around %c problem with qsprintf					Done
Add minimize secondary applications to options, exec loop		Done
Add auto create ini file from text resource
Check memory considerations.
*/

#define NOMINMAX
#include <windows.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <direct.h>
#include <dos.h>

#include "start.h"
#define LISTID 100
#define APPSTR "                                        "

static	 char  szAppName []   = "Start";
static	 char *szAppSet []    = { APPSTR, APPSTR, APPSTR, APPSTR };
static	 char  szCfgFile [128];
char	 szAppTitle []	      = APPSTR;
char	 szExeFileName [68], szCmdLine [128];

static	 int   nCurSet	      = 0;
OFSTRUCT ofCfgFile ;
BOOL	bCreate, bMinStart, bMin2ndApps;
int   nApps, nCurSel, nExeShow, nDefShow;
HANDLE hInst, hHelpDlg;

char szBlip[256];

long  FAR PASCAL WndProc      (HWND, unsigned, WORD, LONG);
BOOL  FAR PASCAL AboutDlgProc (HWND, unsigned, WORD, LONG);
BOOL  FAR PASCAL HelpDlgProc  (HWND, unsigned, WORD, LONG);
BOOL  FAR PASCAL OptDlgProc   (HWND, unsigned, WORD, LONG);
BOOL  ChangePath(char *, int *);
BOOL  GetAppsList (HWND, HWND, char *);
BOOL  GetAppData (HWND, char *,char *, char *, char * );
int   GetAppStr (int, char *, int );
int   OkMessage (HWND, WORD, char *, WORD);
int   FAR PASCAL Exec( LPSTR, LPSTR, int );
int   qsprintf( char *dest, const char *fmt, ... );

int PASCAL WinMain (hInstance, hPrevInstance, lpszCmdLine, nCmdShow)
   HANDLE   hInstance, hPrevInstance;
   LPSTR    lpszCmdLine;
   int	    nCmdShow;
   {
   MSG	       msg ;
   HWND        hWnd ;
   HANDLE      hAccel ;
   WNDCLASS    wndclass ;

   if (!hPrevInstance)
      {
      wndclass.style	      = CS_HREDRAW | CS_VREDRAW;
      wndclass.lpfnWndProc    = WndProc;
      wndclass.cbClsExtra     = 0;
      wndclass.cbWndExtra     = 0;
      wndclass.hInstance      = hInstance;
      wndclass.hIcon	      = LoadIcon (hInstance, szAppName);
      wndclass.hCursor	      = LoadCursor (NULL, IDC_ARROW);
      wndclass.hbrBackground  = GetStockObject (WHITE_BRUSH);
      wndclass.lpszMenuName   = szAppName;
      wndclass.lpszClassName  = szAppName;

      if (!RegisterClass (&wndclass))
	 return FALSE;
      }

   hInst = hInstance;

   hWnd = CreateWindow (szAppName, NULL,
			WS_OVERLAPPEDWINDOW,
			/* Position in the center of the screen. */
			GetSystemMetrics (SM_CXSCREEN) / 4,
			GetSystemMetrics (SM_CYSCREEN) / 4,
			GetSystemMetrics (SM_CXSCREEN) / 2,
			GetSystemMetrics (SM_CYSCREEN) / 2,
			NULL, NULL, hInstance, lpszCmdLine);

   hAccel = LoadAccelerators (hInstance, szAppName);

   ShowWindow (hWnd, nCmdShow);
   UpdateWindow (hWnd) ;

   while (GetMessage (&msg, NULL, 0, 0))
      {
      /* Look for messages to the modal help dialog box. */
      if (hHelpDlg == 0 || !IsDialogMessage (hHelpDlg, &msg))
	 {
	 if (!TranslateAccelerator (hWnd, hAccel, &msg))
	    {
	    TranslateMessage (&msg);
	    DispatchMessage (&msg);
	    }
	 }
      }
   return msg.wParam;
   }

BOOL  FAR PASCAL AboutDlgProc (hDlg, iMessage, wParam, lParam)
      HWND     hDlg ;
      unsigned iMessage ;
      WORD     wParam ;
      LONG     lParam ;
      {
      switch (iMessage)
	 {
	 case WM_INITDIALOG:
	    break;
	 case WM_COMMAND:
	    switch (wParam)
	       {
	       case IDOK:
		  EndDialog (hDlg, 0) ;
		  break;

	       default:
		  return FALSE ;
	       }
	    default:
	       return FALSE ;
	    }
	 return TRUE ;
	 }

BOOL  FAR PASCAL HelpDlgProc (hDlg, iMessage, wParam, lParam)
      /* Mostly stolen from Petzold's PoePoem.c */
      HWND     hDlg ;
      unsigned iMessage ;
      WORD     wParam ;
      LONG     lParam ;
      {
      static HANDLE  hResource ;
      static HWND    hScroll ;
      static short   nPosition, xChar, yChar, yClient, nNumLines, xScroll;
      char far	     *lpText;
      HDC	     hDC;
      PAINTSTRUCT    ps;
      RECT	     rect;
      TEXTMETRIC     tm ;
      switch (iMessage)
	 {
	 case WM_INITDIALOG:
	    hDC = GetDC (hDlg);
	    SelectObject (hDC, GetStockObject (ANSI_VAR_FONT));
	    GetTextMetrics (hDC, &tm);
	    xChar = tm.tmAveCharWidth * 2 ;
	    yChar = tm.tmHeight + tm.tmExternalLeading ;
	    ReleaseDC (hDlg, hDC) ;

	    xScroll = GetSystemMetrics (SM_CXVSCROLL) ;
	    hScroll = GetDlgItem (hDlg, IDM_HELPSCROLL);
	    GetClientRect (hDlg, &rect) ;
	    InvalidateRect (hDlg, &rect, TRUE) ;
	    MoveWindow (hScroll, rect.right - xScroll, 0,
		  xScroll, rect.bottom, TRUE) ;

	    hResource = LoadResource (hInst,
			   FindResource (hInst, "HELPTEXT", "TEXT"));

	    if (hResource == NULL)
	       {
	       OkMessage(hDlg, IDS_NOHELP, NULL, NULL);
	       DestroyWindow(hDlg) ;
	       break;
	       }
	    lpText = LockResource (hResource);

	    nNumLines = 0;
	    while (*lpText != '\0' && *lpText != '\x1A')
	       {
	       if (*lpText == '\n')
		  nNumLines ++ ;
	       lpText = AnsiNext (lpText);
	       }
	    *lpText = '\0' ;

	    GlobalUnlock (hResource) ;

	    SetScrollRange (hScroll, SB_CTL, 0, nNumLines, FALSE) ;
	    SetScrollPos   (hScroll, SB_CTL, 0, FALSE) ;
	    break;

	 case WM_SIZE:
	    InvalidateRect (hDlg, NULL, TRUE) ;
	    MoveWindow (hScroll, LOWORD (lParam) - xScroll, 0,
		  xScroll, yClient = HIWORD (lParam), TRUE) ;
	    SetFocus (hDlg);
	    break;

	 case WM_SETFOCUS:
	    SetFocus (hScroll) ;
	    break;

	 case WM_VSCROLL:
	    switch (wParam)
	       {
	       case SB_TOP:
		  nPosition = 0;
		  break;
	       case SB_BOTTOM:
		  nPosition = nNumLines;
		  break;
	       case SB_LINEUP:
		  nPosition -= 1;
		  break;
	       case SB_LINEDOWN:
		  nPosition += 1;
		  break;
	       case SB_PAGEUP:
		  nPosition -= yClient / yChar;
		  break;
	       case SB_PAGEDOWN:
		  nPosition += yClient / yChar;
		  break;
	       case SB_THUMBPOSITION:
		  nPosition = LOWORD (lParam);
		  break;
	       }
	 nPosition = max (0, min (nPosition, nNumLines)) ;
	 if (nPosition != GetScrollPos (hScroll, SB_CTL))
	    {
	    SetScrollPos (hScroll, SB_CTL, nPosition, TRUE) ;
	    GetClientRect (hDlg, &rect) ;
	    rect.left += xChar ;
	    rect.right -= xScroll + xChar ;
	    //rect.top	+= yChar * (1 - nPosition) ;
	    InvalidateRect (hDlg, &rect, TRUE) ;
	    }
	 break;

      case WM_PAINT:
	 hDC = BeginPaint (hDlg, &ps) ;
	 SelectObject (hDC, GetStockObject (ANSI_VAR_FONT));

	 lpText = LockResource (hResource) ;

	 GetClientRect (hDlg, &rect) ;
	 rect.left += xChar ;
	 rect.right -= xScroll + xChar ;
	 rect.top  += yChar * (1 - nPosition) ;
	 DrawText (hDC, lpText, -1, &rect,
		     DT_WORDBREAK | /*DT_NOCLIP | */ DT_EXTERNALLEADING) ;

	 GlobalUnlock (hResource) ;

	 EndPaint (hDlg, &ps) ;
	 break ;

	 case WM_CLOSE:
	    FreeResource (hResource);
	    DestroyWindow (hDlg) ;
	    hHelpDlg = 0 ;
	    break;

	 default:
	    return FALSE ;
	 }
      return TRUE ;
      }

void DoCaption (hWnd, szExeApp)
   /* Adapted from Petzold's PopPad.c */
   HWND  hWnd ;
   char  *szExeApp ;
   {
   char  szCaption [40] ;
   qsprintf (szCaption, "%s %s", szAppName,
	       0!=lstrcmp(szAppName,szExeApp) ? szExeApp : "Programs") ;
   SetWindowText (hWnd, szCaption) ;
   }

BOOL  FAR PASCAL OptDlgProc (hDlg, iMessage, wParam, lParam)
      HWND     hDlg ;
      unsigned iMessage ;
      WORD     wParam ;
      LONG     lParam ;
      {
      int n;
      switch (iMessage)
	 {
	 case WM_INITDIALOG:
	    CheckDlgButton (hDlg,  IDM_MINSTART, (WORD) bMinStart);
	    CheckDlgButton (hDlg, IDM_MINSECOND, (WORD) bMin2ndApps);
	    CheckRadioButton (hDlg, IDM_STARTREG, IDM_STARTMAX, nDefShow + 200);
	    break;

	 case WM_COMMAND:
	    switch (wParam)
	       {
	       case IDOK:
		  bMinStart =	(BOOL) IsDlgButtonChecked (hDlg, IDM_MINSTART);
		  bMin2ndApps = (BOOL) IsDlgButtonChecked (hDlg, IDM_MINSECOND);
		  WriteProfileString (szAppName, "Minimize Start",
			       (LPSTR) itoa( (int) bMinStart, szCfgFile, 10));
		  WriteProfileString (szAppName, "Minimize Secondary",
			       (LPSTR) itoa( (int) bMin2ndApps, szCfgFile, 10));
		  for (n = IDM_STARTREG; n <= IDM_STARTMAX; n++)
		     /* Find which radio button is clicked.. */
		     {
		     if (SendMessage(GetDlgItem(hDlg, n), BM_GETCHECK, 0, 0L))
			nExeShow = nDefShow = n - 200;
		     }
		  WriteProfileString (szAppName, "StartApps",
				 (LPSTR) itoa( nDefShow, szCfgFile, 10));

	       case IDCANCEL:
		  EndDialog (hDlg, 0) ;
		  break;

	       default:
		  return FALSE ;
	       }
	    default:
	       return FALSE ;
	    }
	 return TRUE ;
	 }

long FAR PASCAL WndProc (hWnd, iMessage, wParam, lParam)
   HWND     hWnd;
   unsigned iMessage;
   WORD     wParam;
   LONG     lParam;
   {
   static FARPROC lpfnAboutDlgProc, lpfnHelpDlgProc, lpfnOptDlgProc;
   static HWND	  hWndList, hCurWnd;
   static HMENU   hSysMenu ;
   static HANDLE  hResource ;
   char far	  *lpText;
   OFSTRUCT	  of ;
   static char	  szBuffer[80], szCmdBuff[82];
   char 	  szBatchInfo[256];
   static unsigned nDrives, nDefDrive;
   static char	  szDefDir[_MAX_DIR];
   static int	  yFrame, xScroll;
   RECT 	  rect, crect, lrect;
   int		  n, nCode, hIniFile;
   BOOL 	  bNotFirstPass;

   switch (iMessage)
      {
      case WM_CREATE:
	 /* Find functions for the dialog boxes. */
	 lpfnAboutDlgProc =	 MakeProcInstance (AboutDlgProc, hInst) ;
	 lpfnHelpDlgProc =	 MakeProcInstance (HelpDlgProc,  hInst) ;
	 lpfnOptDlgProc =	 MakeProcInstance (OptDlgProc,	 hInst) ;

	 /* Add "About" to the system menu. */
	 hSysMenu = GetSystemMenu(hWnd, FALSE) ;
	 ChangeMenu( hSysMenu, 0, NULL, 0, MF_APPEND) ;
	 ChangeMenu( hSysMenu, 0, "A&bout Start...", IDM_ABOUT, MF_APPEND) ;

	 /* Get default drive & directory. */
	 _dos_getdrive (&nDefDrive);
	 getcwd(szDefDir, _MAX_DIR);

	 /* Get location (and name) of data file from win.ini */
	 GetProfileString (szAppName, "Config File", "start.ini", szCfgFile,
			      sizeof szCfgFile);
	 while (-1 == OpenFile( szCfgFile, &ofCfgFile, OF_EXIST))
	    {
	    /* Add auto creat here..*/
	    if (IDYES == OkMessage (hWnd, IDS_NOINIFILE, szCfgFile,
	       MB_ICONHAND | MB_YESNO))
	       {
	       if (-1 == (hIniFile = OpenFile( szCfgFile, &ofCfgFile,
			      OF_CREATE | OF_WRITE)))
		  {
		  OkMessage(hWnd, IDS_CANTCREATE, szCfgFile, NULL);
		  break;
		  }
	       else
		  {
		  hResource = LoadResource (hInst,
			      FindResource (hInst, "INITEXT", "TEXT"));
		  lpText = LockResource (hResource);

		  while (*lpText != '\0' && *lpText != '\x1A')
		     {
		     _lwrite  (hIniFile, (LPSTR) lpText, 1) ;
		     lpText++;
		     }

		  _lclose  (hIniFile) ;
		  GlobalUnlock (hResource) ;
		  }
	       }
	    else
	       {
	       MessageBeep(0);
	       break;
	       }
	    }

	 /* Get exec defaults from win.ini */
	 bMinStart =   (BOOL) GetProfileInt(szAppName, "Minimize Start", 0);
	 bMin2ndApps = (BOOL) GetProfileInt(szAppName, "Minimize Secondary", 1);
	 nDefShow = GetProfileInt (szAppName, "StartApps", 1);
	 nExeShow = nDefShow;

	 /* Tell the parent window not to redraw until it has been sized to
	    the listbox. */
	 SendMessage(hWnd, WM_SETREDRAW, FALSE, 0L);
	 DoCaption(hWnd, szAppName);

	 /* Set the top level "menulist" name. */
	 nCurSet = 0;
	 qsprintf(szAppSet[nCurSet], "[%s]", szAppName);

	 /* Create the listbox for the "menu" items. */
	 hWndList = CreateWindow ("listbox", NULL,
		  WS_CHILD | WS_VSCROLL | LBS_NOTIFY,
		  0, -1, 0, 0,
		  hWnd, LISTID, hInst, NULL) ;

	 /* Used for sizing the windows. */
	 yFrame =  GetSystemMetrics (SM_CYFRAME) ;
	 xScroll = GetSystemMetrics (SM_CXVSCROLL) ;
	 /* Used for initializing the listbox. */
	 bCreate = TRUE;
	 break;

      case WM_SYSCOMMAND:
	 switch (wParam)
	    {
	    case IDM_ABOUT:
	       DialogBox (hInst, "AboutBox", hWnd, lpfnAboutDlgProc) ;
	       break ;
	    }
	 return DefWindowProc (hWnd, iMessage, wParam, lParam) ;

      case WM_SETFOCUS:
	 SetFocus (hWndList);
	 break;

      case WM_SIZE:
	 /* This sizes the listbox to the parent window.  Because a
	    listbox's vertical size is limited to descrete units based
	    upon the system font text height, we must resize the
	    parent window to the new listbox size.  The MoveWindow()
	    for the parent window will generate another WM_SIZE message.
	    We can prevent processing this second message by comparing
	    the client rects of the parent and the listbox. */

	 GetClientRect(hWnd, &crect);
	 GetClientRect(hWndList, &lrect);
	 if (crect.bottom == lrect.bottom && \
	     crect.right  == lrect.right + xScroll)
	    break;

	 /* Size the listbox to the parent window. The addition of
	    yFrame to the listbox height adds some "dithering" so that
	    the net resize of the parent window will be less. */
	 MoveWindow (hWndList, 0, 0, LOWORD (lParam),
				     HIWORD (lParam)+yFrame, TRUE);

	 /* Don't resize parent if going to full screen. */
	 if (wParam == SIZENORMAL)
	    {
	    /* Size the parent window to the listbox. */
	    GetWindowRect(hWndList, &lrect);
	    GetWindowRect(hWnd, &rect);
	    MoveWindow (hWnd, rect.left, rect.top,
			rect.right - rect.left,
			lrect.bottom + yFrame - rect.top, TRUE);
	    }

	 /* Load the listbox with the top level "menulist." */
	 if (bCreate)
	    {
	    GetAppsList( hWnd, hWndList, szAppSet[nCurSet]);
	    SendMessage (hWndList, LB_SETCURSEL, 0, (LONG) NULL);
	    /* Style of listbox was not initially visible. */
	    ShowWindow(hWndList, SW_SHOWNA);
	    SendMessage(hWnd, WM_SETREDRAW, TRUE, 0L);
	    bCreate = FALSE;
	    }
	 break;

      case WM_COMMAND:
	 switch (wParam)
	    {
	    case IDM_STARTREG:
	       /* "Enter" pressed. */
	       nExeShow = nDefShow;
	       goto START1;
	    case IDM_STARTMIN:
	       /* Shft + Enter pressed. */
	    case IDM_STARTMAX:
	       /* Ctrl + Enter pressed. */
	       /*
	       SW_SHOWNORMAL	  (1) for an open window.
	       SW_SHOWMINNOACTIVE (7) for an iconic window.
	       SW_SHOWMAXIMIZED   (3) for a maximized window.
	       */
	       nExeShow = (int) wParam - 200;
	       START1:
	       SendMessage (hWnd, WM_COMMAND, LISTID,
			     MAKELONG(hWndList,LBN_DBLCLK));
	       break;

	    case IDM_BACK:
	       /* Backspace or Esc pressed...get previous list */
	       nCurSet--;
	       nCurSet = nCurSet < 0 ? 0 : nCurSet;
	       lstrcpy(szBuffer, &szAppSet[nCurSet][1]);
	       szBuffer[lstrlen(szAppSet[nCurSet])-2] = '\0';
	       DoCaption(hWnd, szBuffer);
	       GetAppsList( hWnd, hWndList, szAppSet[nCurSet]);
	       break;

	    case IDM_EDIT:
	       /* This starts notepad with the start.ini file.	If WIN.INI
		  includes a block such as

		  [Start]
		  ConfigFile=c:\blip\flurg.ini

		  the use of the ofCfgFile.szPathName will insure that
		  notepad finds the correct data file to edit. */

	       /* qsprintf does not properly format %c */
	       szCmdBuff[0] = (char) qsprintf(szCmdBuff," %s\r",
				       ofCfgFile.szPathName) - 2;
	       Exec( (LPSTR) "notepad.exe", szCmdBuff, SW_SHOW);
	       break;

	    case IDM_HELP:
	       hHelpDlg = CreateDialog (hInst, "HelpBox",
					hWnd, lpfnHelpDlgProc) ;
	       break;

	    case IDM_OPT:
	       DialogBox (hInst, "OptBox", hWnd, lpfnOptDlgProc) ;
	       break ;

	    case LISTID:
	       if ( HIWORD (lParam) == LBN_DBLCLK )
		  {
		  nCurSel = (int) SendMessage (hWndList, LB_GETCURSEL, 0, 0L);
		  /* This traps a double click past the end of a list.	This
		     is important because a LB_GETTEXT message to the listbox
		     with a negitive index will lockup windows! */
		  if (nCurSel == -1)
		     {
		     nCurSel = nApps;
		     SendMessage (hWndList, LB_SETCURSEL, nCurSel, (LONG) NULL);
		     }

		  SendMessage (hWndList, LB_GETTEXT,
					 nCurSel, (LONG) szAppTitle);
		  DoCaption( hWnd, szAppTitle);

		  /* If data from the config file is found..*/
		  if (GetAppData( hWnd, szAppSet[nCurSet], szAppTitle,
					szExeFileName, szBatchInfo))
					//szExeFileName, szCmdLine))
		     {
		     /* if "..." then get next menu. */
		     if (0==lstrcmp(szExeFileName,"..."))
			{
			nCurSet++;
			/* Start has a menu nesting limit. */
			if (nCurSet > 3)
			   {
			   OkMessage (hWnd, IDS_NESTERROR, NULL, NULL);
			   nCurSet--;
			   break;
			   }
			qsprintf(szAppSet[nCurSet],"[%s]", szAppTitle);
			GetAppsList(hWnd, hWndList, szAppSet[nCurSet]);
			}

		     /* else execute program */
		     else
			{
			if (nExeShow == SW_SHOWMINIMIZED)
			   {
			   nExeShow = SW_SHOWMINNOACTIVE;
			   hCurWnd = GetFocus();
			   }
			lstrcpy(szCmdLine, strtok(szBatchInfo, ", "));
			bNotFirstPass = FALSE;
			while (TRUE)
			   {
			   /* Exec all possible parameters in the
			      command line. */
			   ChangePath( szCmdLine, &nCode);
			   if (nCode == _IS_EXE)
			      lstrcpy(szCmdBuff,"\x00\r");
			   else
			      szCmdBuff[0] = (char) qsprintf(szCmdBuff,
						" %s\r", szCmdLine) - 2;
			   if (szExeFileName[0] != '\0')
			      Exec( (LPSTR) szExeFileName, szCmdBuff,
					  bNotFirstPass && bMin2ndApps ?
				      SW_SHOWMINNOACTIVE : nExeShow);
			   bNotFirstPass = TRUE;
			   /*if (nCode == _IS_EXE)*/
			   lstrcpy(szExeFileName, nCode == _IS_EXE ? \
				    szCmdLine : "\0");

			   _dos_setdrive ( nDefDrive, &nDrives);
			   chdir(szDefDir);

			   lstrcpy(szCmdLine, strtok(NULL, ", "));
			   if (szCmdLine[0] == '\0' && szExeFileName[0] == '\0')
			      break;
			   }
			/* Clean up. */
			if (nExeShow == SW_SHOWMINNOACTIVE)
			   SetFocus(hCurWnd);
			else if (bMinStart)
			   ShowWindow(hWnd,SW_SHOWMINNOACTIVE);
			nExeShow = nDefShow;
			}
		     }
		  }
	    }
	    break ;

      case WM_CLOSE:
	 DestroyWindow (hWnd);
	 break;

      case WM_QUERYENDSESSION:
	 return 1L;
	 break;

      case WM_DESTROY:
	 PostQuitMessage (0);
	 break;

      default:
	 return DefWindowProc (hWnd, iMessage, wParam, lParam) ;
      }
   return 0L;
   }

BOOL ChangePath(szPath, nCode)
   char  *szPath ;
   int	 *nCode ;
   {
   char  szDrive[_MAX_DRIVE], szDir[_MAX_DIR], szFname[9],
	 szExt[_MAX_EXT];
   unsigned nDrive, nDrives, nFileAttr;

   /* if executable file, return FALSE. */
   if ( strstr(strupr(szPath),
		".EXE") || strstr(szPath, ".COM") || strstr(szPath, ".PIF"))
      {
      *nCode = _IS_EXE;
      return FALSE;
      }

   /* if pathname ends with backslash(es) */
   while (szPath[lstrlen(szPath)-1] == '\\')
      szPath[lstrlen(szPath)-1] = '\0';

   /* if valid file or directory */
   if (0 == _dos_getfileattr(szPath, &nFileAttr))
      {
      *nCode = _IS_FILE;
      _splitpath(szPath, szDrive, szDir, szFname, szExt);
      if (nFileAttr == _A_SUBDIR)
	 {
	 *nCode = _IS_DIR;
	 lstrcpy(szDir, szPath);
	 szFname[0] = '\0';
	 szPath[0] = '\0';
	 }

      nDrive = (unsigned) toupper(szDrive[0]) - 64;
      if (nDrive >= 0)
	 _dos_setdrive ( nDrive, &nDrives);

      /* get rid of final backslash */
      if (szDir[lstrlen(szDir)-1] == '\\')
	 szDir[lstrlen(szDir)-1] = '\0';

      if (szDir[0] != '\0')
	 chdir(szDir);

      return TRUE;
      }
   else
      {
      *nCode = _IS_NOT_VALIDPATH;
      return FALSE;
      }
   }



BOOL GetAppsList (hWnd, hList, szApp)
   HWND  hWnd, hList ;		       /* Window handle, listbox handle. */
   char  *szApp ;		       /* Application header title. */
   {
   char  szAppStr1 [128], szAppStr2 [256] ;
   int	 hCfgFile, nAppStrLen ;
   HCURSOR hCursor;

   /* Open configuration file. */
   hCfgFile = OpenFile( szCfgFile, &ofCfgFile, OF_READ | OF_REOPEN);
   if (hCfgFile == -1)
      {
      OkMessage (hWnd, IDS_FILENOTFOUND, ofCfgFile.szPathName, NULL);
      return FALSE;
      }

   hCursor = SetCursor (LoadCursor (NULL, IDC_WAIT));

   /* Read file and find [szApp] */
   nAppStrLen = 1;
   while ( (nAppStrLen = GetAppStr(hCfgFile, szAppStr2, 256)) != -1)
      {
      if (lstrcmp(szApp,szAppStr2)==0)
	 break;
      }

   if (nAppStrLen == -1)
      {
      OkMessage(hWnd, IDS_INFONOTFOUND, szApp, NULL);
      _lclose( hCfgFile );
      SetCursor (hCursor);
      return FALSE;
      }

   /* Parse App strings and load into message box. */
   nApps = -1;
   /* Turn off listbox screen update */
   SendMessage (hList, WM_SETREDRAW, FALSE, 0L);
   SendMessage (hList, LB_RESETCONTENT, 0, 0L);

   while ( (nAppStrLen = GetAppStr(hCfgFile, szAppStr2, 256)) > 0)
      {
      /* Ignore comments. */
      if (szAppStr2[0] == ';')
	 continue;
      /* Break if new application header. */
      if (szAppStr2[0] == '[')
	 break;
      /* Load list box. */
      SendMessage (hList, LB_ADDSTRING, 0, (LONG) strtok(szAppStr2,"="));
      nApps++;
      }
   _lclose( hCfgFile );

   /* This should redraw the list! */
   SendMessage (hList, LB_GETTEXT, 0, (LONG) szAppStr2);
   SendMessage (hList, LB_DELETESTRING, 0, 0L);
   SendMessage (hList, WM_SETREDRAW, TRUE, 0L);
   SendMessage (hList, LB_INSERTSTRING, 0, (LONG) szAppStr2);
   SendMessage (hList, LB_SETCURSEL, 0, (LONG) NULL);

   SetCursor (hCursor);
   return TRUE;
   }

BOOL GetAppData (hWnd, szSectionTitle, szAppTitle, szAppExe, szCmdLine)
   HWND  hWnd;
   char * szSectionTitle;
   char * szAppTitle;
   char * szAppExe;
   char * szCmdLine;
   {
   char szAppData [256];
   int nAppStrLen, hCfgFile, n;
   /* Open configuration file. */
   if (-1 == (hCfgFile = OpenFile( szCfgFile, &ofCfgFile, OF_READ | OF_REOPEN)))
      {
      OkMessage(hWnd, IDS_FILENOTFOUND, ofCfgFile.szPathName, NULL);
      return FALSE;
      }

   /* Search config file for szSectionTitle. */
   nAppStrLen = 1;
   while ( (nAppStrLen = GetAppStr(hCfgFile, szAppData, 256)) != -1)
      {
      if (lstrcmp(szAppData, szSectionTitle)==0)
	 break;
      }

   if (nAppStrLen == -1)
      {
      OkMessage(hWnd, IDS_INFONOTFOUND, szSectionTitle, NULL);
      _lclose( hCfgFile );
      return FALSE;
      }

   /* Search config file for szAppTitle. */
   nAppStrLen = 1;
   while ( (nAppStrLen = GetAppStr(hCfgFile, szAppData, 256)) != -1)
      {
      if (lstrcmp(strtok(szAppData,"="),szAppTitle) == 0)
	 break;
      }

   if (nAppStrLen == -1)
      {
      OkMessage(hWnd, IDS_INFONOTFOUND, szAppTitle, NULL);
      _lclose( hCfgFile );
      return FALSE;
      }

   lstrcpy (szAppExe, strtok(NULL, ", "));
   lstrcpy (szCmdLine, strtok(NULL, ""));
   /* this gives us the rest of the line, including leading spaces...
      so get rid of them..or we won't have a valid file name. */
   while (szCmdLine[0] == ' ')
      {
      for (n=0; n < strlen(szCmdLine); n++)
	 szCmdLine[n] = szCmdLine[n+1];
      }

   _lclose( hCfgFile );
   return TRUE;
   }

int GetAppStr (hFile, szBuff, nLim)
   /* Mostly stolen from a Ray Duncan PC Magazine article. */
   int	 hFile;
   char  *szBuff;
   int	 nLim;
   {
   char *p;
   int	 i, j;

   i = _lread( hFile, szBuff, nLim -1);
   if (i == 0)			       /* EOF */
      return (-1);
   else
      szBuff[i] = 0;

   p = strstr (szBuff, "\x0d\x0a");    /* point to newline */
   if (p == NULL)
      return (-1);		       /* no newline delim or EOF */

   j = p - szBuff + 2;

   _llseek (hFile, (long) (j-i), 1);
   j -= 2;
   szBuff[j] = '\0';

   return (j);
   }

int OkMessage (hWnd, wErrorNumber, szOptParam, nButtons)
   HWND  hWnd ;
   WORD  wErrorNumber ;
   char  *szOptParam ;
   WORD  nButtons;
   {
   char szFormat [128], szBuffer [256];
   nButtons = nButtons == NULL ? MB_OK | MB_ICONEXCLAMATION : nButtons;
   if (! wErrorNumber == NULL)
      {
      LoadString (hInst, wErrorNumber, szFormat, sizeof szFormat);
      qsprintf (szBuffer, szFormat, szOptParam);
      }
   else
      lstrcpy(szBuffer, szOptParam);
   return MessageBox (hWnd, szBuffer, szAppName,
			nButtons);
   }
