/*
 * wvpath.c
 *
 * based on ddlist example from MSDN (Copyright (c)1992 Kraig Brockschmidt)
 *
 * john s. cooper
 * 9/10/94
 *
 * $Id: wvpath.c 1.5 1994/11/28 22:04:33 jcooper Exp $
 */

#include <windows.h>
#include <windowsx.h>
#include "wvglob.h"
#include "winvn.h"
#pragma hdrstop
#include <dos.h>
#include <io.h>					// for _access
#include <stdlib.h>
#include <stdio.h>
#include <direct.h>
#include <string.h>

/* forward declarations */

void DriveListInitialize (HWND, HWND);
UINT DriveType (UINT);
void DirectoryListInitialize (HWND, HWND, LPSTR);
void DriveDirDrawItem (LPDRAWITEMSTRUCT, BOOL);
void TransparentBlt (HDC, UINT, UINT, HBITMAP, COLORREF);


//Special ROP code for TransparentBlt.
#define ROP_DSPDxax  0x00E20746

/*
 * DriveListInitialize
 *
 * Purpose:
 *  Resets and fills the given combobox with a list of current
 *  drives.  The type of drive is stored with the item data.
 *
 * Parameters:
 *  hList           HWND of the combobox to fill.
 *  hTempList       HWND to use for finding drives.
 *
 * Return Value:
 *  None
 */
#define MAXITEMLEN 30
void
DriveListInitialize (HWND hList, HWND hTempList)
{
#ifdef _WIN32
  char VolumeNameBuffer[256];	/* address of name of the volume */
  DWORD MaximumComponentLength;	/* address of system's max. length of filename  */
  DWORD FileSystemFlags;		/* address of file system flags   */
  DWORD iCheck;
#else
  struct find_t fi;
  char ch;
#endif
  UINT i, iItem;
  UINT cch, cItems;
  UINT iDrive, iType;
  UINT iCurDrive;
  char szDrive[10];
  char szNet[64];
  char szItem[MAXITEMLEN];

  if (NULL == hList)
	return;

  //Clear out all the lists.
  SendMessage (hList, CB_RESETCONTENT, 0, 0L);
  SendMessage (hTempList, LB_RESETCONTENT, 0, 0L);

  //Get available drive letters in the temp list
  SendMessage (hTempList, LB_DIR, DDL_DRIVES | DDL_EXCLUSIVE, (LONG) (LPSTR) "*");

  iCurDrive = _getdrive () - 1;	//Fix for zero-based drive indexing

  /*
   * Walk through the list of drives, parsing off the "[-" and "-]"
   * For each drive letter parsed, add a string to the combobox
   * composed of the drive letter and the volume label.  We then
   * determine the drive type and add that information as the item data.
   */
  cItems = (int) SendMessage (hTempList, LB_GETCOUNT, 0, 0L);

  for (i = 0; i < cItems; i++) {
	SendMessage (hTempList, LB_GETTEXT, i, (LONG) (LPSTR) szDrive);

	//Insure lowercase drive letters
	AnsiLower (szDrive);
	iDrive = szDrive[2] - 'a';
	iType = DriveType (iDrive);	//See Below

	if (iType < 2)				//Skip non-existent drive B's
	  continue;

	//Start the item string with the drive letter, color, and two spaces
	wsprintf (szItem, "%c%s", szDrive[2], (LPSTR) ":  ");

	/*
	 * For fixed or ram disks, append the volume label which we find
	 * under WIN32 with GetVolumeInformation() and with DOS
	 * using _dos_findfirst and attribute _A_VOLID.
	 */
	if (DRIVE_FIXED == iType || DRIVE_RAMDISK == iType) {
#ifdef _WIN32
	  wsprintf (szDrive, "%c:\\", szDrive[2]);
	  if (GetVolumeInformation (szDrive,
								VolumeNameBuffer, sizeof (VolumeNameBuffer),
								NULL, &MaximumComponentLength,
								&FileSystemFlags, NULL, 0))
		strncat (szItem, VolumeNameBuffer, MAXITEMLEN - lstrlen (szItem) - 1);
	}
#else
	  wsprintf (szDrive, "%c:\\*.*", szDrive[2]);
	  if (0 == _dos_findfirst (szDrive, _A_VOLID, &fi)) {
		//Convert volume to lowercase and strip any . in the name.
		AnsiLower (fi.name);

		//If a period exists, it has to be in position 8, so clear it.
		ch = fi.name[8];
		fi.name[8] = 0;
		strncat (szItem, fi.name, MAXITEMLEN - lstrlen (szItem) - 1);

		//If we did have a broken volume name, append the last 3 chars
		if ('.' == ch)
		  strncat (szItem, &fi.name[9], MAXITEMLEN - lstrlen (szItem) - 1);
	  }
	}
#endif

	//For network drives, go grab the \\server\share for it.
	if (DRIVE_REMOTE == iType) {
	  szNet[0] = 0;
	  cch = sizeof (szNet);

	  wsprintf (szDrive, "%c:", szDrive[2]);
	  AnsiUpper (szDrive);

#ifdef _WIN32
	  if (WNetGetConnection ((LPSTR) szDrive, (LPSTR) szNet, &cch) == NO_ERROR) {
#else
	  if (WNetGetConnection ((LPSTR) szDrive, (LPSTR) szNet, &cch) == WN_SUCCESS) {
#endif
		AnsiLower (szNet);
		strncat (szItem, szNet, MAXITEMLEN - lstrlen (szItem) - 1);
	  }
	}

#ifdef _WIN32
	//For CDROM first check that it is present.
	//SetErrorMode(SEM_FAILCRITICALERRORS) causes NT to pass on critical errors to the app.
	//This results in no abort/retry/ignore message box if no CD is present.
	//    **(Any way of doing this for DOS-Windows?  Any need?)**
	if (DRIVE_CDROM == iType) {
	  wsprintf (szDrive, "%c:\\.", szDrive[2]);		//A path of \. is required !?

	  SetErrorMode (SEM_FAILCRITICALERRORS);
	  iCheck = GetFileAttributes (szDrive);
	  SetErrorMode (0);
	  if (iCheck != 0xFFFFFFFF)	//No error, so CD must be in.

		if (GetVolumeInformation ((LPSTR) szDrive, (LPSTR) VolumeNameBuffer, (DWORD) sizeof (VolumeNameBuffer), NULL, NULL, NULL, NULL, 0))
		  strncat (szItem, VolumeNameBuffer, MAXITEMLEN - lstrlen (szItem) - 1);
	}
#endif

	iItem = (int) SendMessage (hList, CB_ADDSTRING, 0, (LONG) (LPSTR) szItem);
	SendMessage (hList, CB_SETITEMDATA, iItem, MAKELONG (iDrive, iType));

	if (iDrive == iCurDrive)
	  SendMessage (hList, CB_SETCURSEL, iItem, 0L);
  }

  return;
}

