
/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples. 
*       Copyright (C) 1996 Microsoft Corporation.
*       All rights reserved. 
*       This source code is only intended as a supplement to 
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the 
*       Microsoft samples programs.
\******************************************************************************/

//    PROGRAM: EXPDIR.C
//
//    PURPOSE: Directory Listbox expansion/collapse functions

#define  STRICT

#include "cfiler.h"

extern HANDLE ghModule;

/**********************************************************************************\
* ExpDir()
*
* Thread Function
* Called when directory is selected (Enter or Double-click).  Expands subdirs
*  of selected directory if not expanded, indenting subdirs.  If
*  already expanded, deletes all subdirs from listbox
*
* Returns:  TRUE if successful, FALSE if error.
\***********************************************************************************/

BOOL ExpDir(LPCINFO lpCInfo)
{
    HANDLE          hFile = NULL;   //  Find file handle
    WIN32_FIND_DATA FileData;       //  Find file info structure

    TCHAR           szFileName[BUF_SIZE];
	TCHAR			szFileName2[BUF_SIZE];
	TCHAR			szInsertString[DIRECTORY_STRING_SIZE << 1];
	TCHAR			szBuf[BUF_SIZE];

    PTCHAR          lpszHold;      //  Points to actual name in LB string

    BOOL            fDone = FALSE;  //  Loop flag for finding files in dir

    DWORD           dwAttrib;       //  Holds file attribute flags

    LONG            lIndex,         //  Index of selected listbox member
                    lDirDepth;
	
    INT i;
	
   	if (!lpCInfo) {
   		ErrorMsg(TEXT("ExpDir: lpCInfo is NULL."));
   		return FALSE;
   	}
   	
   	for (i = 0; i < BUF_SIZE; i++) {
		szFileName[i] = TEXT('\0');
		szFileName2[i] = TEXT('\0');
	}
    
    if( WaitForSingleObject( lpCInfo->hDirMutex, MUTEX_TIMEOUT)
            == WAIT_TIMEOUT ){
        ErrorMsg(TEXT("ExpDir: Dir LB Mutex Timeout."));
        return(0);
    }

    //
    // If the LB is empty, we insert the root, unexpanded
    //
    if( !SendMessage( lpCInfo->hDirLB, LB_GETCOUNT, 0, 0) ){

        TCHAR   szHold[DIRECTORY_STRING_SIZE << 1];

        szHold[0] = TEXT('+');
        lstrcpy( &(szHold[1]), lpCInfo->lpDriveInfo->DriveName );

        //
        // Also copy the DriveName to the Caption Bar.
        //
        lstrcpy(lpCInfo->CaptionBarText, lpCInfo->lpDriveInfo->DriveName );

        SendMessage(lpCInfo->hDirLB, LB_ADDSTRING, 0,
                    (LPARAM)szHold );

        if (!TableAdd(lpCInfo->DirTable, lpCInfo->lpDriveInfo->DriveName, szHold)) {
			ErrorMsg(TEXT("ExpDir: TableAdd failed."));
			return FALSE;
		}
        
        ReleaseMutex( lpCInfo->hDirMutex );
        return(1);
    }

    //
    // Retrieve index of selected (careted) directory.
    //
    lIndex = SendMessage( lpCInfo->hDirLB, LB_GETCARETINDEX,
                          (WPARAM)NULL, (LPARAM)NULL );

    if( SendMessage( lpCInfo->hDirLB, LB_GETTEXT, (WPARAM)lIndex,
                        (LPARAM)szFileName ) < 0 ){
        ErrorMsg(TEXT("Expand Directory:  Get listbox text failure"));
        ExpDirExit(lpCInfo, hFile);
        return(0);
    }

    lDirDepth = GetDirDepth( szFileName, &lpszHold );

    if( lDirDepth == -1 ){
        ErrorMsg(TEXT(" Expand Directory:  GetDirDepth failure"));
        ExpDirExit(lpCInfo, hFile);
        return(0);
    }

    //
    // If Directory is already expanded, collapse it, and vice versa.
    //  First, change the symbol.
    //
    if( *lpszHold == TEXT('-'))
        *lpszHold = TEXT('+');
    else
        *lpszHold = TEXT('-');

    //
    // Clear WM_SETREDRAW flag, so changes will not be seen until entire
    //  expansion/collapse is complete.  Reset flag at end of function
    //
    if( SendMessage(lpCInfo->hDirLB, WM_SETREDRAW, (WPARAM)FALSE,
                (LPARAM)0 ) < 0){
        ErrorMsg(TEXT("Expand Directory:  Clear redraw flag failure"));
        ExpDirExit(lpCInfo, hFile);
        return(0);
    }

    //
    // Delete old dir string, insert new, and reset the selection
    //
    if( SendMessage(lpCInfo->hDirLB, LB_DELETESTRING, (WPARAM)lIndex,
                (LPARAM)0 ) < 0){
        ErrorMsg(TEXT("Expand Directory:  Delete dir string failure"));
        ExpDirExit(lpCInfo, hFile);
        return(0);
    }
	else {
		if (!TableGetHidden(lpCInfo->DirTable, lIndex, szBuf)) {
			ErrorMsg(TEXT("ExpDir: TableGetHidden failed."));
			return 0;
		}
		lstrcpy(szInsertString, szBuf);
		TableRemove(lpCInfo->DirTable, lIndex);
	}

    if( SendMessage(lpCInfo->hDirLB, LB_INSERTSTRING, (WPARAM)lIndex,
               (LPARAM)szFileName ) < 0){
        ErrorMsg(TEXT("Expand Directory:  Insert dir string failure"));
        ExpDirExit(lpCInfo, hFile);
        return(0);
    }
	else {
		if (!TableInsert(lpCInfo->DirTable, szInsertString, szFileName, lIndex)) {
			ErrorMsg(TEXT("ExpDir: TableInsert failed."));
			return 0;
		}	
	}
    
    if( SendMessage(lpCInfo->hDirLB, LB_SETCURSEL, (WPARAM)lIndex,
               (LPARAM)0 ) < 0){
        ErrorMsg(TEXT("Expand Directory:  Insert dir string failure"));
        ExpDirExit(lpCInfo, hFile);
        return(0);
    }

    //
    // The symbol has been changed, now collapse if needed, then reset
    //  redraw flag, leave critical section, and exit.
    //
    if( *lpszHold == TEXT('+') ){
        CollapseDir( lpCInfo, lIndex, lDirDepth);
        ExpDirExit(lpCInfo, hFile);
        return(1);
    }

    //
    // If we're here, directory needs to be expanded.
    //  Enumerate subdirectories beneath the dir entry in the listbox.
    //
    // First, Get the full path of the directory (in szFileName)
    //
    if (!TableGetHidden(lpCInfo->DirTable, lIndex, szBuf)) {
    	ErrorMsg(TEXT("ExpDir: TableGetHidden failed."));
    	return FALSE;
    }
    
    lstrcpy(szFileName, szBuf);
        
    //
    // Check to see if there is a terminating backslash
    //  Then append a '*' as a wildcard for FindFirstFile.
    //
    lpszHold = &szFileName[lstrlen(szFileName)];

    lpszHold--;
    if( *lpszHold != TEXT('\\') ){
        lpszHold++;
        *lpszHold = TEXT('\\');
    }

    lstrcpy(szFileName2, szFileName);
    
    lpszHold++;
    lstrcpy( lpszHold, TEXT("*"));

    //
    // Start a search on all the files within the directory
    //
    hFile = FindFirstFile( szFileName, &FileData );
    if( hFile == INVALID_HANDLE_VALUE ){
        ErrorMsg(TEXT("Expand Directory: FindFirstFile failure."));
        ExpDirExit(lpCInfo, hFile);
        return(0);
    }

    //
    // Walk all the files in the directory.
    //
    while( !fDone ){

        //
        // Check to see if the thread has been requested to kill itself.
        //  This code does not clear the suicide flag for synchronization
        //  reasons - it is left to the calling code.
        //
        if( lpCInfo->fSuicide ){
            ErrorMsg(TEXT("Expand Directory: killing thread per request."));
            ExpDirExit(lpCInfo, hFile);

            //
            // Post an MM_REFRESH Message if the user has re-collapsed the
            //  Dir LB.
            //
            if( !lpCInfo->fDirExpand )
                if( !PostMessage(lpCInfo->hwnd, WM_COMMAND, MM_REFRESH,
                                 (LPARAM)0) )
                    ErrorMsg(TEXT("ExpDir: MM_REFRESH call failure."));

            //
            // We must return failure (0) here, so if FullExpand is calling,
            //  it will terminate.
            //
            return(0);
        }

        //
        // Append filename to path, and get attributes
        //
        lstrcpy(lpszHold, FileData.cFileName);
        dwAttrib = GetFileAttributes( szFileName );

        //
        // Check if file is a directory.  If not, or if '.' or '..', fall
        //  through.  If so, add it to the directory listbox.
        //
        if( dwAttrib & FILE_ATTRIBUTE_DIRECTORY )
            if( lstrcmp( FileData.cFileName,TEXT(".") ) )
                if( lstrcmp( FileData.cFileName,TEXT("..") ) ){
                    TCHAR   szLBEntry[DIRECTORY_STRING_SIZE << 1];
					TCHAR	szHidden[DIRECTORY_STRING_SIZE << 1];
					
					lstrcpy(szHidden, szFileName2);
					lstrcat(szHidden, FileData.cFileName);

                    if (IsEncrypted(szHidden)) {
                    	TCHAR	szDecryptedName[BUF_SIZE];
						HANDLE	hFileRead;
							
						GetDecryptedDirName(lpCInfo->hDirLB, 
											szHidden, 
											szDecryptedName, 
											0, 
											&hFileRead);	
                    	CloseHandle(hFileRead);
                    	
						if (!SimplifyFileName(szDecryptedName, szBuf)) {
							ErrorMsg(TEXT("ExpDir: SimplifyFileName failed."));
							return FALSE;
						}
						                    	
                    	lstrcpy(szDecryptedName, szBuf);

                    	ConstructLBEntry(lDirDepth, szDecryptedName, szLBEntry);
                    }
					else
						ConstructLBEntry(lDirDepth, FileData.cFileName, szLBEntry);
                    
                    //
                    //  Increment index in order to add subdir after
                    //  dirs just inserted.
                    //
                    lIndex++;
                    
                    if( SendMessage(lpCInfo->hDirLB, LB_INSERTSTRING,
                                       (WPARAM)lIndex,
                                       (LPARAM)szLBEntry) < 0){
                        ErrorMsg(TEXT("Expand Directory: error inserting string."));
                        ExpDirExit(lpCInfo, hFile);
                        return(0);
                    }
                    
                    if (!TableInsert(lpCInfo->DirTable, szHidden, szLBEntry, lIndex)) {
						ErrorMsg(TEXT("ExpDir: TableInsert failed."));
						return FALSE;
					}
     			}

        fDone = !FindNextFile( hFile, &FileData );
    }

    ExpDirExit(lpCInfo, hFile);
    return(1);
}

