changeset 632:bf3a6d596cd4

Use better directory browser widget.
author mhessling@81767d24-ef19-dc11-ae90-00e081727c95
date Thu, 30 Oct 2008 10:46:03 +0000
parents fa6c46796883
children 87db549e79bc
files win/XBrowseForFolder.cpp win/XBrowseForFolder.h
diffstat 2 files changed, 478 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /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;
+}
--- /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