/*
 * DriveType
 *
 * Purpose:
 *  Augments the Windows API GetDriveType with a call to the CD-ROM
 *  extensions to determine if a drive is a floppy, hard disk, CD-ROM,
 *  RAM-drive, or networked  drive.
 *
 * Parameters:
 *  iDrive          UINT containing the zero-based drive index
 *
 * Return Value:
 *  UINT            One of the following values describing the drive:
 *                  DRIVE_FLOPPY, DRIVE_HARD, DRIVE_CDROM, DRIVE_RAMDISK,
 *                  DRIVE_NETWORK.
 */

UINT DriveType(UINT iDrive)
    {
    int     iType;
    BOOL    fCDROM=FALSE;
    BOOL    fRAM=FALSE;
#ifdef _WIN32
   char  szDrive[4];
#endif

    //Validate possible drive indices
    if (0 > iDrive  || 25 < iDrive)
        return 0xFFFF;  //Should this be -1, not the same in WIN32


#ifdef _WIN32
   //In WIN32, GetDriveType() takes a string with root path name,
   //not drive numbers!
   szDrive[0] = (char) iDrive + 'A';
   szDrive[1] = ':';
   szDrive[2] = '\\';
   szDrive[3] = '\0';
    iType = GetDriveType(szDrive);
#else
    iType = GetDriveType(iDrive);

    //Check for CDROM on FIXED and REMOTE drives only
    if (DRIVE_FIXED==iType || DRIVE_REMOTE==iType)
        {
        _asm
            {
            mov     ax,1500h        //Check if MSCDEX exists
            xor     bx,bx
            int     2fh

            or      bx,bx           //BX unchanged if MSCDEX is not around
            jz      CheckRAMDrive   //No?  Go check for RAM drive.

            mov     ax,150Bh        //Check if drive is using CD driver
            mov     cx,iDrive
            int     2fh

            mov     fCDROM,ax       //AX if the CD-ROM flag
            or      ax,ax
            jnz     Exit            //Leave if we found a CD-ROM drive.

            CheckRAMDrive:
            }
        }

    //Check for RAM drives on FIXED disks only.
    if (DRIVE_FIXED==iType)
        {
        /*
         * Check for RAM drive is done by reading the boot sector and
         * looking at the number of FATs.  Ramdisks only have 1 while
         * all others have 2.
         */
        _asm
            {
            push    ds

            mov     bx,ss
            mov     ds,bx

            sub     sp,0200h            //Reserve 512 bytes to read a sector
            mov     bx,sp               //and point BX there.

            mov     ax,iDrive           //Read the boot sector of the drive
            mov     cx,1
            xor     dx,dx

            int     25h
            add     sp,2                //Int 25h requires our stack cleanup.
            jc      DriveNotRAM

            mov     bx,sp
            cmp     ss:[bx+15h],0f8h    //Reverify fixed disk
            jne     DriveNotRAM
            cmp     ss:[bx+10h],1       //Check if there's only one FATs
            jne     DriveNotRAM
            mov     fRAM,1

            DriveNotRAM:
            add     sp,0200h
            pop     ds

            Exit:
            //Leave fRAM untouched  it's FALSE by default.
            }
        }

    /*
     * If either CD-ROM or RAM drive flags are set, return privately
     * defined flags for them (outside of Win32).  Otherwise return
     * the type given from GetDriveType.
     */

    if (fCDROM)
        return DRIVE_CDROM;

    if (fRAM)
        return DRIVE_RAMDISK;

    //Drive B on a one drive system returns < 2 from GetDriveType.
#endif
    return iType;
    }