/***********************************************************************************\
* ExpDirExit()
*
* Performs clean-up operations for ExpDir, closing handles, etc.
\***********************************************************************************/

void ExpDirExit(LPCINFO lpCInfo, HANDLE hFile)
{
    if (!lpCInfo) {
    	ErrorMsg(TEXT("ExpDirExit: lpCInfo is NULL."));
    	return;
    }
    
    // Reset redraw flag. Post this message, to avoid synchro problems
  
    if( PostMessage(lpCInfo->hDirLB, WM_SETREDRAW, (WPARAM)TRUE,
                (LPARAM)0 ) < 0)
        ErrorMsg(TEXT("Expand Directory:  Clear redraw flag failure"));

    //
    // Close FindFirstFile session
    //
    if( hFile != NULL)
        FindClose(hFile);

    //
    // Release Dir LB Mutex
    //
    ReleaseMutex( lpCInfo->hDirMutex);
}

/***********************************************************************************\
* ConstructDirName()
*
* Builds the fully qualified path of the current directory, by walking back
*  through the Dir LB tree.
*
* Returns:  TRUE if successful, FALSE if error.
*           Returns the full directory path of given directory name in
*             lpszDirName.
\***********************************************************************************/

BOOL ConstructDirName(LPCINFO lpCInfo, LONG lIndex, LPTSTR lpszDirName)
{
    LONG    lDirDepth,      //  Depth of selected directory
            lSeekDepth = LONG_MAX;

    TCHAR   szFileName[DIRECTORY_STRING_SIZE << 1];   // file buffer
    LPTSTR  lpszInfoPtr,
            lpszHold;

    if (!lpCInfo) {
    	ErrorMsg(TEXT("ConstructDirName: lpCInfo is NULL."));
    	return FALSE;
    }
    
    //
    // Clear the directory name buffer
    //
    *lpszDirName = TEXT('\0');

    //
    // Walk up the entries in the listbox, constructing the full path from
    //   the bottom up.
    //
    while( lIndex >= 0 ){

        //
        // Get listbox text, and compute the depth of the directory
        //
        if( SendMessage( lpCInfo->hDirLB, LB_GETTEXT, (WPARAM)lIndex,
                            (LPARAM)szFileName ) < 0 ){
            ErrorMsg(TEXT(" Expand Directory:  Get listbox text failure"));
            return(0);
        }

        lDirDepth = GetDirDepth(szFileName, &lpszInfoPtr);
        if( lDirDepth == -1){
            ErrorMsg(TEXT("ConstructDirName: GetDirDepth failed"));
            return(0);
        }

        //
        // If we've reached the next level up, add to the directory name
        //
        if( lDirDepth < lSeekDepth ){
            lSeekDepth = lDirDepth;

            // check if we will exceed the size of our buffer
            if( lstrlen(lpszInfoPtr) + lstrlen(lpszDirName) >
                    (DIRECTORY_STRING_SIZE << 1) ){
                ErrorMsg(TEXT("ConstructDirName:  Exceeded Directory Size limit"));
                return(0);
            }

            // Find the end of the directory name
            lpszHold = &lpszInfoPtr[lstrlen(lpszInfoPtr)];
           
            // If we're not at the root, add a '\'
            if( lIndex && (*lpszDirName != TEXT('\0')) )
                *lpszHold++ = TEXT('\\');

            // Append the heretofore computed path to the end of the dir name
            lstrcpy( lpszHold, lpszDirName);
            // Copy the whole path so far back into the final buffer
            lstrcpy( lpszDirName, ++lpszInfoPtr);
        }

        // If the first level dir has been added, jump to the root,
        //   else go up to the previous entry in the listbox.
        if( lDirDepth == 1 )
            lIndex = 0;
        else
            lIndex--;
    }

	return(TRUE);
}

