EXPDIR.C


/******************************************************************************\
* This is a part of the Microsoft Source Code Samples.
* Copyright 1996 - 1998 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];
TCHARszFileName2[BUF_SIZE];
TCHARszInsertString[DIRECTORY_STRING_SIZE << 1];
TCHARszBuf[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];
TCHARszHidden[DIRECTORY_STRING_SIZE << 1];

lstrcpy(szHidden, szFileName2);
lstrcat(szHidden, FileData.cFileName);

if (IsEncrypted(szHidden)) {
TCHARszDecryptedName[BUF_SIZE];
HANDLEhFileRead;

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);
}