/*
 * DirectoryListInitialize
 *
 * Purpose:
 *  Initializes strings in a listbox given a directory path.  The first
 *  string in the listbox is the drive letter.  The remaining items are
 *  each directory in the path completed with a listing of all sub-
 *  directories under the last directory in the path.
 *
 * Parameters:
 *  hList           HWND of the listbox to fill.
 *  hTempList       HWND of a listbox to use for  directory enumerations.
 *  pszDir          LPSTR of the path to use in initialization assumed
 *                  to be at least _MAX_DIR long.
 *
 * Return Value:
 *  None
 */

void
DirectoryListInitialize (HWND hList, HWND hTempList, LPSTR pszDir)
{
  LPSTR psz, pszLast;
  char ch;
  char szDir[_MAX_DIR];
  UINT cch, cItems;
  UINT i, iItem, iIndent;
  BOOL fFirst = TRUE;

  if (NULL == hList || NULL == pszDir)
	return;

  //If the path ends in a \, strip the '\'
  cch = lstrlen (pszDir);

  if ('\\' == pszDir[cch - 1])
	pszDir[cch - 1] = 0;

  //Clear out all the lists.
  SendMessage (hList, WM_SETREDRAW, FALSE, 0L);
  SendMessage (hList, LB_RESETCONTENT, 0, 0L);
  SendMessage (hTempList, LB_RESETCONTENT, 0, 0L);

  /*
   * Walk through the path one \ at a time.  At each one found,
   * we add the string composed of the characters between it and
   * the last \ found.  We also make sure everything is lower case.
   */

  pszLast = AnsiLower (pszDir);

  //Save this for changing directories
  SetWindowText (hList, pszDir);

  //Save the directory appended with \*.*
  wsprintf (szDir, "%s\\*.*", pszDir);

  while (TRUE) {
	psz = _fstrchr (pszLast, '\\');

	if (NULL != psz) {
	  /*
	   * Save the character here so we can NULL terminate.  If this
	   * if the first entry, it's a drive root, so keep the \
	   */

	  if (fFirst)
		ch = *(++psz);
	  else
		ch = *psz;

	  *psz = 0;
	}
	else {
	  //If we're looking at a drive only, then append a backslash
	  if (pszLast == pszDir && fFirst)
		lstrcat (pszLast, "\\");
	}

	//Add the drive string--includes the last one where psz==NULL
	iItem = (UINT) SendMessage (hList, LB_ADDSTRING, 0, (LONG) pszLast);

	/*
	 * The item data here has in the HIWORD the bitmap to use for
	 * the item with the LOWORD containing the indentation.  The
	 * bitmap is IDB_FOLDEROPEN for anything above the current
	 * directory (that is, c:\foo is above than c:\foo\bar),
	 * IDB_FOLDERCLOSED for anything below the current, and
	 * IDB_FOLDEROPENSELECT for the current directory.
	 */

	i = (NULL != psz) ? IDB_FOLDEROPEN : IDB_FOLDEROPENSELECT;
	SendMessage (hList, LB_SETITEMDATA, iItem, MAKELONG (iItem, i));

	if (NULL == psz)
	  break;

	//Restore last character.
	*psz = ch;
	psz += (fFirst) ? 0 : 1;

	fFirst = FALSE;
	pszLast = psz;
  }


  /*
   * Now that we have the path in, enumerate the subdirectories here
   * and place them in the list at the indentation iItem+1 since iItem
   * was the index of the last item in the path added to the list.
   *
   * To enumerate the directories, we send LB_DIR to an alternate
   * listbox.  On return, we have to parse off the brackets around
   * those directory names before bringing them into this listbox.
   */

  iIndent = iItem + 1;

  //Get available directories; szDir is pszDir\*.*
  SendMessage (hTempList, LB_DIR, DDL_DIRECTORY | DDL_EXCLUSIVE
			   ,(LONG) (LPSTR) szDir);

  cItems = (int) SendMessage (hTempList, LB_GETCOUNT, 0, 0L);

  for (i = 0; i < cItems; i++) {
	cch = (UINT) SendMessage (hTempList, LB_GETTEXT, i, (LONG) (LPSTR) szDir);

	//Skip directories beginning with . (skipping . and ..)
	if ('.' == szDir[1])
	  continue;

	//Remove the ending ']'
	szDir[cch - 1] = 0;

	//Add the string to the real directory list.
	iItem = (UINT) SendMessage (hList, LB_ADDSTRING, 0
								,(LONG) (LPSTR) (szDir + 1));
	SendMessage (hList, LB_SETITEMDATA, iItem
				 ,MAKELONG (iIndent, IDB_FOLDERCLOSED));
  }

  //Force a listbox repaint.
  SendMessage (hList, WM_SETREDRAW, TRUE, 0L);
  InvalidateRect (hList, NULL, TRUE);

  /*
   * If there's a vertical scrollbar, then we've added more items than
   * are visible at once.  To meet the UI specifications, we're supposed
   * to make the next directory up the top visible one.
   */
  GetScrollRange (hList, SB_VERT, (LPINT) & i, (LPINT) & iItem);

  if (!(0 == i && 0 == iItem))
	SendMessage (hList, LB_SETTOPINDEX, max ((int) (iIndent - 2), 0), 0L);

  //Last thing is to set the current directory as the selection
  SendMessage (hList, LB_SETCURSEL, iIndent - 1, 0L);
  return;
}