/***********************************************************************************\
* GetDirDepth()
*
* Returns:  -1 if error
*           otherwise, the depth of the directory (i.e. root is depth 0,
*           c:\foo is depth 1, etc.  This is computed in perhaps not the
*           most efficient way, by counting the ' 's proceeding the name.
*
*           This function also returns in lpszDirName a pointer to the
*           end of the preceeding ' ' characters within the given listbox str.
*           If the function fails, this pointer value is undefined.
*
* The left shifted index is to skip the tab characters. See ConstructLBEntry().
\***********************************************************************************/

LONG GetDirDepth(LPTSTR lpszLBString, LPTSTR *lpszDirName)
{
    TCHAR cBar;
    LONG  lCount = 0;

    do{

        cBar = lpszLBString[lCount << 1];

        if( cBar == TEXT(' ') )
            lCount++;
        else
            if( cBar != TEXT('+') && cBar != TEXT('-') ){
                ErrorMsg(TEXT("GetDirDepth: string parse error"));
                return(-1);
            }

    }while( cBar != TEXT('+') && cBar != TEXT('-') );

    *lpszDirName = &(lpszLBString[lCount << 1]);

    return( lCount );
}

/***********************************************************************************\
* CollapseDir()
*
* If directory is expanded, collapses it, by deleteing any subdirectory
*  entries below it.
*
* Returns:  TRUE if successful, FALSE if error
\***********************************************************************************/

