// // BrowseDir.cpp // // Functionality for the "Browse Directory" dialog. // Requires dialog resource IDD_BROWSEDIRECTORY. // // History: // // 10/04/95 KenSh Created // 10/09/95 KenSh Fixed bugs, removed globals and statics // #include "stdafx.h" #include #include //*** Custom window messages // #define CM_UPDATEEDIT (WM_USER + 42) // Update text in the edit (sent to the dialog) //*** Dialog control IDs // #define IDC_FILENAME edt1 // Edit box w/ current directory #define IDC_FILELIST lst2 // Listbox with current directory hierarchy #define IDC_DRIVECOMBO cmb2 // Combo-box with current drive #define IDC_NETWORK psh14 // Network button (added at runtime) //*** Window property names // const TCHAR c_szOFNProp[] = _T("OFNStruct"); const TCHAR c_szRedrawProp[] = _T("Redraw"); //*** Globals // // Note: Use of thse globals assumes that any multiple threads using // our subclass at the same time always have the same original edit/ // combo window procs. I think this is true. // WNDPROC g_pfnPrevEditProc; // original edit proc (before subclass) WNDPROC g_pfnPrevComboProc; // original combo proc (before subclass) //*** Local function declarations // BOOL CALLBACK BrowseDirDlgHook( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ); BOOL BrowseDir_OnOK( HWND hwnd ); // This struct is used internally and kept as a window property, // in lieu of using static variables or MFC. typedef struct _OFNINFO { OPENFILENAME ofn; // OFN struct passed to GetOpenFileName TCHAR szLastDirName[_MAX_PATH]; // last known good directory BOOL fAllowSetText; // should we allow WM_SETTEXT for the Edit // BOOL fNetworking; // is Connect Network Drive dialog open } OFNINFO, *LPOFNINFO; //---------------------------------------------------------------------------- // Procedure BrowseForDirectory // // Purpose Displays a dialog that lets the user choose a directory // name, either local or UNC. // // Parameters hwndParent Parent window for the dialog // pszInitialDir Directory to use as the default // pszBuf Where to store the answer // cchBuf Number of characters in this buffer // pszDialogTitle Title for the dialog // // Returns nonzero if successful, zero if not. If successful, pszBuf // will be filled with the full pathname of the chosen directory. // // History 10/06/95 KenSh Created // 10/09/95 KenSh Use lCustData member instead of global // CString strSelectDir; BOOL BrowseForDirectory( HWND hwndParent, LPCTSTR pszInitialDir, LPTSTR pszBuf, int cchBuf, LPCTSTR pszDialogTitle, BOOL bRemoveTrailingBackslash ) { TCHAR szInitialDir[MAX_PATH]; OFNINFO ofnInfo; pszBuf[0] = _T('\0'); // Prepare the initial directory... add a backslash if it's // a 2-character path _tcscpy( szInitialDir, pszInitialDir ); if( !szInitialDir[2] ) { szInitialDir[2] = _T('\\'); szInitialDir[3] = _T('\0'); } if( pszDialogTitle ) { ofnInfo.ofn.lpstrTitle = pszDialogTitle; } else { MyLoadString( IDS_SELECT_DIR, strSelectDir ); ofnInfo.ofn.lpstrTitle = (LPCTSTR)strSelectDir; } ofnInfo.ofn.lStructSize = sizeof(OPENFILENAME); ofnInfo.ofn.hwndOwner = hwndParent; ofnInfo.ofn.hInstance = (HINSTANCE) g_MyModuleHandle; ofnInfo.ofn.lpstrFilter = NULL; ofnInfo.ofn.lpstrCustomFilter = NULL; ofnInfo.ofn.nMaxCustFilter = 0; ofnInfo.ofn.nFilterIndex = 0; ofnInfo.ofn.lpstrFile = pszBuf; ofnInfo.ofn.nMaxFile = cchBuf; ofnInfo.ofn.lpstrFileTitle = NULL; ofnInfo.ofn.nMaxFileTitle = 0; ofnInfo.ofn.lpstrInitialDir = szInitialDir; ofnInfo.ofn.nFileOffset = 0; ofnInfo.ofn.nFileExtension = 0; ofnInfo.ofn.lpstrDefExt = NULL; ofnInfo.ofn.lCustData = (LPARAM)&ofnInfo; ofnInfo.ofn.lpfnHook = (LPOFNHOOKPROC)BrowseDirDlgHook; ofnInfo.ofn.lpTemplateName = MAKEINTRESOURCE( IDD_BROWSEDIRECTORY ); ofnInfo.ofn.Flags = OFN_ENABLEHOOK | OFN_PATHMUSTEXIST | OFN_NONETWORKBUTTON | OFN_ENABLETEMPLATE | OFN_HIDEREADONLY; iisDebugOut((LOG_TYPE_TRACE_WIN32_API, _T("comdlg32:GetOpenFileName().Start."))); int nResult = ::GetOpenFileName( &ofnInfo.ofn ); iisDebugOut((LOG_TYPE_TRACE_WIN32_API, _T("comdlg32:GetOpenFileName().End."))); DWORD dw = 0; if (nResult == 0) { iisDebugOut((LOG_TYPE_TRACE_WIN32_API, _T("comdlg32:CommDlgExtendedError().Start."))); dw = CommDlgExtendedError(); iisDebugOut((LOG_TYPE_TRACE_WIN32_API, _T("comdlg32:CommDlgExtendedError().End."))); } return (BOOL)( IDOK == nResult ); } //---------------------------------------------------------------------------- // Procedure pszSkipDriveSpec // // Purpose Returns a pointer to whatever comes after the drive part // of a filename. For example: // c:\foo\bar.bat \\server\share\file.txt // ^ ^ // // Returns Pointer to the appropriate part of the string, or a pointer // to the end of the string if it's not in the right format // // History 10/06/95 KenSh Created // LPTSTR pszSkipDriveSpec( LPTSTR pszPathName ) { LPTSTR pch = NULL; if( pszPathName[0] == _T('\\') && pszPathName[1] == _T('\\') ) { pch = _tcschr(pszPathName+2, _T('\\')); if( NULL != pch) { LPTSTR pchResult; pchResult = _tcschr( pch, _T('\\') ); if( pchResult ) { pchResult = _tcschr( pchResult+1, _T('\\') ); } if( pchResult ) { return pchResult; } else { return _tcschr( pch, _T('\0') ); } } else { return _tcschr( pszPathName, _T('\0') ); } } else { pch = _tcschr( pszPathName, _T(':') ); if( pch ) { return _tcsinc(pch); } else { return _tcschr( pszPathName, _T('\0') ); } } } //---------------------------------------------------------------------------- // Procedure BrowseDirEditProc // // Purpose Subclassed window proc for the edit control in the Browse // for Directory dialog. We override the WM_SETTEXT message // to control when the window text can be programatically // changed. // // Parameters standard // // Returns standard // // History 10/06/95 KenSh Created // 10/09/95 KenSh Moved most code into UpdateEditText() // LRESULT CALLBACK BrowseDirEditProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) { switch( message ) { // In order to prevent default functionality from setting the Edit's text // to strings like "*.*", we capture WM_SETTEXT and only allow it to occur // if the fAllowSetText flag is specified in the OFNINFO struct. case WM_SETTEXT: { LPOFNINFO lpOFNInfo = (LPOFNINFO)GetProp( GetParent(hwnd), c_szOFNProp ); if( lpOFNInfo->fAllowSetText ) { break; // default processing } else { return 0L; // suppress the urge to change the text } } default: break; } return CallWindowProc( g_pfnPrevEditProc, hwnd, message, wParam, lParam ); } //---------------------------------------------------------------------------- // Procedure BrowseDirComboProc // // Purpose Subclassed window proc for the combo box in the Browse // Directory dialog. We need to subclass so we can catch the // change to selection after the Network button is used to // switch drives. // // Parameters standard // // Returns standard // // History 10/09/95 KenSh Created // LRESULT CALLBACK BrowseDirComboProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) { switch( message ) { // We catch the WM_SETREDRAW message so that we ignore CB_SETCURSEL // messages when the combo is not supposed to be updated. This is // pretty much of a hack due to the ordering of messages after the // Network button is clicked. case WM_SETREDRAW: { if( wParam ) { SetProp( hwnd, c_szRedrawProp, (HANDLE)1 ); } else { if( GetProp( hwnd, c_szRedrawProp ) ) { RemoveProp( hwnd, c_szRedrawProp ); } } break; // continue with default processing } // We catch CB_SETCURSEL and use it to update our edit box after the // Network button has been pressed. case CB_SETCURSEL: { LPOFNINFO lpOFNInfo = (LPOFNINFO)GetProp( GetParent(hwnd), c_szOFNProp ); #ifdef NEVER // If "Network" was pressed and redraw has been re-enabled... if( lpOFNInfo->fNetworking && GetProp( hwnd, c_szRedrawProp ) ) { TCHAR szBuf[_MAX_PATH]; LRESULT lResult = CallWindowProc( g_pfnPrevComboProc, hwnd, message, wParam, lParam ); // Turn off the networking flag lpOFNInfo->fNetworking = FALSE; // Force the selected drive to be the current drive and // get the current directory on that drive GetWindowText( hwnd, szBuf, _MAX_PATH ); _chdrive( _totupper(szBuf[0]) - 'A' + 1 ); GetCurrentDirectory( _MAX_PATH, szBuf ); // Update the edit box with the new text. lpOFNInfo->fAllowSetText = TRUE; SetDlgItemText( GetParent(hwnd), IDC_FILENAME, szBuf ); lpOFNInfo->fAllowSetText = FALSE; SendDlgItemMessage( GetParent(hwnd), IDC_FILENAME, EM_SETSEL, 0, (LPARAM)-1 ); return lResult; } #endif break; } case WM_DESTROY: { if( GetProp( hwnd, c_szRedrawProp ) ) { RemoveProp( hwnd, c_szRedrawProp ); } break; // continue with default processing } default: break; } return CallWindowProc( g_pfnPrevComboProc, hwnd, message, wParam, lParam ); } //---------------------------------------------------------------------------- // Procedure UpdateEditText // // Purpose Based on which control has focus and the current state of // the edit control, calculates the new "current directory" and // sets the Edit control's text to match. // // Parameters hwndDlg handle to the dialog window. This function will // access the lpOFNInfo window property. // // Returns nothing // // History 10/09/95 KenSh Created // VOID UpdateEditText( HWND hwndDlg ) { HWND hwndLB = GetDlgItem(hwndDlg, IDC_FILELIST); int nCurSel = (int)SendMessage(hwndLB, LB_GETCURSEL, 0, 0); TCHAR szDirName[_MAX_PATH]; LPTSTR pchStart = NULL; LPOFNINFO lpOFNInfo = (LPOFNINFO)GetProp( hwndDlg, c_szOFNProp ); int i; HWND hwndFocus = GetFocus(); int idFocus = GetDlgCtrlID( hwndFocus ); if( idFocus == IDC_FILELIST ) // Listbox: build name up through current LB selection { // First get the top entry in the listbox, this will tell us // if it's a connected drive or a UNC drive SendMessage( hwndLB, LB_GETTEXT, 0, (LPARAM)szDirName ); // Run down the entries in the listbox appending them // and stop when we get to the current selection. if( szDirName[0] == _T('\\') && szDirName[1] == _T('\\') ) pchStart = _tcschr(szDirName+2, _T('\0')); else pchStart = _tcsinc(szDirName) + 1; // skip 2 chars, first may be MBCS if (NULL != pchStart) { for( i = 1; i <= nCurSel; i++ ) { if( *pchStart != _T('\\') ) *pchStart = _T('\\'); pchStart = _tcsinc(pchStart); SendMessage( hwndLB, LB_GETTEXT, i, (LPARAM)pchStart ); pchStart = _tcschr(pchStart, _T('\0')); } } } else if( idFocus == IDC_DRIVECOMBO ) // Combo box: use current working directory { GetCurrentDirectory( _MAX_PATH, szDirName ); } else if( idFocus == IDC_FILENAME ) // Edit control: build the new path using the edit contents { TCHAR szBuf[_MAX_PATH]; GetDlgItemText( hwndDlg, IDC_FILENAME, szBuf, _MAX_PATH ); if( szBuf[0] == _T('\\') ) { if( szBuf[1] == _T('\\') ) // full UNC path was specified { _tcscpy( szDirName, szBuf ); } else // new directory on the current drive { _tcscpy( szDirName, lpOFNInfo->szLastDirName ); LPTSTR pch = pszSkipDriveSpec(szDirName); _tcscpy( pch, szBuf ); } } else if( *_tcsinc(szBuf) == _T(':') ) { // Change to the requested drive and use the current directory // on that drive. if( 0 == _chdrive( _totupper(szBuf[0]) - 'A' + 1 ) && szBuf[2] != _T('\\') ) { // It's a legal drive and no directory was specified, // so use the current default. GetCurrentDirectory( _MAX_PATH, szDirName ); } else { // A directory was specified or the drive does not exist. // Copy the text verbatim to test it and possibly display // an error message. _tcscpy( szDirName, szBuf ); } } else // Subdirectory of the current directory { // Start with the current directory _tcscpy( szDirName, lpOFNInfo->szLastDirName ); // Append a backslash if there isn't already one there LPTSTR pch = _tcsrchr( szDirName, _T('\\') ); if (pch) { if( *_tcsinc(pch) ) { pch = _tcschr( pch, _T('\0') ); if (pch) { *pch = _T('\\'); } } pch = _tcsinc(pch); // Append the new directory name _tcscpy( pch, szBuf ); } // Attempt to change into the new directory if( SetCurrentDirectory(szDirName) ) { // The directory exists, get the official name and use that // instead of whatever we're using now GetCurrentDirectory( _MAX_PATH, szDirName ); } } } else { // Some other control, likely an error message is going on _tcscpy( szDirName, lpOFNInfo->szLastDirName ); } lpOFNInfo->fAllowSetText = TRUE; SetDlgItemText( hwndDlg, IDC_FILENAME, szDirName ); lpOFNInfo->fAllowSetText = FALSE; } //---------------------------------------------------------------------------- // Procedure BrowseDirDlgHook // // Purpose Dialog proc for the Browse for Directory subclassed common // dialog. We spend most of our effort trying to get the right // string into the edit control (IDC_FILENAME). // // Parameters standard // // Returns TRUE to halt further processing, FALSE to do the default. // // History 10/06/95 KenSh Created // BOOL CALLBACK BrowseDirDlgHook( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) { switch(message) { case WM_INITDIALOG: { LPOFNINFO lpOFNInfo = (LPOFNINFO)((LPOPENFILENAME)lParam)->lCustData; // Store the OFN struct pointer as a window property SetProp( hwnd, c_szOFNProp, (HANDLE)lpOFNInfo ); // Initialize the OFNInfo struct _tcscpy( lpOFNInfo->szLastDirName, lpOFNInfo->ofn.lpstrInitialDir ); lpOFNInfo->fAllowSetText = FALSE; //lpOFNInfo->fNetworking = FALSE; // Start our edit off with the initial directory SetDlgItemText( hwnd, IDC_FILENAME, lpOFNInfo->ofn.lpstrInitialDir ); // Subclass the edit to let us display only what we want to display g_pfnPrevEditProc = (WNDPROC)SetWindowLongPtr( GetDlgItem(hwnd, IDC_FILENAME), GWLP_WNDPROC, (LONG_PTR)BrowseDirEditProc ); // Subclass the combo so we know when the Network button has been used. g_pfnPrevComboProc = (WNDPROC)SetWindowLongPtr( GetDlgItem(hwnd, IDC_DRIVECOMBO), GWLP_WNDPROC, (LONG_PTR)BrowseDirComboProc ); return TRUE; // set default focus } case WM_DESTROY: { // Clean up. RemoveProp( hwnd, c_szOFNProp ); return FALSE; // continue with default processing } case WM_COMMAND: { switch( LOWORD(wParam) ) { case IDOK: { return BrowseDir_OnOK( hwnd ); } case IDC_FILELIST: //directory listbox. { if( HIWORD(wParam) == LBN_DBLCLK ) { // Post this message telling us to change the edit box. PostMessage( hwnd, CM_UPDATEEDIT, 0, 0L ); } return FALSE; // continue with default processing. } case IDC_DRIVECOMBO: // drive combo box: { if( HIWORD(wParam) == CBN_SELCHANGE ) { // Post this message telling us to change the edit box. PostMessage( hwnd, CM_UPDATEEDIT, 0, 0L ); } return FALSE; // continue with default processing. } #ifdef NEVER case IDC_NETWORK: // "Network..." button { // Set the fNetworking flag which the combo box looks for when // processing CB_SETCURSEL. LPOFNINFO lpOFNInfo = (LPOFNINFO)GetProp( hwnd, c_szOFNProp ); lpOFNInfo->fNetworking = TRUE; return FALSE; // default processing. } #endif default: return FALSE; // default processing. } } case CM_UPDATEEDIT: //update edit box with directory { LPOFNINFO lpOFNInfo = (LPOFNINFO)GetProp( hwnd, c_szOFNProp ); // Change the text of the edit control. UpdateEditText( hwnd ); SendDlgItemMessage( hwnd, IDC_FILENAME, EM_SETSEL, 0, (LPARAM)(INT)-1 ); // Store this text as the "last known good" directory GetDlgItemText( hwnd, IDC_FILENAME, lpOFNInfo->szLastDirName, _MAX_PATH ); return TRUE; } default: return FALSE; //default processing } } //---------------------------------------------------------------------------- // Procedure BrowseDir_OnOK // // Purpose Handles a click of the OK button in the Browse Directory // dialog. We have to do some sneaky stuff here with checking // which control has focus, because we want the dialog to go // away enter when the OK button is clicked directly (i.e. just // hitting Enter doesn't count). // // Parameters hwnd The dialog window // // Returns TRUE if processing should halt, FALSE if default processing // should still happen. // // History 10/09/95 KenSh Created // BOOL BrowseDir_OnOK( HWND hwnd ) { LPOFNINFO lpOFNInfo = (LPOFNINFO)GetProp( hwnd, c_szOFNProp ); HWND hwndFocus = GetFocus(); int idFocus = GetDlgCtrlID(hwndFocus); if( idFocus != IDC_FILENAME && idFocus != IDC_FILELIST && idFocus != IDC_DRIVECOMBO ) { ASSERT( lpOFNInfo->ofn.lpstrFile != NULL ); //UpdateEditText( hwnd ); GetDlgItemText( hwnd, IDC_FILENAME, lpOFNInfo->ofn.lpstrFile, lpOFNInfo->ofn.nMaxFile ); EndDialog( hwnd, IDOK ); return TRUE; // halt processing here. } else { // We don't want the dialog to go away at this point. // Because the default functionality is expecting // a file to be found, not a directory, we must make // sure what's been typed in is actually a directory // before we hand this message to the default handler. TCHAR szBuf[_MAX_PATH]; // Calculate the new current directory and put it into the Edit UpdateEditText( hwnd ); // Read the newly generated directory name GetDlgItemText( hwnd, IDC_FILENAME, szBuf, _MAX_PATH ); // Update our "last good" directory _tcscpy( lpOFNInfo->szLastDirName, szBuf ); // Post this message to update the edit after the default handler // updates the list box PostMessage( hwnd, CM_UPDATEEDIT, 0, 0 ); return FALSE; // let the original common dialog handle it. } } BOOL BrowseForFile( HWND hwndParent, LPCTSTR pszInitialDir, LPTSTR pszBuf, int cchBuf) { TCHAR szInitialDir[MAX_PATH]; TCHAR szFileExtension[_MAX_PATH] = _T(""); LPTSTR p = NULL; TCHAR szFileFilter[_MAX_PATH]; CString csTitle; OFNINFO ofnInfo; // Prepare the initial directory... add a backslash if it's // a 2-character path _tcscpy( szInitialDir, pszInitialDir ); if( !szInitialDir[2] ) { szInitialDir[2] = _T('\\'); szInitialDir[3] = _T('\0'); } // calculate file extension p = _tcsrchr(pszBuf, _T('.')); if (p) { p = _tcsinc(p); if (*p) { _tcscpy(szFileExtension, _T("*.")); _tcscat(szFileExtension, p); } } memset( (PVOID)szFileFilter, 0, sizeof(szFileFilter)); p = szFileFilter; if (*szFileExtension) { _tcscpy(p, szFileExtension); p = _tcsninc(p, _tcslen(szFileExtension) + 1); _tcscpy(p, szFileExtension); p = _tcsninc(p, _tcslen(szFileExtension) + 1); } _tcscpy(p, _T("*.*")); p = _tcsninc(p, _tcslen(_T("*.*")) + 1); _tcscpy(p, _T("*.*")); // dialog title "Locate File" MyLoadString(IDS_LOCATE_FILE, csTitle); // fill in the OFNINFO struct ofnInfo.ofn.lStructSize = sizeof(OPENFILENAME); ofnInfo.ofn.hwndOwner = hwndParent; ofnInfo.ofn.hInstance = (HINSTANCE) g_MyModuleHandle; ofnInfo.ofn.lpstrFilter = szFileFilter; // extention of the file we're looking for ofnInfo.ofn.lpstrCustomFilter = NULL; ofnInfo.ofn.nMaxCustFilter = 0; ofnInfo.ofn.nFilterIndex = 1; ofnInfo.ofn.lpstrFile = pszBuf; // Buffer for file name ofnInfo.ofn.nMaxFile = cchBuf; ofnInfo.ofn.lpstrFileTitle = NULL; ofnInfo.ofn.nMaxFileTitle = 0; ofnInfo.ofn.lpstrInitialDir = szInitialDir; ofnInfo.ofn.lpstrTitle = (LPCTSTR)csTitle; ofnInfo.ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_EXPLORER | OFN_NOCHANGEDIR | OFN_LONGNAMES | OFN_NONETWORKBUTTON; ofnInfo.ofn.nFileOffset = 0; ofnInfo.ofn.nFileExtension = 0; ofnInfo.ofn.lpstrDefExt = NULL; ofnInfo.ofn.lCustData = (LPARAM)&ofnInfo; ofnInfo.ofn.lpfnHook = NULL; ofnInfo.ofn.lpTemplateName = NULL; int nResult = ::GetOpenFileName( &ofnInfo.ofn ); DWORD dw = 0; if (nResult == 0) { dw = CommDlgExtendedError(); } if (nResult == IDOK) { iisDebugOut((LOG_TYPE_TRACE, _T("pszBuf=%s\n"), pszBuf)); *(pszBuf + ofnInfo.ofn.nFileOffset - 1) = _T('\0'); } return (BOOL)( IDOK == nResult ); }