/*
 * DriveDirDrawItem
 *
 * Purpose:
 *  Handles WM_DRAWITEM for both drive and directory listboxes.
 *
 * Parameters:
 *  pDI             LPDRAWITEMSTRUCT passed with the WM_DRAWITEM message.
 *  fDrive          BOOL TRUE to draw a drive, FALSE to draw directory.
 *
 * Return Value:
 *  None
 */

void
DriveDirDrawItem (LPDRAWITEMSTRUCT pDI, BOOL fDrive)
{
  char szItem[MAXITEMLEN + 1];
  int iType = 0;
  int iIndent = 0;
  UINT uMsg;
  DWORD dw;
  BITMAP bm;
  COLORREF crText, crBack;
  HBITMAP hBmp;

  if ((int) pDI->itemID < 0)
	return;

  if (fDrive)
	dw = SendMessage (pDI->hwndItem, CB_GETITEMDATA, pDI->itemID, 0L);

  //Get the text string for this item (controls have different messages)
  uMsg = (fDrive) ? CB_GETLBTEXT : LB_GETTEXT;
  SendMessage (pDI->hwndItem, uMsg, pDI->itemID, (LONG) (LPSTR) szItem);

  if ((ODA_DRAWENTIRE | ODA_SELECT) & pDI->itemAction) {
	if (ODS_SELECTED & pDI->itemState) {
	  //Select the appropriate text colors
	  crText = SetTextColor (pDI->hDC, GetSysColor (COLOR_HIGHLIGHTTEXT));
	  crBack = SetBkColor (pDI->hDC, GetSysColor (COLOR_HIGHLIGHT));
	}

	/*
	 * References to the two bitmap arrays here are the only external
	 * dependencies of this code.  To keep it simple, we didn't use
	 * a more complex scheme like putting all the images into one bitmap.
	 */
	if (fDrive) {
	  //For drives, get the type, which determines the bitmap.
	  iType = (int) HIWORD (dw);
	  hBmp = DiskBitmaps[iType - IDB_DRIVEMIN];
	}
	else {
	  //For directories, indentation level is 4 pixels per indent.
	  iIndent = 4 * (1 + LOWORD (pDI->itemData));
	  hBmp = FolderBitmaps[HIWORD (pDI->itemData) - IDB_FOLDERMIN];
	}

	GetObject (hBmp, sizeof (bm), &bm);

	/*
	 * Paint the text and the rectangle in whatever colors.  If
	 * we're drawing drives, iIndent is zero so it's ineffective.
	 */
	ExtTextOut (pDI->hDC, pDI->rcItem.left + bm.bmWidth + 4 + iIndent
				,pDI->rcItem.top, ETO_OPAQUE, &pDI->rcItem, szItem
				,lstrlen (szItem), (LPINT) NULL);


	//Go draw the bitmap we want.
	TransparentBlt (pDI->hDC, pDI->rcItem.left + iIndent
					,pDI->rcItem.top, hBmp, RGB (0, 0, 255));

	//Restore original colors if we changed them above.
	if (ODS_SELECTED & pDI->itemState) {
	  SetTextColor (pDI->hDC, crText);
	  SetBkColor (pDI->hDC, crBack);
	}
  }

  if ((ODA_FOCUS & pDI->itemAction) || (ODS_FOCUS & pDI->itemState))
	DrawFocusRect (pDI->hDC, &pDI->rcItem);

  return;
}