BOOL CollapseDir(LPCINFO lpCInfo, LONG lIndex, LONG lDirDepth)
{
    TCHAR   szFileName[DIRECTORY_STRING_SIZE << 1];   // file name buffer
    LPTSTR  lpszNamePtr;

    LONG    lDepthHold;
    	
    if (!lpCInfo) {
    	ErrorMsg(TEXT("CollapseDir: lpCInfo is NULL."));
    	return FALSE;
    }
    
    //
    // Remove any following LB entries until we return to same depth
    //
    do{
	    if (lpCInfo->DirTable->iNumElems == 1)
    		return 1;
    	
    	if( SendMessage( lpCInfo->hDirLB, LB_GETTEXT, (WPARAM)lIndex + 1,
                            (LPARAM)szFileName ) < 0 ){
            ErrorMsg(TEXT(" Expand Directory:  Get listbox text failure"));
            return(0);
        }

        lDepthHold = GetDirDepth( szFileName, &lpszNamePtr);
        if( lDirDepth == -1 ){
            ErrorMsg(TEXT(" Expand Directory:  GetDirDepth failure"));
            return(0);
        }

        if( lDirDepth < lDepthHold ) {
            if( SendMessage( lpCInfo->hDirLB, LB_DELETESTRING, (WPARAM)lIndex + 1,
                                (LPARAM)0 ) < 0 ){
                ErrorMsg(TEXT(" Expand Directory:  Delete String failure"));
                return(0);
            }
			if (!TableRemove(lpCInfo->DirTable, lIndex + 1)) {
				ErrorMsg(TEXT("CollapseDir: TableRemove failed."));
				return 0;
			}
		}
    }while( lDirDepth < lDepthHold );

    return(1);
}

