Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

630 lines
19 KiB

  1. // BrowseDlg.cpp
  2. // Dialog box to enable user to select a directory and/or files.
  3. // Author: t-michkr (June 22, 2000)
  4. #include <windows.h>
  5. #include <commctrl.h>
  6. #include <shlwapi.h>
  7. #include <shellapi.h>
  8. #include <tchar.h>
  9. #include <assert.h>
  10. #include "main.h"
  11. #include "filebrowser.h"
  12. #include "resource.h"
  13. // Display browse dialog box, and return dir string.
  14. PSTR BrowseForFolder(HWND hwnd, PSTR szInitialPath, UINT uiFlags);
  15. // Expand a tree item to include sub items.
  16. void AddTreeSubItems(HWND hwTree, HTREEITEM hParent);
  17. // Remove a tree item's subitems
  18. void RemoveTreeSubItems(HWND hwTree, HTREEITEM hParent);
  19. void CheckTreeSubItems(HWND hwTree, HTREEITEM hChild);
  20. // Given a path, select the appropriate item in the tree.
  21. // If path is invalid, it will expand as much as possible
  22. // (until invalid element appears)
  23. void SelectItemFromFullPath(HWND hwTree, PTSTR szPath);
  24. // Get full item path. Assumes szPath is a buffer of MAX_PATH size,
  25. // initialized with '\0'.
  26. void GetItemPath(HWND hwTree, HTREEITEM hItem, PTSTR szPath);
  27. // Browse dialog proc
  28. BOOL CALLBACK BrowseDialogProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam);
  29. // Browse dialog box message handlers.
  30. BOOL HandleInitBrowse(HWND hwnd);
  31. void HandleBrowseCommand(HWND hwnd, UINT uiCtrlID, UINT uiNotify, HWND hwChild);
  32. void HandleBrowseNotify(HWND hwnd, void* pvArg);
  33. // Buffer to hold returned path
  34. static TCHAR s_szPathBuffer[MAX_PATH];
  35. static PTSTR s_szInitialPath = 0;
  36. static UINT s_uiFlags;
  37. static HIMAGELIST s_himlSystem = 0;
  38. // Create browse dialog box, and return a path string, or
  39. // NULL if cancel was selected.
  40. PTSTR BrowseForFolder(HWND hwnd, PTSTR szInitialPath, UINT uiFlags)
  41. {
  42. CoInitialize(0);
  43. s_szInitialPath = szInitialPath;
  44. s_uiFlags = uiFlags;
  45. PTSTR szRet = reinterpret_cast<TCHAR*>(DialogBox(GetModuleHandle(0),
  46. MAKEINTRESOURCE(IDD_BROWSE), hwnd, BrowseDialogProc));
  47. CoUninitialize();
  48. return szRet;
  49. }
  50. // Browse dialog box proc.
  51. BOOL CALLBACK BrowseDialogProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
  52. {
  53. switch(uiMsg)
  54. {
  55. case WM_INITDIALOG:
  56. return HandleInitBrowse(hwnd);
  57. break;
  58. case WM_COMMAND:
  59. HandleBrowseCommand(hwnd, LOWORD(wParam), HIWORD(wParam),
  60. reinterpret_cast<HWND>(lParam));
  61. break;
  62. case WM_NOTIFY:
  63. HandleBrowseNotify(hwnd, reinterpret_cast<void*>(lParam));
  64. break;
  65. default:
  66. return FALSE;
  67. }
  68. return TRUE;
  69. }
  70. // Dialog box initialization, init tree and root tree items.
  71. BOOL HandleInitBrowse(HWND hwnd)
  72. {
  73. // Get the treeview control
  74. HWND hwTree = GetDlgItem(hwnd, IDC_DIRTREE);
  75. if(!hwTree)
  76. return FALSE;
  77. SHFILEINFO sfi;
  78. TreeView_SetImageList(hwTree, reinterpret_cast<HIMAGELIST>(SHGetFileInfo(TEXT("C:\\"),
  79. 0,&sfi, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_SMALLICON)),
  80. TVSIL_NORMAL);
  81. // Get all user drives
  82. DWORD dwLength = GetLogicalDriveStrings(0,0);
  83. if(dwLength == 0)
  84. return FALSE;
  85. TCHAR* szDrives = new TCHAR[dwLength+1];
  86. if(!szDrives)
  87. return FALSE;
  88. GetLogicalDriveStrings(dwLength, szDrives);
  89. TCHAR* szCurrDrive = szDrives;
  90. // Go through each drive
  91. while(*szCurrDrive)
  92. {
  93. // Only pay attention to fixed drives (non-network, non-CD, non-floppy)
  94. if(((GetDriveType(szCurrDrive) == DRIVE_FIXED) && (s_uiFlags & BF_HARDDRIVES))
  95. || ((GetDriveType(szCurrDrive) == DRIVE_REMOVABLE) && (s_uiFlags & BF_FLOPPYDRIVES))
  96. || ((GetDriveType(szCurrDrive) == DRIVE_CDROM) && (s_uiFlags & BF_CDROMDRIVES))
  97. || ((GetDriveType(szCurrDrive) == DRIVE_REMOTE) && (s_uiFlags & BF_NETWORKDRIVES)))
  98. {
  99. SHGetFileInfo(szCurrDrive, 0, &sfi, sizeof(sfi),
  100. SHGFI_SYSICONINDEX);
  101. // Get rid of the terminating '\'
  102. szCurrDrive[lstrlen(szCurrDrive)-1] = TEXT('\0');
  103. // Insert a disk drive item into the tree root.
  104. TVINSERTSTRUCT tvis;
  105. tvis.hParent = TVI_ROOT;
  106. tvis.hInsertAfter = TVI_LAST;
  107. tvis.itemex.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE| TVIF_TEXT;
  108. tvis.itemex.iImage = sfi.iIcon;
  109. tvis.itemex.iSelectedImage = sfi.iIcon;
  110. tvis.itemex.pszText = szCurrDrive;
  111. tvis.itemex.cchTextMax = lstrlen(szCurrDrive);
  112. HTREEITEM hTreeItem = TreeView_InsertItem(hwTree, &tvis);
  113. assert(hTreeItem);
  114. // Add subitems to the item
  115. AddTreeSubItems(hwTree, hTreeItem);
  116. // Move to next drive
  117. szCurrDrive += lstrlen(szCurrDrive) + 2;
  118. }
  119. else
  120. // Move to next drive.
  121. szCurrDrive += lstrlen(szCurrDrive) + 1;
  122. }
  123. delete szDrives;
  124. // Select the first element.
  125. HTREEITEM hItem = TreeView_GetChild(hwTree, TVI_ROOT);
  126. TreeView_SelectItem(hwTree, hItem);
  127. // Force tree to update, and restore original focus
  128. SetFocus(hwTree);
  129. SetFocus(GetDlgItem(hwnd, IDOK));
  130. return TRUE;
  131. }
  132. // Catch notification messages, so we can control expansion/collapsing.
  133. void HandleBrowseNotify(HWND hwnd, void* pvArg)
  134. {
  135. // Get tree control
  136. HWND hwTree = GetDlgItem(hwnd, IDC_DIRTREE);
  137. HWND hwFileList = GetDlgItem(hwnd, IDC_FILELISTCOMBO);
  138. if(!hwTree || !hwFileList)
  139. {
  140. DestroyWindow(GetParent(hwnd));
  141. return;
  142. }
  143. HTREEITEM hItem;
  144. TCHAR szPath[MAX_PATH] = TEXT("\0");
  145. // Get notification headers
  146. NMHDR* pHdr = reinterpret_cast<NMHDR*>(pvArg);
  147. LPNMTREEVIEW pnmTreeView = reinterpret_cast<LPNMTREEVIEW>(pvArg);
  148. switch(pHdr->code)
  149. {
  150. // Expanding or collapsing, called for each child.
  151. case TVN_ITEMEXPANDED:
  152. // If we're expanding, get the sub items of all children
  153. if(pnmTreeView->action & TVE_EXPAND)
  154. {
  155. // Switch our parent to an open folder icon.
  156. if(TreeView_GetParent(hwTree, pnmTreeView->itemNew.hItem))
  157. {
  158. szPath[0] = TEXT('\0');
  159. GetItemPath(hwTree, pnmTreeView->itemNew.hItem, szPath);
  160. SHFILEINFO sfi;
  161. SHGetFileInfo(szPath, 0, &sfi, sizeof(sfi),
  162. SHGFI_SYSICONINDEX | SHGFI_OPENICON);
  163. TVITEMEX tvitemex;
  164. tvitemex.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_HANDLE;
  165. tvitemex.hItem = pnmTreeView->itemNew.hItem;
  166. tvitemex.iImage = sfi.iIcon;
  167. tvitemex.iSelectedImage = sfi.iIcon;
  168. TreeView_SetItem(hwTree, &tvitemex);
  169. }
  170. // Add all sub-items to this item.
  171. AddTreeSubItems(hwTree, pnmTreeView->itemNew.hItem);
  172. // Go through each child, and and check if expansion should be allowed
  173. HTREEITEM hChild = TreeView_GetChild(hwTree, pnmTreeView->itemNew.hItem);
  174. while(hChild != NULL)
  175. {
  176. CheckTreeSubItems(hwTree, hChild);
  177. hChild = TreeView_GetNextSibling(hwTree, hChild);
  178. }
  179. }
  180. else if(pnmTreeView->action & TVE_COLLAPSE)
  181. {
  182. // Switch parent to a closed icon.
  183. if(TreeView_GetParent(hwTree, pnmTreeView->itemNew.hItem))
  184. {
  185. szPath[0] = TEXT('\0');
  186. GetItemPath(hwTree, pnmTreeView->itemNew.hItem, szPath);
  187. SHFILEINFO sfi;
  188. SHGetFileInfo(szPath, 0, &sfi, sizeof(sfi),
  189. SHGFI_SYSICONINDEX | SHGFI_OPENICON);
  190. TVITEMEX tvitemex;
  191. tvitemex.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_HANDLE;
  192. tvitemex.hItem = pnmTreeView->itemNew.hItem;
  193. tvitemex.iImage = sfi.iIcon;
  194. tvitemex.iSelectedImage = sfi.iIcon;
  195. TreeView_SetItem(hwTree, &tvitemex);
  196. }
  197. // Remove all subitems for every child.
  198. RemoveTreeSubItems(hwTree, pnmTreeView->itemNew.hItem);
  199. CheckTreeSubItems(hwTree, pnmTreeView->itemNew.hItem);
  200. }
  201. break;
  202. case TVN_SELCHANGED:
  203. // Only bother updating edit box if the tree has the focus
  204. if(GetFocus() == hwTree)
  205. {
  206. GetItemPath(hwTree, pnmTreeView->itemNew.hItem, szPath);
  207. SetWindowText(hwFileList, szPath);
  208. }
  209. break;
  210. // When treeview gains focus, make sure file list and tree view
  211. // selection are in sync.
  212. case NM_SETFOCUS:
  213. hItem = TreeView_GetSelection(hwTree);
  214. GetItemPath(hwTree, hItem, szPath);
  215. SetWindowText(hwFileList, szPath);
  216. break;
  217. }
  218. }
  219. // Handle a command message.
  220. void HandleBrowseCommand(HWND hwnd, UINT uiCtrlID, UINT uiNotify, HWND hwCtrl)
  221. {
  222. HWND hwTree = GetDlgItem(hwnd, IDC_DIRTREE);
  223. HTREEITEM hSelected;
  224. TVITEMEX tvItem;
  225. TCHAR szPath[MAX_PATH];
  226. switch(uiCtrlID)
  227. {
  228. // Get path of item, and return it.
  229. case IDOK:
  230. // Retrieve item from tree view.
  231. hSelected = TreeView_GetSelection(hwTree);
  232. if(!hSelected)
  233. {
  234. MessageBeep(0);
  235. break;
  236. }
  237. s_szPathBuffer[0] = TEXT('\0');
  238. GetItemPath(hwTree, hSelected, s_szPathBuffer);
  239. if(s_szPathBuffer[lstrlen(s_szPathBuffer)-1]== TEXT('\\'))
  240. s_szPathBuffer[lstrlen(s_szPathBuffer)-1] = TEXT('\0');
  241. // Validate the path
  242. if(GetFileAttributes(s_szPathBuffer)==static_cast<DWORD>(-1))
  243. Error(hwnd, IDS_INVALIDPATH);
  244. else
  245. EndDialog(hwnd, reinterpret_cast<INT_PTR>(s_szPathBuffer));
  246. break;
  247. case IDCANCEL:
  248. // User selected cancel, just return null.
  249. EndDialog(hwnd, 0);
  250. break;
  251. case IDC_FILELISTCOMBO:
  252. switch(uiNotify)
  253. {
  254. case CBN_EDITCHANGE:
  255. SendMessage(hwCtrl, WM_GETTEXT, MAX_PATH,
  256. reinterpret_cast<LPARAM>(szPath));
  257. SelectItemFromFullPath(hwTree, szPath);
  258. break;
  259. case CBN_DROPDOWN:
  260. // clear the combo box.
  261. SendMessage(hwCtrl, CB_RESETCONTENT, 0, 0);
  262. // Fill the combo box with all the lowest level items under
  263. // treeview selection
  264. hSelected = TreeView_GetSelection(hwTree);
  265. tvItem.mask = TVIF_STATE | TVIF_HANDLE;
  266. tvItem.hItem = hSelected;
  267. TreeView_GetItem(hwTree, &tvItem);
  268. if(tvItem.state & TVIS_EXPANDED)
  269. {
  270. szPath[0] = TEXT('\0');
  271. GetItemPath(hwTree, hSelected, szPath);
  272. SendMessage(hwCtrl, CB_ADDSTRING, 0,
  273. reinterpret_cast<LPARAM>(szPath));
  274. HTREEITEM hItem = TreeView_GetChild(hwTree, tvItem.hItem);
  275. while(hItem)
  276. {
  277. szPath[0] = TEXT('\0');
  278. GetItemPath(hwTree, hItem, szPath);
  279. SendMessage(hwCtrl, CB_ADDSTRING, 0,
  280. reinterpret_cast<LPARAM>(szPath));
  281. hItem = TreeView_GetNextSibling(hwTree, hItem);
  282. }
  283. }
  284. else
  285. {
  286. HTREEITEM hItem;
  287. hItem = TreeView_GetParent(hwTree, tvItem.hItem);
  288. hItem = TreeView_GetChild(hwTree, hItem);
  289. while(hItem)
  290. {
  291. szPath[0] = TEXT('\0');
  292. GetItemPath(hwTree, hItem, szPath);
  293. SendMessage(hwCtrl, CB_ADDSTRING, 0,
  294. reinterpret_cast<LPARAM>(szPath));
  295. hItem = TreeView_GetNextSibling(hwTree, hItem);
  296. }
  297. }
  298. break;
  299. }
  300. break;
  301. };
  302. }
  303. // Expand an item to get its full path.
  304. void GetItemPath(HWND hwTree, HTREEITEM hItem, PTSTR szPath)
  305. {
  306. assert(hwTree);
  307. assert(hItem);
  308. assert(szPath);
  309. assert(szPath[0] == TEXT('\0'));
  310. // Recurse to get parent's path.
  311. HTREEITEM hParent = TreeView_GetParent(hwTree, hItem);
  312. if(hParent)
  313. {
  314. GetItemPath(hwTree, hParent, szPath);
  315. lstrcat(szPath, TEXT("\\"));
  316. }
  317. // Get item text, concatenate on current path..
  318. TVITEMEX tvItem;
  319. tvItem.mask = TVIF_TEXT | TVIF_HANDLE;
  320. tvItem.hItem = hItem;
  321. tvItem.pszText = szPath + lstrlen(szPath);
  322. tvItem.cchTextMax = MAX_PATH - lstrlen(szPath);
  323. TreeView_GetItem(hwTree, &tvItem);
  324. }
  325. // Remove all subitems below an element.
  326. void RemoveTreeSubItems(HWND hwTree, HTREEITEM hParent)
  327. {
  328. assert(hwTree);
  329. // Go through each child and delete.
  330. HTREEITEM hChild = TreeView_GetChild(hwTree, hParent);
  331. while(hChild != NULL)
  332. {
  333. HTREEITEM hSibling = TreeView_GetNextSibling(hwTree, hChild);
  334. // Recursively delete all subitems in this child.
  335. RemoveTreeSubItems(hwTree, hChild);
  336. // Remove this item.
  337. TreeView_DeleteItem(hwTree, hChild);
  338. // Move to next.
  339. hChild = hSibling;
  340. }
  341. }
  342. // Add items below an element.
  343. void AddTreeSubItems(HWND hwTree, HTREEITEM hParent)
  344. {
  345. assert(hwTree);
  346. // Clear-out (to ensure we don't add items twice)
  347. RemoveTreeSubItems(hwTree, hParent);
  348. // Do an early out if the item has already been expanded
  349. TVITEMEX tvitem;
  350. tvitem.mask = TVIF_CHILDREN | TVIF_HANDLE;
  351. tvitem.hItem = hParent;
  352. TreeView_GetItem(hwTree, &tvitem);
  353. if(tvitem.cChildren)
  354. return;
  355. // Do a search on all directories
  356. TCHAR szPath[MAX_PATH] = TEXT("");
  357. GetItemPath(hwTree, hParent, szPath);
  358. WIN32_FIND_DATA findData;
  359. lstrcat(szPath, TEXT("\\*.*"));
  360. HANDLE hSearch = FindFirstFile(szPath, &findData);
  361. if(hSearch == INVALID_HANDLE_VALUE)
  362. return;
  363. do
  364. {
  365. // Ignore if a relative directory (. or ..)
  366. // or if no select files were selected and it is not a directory
  367. // otherwise
  368. if((findData.cFileName[0] != TEXT('.')) &&
  369. ((!(s_uiFlags & BF_SELECTFILES) &&
  370. (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) ||
  371. (s_uiFlags & BF_SELECTFILES)))
  372. {
  373. SHFILEINFO sfi;
  374. szPath[0] = TEXT('\0');
  375. GetItemPath(hwTree, hParent, szPath);
  376. lstrcat(szPath, TEXT("\\"));
  377. lstrcat(szPath, findData.cFileName);
  378. SHGetFileInfo(szPath, 0, &sfi, sizeof(sfi),
  379. SHGFI_SYSICONINDEX);
  380. // Insert an item representing this directory.
  381. TVINSERTSTRUCT tvis;
  382. tvis.hParent = hParent;
  383. tvis.hInsertAfter = TVI_SORT;
  384. tvis.itemex.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_TEXT;
  385. tvis.itemex.iImage = sfi.iIcon;
  386. tvis.itemex.iSelectedImage = sfi.iIcon;
  387. tvis.itemex.pszText = findData.cFileName;
  388. tvis.itemex.cchTextMax = lstrlen(findData.cFileName);
  389. TreeView_InsertItem(hwTree, &tvis);
  390. }
  391. // Move to next file.
  392. } while(FindNextFile(hSearch, &findData));
  393. FindClose(hSearch);
  394. }
  395. void CheckTreeSubItems(HWND hwTree, HTREEITEM hParent)
  396. {
  397. assert(hwTree);
  398. // Do a search on all directories
  399. TCHAR szPath[MAX_PATH] = TEXT("");
  400. GetItemPath(hwTree, hParent, szPath);
  401. WIN32_FIND_DATA findData;
  402. lstrcat(szPath, TEXT("\\*.*"));
  403. HANDLE hSearch = FindFirstFile(szPath, &findData);
  404. if(hSearch == INVALID_HANDLE_VALUE)
  405. return;
  406. do
  407. {
  408. // Ignore if a relative directory (. or ..)
  409. // or if no select files were selected and it is not a directory
  410. // otherwise
  411. if((findData.cFileName[0] != TEXT('.')) &&
  412. ((!(s_uiFlags & BF_SELECTFILES) &&
  413. (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) ||
  414. (s_uiFlags & BF_SELECTFILES)))
  415. {
  416. SHFILEINFO sfi;
  417. szPath[0] = TEXT('\0');
  418. GetItemPath(hwTree, hParent, szPath);
  419. lstrcat(szPath, TEXT("\\"));
  420. lstrcat(szPath, findData.cFileName);
  421. SHGetFileInfo(szPath, 0, &sfi, sizeof(sfi),
  422. SHGFI_SYSICONINDEX);
  423. // Insert an item representing this directory.
  424. TVINSERTSTRUCT tvis;
  425. tvis.hParent = hParent;
  426. tvis.hInsertAfter = TVI_SORT;
  427. tvis.itemex.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_TEXT;
  428. tvis.itemex.iImage = sfi.iIcon;
  429. tvis.itemex.iSelectedImage = sfi.iIcon;
  430. tvis.itemex.pszText = findData.cFileName;
  431. tvis.itemex.cchTextMax = lstrlen(findData.cFileName);
  432. TreeView_InsertItem(hwTree, &tvis);
  433. FindClose(hSearch);
  434. return;
  435. }
  436. // Move to next file.
  437. } while(FindNextFile(hSearch, &findData));
  438. FindClose(hSearch);
  439. }
  440. // Given a relative path and a tree item, select a subitem from the relative path.
  441. // Returns true if item successfully selected, false otherwise.
  442. bool SelectSubitemFromPartialPath(HWND hwTree, HTREEITEM hItem, PTSTR szPath)
  443. {
  444. bool fExpandIt = false;
  445. TCHAR* szPathDelim = _tcschr(szPath, TEXT('\\'));
  446. if(szPathDelim)
  447. {
  448. if(szPathDelim == szPath)
  449. return false;
  450. *szPathDelim = TEXT('\0');
  451. if(szPathDelim[1] == TEXT('\0'))
  452. {
  453. szPathDelim = 0;
  454. fExpandIt = true;
  455. }
  456. }
  457. // Find this path.
  458. HTREEITEM hClosestChild = 0;
  459. HTREEITEM hChild = TreeView_GetChild(hwTree, hItem);
  460. while(hChild)
  461. {
  462. TCHAR szItemPath[MAX_PATH];
  463. TVITEMEX tvitem;
  464. tvitem.mask = TVIF_HANDLE | TVIF_TEXT;
  465. tvitem.hItem = hChild;
  466. tvitem.pszText = szItemPath;
  467. tvitem.cchTextMax = MAX_PATH;
  468. TreeView_GetItem(hwTree, &tvitem);
  469. if(lstrcmpi(szPath,tvitem.pszText) == 0)
  470. break;
  471. else if((StrStrI(tvitem.pszText, szPath) == tvitem.pszText) && !fExpandIt)
  472. {
  473. hClosestChild = hChild;
  474. break;
  475. }
  476. hChild = TreeView_GetNextSibling(hwTree, hChild);
  477. }
  478. if(!hChild)
  479. {
  480. if(!hClosestChild)
  481. return false;
  482. else
  483. {
  484. hChild = hClosestChild;
  485. szPathDelim = 0;
  486. }
  487. }
  488. // If nothing more on the path, select this item,
  489. // or expand and continue
  490. if(szPathDelim == 0)
  491. {
  492. if(fExpandIt)
  493. TreeView_Expand(hwTree, hChild, TVE_EXPAND);
  494. TreeView_SelectItem(hwTree, hChild);
  495. }
  496. else
  497. {
  498. if(fExpandIt)
  499. TreeView_Expand(hwTree, hChild, TVE_EXPAND);
  500. if(!SelectSubitemFromPartialPath(hwTree, hChild, szPathDelim+1))
  501. return false;
  502. }
  503. return true;
  504. }
  505. // Given a path, select the appropriate item in the tree.
  506. // If path is invalid, it will expand as much as possible
  507. // (until invalid element appears)
  508. // szPath is trashed.
  509. void SelectItemFromFullPath(HWND hwTree, PTSTR szPath)
  510. {
  511. if(!SelectSubitemFromPartialPath(hwTree, 0, szPath))
  512. TreeView_SelectItem(hwTree, 0);
  513. }