/*
 * TransparentBlt
 *
 *  Given a DC, a bitmap, and a color to assume as transparent in that
 *  bitmap, BitBlts the bitmap to the DC letting the existing background
 *  show in place of the transparent color.
 *
 * Parameters:
 *  hDC             HDC on which to draw.
 *  x, y            UINT location at which to draw the bitmap
 *  hBmp            HBITMIP to draw
 *  cr              COLORREF to consider as transparent.
 */

void
TransparentBlt (HDC hDC, UINT x, UINT y, HBITMAP hBmp, COLORREF cr)
{
  HDC hDCSrc, hDCMid, hMemDC;
  HBITMAP hBmpMono, hBmpT;
  HBRUSH hBr, hBrT;
  COLORREF crBack, crText;
  BITMAP bm;

  if (NULL == hBmp)
	return;

  GetObject (hBmp, sizeof (bm), &bm);

  //Get three intermediate DC's
  hDCSrc = CreateCompatibleDC (hDC);
  hDCMid = CreateCompatibleDC (hDC);
  hMemDC = CreateCompatibleDC (hDC);

  SelectObject (hDCSrc, hBmp);

  //Create a monochrome bitmap for masking
  hBmpMono = CreateCompatibleBitmap (hDCMid, bm.bmWidth, bm.bmHeight);
  SelectObject (hDCMid, hBmpMono);

  //Create a middle bitmap
  hBmpT = CreateCompatibleBitmap (hDC, bm.bmWidth, bm.bmHeight);
  SelectObject (hMemDC, hBmpT);


  //Create a monochrome mask where we have 0's in the image, 1's elsewhere.
  crBack = SetBkColor (hDCSrc, cr);
  BitBlt (hDCMid, 0, 0, bm.bmWidth, bm.bmHeight, hDCSrc, 0, 0, SRCCOPY);
  SetBkColor (hDCSrc, crBack);

  //Put the unmodified image in the temporary bitmap
  BitBlt (hMemDC, 0, 0, bm.bmWidth, bm.bmHeight, hDCSrc, 0, 0, SRCCOPY);

  //Create an select a brush of the background color
  hBr = CreateSolidBrush (GetBkColor (hDC));
  hBrT = SelectObject (hMemDC, hBr);

  //Force conversion of the monochrome to stay black and white.
  crText = SetTextColor (hMemDC, 0L);
  crBack = SetBkColor (hMemDC, RGB (255, 255, 255));

  /*
   * Where the monochrome mask is 1, Blt the brush; where the mono mask
   * is 0, leave the destination untouches.  This results in painting
   * around the image with the background brush.  We do this first
   * in the temporary bitmap, then put the whole thing to the screen.
   */
  BitBlt (hMemDC, 0, 0, bm.bmWidth, bm.bmHeight, hDCMid, 0, 0, ROP_DSPDxax);
  BitBlt (hDC, x, y, bm.bmWidth, bm.bmHeight, hMemDC, 0, 0, SRCCOPY);


  SetTextColor (hMemDC, crText);
  SetBkColor (hMemDC, crBack);

  SelectObject (hMemDC, hBrT);
  DeleteObject (hBr);

  DeleteDC (hMemDC);
  DeleteDC (hDCSrc);
  DeleteDC (hDCMid);
  DeleteObject (hBmpT);
  DeleteObject (hBmpMono);

  return;
}