/***********************************************************************************\
* ConstructLBEntry()
*
* Given the parent's directory depth, and the subdirectory name, inserts
* ' 's equal to the depth+1 of the parent (plus tab character), and an
* unexpanded '+' directory marker, then the name of the subdirectory.
*
* The left shifted index is to skip the tab characters.
*
* Returns:  void.  the completed listbox entry is returned in szLBEntry.
\***********************************************************************************/

void ConstructLBEntry(LONG lDirDepth, LPTSTR szFileName, LPTSTR szLBEntry)
{
    int i;

    for(i = 0; i <= lDirDepth; i++){
        szLBEntry[i << 1] = TEXT(' ');
        szLBEntry[(i << 1) + 1] = 9;
    }

    szLBEntry[i << 1] = TEXT('+');
    szLBEntry[(i << 1) + 1] = TEXT('\0');

    lstrcat(szLBEntry, szFileName);
}

/***********************************************************************************\
* FullExpand()
*
* From the unexpanded root, walks down directory LB, expanding each directory
*  until it reaches the end of the tree.
\***********************************************************************************/

BOOL FullExpand(LPCINFO lpCInfo)
{
    LONG lIndex = 0;

    if (!lpCInfo) {
    	ErrorMsg(TEXT("FullExpand: lpCInfo is NULL."));
    	return FALSE;
    }
    
    if( WaitForSingleObject( lpCInfo->hDirMutex, MUTEX_TIMEOUT)
            == WAIT_TIMEOUT ){
        ErrorMsg(TEXT("FullExpand: Dir LB Mutex Timeout."));
        return(0);
    }

    while( SendMessage(lpCInfo->hDirLB, LB_SETCURSEL,
                        (WPARAM)lIndex,
                        (LPARAM)0) != LB_ERR ){
        if( !ExpDir( lpCInfo ) ){
            ErrorMsg(TEXT("Full Expand: ExpDir failure."));
            ReleaseMutex( lpCInfo->hDirMutex );

            // This is in case the ExpDir failed because a change drive
            //  command caused a kill.
            if( !PostMessage(lpCInfo->hwnd, WM_COMMAND, MM_REFRESH,
                            (LPARAM)0) )
                ErrorMsg(TEXT("ExpDir: MM_REFRESH call failure."));
            return(0);
        }

        lIndex++;
    }

    //  Set selection in listboxes to first item.
    if( SendMessage(lpCInfo->hDirLB, LB_SETCURSEL,
                        (WPARAM)0,
                        (LPARAM)0) == LB_ERR ){
        ErrorMsg(TEXT("Full Expand: Dir LB Set Selection Error"));
        ReleaseMutex( lpCInfo->hDirMutex );
        return(0);
    }

    ReleaseMutex( lpCInfo->hDirMutex );
    return(1);
}
