# HG changeset patch # User mhessling@81767d24-ef19-dc11-ae90-00e081727c95 # Date 1225363563 0 # Node ID bf3a6d596cd4d8aab24c40e76fa8a65d89e58386 # Parent fa6c46796883c4e79669ae12b3967cd011f5a7ac Use better directory browser widget. diff -r fa6c46796883 -r bf3a6d596cd4 win/XBrowseForFolder.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/win/XBrowseForFolder.cpp Thu Oct 30 10:46:03 2008 +0000 @@ -0,0 +1,445 @@ +// XBrowseForFolder.cpp Version 1.2 +// +// Author: Hans Dietrich +// hdietrich@gmail.com +// +// Description: +// XBrowseForFolder.cpp implements XBrowseForFolder(), a function that +// wraps SHBrowseForFolder(). +// +// History +// Version 1.2 - 2008 February 29 +// - Changed API to allow for initial CSIDL. +// - Added option to set dialog caption, suggested by SimpleDivX. +// - Added option to set root, suggested by Jean-Michel Reghem. +// +// Version 1.1 - 2003 September 29 (not released) +// - Added support for edit box +// +// Version 1.0 - 2003 September 25 +// - Initial public release +// +// License: +// This software is released into the public domain. You are free to use +// it in any way you like, except that you may not sell this source code. +// +// This software is provided "as is" with no expressed or implied warranty. +// I accept no liability for any damage or loss of business that this +// software may cause. +// +/////////////////////////////////////////////////////////////////////////////// + +// if you don't want to use MFC, comment out the following line: +//#include "stdafx.h" + +#ifndef __AFX_H__ +#include "windows.h" +#include "crtdbg.h" +#include "tchar.h" +#endif + +#include "Shlobj.h" +#include "io.h" +#include "XBrowseForFolder.h" + +#pragma warning(disable: 4127) // conditional expression is constant (_ASSERTE) +#pragma warning(disable : 4996) // disable bogus deprecation warning + +#ifndef __noop +#if _MSC_VER < 1300 +#define __noop ((void)0) +#endif +#endif + +#undef TRACE +#define TRACE __noop + +//============================================================================= +// if you want to see the TRACE output, uncomment this line: +//#include "XTrace.h" +//============================================================================= + +//============================================================================= +// struct to pass to callback function +//============================================================================= +struct FOLDER_PROPS +{ + LPCTSTR lpszTitle; + LPCTSTR lpszInitialFolder; + UINT ulFlags; +}; + +#ifndef __AFX_H__ + +/////////////////////////////////////////////////////////////////////////////// +// CRect - a minimal CRect class + +class CRect : public tagRECT +{ +public: + //CRect() { } + CRect(int l = 0, int t = 0, int r = 0, int b = 0) + { + left = l; + top = t; + right = r; + bottom = b; + } + int Width() const { return right - left; } + int Height() const { return bottom - top; } + void SwapLeftRight() { SwapLeftRight(LPRECT(this)); } + static void SwapLeftRight(LPRECT lpRect) { LONG temp = lpRect->left; + lpRect->left = lpRect->right; + lpRect->right = temp; } + operator LPRECT() { return this; } +}; +#endif + +/////////////////////////////////////////////////////////////////////////////// +// ScreenToClientX - helper function in case non-MFC +static void ScreenToClientX(HWND hWnd, LPRECT lpRect) +{ + _ASSERTE(::IsWindow(hWnd)); + ::ScreenToClient(hWnd, (LPPOINT)lpRect); + ::ScreenToClient(hWnd, ((LPPOINT)lpRect)+1); +} + +/////////////////////////////////////////////////////////////////////////////// +// MoveWindowX - helper function in case non-MFC +static void MoveWindowX(HWND hWnd, CRect& rect, BOOL bRepaint) +{ + _ASSERTE(::IsWindow(hWnd)); + ::MoveWindow(hWnd, rect.left, rect.top, + rect.Width(), rect.Height(), bRepaint); +} + +/////////////////////////////////////////////////////////////////////////////// +// SizeBrowseDialog - resize dialog, move controls +static void SizeBrowseDialog(HWND hWnd, FOLDER_PROPS *fp) +{ + TRACE(_T("in void SizeBrowseDialog\n")); + + // find the folder tree and make dialog larger + HWND hwndTree = FindWindowEx(hWnd, NULL, _T("SysTreeView32"), NULL); + + if (!hwndTree) + { + // ... this usually means that BIF_NEWDIALOGSTYLE is enabled. + // Then the class name is as used in the code below. + hwndTree = FindWindowEx(hWnd, NULL, _T("SHBrowseForFolder ShellNameSpace Control"), NULL); + TRACE(_T("SHBrowseForFolder ShellNameSpace Control: hwndTree=%X\n"), hwndTree); + } + + CRect rectDlg; + + _ASSERTE(IsWindow(hwndTree)); + + if (hwndTree) + { + // check if edit box + int nEditHeight = 0; + HWND hwndEdit = FindWindowEx(hWnd, NULL, _T("Edit"), NULL); + TRACE(_T("hwndEdit=%x\n"), hwndEdit); + CRect rectEdit; + if (hwndEdit && (fp->ulFlags & BIF_EDITBOX)) + { + ::GetWindowRect(hwndEdit, &rectEdit); + ScreenToClientX(hWnd, &rectEdit); + nEditHeight = rectEdit.Height(); + } + else if (hwndEdit) + { + ::MoveWindow(hwndEdit, 20000, 20000, 10, 10, FALSE); + ::ShowWindow(hwndEdit, SW_HIDE); + hwndEdit = 0; + } + + // make the dialog larger + ::GetWindowRect(hWnd, &rectDlg); + rectDlg.right += 40; + rectDlg.bottom += 30; + if (hwndEdit) + rectDlg.bottom += nEditHeight + 5; + MoveWindowX(hWnd, rectDlg, TRUE); + ::GetClientRect(hWnd, &rectDlg); + + int hMargin = 10; + int vMargin = 10; + + // check if new dialog style - this means that there will be a resizing + // grabber in lower right corner + if (fp->ulFlags & BIF_NEWDIALOGSTYLE) + hMargin = ::GetSystemMetrics(SM_CXVSCROLL); + + // move the Cancel button + CRect rectCancel; + HWND hwndCancel = ::GetDlgItem(hWnd, IDCANCEL); + if (hwndCancel) + ::GetWindowRect(hwndCancel, &rectCancel); + ScreenToClientX(hWnd, &rectCancel); + int h = rectCancel.Height(); + int w = rectCancel.Width(); + rectCancel.bottom = rectDlg.bottom - vMargin;//nMargin; + rectCancel.top = rectCancel.bottom - h; + rectCancel.right = rectDlg.right - hMargin; //(scrollWidth + 2*borderWidth); + rectCancel.left = rectCancel.right - w; + if (hwndCancel) + { + //TRACERECT(rectCancel); + MoveWindowX(hwndCancel, rectCancel, FALSE); + } + + // move the OK button + CRect rectOK(0, 0, 0, 0); + HWND hwndOK = ::GetDlgItem(hWnd, IDOK); + if (hwndOK) + ::GetWindowRect(hwndOK, &rectOK); + ScreenToClientX(hWnd, &rectOK); + rectOK.bottom = rectCancel.bottom; + rectOK.top = rectCancel.top; + rectOK.right = rectCancel.left - 10; + rectOK.left = rectOK.right - w; + if (hwndOK) + { + MoveWindowX(hwndOK, rectOK, FALSE); + } + + // expand the folder tree to fill the dialog + CRect rectTree; + ::GetWindowRect(hwndTree, &rectTree); + ScreenToClientX(hWnd, &rectTree); + if (hwndEdit) + { + rectEdit.left = hMargin; + rectEdit.right = rectDlg.right - hMargin; + rectEdit.top = vMargin; + rectEdit.bottom = rectEdit.top + nEditHeight; + MoveWindowX(hwndEdit, rectEdit, FALSE); + rectTree.top = rectEdit.bottom + 5; + } + else + { + rectTree.top = vMargin; + } + rectTree.left = hMargin; + rectTree.bottom = rectOK.top - 10;//nMargin; + rectTree.right = rectDlg.right - hMargin; + //TRACERECT(rectTree); + MoveWindowX(hwndTree, rectTree, FALSE); + } + else + { + TRACE(_T("ERROR - tree control not found.\n")); + //_ASSERTE(hwndTree); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// BrowseCallbackProc - SHBrowseForFolder callback function +static int CALLBACK BrowseCallbackProc(HWND hWnd, // Window handle to the browse dialog box + UINT uMsg, // Value identifying the event + LPARAM lParam, // Value dependent upon the message + LPARAM lpData) // Application-defined value that was + // specified in the lParam member of the + // BROWSEINFO structure +{ + switch (uMsg) + { + case BFFM_INITIALIZED: // sent when the browse dialog box has finished initializing. + { + TRACE(_T("hWnd=%X\n"), hWnd); + + // remove context help button from dialog caption + LONG lStyle = ::GetWindowLong(hWnd, GWL_STYLE); + lStyle &= ~DS_CONTEXTHELP; + ::SetWindowLong(hWnd, GWL_STYLE, lStyle); + lStyle = ::GetWindowLong(hWnd, GWL_EXSTYLE); + lStyle &= ~WS_EX_CONTEXTHELP; + ::SetWindowLong(hWnd, GWL_EXSTYLE, lStyle); + + FOLDER_PROPS *fp = (FOLDER_PROPS *) lpData; + if (fp) + { + if (fp->lpszInitialFolder && (fp->lpszInitialFolder[0] != _T('\0'))) + { + // set initial directory + ::SendMessage(hWnd, BFFM_SETSELECTION, TRUE, (LPARAM)fp->lpszInitialFolder); + } + + if (fp->lpszTitle && (fp->lpszTitle[0] != _T('\0'))) + { + // set window caption + ::SetWindowText(hWnd, fp->lpszTitle); + } + } + + SizeBrowseDialog(hWnd, fp); + } + break; + + case BFFM_SELCHANGED: // sent when the selection has changed + { + TCHAR szDir[MAX_PATH*2] = { 0 }; + + // fail if non-filesystem + BOOL bRet = SHGetPathFromIDList((LPITEMIDLIST) lParam, szDir); + if (bRet) + { + // fail if folder not accessible + if (_taccess(szDir, 00) != 0) + { + bRet = FALSE; + } + else + { + SHFILEINFO sfi; + ::SHGetFileInfo((LPCTSTR)lParam, 0, &sfi, sizeof(sfi), + SHGFI_PIDL | SHGFI_ATTRIBUTES); + TRACE(_T("dwAttributes=0x%08X\n"), sfi.dwAttributes); + + // fail if pidl is a link + if (sfi.dwAttributes & SFGAO_LINK) + { + TRACE(_T("SFGAO_LINK\n")); + bRet = FALSE; + } + } + } + + // if invalid selection, disable the OK button + if (!bRet) + { + ::EnableWindow(GetDlgItem(hWnd, IDOK), FALSE); + } + + TRACE(_T("szDir=%s\n"), szDir); + } + break; + } + + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// +// +// XBrowseForFolder() +// +// Purpose: Invoke the SHBrowseForFolder API. If lpszInitialFolder is +// supplied, it will be the folder initially selected in the tree +// folder list. Otherwise, the initial folder will be set to the +// current directory. The selected folder will be returned in +// lpszBuf. +// +// Parameters: hWnd - handle to the owner window for the dialog +// lpszInitialFolder - initial folder in tree; if NULL, the initial +// folder will be the current directory; if +// if this is a CSIDL, must be a real folder. +// nRootFolder - optional CSIDL of root folder for tree; +// -1 = use default. +// lpszCaption - optional caption for folder dialog +// lpszBuf - buffer for the returned folder path +// dwBufSize - size of lpszBuf in TCHARs +// bEditBox - TRUE = include edit box in dialog +// +// Returns: BOOL - TRUE = success; FALSE = user hit Cancel +// +BOOL _cdecl XBrowseForFolder(HWND hWnd, + LPCTSTR lpszInitialFolder, + int nRootFolder, + LPCTSTR lpszCaption, + LPTSTR lpszBuf, + DWORD dwBufSize, + BOOL bEditBox /*= FALSE*/) +{ + _ASSERTE(lpszBuf); + _ASSERTE(dwBufSize >= MAX_PATH); + + if (lpszBuf == NULL || dwBufSize < MAX_PATH) + return FALSE; + + ZeroMemory(lpszBuf, dwBufSize*sizeof(TCHAR)); + + BROWSEINFO bi = { 0 }; + + // check if there is a special root folder + LPITEMIDLIST pidlRoot = NULL; + if (nRootFolder != -1) + { + if (SUCCEEDED(SHGetSpecialFolderLocation(hWnd, nRootFolder, &pidlRoot))) + bi.pidlRoot = pidlRoot; + } + + TCHAR szInitialPath[MAX_PATH*2] = { _T('\0') }; + if (lpszInitialFolder) + { + // is this a folder path string or a csidl? + if (HIWORD(lpszInitialFolder) == 0) + { + // csidl + int nFolder = LOWORD((UINT)(UINT_PTR)lpszInitialFolder); + TRACE(_T("csidl: nFolder=0x%X\n"), nFolder); + SHGetSpecialFolderPath(hWnd, szInitialPath, nFolder, FALSE); + } + else + { + // string + _tcsncpy(szInitialPath, lpszInitialFolder, + sizeof(szInitialPath)/sizeof(TCHAR)-2); + } + TRACE(_T("szInitialPath=<%s>\n"), szInitialPath); + } + + if ((szInitialPath[0] == _T('\0')) && (bi.pidlRoot == NULL)) + { + // no initial folder and no root, set to current directory + ::GetCurrentDirectory(sizeof(szInitialPath)/sizeof(TCHAR)-2, + szInitialPath); + } + + FOLDER_PROPS fp; + + bi.hwndOwner = hWnd; + bi.ulFlags = BIF_RETURNONLYFSDIRS; // do NOT use BIF_NEWDIALOGSTYLE, + // or BIF_STATUSTEXT + if (bEditBox) + bi.ulFlags |= BIF_EDITBOX; + bi.ulFlags |= BIF_NONEWFOLDERBUTTON; + bi.lpfn = BrowseCallbackProc; + bi.lParam = (LPARAM) &fp; + + fp.lpszInitialFolder = szInitialPath; + fp.lpszTitle = lpszCaption; + fp.ulFlags = bi.ulFlags; + + BOOL bRet = FALSE; + + LPITEMIDLIST pidlFolder = SHBrowseForFolder(&bi); + + if (pidlFolder) + { + TCHAR szBuffer[MAX_PATH*2] = { _T('\0') }; + + if (SHGetPathFromIDList(pidlFolder, szBuffer)) + { + _tcsncpy(lpszBuf, szBuffer, dwBufSize-1); + bRet = TRUE; + } + else + { + TRACE(_T("SHGetPathFromIDList failed\n")); + } + } + + // free up pidls + IMalloc *pMalloc = NULL; + if (SUCCEEDED(SHGetMalloc(&pMalloc)) && pMalloc) + { + if (pidlFolder) + pMalloc->Free(pidlFolder); + if (pidlRoot) + pMalloc->Free(pidlRoot); + pMalloc->Release(); + } + + return bRet; +} diff -r fa6c46796883 -r bf3a6d596cd4 win/XBrowseForFolder.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/win/XBrowseForFolder.h Thu Oct 30 10:46:03 2008 +0000 @@ -0,0 +1,33 @@ +// XBrowseForFolder.h Version 1.2 +// +// Author: Hans Dietrich +// hdietrich@gmail.com +// +// This software is released into the public domain. You are free to use +// it in any way you like, except that you may not sell this source code. +// +// This software is provided "as is" with no expressed or implied warranty. +// I accept no liability for any damage or loss of business that this +// software may cause. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef XBROWSEFORFOLDER_H +#define XBROWSEFORFOLDER_H + +#ifdef __cplusplus +extern "C" { +#endif + +BOOL XBrowseForFolder(HWND hWnd, + LPCTSTR lpszInitialFolder, + int nFolder, + LPCTSTR lpszCaption, + LPTSTR lpszBuf, + DWORD dwBufSize, + BOOL bEditBox); +#ifdef __cplusplus +} +#endif + +#endif //XBROWSEFORFOLDER_H