/*
 * WinVnSelectPathDialogDlg
 *
 *  Handles the dialog that displays a drive and directory owner-draw
 *  combobox and listbox, respectiviely.  This procedure handles only
 *  the WM_INITDIALOG and WM_DRAWITEM messages,dispatching them to
 *  alternate functions that handle each message for each list.
 *
 */

BOOL FAR PASCAL
WinVnSelectPathDlg (HWND hDlg, unsigned iMessage, WPARAM wParam, LPARAM lParam)
{

  char cwd[_MAX_PATH];
  char drive;

  WORD wID;
  WORD wCode;
  HWND hWndMsg;

  hWndMsg = (HWND) (UINT) lParam;
  wID = LOWORD (wParam);
#ifdef WIN32
  wCode = HIWORD (wParam);
#else
  wCode = HIWORD (lParam);
#endif

  switch (iMessage) {
  case WM_INITDIALOG:
	if (lParam && _access ((char *) lParam, 0) >= 0) {
	  strcpy (cwd, (char *) lParam);
	  _chdir (cwd);
	  drive = tolower(*cwd);
	  if (drive >= 'a' && drive <= 'z' && cwd[1] == ':') {
	    _chdrive ((int) (drive - 'a' + 1));
	  }
	}
	else {
	  _getcwd (cwd, _MAX_PATH);
	}

	DriveListInitialize (GetDlgItem (hDlg, ID_DRIVELIST), GetDlgItem (hDlg, ID_TEMPLIST));
	DirectoryListInitialize (GetDlgItem (hDlg, ID_DIRECTORYLIST), GetDlgItem (hDlg, ID_TEMPLIST), cwd);
	ShowWindow (hDlg, SW_SHOW);
	UpdateWindow (hDlg);
	return TRUE;

  case WM_MEASUREITEM:
	{
	  static int cyItem = -1;	//Height of a listbox item

	  LPMEASUREITEMSTRUCT pMI;

	  pMI = (LPMEASUREITEMSTRUCT) lParam;

	  if (-1 == cyItem) {
		HFONT hFont;
		HDC hDC;
		TEXTMETRIC tm;
		BITMAP bm;

		/*
		 * Attempt to get the font of the dialog. However,
		 * on the first attempt in the life of the dialog,
		 * this could fail; in that case use the system font.
		 */
		hFont = (HANDLE) (UINT) SendMessage (hDlg, WM_GETFONT, 0, 0L);

		if (NULL == hFont)
		  hFont = GetStockObject (SYSTEM_FONT);

		hDC = GetDC (hDlg);
		hFont = SelectObject (hDC, hFont);

		/*
		 * Item height is the maximum of the font height and the
		 * bitmap height.  We know that all bitmaps are the same
		 * size, so we just get information for one of them.
		 */
		GetTextMetrics (hDC, &tm);
		GetObject (FolderBitmaps[0], sizeof (bm), &bm);
		cyItem = max (bm.bmHeight, tm.tmHeight);

		ReleaseDC (hDlg, hDC);
	  }

	  pMI->itemHeight = cyItem;
	}
	break;


  case WM_DRAWITEM:
	//Go draw the appropriate item and bitmap.
	DriveDirDrawItem ((LPDRAWITEMSTRUCT) lParam, (ID_DRIVELIST == wID));

	//Prevent default actions in listboxes (drawing focus rect)
	return TRUE;


  case WM_COMMAND:
	switch (wID) {
	case ID_DIRECTORYLIST:
	  if (LBN_DBLCLK == wCode) {
		UINT i;
		DWORD dw;
		char szDir[_MAX_PATH];
		LPSTR psz;

		/*
		 * On double-click, change directory and reinit the
		 * listbox.  But all we stored in the string was
		 * the directory's single name, so we use the bitmap
		 * type to tell if we're below the current directory
		 * (in which case we just chdir to our name) or above
		 * (in which case we prepend "..\"'s as necessary.
		 */
		i = (UINT) SendMessage (hWndMsg, LB_GETCURSEL, 0, 0L);
		dw = SendMessage (hWndMsg, LB_GETITEMDATA, i, 0L);

		/*
		 * If out bitmap is IDB_FOLDERCLOSED or the root,
		 * then just .  If we're IDB_FOLDEROPENSELECT,
		 * don't do anything.  If we're IDB_FOLDEROPEN then
		 * we get the full current path and truncate it
		 * after the directory to which we're switching.
		 */
		if (IDB_FOLDEROPENSELECT == HIWORD (dw))
		  break;

		//Get get the directory for sub-directory changes.
		SendMessage (hWndMsg, LB_GETTEXT, i, (LONG) (LPSTR) cwd);

		if (IDB_FOLDEROPEN == HIWORD (dw) && 0 != i) {
		  //Get the current path and find us in this path
		  GetWindowText (hWndMsg, szDir, sizeof (szDir));
		  psz = _fstrstr (szDir, cwd);

		  //Null terminate right after us.
		  *(psz + lstrlen (cwd)) = 0;

		  //Get this new directory in the right place
		  lstrcpy (cwd, szDir);
		}

		//chdir has a nice way of validating for us.
		if (0 == _chdir (cwd)) {
		  //Get the new full path.
		  _getcwd (cwd, _MAX_PATH);

		  DirectoryListInitialize (hWndMsg
								   ,GetDlgItem (hDlg, ID_TEMPLIST), cwd);
		}
	  }
	  break;


	case ID_DRIVELIST:
	  if (CBN_SELCHANGE == wCode) {
		UINT i, iCurDrive;
		char szDrive[18];		//Enough for drive:volume

		//Get the first letter in the current selection
		i = (UINT) SendMessage (hWndMsg, CB_GETCURSEL, 0, 0L);
		SendMessage (hWndMsg, CB_GETLBTEXT
					 ,i, (LONG) (LPSTR) szDrive);

		iCurDrive = _getdrive ();	//Save in case of restore

		/*
		 * Attempt to set the drive and get the current
		 * directory on it.  Both must work for the change
		 * to be certain.  If we are certain, reinitialize
		 * the directories.  Note that we depend on drives
		 * stored as lower case in the combobox.
		 */

		if (0 == _chdrive ((int) (szDrive[0] - 'a' + 1))
			&& NULL != _getcwd (cwd, _MAX_PATH)) {
		  DirectoryListInitialize (
									GetDlgItem (hDlg, ID_DIRECTORYLIST),
									GetDlgItem (hDlg, ID_TEMPLIST), cwd);

		  //Insure that the root is visible (UI guideline)
		  SendDlgItemMessage (hDlg, ID_DIRECTORYLIST
							  ,LB_SETTOPINDEX, 0, 0L);

		  break;
		}

		//Changing drives failed so restore drive and selection
		_chdrive ((int) iCurDrive);

		wsprintf (szDrive, "%c:", (char) (iCurDrive + 'a' - 1));
		i = (UINT) SendMessage (hWndMsg, CB_SELECTSTRING
								,(WPARAM) - 1, (LONG) (LPSTR) szDrive);
	  }

	  break;

	case IDOK:
	  _getcwd (DialogString, MAXINTERNALLINE);
	  EndDialog (hDlg, TRUE);
	  break;
	case IDCANCEL:
	  EndDialog (hDlg, FALSE);
	  break;
	}
	break;

  default:
	break;
  }

  return FALSE;
}
