Reactos
at master 1488 lines 46 kB view raw
1/* 2 * PROJECT: ReactOS shell32 3 * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later) 4 * PURPOSE: SHBrowseForFolderA/W functions 5 * COPYRIGHT: Copyright 1999 Juergen Schmied 6 * Copyright 2024 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com> 7 */ 8 9// FIXME: Many flags unimplemented 10 11#include "precomp.h" 12 13#include <ui/layout.h> // Resizable window 14#include <compat_undoc.h> // RosGetProcessEffectiveVersion 15 16WINE_DEFAULT_DEBUG_CHANNEL(shell); 17 18INT WINAPI Shell_GetCachedImageIndexW(LPCWSTR szPath, INT nIndex, UINT Flags); 19 20#define SHV_CHANGE_NOTIFY (WM_USER + 0x1111) 21enum { SHGDN_TREE = SHGDN_NORMAL | SHGDN_INFOLDER }; 22 23static LPITEMIDLIST 24ILCloneToDepth(LPCITEMIDLIST pidlSrc, UINT depth) 25{ 26 SIZE_T cb = 0; 27 for (LPCITEMIDLIST pidl = pidlSrc; pidl && depth--; pidl = ILGetNext(pidl)) 28 cb += pidl->mkid.cb; 29 30 LPITEMIDLIST pidlOut = (LPITEMIDLIST)SHAlloc(cb + sizeof(WORD)); 31 if (pidlOut) 32 { 33 CopyMemory(pidlOut, pidlSrc, cb); 34 ZeroMemory(((BYTE*)pidlOut) + cb, sizeof(WORD)); 35 } 36 return pidlOut; 37} 38 39struct BrFolder 40{ 41 LPBROWSEINFOW lpBrowseInfo; 42 HWND hWnd; 43 HWND hwndTreeView; 44 PIDLIST_ABSOLUTE pidlRet; 45 LAYOUT_DATA* layout; // Filled by LayoutInit, used by LayoutUpdate 46 SIZE szMin; 47 ULONG hChangeNotify; // Change notification handle 48 IContextMenu* pContextMenu; // Active context menu 49}; 50 51struct BrItemData 52{ 53 CComPtr<IShellFolder> lpsfParent; // IShellFolder of the parent 54 PCIDLIST_RELATIVE pidlChild; // PIDL relative to parent 55 CComHeapPtr<ITEMIDLIST_ABSOLUTE> pidlFull; // Fully qualified PIDL 56}; 57 58static const LAYOUT_INFO g_layout_info[] = 59{ 60 { IDC_BROWSE_FOR_FOLDER_TITLE, BF_TOP | BF_LEFT | BF_RIGHT }, 61 { IDC_BROWSE_FOR_FOLDER_STATUS, BF_TOP | BF_LEFT | BF_RIGHT }, 62 { IDC_BROWSE_FOR_FOLDER_TREEVIEW, BF_TOP | BF_BOTTOM | BF_LEFT | BF_RIGHT }, 63 { IDC_BROWSE_FOR_FOLDER_FOLDER_TEXT, BF_TOP | BF_LEFT | BF_RIGHT }, 64 { IDC_BROWSE_FOR_FOLDER_NEW_FOLDER, BF_BOTTOM | BF_LEFT }, 65 { IDOK, BF_BOTTOM | BF_RIGHT }, 66 { IDCANCEL, BF_BOTTOM | BF_RIGHT }, 67}; 68 69#define SUPPORTED_FLAGS (BIF_STATUSTEXT | BIF_BROWSEFORCOMPUTER | BIF_RETURNFSANCESTORS | \ 70 BIF_RETURNONLYFSDIRS | BIF_NONEWFOLDERBUTTON | BIF_NEWDIALOGSTYLE | \ 71 BIF_BROWSEINCLUDEFILES) 72 73static HTREEITEM 74BrFolder_InsertItem( 75 _Inout_ BrFolder *info, 76 _Inout_ IShellFolder *lpsf, 77 _In_ PCUITEMID_CHILD pidlChild, 78 _In_ PCIDLIST_ABSOLUTE pidlParent, 79 _In_ HTREEITEM hParent); 80 81static inline DWORD 82BrowseFlagsToSHCONTF(UINT ulFlags) 83{ 84 return SHCONTF_FOLDERS | ((ulFlags & BIF_BROWSEINCLUDEFILES) ? SHCONTF_NONFOLDERS : 0); 85} 86 87static void 88BrFolder_Callback(LPBROWSEINFOW lpBrowseInfo, HWND hWnd, UINT uMsg, LPARAM lParam) 89{ 90 if (!lpBrowseInfo->lpfn) 91 return; 92 lpBrowseInfo->lpfn(hWnd, uMsg, lParam, lpBrowseInfo->lParam); 93} 94 95static BrItemData * 96BrFolder_GetItemData(BrFolder *info, HTREEITEM hItem) 97{ 98 TVITEMW item = { TVIF_HANDLE | TVIF_PARAM }; 99 item.hItem = hItem; 100 if (!TreeView_GetItem(info->hwndTreeView, &item)) 101 { 102 ERR("TreeView_GetItem failed\n"); 103 return NULL; 104 } 105 return (BrItemData *)item.lParam; 106} 107 108static SFGAOF 109BrFolder_GetItemAttributes(BrFolder *info, HTREEITEM hItem, SFGAOF Query) 110{ 111 BrItemData *item = BrFolder_GetItemData(info, hItem); 112 SFGAOF Att = Query; 113 HRESULT hr = item ? item->lpsfParent->GetAttributesOf(1, &item->pidlChild, &Att) : E_FAIL; 114 return SUCCEEDED(hr) ? (Query & Att) : 0; 115} 116 117static HRESULT 118BrFolder_GetChildrenEnum( 119 _In_ BrFolder *info, 120 _In_ BrItemData *pItemData, 121 _Outptr_opt_ IEnumIDList **ppEnum) 122{ 123 if (!pItemData) 124 return E_FAIL; 125 126 CComPtr<IEnumIDList> pEnumIL; 127 PCUITEMID_CHILD pidlRef = pItemData->pidlChild; 128 ULONG attrs = SFGAO_HASSUBFOLDER | SFGAO_FOLDER; 129 IShellFolder *lpsf = pItemData->lpsfParent; 130 HRESULT hr = lpsf->GetAttributesOf(1, &pidlRef, &attrs); 131 if (FAILED_UNEXPECTEDLY(hr) || !(attrs & SFGAO_FOLDER)) 132 return E_FAIL; 133 134 CComPtr<IShellFolder> psfChild; 135 if (_ILIsDesktop(pItemData->pidlFull)) 136 hr = SHGetDesktopFolder(&psfChild); 137 else 138 hr = lpsf->BindToObject(pidlRef, NULL, IID_PPV_ARG(IShellFolder, &psfChild)); 139 if (FAILED_UNEXPECTEDLY(hr)) 140 return E_FAIL; 141 142 DWORD flags = BrowseFlagsToSHCONTF(info->lpBrowseInfo->ulFlags); 143 hr = psfChild->EnumObjects(info->hWnd, flags, &pEnumIL); 144 if (hr == S_OK) 145 { 146 if ((pEnumIL->Skip(1) != S_OK) || FAILED(pEnumIL->Reset())) 147 pEnumIL = NULL; 148 } 149 150 if (!pEnumIL || !(attrs & SFGAO_HASSUBFOLDER)) 151 return E_FAIL; // No children 152 153 if (ppEnum) 154 *ppEnum = pEnumIL.Detach(); 155 156 return S_OK; // There are children 157} 158 159/****************************************************************************** 160 * BrFolder_InitTreeView [Internal] 161 * 162 * Called from WM_INITDIALOG handler. 163 */ 164static void 165BrFolder_InitTreeView(BrFolder *info) 166{ 167 HIMAGELIST hImageList; 168 HRESULT hr; 169 HTREEITEM hItem; 170 171 Shell_GetImageLists(NULL, &hImageList); 172 if (hImageList) 173 TreeView_SetImageList(info->hwndTreeView, hImageList, 0); 174 175 /* We want to call BrFolder_InsertItem down the code, in order to insert 176 * the root item of the treeview. Due to BrFolder_InsertItem's signature, 177 * we need the following to do this: 178 * 179 * + An ITEMIDLIST corresponding to _the parent_ of root. 180 * + An ITEMIDLIST, which is a relative path from root's parent to root 181 * (containing a single SHITEMID). 182 * + An IShellFolder interface pointer of root's parent folder. 183 * 184 * If root is 'Desktop', then root's parent is also 'Desktop'. 185 */ 186 187 PCIDLIST_ABSOLUTE pidlRoot = info->lpBrowseInfo->pidlRoot; 188 CComHeapPtr<ITEMIDLIST_ABSOLUTE> pidlParent(ILClone(pidlRoot)); 189 ILRemoveLastID(pidlParent); 190 PCIDLIST_RELATIVE pidlChild = ILFindLastID(pidlRoot); 191 192 CComPtr<IShellFolder> lpsfParent; 193 hr = SHBindToObject(NULL, pidlParent, /*NULL, */ IID_PPV_ARG(IShellFolder, &lpsfParent)); 194 if (FAILED_UNEXPECTEDLY(hr)) 195 return; 196 197 TreeView_DeleteItem(info->hwndTreeView, TVI_ROOT); 198 hItem = BrFolder_InsertItem(info, lpsfParent, pidlChild, pidlParent, TVI_ROOT); 199 TreeView_Expand(info->hwndTreeView, hItem, TVE_EXPAND); 200} 201 202static HRESULT 203BrFolder_GetIconPair(BrItemData &item, TVITEMW &tvi) 204{ 205 INT open = -1; 206 INT normal = SHMapPIDLToSystemImageListIndex(item.lpsfParent, item.pidlChild, &open); 207 if (normal >= 0) 208 { 209 tvi.iImage = normal; 210 tvi.iSelectedImage = (open >= 0) ? open : normal; 211 return S_OK; 212 } 213 tvi.iImage = tvi.iSelectedImage = Shell_GetCachedImageIndexW(swShell32Name, IDI_SHELL_FOLDER - 1, 0); 214 return S_FALSE; 215} 216 217/****************************************************************************** 218 * BrFolder_GetName [Internal] 219 * 220 * Query a shell folder for the display name of one of its children 221 * 222 * PARAMS 223 * lpsf [I] IShellFolder interface of the folder to be queried. 224 * pidlChild [I] ITEMIDLIST of the child, relative to parent 225 * dwFlags [I] as in IShellFolder::GetDisplayNameOf 226 * lpFriendlyName [O] The desired display name in unicode 227 * 228 * RETURNS 229 * Success: TRUE 230 * Failure: FALSE 231 */ 232static BOOL 233BrFolder_GetName( 234 _In_ IShellFolder *lpsf, 235 _In_ PCIDLIST_RELATIVE pidlChild, 236 _In_ DWORD dwFlags, 237 _Out_ PWSTR lpFriendlyName) 238{ 239 BOOL bSuccess = FALSE; 240 STRRET str; 241 242 TRACE("%p %p %x %p\n", lpsf, pidlChild, dwFlags, lpFriendlyName); 243 if (!FAILED_UNEXPECTEDLY(lpsf->GetDisplayNameOf(pidlChild, dwFlags, &str))) 244 bSuccess = StrRetToStrNW(lpFriendlyName, MAX_PATH, &str, pidlChild); 245 246 TRACE("-- %s\n", debugstr_w(lpFriendlyName)); 247 return bSuccess; 248} 249 250static BOOL 251BrFolder_GetName( 252 _In_ BrFolder *info, 253 _In_ HTREEITEM hItem, 254 _In_ UINT Flags, 255 _Out_ PWSTR Buffer) 256{ 257 if (BrItemData *item = BrFolder_GetItemData(info, hItem)) 258 return BrFolder_GetName(item->lpsfParent, item->pidlChild, Flags, Buffer); 259 return FALSE; 260} 261 262static BOOL 263BrFolder_IsTreeItemInEnum( 264 _Inout_ BrFolder *info, 265 _In_ HTREEITEM hItem, 266 _Inout_ IEnumIDList *pEnum) 267{ 268 BrItemData *pItemData = BrFolder_GetItemData(info, hItem); 269 if (!pItemData) 270 return FALSE; 271 272 pEnum->Reset(); 273 274 CComHeapPtr<ITEMIDLIST_RELATIVE> pidlTemp; 275 while (pEnum->Next(1, &pidlTemp, NULL) == S_OK) 276 { 277 if (ILIsEqual(pidlTemp, pItemData->pidlChild)) 278 return TRUE; 279 280 pidlTemp.Free(); 281 } 282 283 return FALSE; 284} 285 286static BOOL 287BrFolder_TreeItemHasThisChild( 288 _In_ BrFolder *info, 289 _In_ HTREEITEM hItem, 290 _Outptr_opt_ PITEMID_CHILD pidlChild) 291{ 292 for (hItem = TreeView_GetChild(info->hwndTreeView, hItem); hItem; 293 hItem = TreeView_GetNextSibling(info->hwndTreeView, hItem)) 294 { 295 BrItemData *pItemData = BrFolder_GetItemData(info, hItem); 296 if (ILIsEqual(pItemData->pidlChild, pidlChild)) 297 return TRUE; 298 } 299 300 return FALSE; 301} 302 303static HTREEITEM 304BrFolder_FindTreeItemOfAbsoluteItem( 305 _In_ BrFolder &info, 306 _In_ PCIDLIST_ABSOLUTE pidl, 307 _In_opt_ HTREEITEM hItem = NULL) 308{ 309 if (!hItem) 310 hItem = TreeView_GetRoot(info.hwndTreeView); 311 312 for (; pidl && hItem; hItem = TreeView_GetNextSibling(info.hwndTreeView, hItem)) 313 { 314 BrItemData *pItemData = BrFolder_GetItemData(&info, hItem); 315 if (ILIsEqual(pItemData->pidlFull, pidl)) 316 return hItem; 317 HTREEITEM hChild = TreeView_GetChild(info.hwndTreeView, hItem); 318 if (hChild && (hChild = BrFolder_FindTreeItemOfAbsoluteItem(info, pidl, hChild)) != NULL) 319 return hChild; 320 } 321 return NULL; 322} 323 324static BOOL 325BrFolder_UpdateItemEx( 326 _In_ BrFolder &info, 327 _In_ HTREEITEM hItem, 328 _In_opt_ PCIDLIST_ABSOLUTE pidlFull, 329 _In_ UINT Flags = TVIF_TEXT | TVIF_IMAGE | TVIF_CHILDREN) 330{ 331 ASSERT(hItem); 332 TVITEMW item = { (TVIF_HANDLE | Flags), hItem }; 333 BrItemData *data = BrFolder_GetItemData(&info, hItem); 334 if (!data) 335 return FALSE; 336 337 if (pidlFull) 338 { 339 if ((pidlFull = ILClone(pidlFull)) == NULL) 340 return FALSE; 341 data->pidlFull.Free(); 342 data->pidlFull.Attach(const_cast<PIDLIST_ABSOLUTE>(pidlFull)); 343 data->pidlChild = ILFindLastID(data->pidlFull); 344 // Note: We assume lpsfParent does not change 345 } 346 347 WCHAR Name[MAX_PATH]; 348 if (Flags & TVIF_TEXT) 349 { 350 item.pszText = Name; 351 if (!BrFolder_GetName(data->lpsfParent, data->pidlChild, SHGDN_TREE, Name)) 352 return FALSE; 353 } 354 355 if (Flags & TVIF_IMAGE) 356 { 357 item.mask |= TVIF_SELECTEDIMAGE; 358 BrFolder_GetIconPair(*data, item); 359 } 360 361 item.cChildren = 0; 362 CComPtr<IEnumIDList> pEnum; 363 if ((Flags & TVIF_CHILDREN) && BrFolder_GetChildrenEnum(&info, data, &pEnum) == S_OK) 364 { 365 CComHeapPtr<ITEMIDLIST_RELATIVE> pidlTemp; 366 if (pEnum->Next(1, &pidlTemp, NULL) == S_OK) 367 ++item.cChildren; 368 } 369 370 return TreeView_SetItem(info.hwndTreeView, &item); 371} 372 373static void 374BrFolder_UpdateItem( 375 _In_ BrFolder *info, 376 _In_ HTREEITEM hItem) 377{ 378 BrFolder_UpdateItemEx(*info, hItem, NULL, TVIF_IMAGE | TVIF_CHILDREN); 379} 380 381/****************************************************************************** 382 * BrFolder_InsertItem [Internal] 383 * 384 * PARAMS 385 * info [I] data for the dialog 386 * lpsf [I] IShellFolder interface of the item's parent shell folder 387 * pidlChild [I] ITEMIDLIST of the child to insert, relative to parent 388 * pidlParent [I] ITEMIDLIST of the parent shell folder 389 * hParent [I] The treeview-item that represents the parent shell folder 390 * 391 * RETURNS 392 * Success: Handle to the created and inserted treeview-item 393 * Failure: NULL 394 */ 395static HTREEITEM 396BrFolder_InsertItem( 397 _Inout_ BrFolder *info, 398 _Inout_ IShellFolder *lpsf, 399 _In_ PCUITEMID_CHILD pidlChild, 400 _In_ PCIDLIST_ABSOLUTE pidlParent, 401 _In_ HTREEITEM hParent) 402{ 403 if (!(BrowseFlagsToSHCONTF(info->lpBrowseInfo->ulFlags) & SHCONTF_NONFOLDERS)) 404 { 405#ifdef BIF_BROWSEFILEJUNCTIONS 406 if (!(info->lpBrowseInfo->ulFlags & BIF_BROWSEFILEJUNCTIONS)) 407#endif 408 if (SHGetAttributes(lpsf, pidlChild, SFGAO_STREAM) & SFGAO_STREAM) 409 return NULL; // .zip files have both FOLDER and STREAM attributes set 410 } 411 412 WCHAR szName[MAX_PATH]; 413 if (!BrFolder_GetName(lpsf, pidlChild, SHGDN_TREE, szName)) 414 return NULL; 415 416 BrItemData *pItemData = new BrItemData(); 417 418 TVITEMW item = { TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM | TVIF_CHILDREN }; 419 item.pszText = szName; 420 item.cchTextMax = _countof(szName); 421 item.lParam = (LPARAM)pItemData; 422 423 PIDLIST_ABSOLUTE pidlFull = 424 (pidlParent ? ILCombine(pidlParent, pidlChild) : ILClone(pidlChild)); 425 426 pItemData->lpsfParent = lpsf; 427 pItemData->pidlFull.Attach(pidlFull); 428 pItemData->pidlChild = ILFindLastID(pItemData->pidlFull); 429 BrFolder_GetIconPair(*pItemData, item); 430 431 if (BrFolder_GetChildrenEnum(info, pItemData, NULL) == S_OK) 432 item.cChildren = 1; 433 434 TVINSERTSTRUCTW tvins = { hParent }; 435 tvins.item = item; 436 return TreeView_InsertItem(info->hwndTreeView, &tvins); 437} 438 439/****************************************************************************** 440 * BrFolder_Expand [Internal] 441 * 442 * For each child (given by pEnum) of the parent shell folder, which is given by 443 * lpsf and whose PIDL is pidl, insert a treeview-item right under hParent 444 * 445 * PARAMS 446 * info [I] data for the dialog 447 * lpsf [I] IShellFolder interface of the parent shell folder 448 * pidl [I] ITEMIDLIST of the parent shell folder 449 * hParent [I] The treeview item that represents the parent shell folder 450 * pEnum [I] An iterator for the children of the parent shell folder 451 */ 452static void 453BrFolder_Expand( 454 _In_ BrFolder *info, 455 _In_ IShellFolder *lpsf, 456 _In_ PCIDLIST_ABSOLUTE pidlFull, 457 _In_ HTREEITEM hParent) 458{ 459 TRACE("%p %p %p\n", lpsf, pidlFull, hParent); 460 461 // No IEnumIDList -> No children 462 BrItemData *pItemData = BrFolder_GetItemData(info, hParent); 463 CComPtr<IEnumIDList> pEnum; 464 HRESULT hr = BrFolder_GetChildrenEnum(info, pItemData, &pEnum); 465 if (FAILED(hr)) 466 return; 467 468 SetCapture(info->hWnd); 469 SetCursor(LoadCursorW(NULL, (LPWSTR)IDC_WAIT)); 470 471 CComHeapPtr<ITEMIDLIST_RELATIVE> pidlTemp; 472 ULONG ulFetched; 473 while (S_OK == pEnum->Next(1, &pidlTemp, &ulFetched)) 474 { 475 /* Ignore return value of BrFolder_InsertItem to avoid incomplete folder listing */ 476 BrFolder_InsertItem(info, lpsf, pidlTemp, pidlFull, hParent); 477 pidlTemp.Free(); // Finally, free the pidl that the shell gave us... 478 } 479 480 ReleaseCapture(); 481 SetCursor(LoadCursorW(NULL, (LPWSTR)IDC_ARROW)); 482} 483 484static inline BOOL 485PIDLIsType(LPCITEMIDLIST pidl, PIDLTYPE type) 486{ 487 LPPIDLDATA data = _ILGetDataPointer(pidl); 488 if (!data) 489 return FALSE; 490 return (data->type == type); 491} 492 493static void 494BrFolder_CheckValidSelection(BrFolder *info, BrItemData *pItemData) 495{ 496 LPBROWSEINFOW lpBrowseInfo = info->lpBrowseInfo; 497 PCIDLIST_RELATIVE pidlChild = pItemData->pidlChild; 498 DWORD dwAttributes; 499 HRESULT hr; 500 501 BOOL bEnabled = TRUE; 502 if ((lpBrowseInfo->ulFlags & BIF_BROWSEFORCOMPUTER) && !PIDLIsType(pidlChild, PT_COMP)) 503 bEnabled = FALSE; 504 505 if (lpBrowseInfo->ulFlags & BIF_RETURNFSANCESTORS) 506 { 507 dwAttributes = SFGAO_FILESYSANCESTOR | SFGAO_FILESYSTEM; 508 hr = pItemData->lpsfParent->GetAttributesOf(1, &pidlChild, &dwAttributes); 509 if (FAILED(hr) || !(dwAttributes & (SFGAO_FILESYSANCESTOR | SFGAO_FILESYSTEM))) 510 bEnabled = FALSE; 511 } 512 513 dwAttributes = SFGAO_FOLDER | SFGAO_FILESYSTEM; 514 hr = pItemData->lpsfParent->GetAttributesOf(1, &pidlChild, &dwAttributes); 515 if (FAILED_UNEXPECTEDLY(hr) || 516 ((dwAttributes & (SFGAO_FOLDER | SFGAO_FILESYSTEM)) != (SFGAO_FOLDER | SFGAO_FILESYSTEM))) 517 { 518 if (lpBrowseInfo->ulFlags & BIF_RETURNONLYFSDIRS) 519 bEnabled = FALSE; 520 EnableWindow(GetDlgItem(info->hWnd, IDC_BROWSE_FOR_FOLDER_NEW_FOLDER), FALSE); 521 } 522 else 523 { 524 EnableWindow(GetDlgItem(info->hWnd, IDC_BROWSE_FOR_FOLDER_NEW_FOLDER), TRUE); 525 } 526 527 SendMessageW(info->hWnd, BFFM_ENABLEOK, 0, bEnabled); 528} 529 530static LRESULT 531BrFolder_Treeview_Delete(BrFolder *info, NMTREEVIEWW *pnmtv) 532{ 533 BrItemData *pItemData = (BrItemData *)pnmtv->itemOld.lParam; 534 535 TRACE("TVN_DELETEITEMA/W %p\n", pItemData); 536 537 delete pItemData; 538 return 0; 539} 540 541static LRESULT 542BrFolder_Treeview_Expand(BrFolder *info, NMTREEVIEWW *pnmtv) 543{ 544 BrItemData *pItemData = (BrItemData *)pnmtv->itemNew.lParam; 545 546 TRACE("TVN_ITEMEXPANDINGA/W\n"); 547 548 if ((pnmtv->itemNew.state & TVIS_EXPANDEDONCE)) 549 return 0; 550 551 HRESULT hr = S_OK; 552 CComPtr<IShellFolder> lpsf2; 553 if (!_ILIsEmpty(pItemData->pidlChild)) 554 { 555 hr = pItemData->lpsfParent->BindToObject(pItemData->pidlChild, NULL, 556 IID_PPV_ARG(IShellFolder, &lpsf2)); 557 } 558 else 559 { 560 lpsf2 = pItemData->lpsfParent; 561 } 562 563 HTREEITEM hItem = pnmtv->itemNew.hItem; 564 if (!FAILED_UNEXPECTEDLY(hr)) 565 BrFolder_Expand(info, lpsf2, pItemData->pidlFull, hItem); 566 567 // My Computer is already sorted and trying to do a simple text 568 // sort will only mess things up 569 if (!_ILIsMyComputer(pItemData->pidlChild)) 570 TreeView_SortChildren(info->hwndTreeView, hItem, FALSE); 571 572 return 0; 573} 574 575static HRESULT 576BrFolder_Treeview_Changed(BrFolder *info, NMTREEVIEWW *pnmtv) 577{ 578 BrItemData *pItemData = (BrItemData *)pnmtv->itemNew.lParam; 579 580 ILFree(info->pidlRet); 581 info->pidlRet = ILClone(pItemData->pidlFull); 582 583 WCHAR szName[MAX_PATH]; 584 if (BrFolder_GetName(pItemData->lpsfParent, pItemData->pidlChild, SHGDN_NORMAL | SHGDN_INFOLDER, szName)) 585 SetDlgItemTextW(info->hWnd, IDC_BROWSE_FOR_FOLDER_FOLDER_TEXT, szName); 586 587 BrFolder_Callback(info->lpBrowseInfo, info->hWnd, BFFM_SELCHANGED, (LPARAM)info->pidlRet); 588 BrFolder_CheckValidSelection(info, pItemData); 589 return S_OK; 590} 591 592static LRESULT 593BrFolder_Treeview_Rename(BrFolder *info, NMTVDISPINFOW *pnmtv) 594{ 595 if (!pnmtv->item.pszText) 596 return FALSE; 597 598 HTREEITEM hItem = TreeView_GetSelection(info->hwndTreeView); 599 BrItemData *data = BrFolder_GetItemData(info, hItem); 600 ASSERT(data); 601 ASSERT(BrFolder_GetItemAttributes(info, hItem, SFGAO_CANRENAME) & SFGAO_CANRENAME); 602 603 PITEMID_CHILD newChild; 604 HRESULT hr = data->lpsfParent->SetNameOf(info->hWnd, data->pidlChild, pnmtv->item.pszText, 605 SHGDN_NORMAL | SHGDN_INFOLDER, &newChild); 606 if (FAILED(hr)) 607 return FALSE; 608 609 LPITEMIDLIST newFull; 610 if (SUCCEEDED(hr = SHILClone(data->pidlFull, &newFull))) 611 { 612 ILRemoveLastID(newFull); 613 if (SUCCEEDED(hr = SHILAppend(newChild, &newFull))) 614 { 615 data->pidlFull.Free(); 616 data->pidlFull.Attach(newFull); 617 data->pidlChild = ILFindLastID(data->pidlFull); 618 } 619 newChild = NULL; // SHILAppend is nuts and frees this 620 } 621 ILFree(newChild); 622 623 BOOL AllowTreeTextChange = !BrFolder_UpdateItemEx(*info, hItem, NULL, TVIF_TEXT); 624 625 NMTREEVIEWW nmtv; 626 nmtv.itemNew.hItem = hItem; 627 nmtv.itemNew.mask = TVIF_PARAM; 628 nmtv.itemNew.lParam = (LPARAM)data; 629 BrFolder_Treeview_Changed(info, &nmtv); 630 return AllowTreeTextChange; 631} 632 633static HRESULT 634BrFolder_Rename(BrFolder *info, HTREEITEM hItem) 635{ 636 TreeView_SelectItem(info->hwndTreeView, hItem); 637 TreeView_EditLabel(info->hwndTreeView, hItem); 638 return S_OK; 639} 640 641static void 642BrFolder_Delete(BrFolder *info, HTREEITEM hItem) 643{ 644 BrItemData *data = BrFolder_GetItemData(info, hItem); 645 if (!data || !BrFolder_GetItemAttributes(info, hItem, SFGAO_CANDELETE)) 646 return; 647 CComPtr<IContextMenu> pCM; 648 if (FAILED(data->lpsfParent->GetUIObjectOf(info->hWnd, 1, &data->pidlChild, IID_NULL_PPV_ARG(IContextMenu, &pCM)))) 649 return; 650 UINT fCMIC = 0; 651 if (GetKeyState(VK_SHIFT) < 0) 652 fCMIC |= CMIC_MASK_SHIFT_DOWN; 653 if (GetKeyState(VK_CONTROL) < 0) 654 fCMIC |= CMIC_MASK_CONTROL_DOWN; 655 SHInvokeCommandOnContextMenu(info->hWnd, NULL, pCM, fCMIC, "delete"); 656} 657 658static void 659BrFolder_Refresh(_Inout_ BrFolder *info); 660 661static LRESULT 662BrFolder_Treeview_Keydown(BrFolder *info, LPNMTVKEYDOWN keydown) 663{ 664 // Old dialog doesn't support those advanced features 665 if (!(info->lpBrowseInfo->ulFlags & BIF_USENEWUI)) 666 return 0; 667 668 HTREEITEM hItem = TreeView_GetSelection(info->hwndTreeView); 669 670 switch (keydown->wVKey) 671 { 672 case VK_F2: 673 BrFolder_Rename(info, hItem); 674 break; 675 case VK_DELETE: 676 BrFolder_Delete(info, hItem); 677 break; 678 case 'R': 679 { 680 if (GetKeyState(VK_CONTROL) < 0) // Ctrl+R 681 BrFolder_Refresh(info); 682 break; 683 } 684 case VK_F5: 685 { 686 BrFolder_Refresh(info); 687 break; 688 } 689 } 690 return 0; 691} 692 693static LRESULT 694BrFolder_OnNotify(BrFolder *info, UINT CtlID, LPNMHDR lpnmh) 695{ 696 NMTREEVIEWW *pnmtv = (NMTREEVIEWW *)lpnmh; 697 698 TRACE("%p %x %p msg=%x\n", info, CtlID, lpnmh, pnmtv->hdr.code); 699 700 if (pnmtv->hdr.idFrom != IDC_BROWSE_FOR_FOLDER_TREEVIEW) 701 return 0; 702 703 switch (pnmtv->hdr.code) 704 { 705 case TVN_DELETEITEMA: 706 case TVN_DELETEITEMW: 707 return BrFolder_Treeview_Delete(info, pnmtv); 708 709 case TVN_ITEMEXPANDINGA: 710 case TVN_ITEMEXPANDINGW: 711 return BrFolder_Treeview_Expand(info, pnmtv); 712 713 case TVN_SELCHANGEDA: 714 case TVN_SELCHANGEDW: 715 return BrFolder_Treeview_Changed(info, pnmtv); 716 717 case TVN_BEGINLABELEDITA: 718 case TVN_BEGINLABELEDITW: 719 { 720 HWND hWndEdit = TreeView_GetEditControl(lpnmh->hwndFrom); 721 NMTVDISPINFO &tvdi = *(NMTVDISPINFO*)lpnmh; 722 UINT att = BrFolder_GetItemAttributes(info, tvdi.item.hItem, SFGAO_CANRENAME); 723 if (!(att & SFGAO_CANRENAME)) 724 { 725 MessageBeep(0xffffffff); 726 return S_FALSE; 727 } 728 WCHAR Name[MAX_PATH]; 729 if (BrFolder_GetName(info, tvdi.item.hItem, SHGDN_INFOLDER | SHGDN_FOREDITING, Name)) 730 SetWindowTextW(hWndEdit, Name); 731 return S_OK; 732 } 733 734 case TVN_ENDLABELEDITW: 735 return BrFolder_Treeview_Rename(info, (LPNMTVDISPINFOW)pnmtv); 736 737 case TVN_KEYDOWN: 738 return BrFolder_Treeview_Keydown(info, (LPNMTVKEYDOWN)pnmtv); 739 740 default: 741 WARN("unhandled (%d)\n", pnmtv->hdr.code); 742 break; 743 } 744 745 return 0; 746} 747 748static BOOL 749BrFolder_OnInitDialog(HWND hWnd, BrFolder *info) 750{ 751 CComHeapPtr<ITEMIDLIST_ABSOLUTE> pidlDesktop; 752 SHChangeNotifyEntry ntreg; 753 LPBROWSEINFOW lpBrowseInfo = info->lpBrowseInfo; 754 755 info->hWnd = hWnd; 756 SetWindowLongPtrW(hWnd, DWLP_USER, (LONG_PTR)info); 757 758 if (lpBrowseInfo->ulFlags & BIF_NEWDIALOGSTYLE) 759 FIXME("flags BIF_NEWDIALOGSTYLE partially implemented\n"); 760 761 if (lpBrowseInfo->ulFlags & ~SUPPORTED_FLAGS) 762 FIXME("flags %x not implemented\n", (lpBrowseInfo->ulFlags & ~SUPPORTED_FLAGS)); 763 764 info->layout = NULL; 765 if (lpBrowseInfo->ulFlags & BIF_USENEWUI) 766 { 767 RECT rcWnd; 768 769 // Resize the treeview if there's not editbox 770 if ((lpBrowseInfo->ulFlags & BIF_NEWDIALOGSTYLE) && 771 !(lpBrowseInfo->ulFlags & BIF_EDITBOX)) 772 { 773 RECT rcEdit, rcTreeView; 774 GetWindowRect(GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_FOLDER_TEXT), &rcEdit); 775 GetWindowRect(GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_TREEVIEW), &rcTreeView); 776 LONG cy = rcTreeView.top - rcEdit.top; 777 MapWindowPoints(NULL, hWnd, (LPPOINT)&rcTreeView, sizeof(RECT) / sizeof(POINT)); 778 rcTreeView.top -= cy; 779 MoveWindow(GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_TREEVIEW), 780 rcTreeView.left, rcTreeView.top, 781 rcTreeView.right - rcTreeView.left, 782 rcTreeView.bottom - rcTreeView.top, TRUE); 783 } 784 785 if (lpBrowseInfo->ulFlags & BIF_NEWDIALOGSTYLE) 786 info->layout = LayoutInit(hWnd, g_layout_info, _countof(g_layout_info)); 787 788 // TODO: Windows allows shrinking the windows a bit 789 GetWindowRect(hWnd, &rcWnd); 790 info->szMin.cx = rcWnd.right - rcWnd.left; 791 info->szMin.cy = rcWnd.bottom - rcWnd.top; 792 } 793 794 if (lpBrowseInfo->lpszTitle) 795 SetDlgItemTextW(hWnd, IDC_BROWSE_FOR_FOLDER_TITLE, lpBrowseInfo->lpszTitle); 796 else 797 ShowWindow(GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_TITLE), SW_HIDE); 798 799 if (!(lpBrowseInfo->ulFlags & BIF_STATUSTEXT) || (lpBrowseInfo->ulFlags & BIF_USENEWUI)) 800 ShowWindow(GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_STATUS), SW_HIDE); 801 802 // Hide "Make New Folder" Button? 803 if ((lpBrowseInfo->ulFlags & BIF_NONEWFOLDERBUTTON) || 804 !(lpBrowseInfo->ulFlags & BIF_NEWDIALOGSTYLE)) 805 { 806 ShowWindow(GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_NEW_FOLDER), SW_HIDE); 807 } 808 809 // Hide the editbox? 810 if (!(lpBrowseInfo->ulFlags & BIF_EDITBOX)) 811 { 812 ShowWindow(GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_FOLDER), SW_HIDE); 813 ShowWindow(GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_FOLDER_TEXT), SW_HIDE); 814 } 815 816 info->hwndTreeView = GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_TREEVIEW); 817 if (info->hwndTreeView) 818 BrFolder_InitTreeView(info); 819 else 820 ERR("treeview control missing!\n"); 821 822 // Register for change notifications 823 SHGetFolderLocation(NULL, CSIDL_DESKTOP, NULL, 0, &pidlDesktop); 824 825 ntreg.pidl = pidlDesktop; 826 ntreg.fRecursive = TRUE; 827 info->hChangeNotify = SHChangeNotifyRegister(hWnd, 828 SHCNRF_ShellLevel | SHCNRF_NewDelivery, 829 SHCNE_ALLEVENTS, 830 SHV_CHANGE_NOTIFY, 1, &ntreg); 831 832 if (!lpBrowseInfo->pidlRoot) 833 { 834 UINT csidl = (lpBrowseInfo->ulFlags & BIF_NEWDIALOGSTYLE) ? CSIDL_PERSONAL : CSIDL_DRIVES; 835 LPITEMIDLIST pidl = SHCloneSpecialIDList(NULL, csidl, TRUE); 836 if (pidl) 837 { 838 SendMessageW(info->hWnd, BFFM_SETSELECTION, FALSE, (LPARAM)pidl); 839 if (csidl == CSIDL_DRIVES) 840 SendMessageW(info->hWnd, BFFM_SETEXPANDED, FALSE, (LPARAM)pidl); 841 ILFree(pidl); 842 } 843 } 844 845 BrFolder_Callback(info->lpBrowseInfo, hWnd, BFFM_INITIALIZED, 0); 846 847 SHAutoComplete(GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_FOLDER_TEXT), 848 (SHACF_FILESYS_ONLY | SHACF_URLHISTORY | SHACF_FILESYSTEM)); 849 return TRUE; 850} 851 852static HRESULT 853BrFolder_NewFolder(BrFolder *info) 854{ 855 CComPtr<IShellFolder> desktop, cur; 856 WCHAR wszNewFolder[25], path[MAX_PATH], name[MAX_PATH]; 857 858 HRESULT hr = SHGetDesktopFolder(&desktop); 859 if (FAILED_UNEXPECTEDLY(hr)) 860 return hr; 861 862 if (info->pidlRet) 863 { 864 hr = desktop->BindToObject(info->pidlRet, NULL, IID_PPV_ARG(IShellFolder, &cur)); 865 if (FAILED_UNEXPECTEDLY(hr)) 866 return hr; 867 868 hr = SHGetPathFromIDListW(info->pidlRet, path); 869 } 870 else 871 { 872 cur = desktop; 873 hr = SHGetFolderPathW(NULL, CSIDL_DESKTOPDIRECTORY, NULL, SHGFP_TYPE_CURRENT, path); 874 } 875 876 if (FAILED_UNEXPECTEDLY(hr)) 877 return hr; 878 879 hr = E_FAIL; 880 if (!LoadStringW(shell32_hInstance, IDS_NEWFOLDER, wszNewFolder, _countof(wszNewFolder))) 881 return hr; 882 883 if (!PathYetAnotherMakeUniqueName(name, path, NULL, wszNewFolder)) 884 return hr; 885 886 INT len = lstrlenW(path); 887 if (len < MAX_PATH && name[len] == L'\\') 888 len++; 889 890 if (!CreateDirectoryW(name, NULL)) 891 return hr; 892 893 // Update parent of newly created directory 894 HTREEITEM hParent = TreeView_GetSelection(info->hwndTreeView); 895 if (!hParent) 896 return hr; 897 898 TreeView_Expand(info->hwndTreeView, hParent, TVE_EXPAND); 899 900 TVITEMW item = { TVIF_PARAM | TVIF_STATE }; 901 item.hItem = hParent; 902 TreeView_GetItem(info->hwndTreeView, &item); 903 BrItemData *item_data = (BrItemData *)item.lParam; 904 if (!item_data) 905 return hr; 906 907 // Update treeview 908 if (!(item.state & TVIS_EXPANDEDONCE)) 909 { 910 item.mask = TVIF_STATE; 911 item.state = item.stateMask = TVIS_EXPANDEDONCE; 912 TreeView_SetItem(info->hwndTreeView, &item); 913 } 914 915 CComHeapPtr<ITEMIDLIST_RELATIVE> pidlNew; 916 hr = cur->ParseDisplayName(NULL, NULL, name + len, NULL, &pidlNew, NULL); 917 if (FAILED_UNEXPECTEDLY(hr)) 918 return hr; 919 920 HTREEITEM hAdded = BrFolder_InsertItem(info, cur, pidlNew, item_data->pidlFull, hParent); 921 TreeView_SortChildren(info->hwndTreeView, hParent, FALSE); 922 return BrFolder_Rename(info, hAdded); 923} 924 925static void 926BrFolder_OnOK(BrFolder *info) 927{ 928 // Get the text 929 WCHAR szPath[MAX_PATH]; 930 GetDlgItemTextW(info->hWnd, IDC_BROWSE_FOR_FOLDER_FOLDER_TEXT, szPath, _countof(szPath)); 931 StrTrimW(szPath, L" \t"); 932 933 // The original pidl is owned by the treeview and will be free'd. 934 if (!PathIsRelativeW(szPath) && PathIsDirectoryW(szPath)) 935 info->pidlRet = ILCreateFromPathW(szPath); // It's valid path 936 else 937 info->pidlRet = ILClone(info->pidlRet); 938 939 if (!info->pidlRet) // A null pidl would mean a cancel 940 info->pidlRet = _ILCreateDesktop(); 941 942 pdump(info->pidlRet); 943 944 LPBROWSEINFOW lpBrowseInfo = info->lpBrowseInfo; 945 if (!lpBrowseInfo->pszDisplayName) 946 return; 947 948 SHFILEINFOW fileInfo = { NULL }; 949 lpBrowseInfo->pszDisplayName[0] = UNICODE_NULL; 950 if (SHGetFileInfoW((LPCWSTR)info->pidlRet, 0, &fileInfo, sizeof(fileInfo), 951 SHGFI_PIDL | SHGFI_DISPLAYNAME)) 952 { 953 lstrcpynW(lpBrowseInfo->pszDisplayName, fileInfo.szDisplayName, MAX_PATH); 954 } 955} 956 957static void 958BrFolder_OnCommand(BrFolder *info, UINT id) 959{ 960 switch (id) 961 { 962 case IDOK: 963 { 964 BrFolder_OnOK(info); 965 EndDialog(info->hWnd, IDOK); 966 break; 967 } 968 case IDCANCEL: 969 { 970 EndDialog(info->hWnd, IDCANCEL); 971 break; 972 } 973 case IDC_BROWSE_FOR_FOLDER_NEW_FOLDER: 974 { 975 BrFolder_NewFolder(info); 976 break; 977 } 978 } 979} 980 981static void 982GetTreeViewItemContextMenuPos(HWND hWnd, HTREEITEM hItem, POINT *ppt) 983{ 984 RECT rc; 985 if (TreeView_GetItemRect(hWnd, hItem, &rc, TRUE)) 986 { 987 ppt->x = (rc.left + rc.right) / 2; 988 ppt->y = (rc.top + rc.bottom) / 2; 989 } 990 ClientToScreen(hWnd, ppt); 991} 992 993static void 994BrFolder_OnContextMenu(BrFolder &info, LPARAM lParam) 995{ 996 enum { IDC_TOGGLE = 1, ID_FIRSTCMD, ID_LASTCMD = 0xffff }; 997 HTREEITEM hSelected = TreeView_GetSelection(info.hwndTreeView); 998 CMINVOKECOMMANDINFOEX ici = { sizeof(ici), CMIC_MASK_PTINVOKE, info.hWnd }; 999 ici.nShow = SW_SHOW; 1000 ici.ptInvoke.x = GET_X_LPARAM(lParam); 1001 ici.ptInvoke.y = GET_Y_LPARAM(lParam); 1002 if ((int)lParam == -1) // Keyboard 1003 { 1004 GetTreeViewItemContextMenuPos(info.hwndTreeView, hSelected, &ici.ptInvoke); 1005 } 1006 else // Get correct item for right-click on not current item 1007 { 1008 TVHITTESTINFO hti = { ici.ptInvoke }; 1009 ScreenToClient(info.hwndTreeView, &hti.pt); 1010 hSelected = TreeView_HitTest(info.hwndTreeView, &hti); 1011 } 1012 BrItemData *item = BrFolder_GetItemData(&info, hSelected); 1013 if (!item) 1014 return; // Not on an item 1015 1016 TV_ITEM tvi; 1017 tvi.mask = TVIF_STATE | TVIF_CHILDREN; 1018 tvi.stateMask = TVIS_EXPANDED; 1019 tvi.hItem = hSelected; 1020 TreeView_GetItem(info.hwndTreeView, &tvi); 1021 1022 CComPtr<IContextMenu> pcm; 1023 HRESULT hr = item->lpsfParent->GetUIObjectOf(info.hWnd, 1, &item->pidlChild, 1024 IID_IContextMenu, NULL, (void**)&pcm); 1025 if (FAILED(hr)) 1026 return; 1027 1028 HMENU hMenu = CreatePopupMenu(); 1029 if (!hMenu) 1030 return; 1031 info.pContextMenu = pcm; 1032 UINT cmf = ((GetKeyState(VK_SHIFT) < 0) ? CMF_EXTENDEDVERBS : 0) | CMF_CANRENAME; 1033 hr = pcm->QueryContextMenu(hMenu, 0, ID_FIRSTCMD, ID_LASTCMD, CMF_EXPLORE | cmf); 1034 if (hr > 0) 1035 _InsertMenuItemW(hMenu, 0, TRUE, 0, MFT_SEPARATOR, NULL, 0); 1036 _InsertMenuItemW(hMenu, 0, TRUE, IDC_TOGGLE, MFT_STRING, 1037 MAKEINTRESOURCEW((tvi.state & TVIS_EXPANDED) ? IDS_COLLAPSE : IDS_EXPAND), 1038 MFS_DEFAULT | (tvi.cChildren ? 0 : MFS_GRAYED)); 1039 1040 UINT cmd = TrackPopupMenuEx(hMenu, TPM_RETURNCMD, ici.ptInvoke.x, ici.ptInvoke.y, info.hWnd, NULL); 1041 ici.lpVerb = MAKEINTRESOURCEA(cmd - ID_FIRSTCMD); 1042 if (cmd == IDC_TOGGLE) 1043 { 1044 TreeView_SelectItem(info.hwndTreeView, hSelected); 1045 TreeView_Expand(info.hwndTreeView, hSelected, TVE_TOGGLE); 1046 } 1047 else if (cmd != 0 && GetDfmCmd(pcm, ici.lpVerb) == DFM_CMD_RENAME) 1048 { 1049 BrFolder_Rename(&info, hSelected); 1050 } 1051 else if (cmd != 0) 1052 { 1053 if (GetKeyState(VK_SHIFT) < 0) 1054 ici.fMask |= CMIC_MASK_SHIFT_DOWN; 1055 if (GetKeyState(VK_CONTROL) < 0) 1056 ici.fMask |= CMIC_MASK_CONTROL_DOWN; 1057 pcm->InvokeCommand((CMINVOKECOMMANDINFO*)&ici); 1058 } 1059 info.pContextMenu = NULL; 1060 DestroyMenu(hMenu); 1061} 1062 1063static BOOL 1064BrFolder_ExpandToPidl(BrFolder *info, LPITEMIDLIST pidlSelection, HTREEITEM *phItem) 1065{ 1066 LPITEMIDLIST pidlCurrent = pidlSelection; 1067 if (_ILIsDesktop(pidlSelection)) 1068 { 1069 if (phItem) 1070 *phItem = TVI_ROOT; 1071 return TRUE; 1072 } 1073 1074 // Initialize item to point to the first child of the root folder. 1075 TVITEMEXW item = { TVIF_PARAM }; 1076 item.hItem = TreeView_GetRoot(info->hwndTreeView); 1077 if (item.hItem) 1078 item.hItem = TreeView_GetChild(info->hwndTreeView, item.hItem); 1079 1080 // Walk the tree along the nodes corresponding to the remaining ITEMIDLIST 1081 UINT depth = _ILGetDepth(info->lpBrowseInfo->pidlRoot); 1082 while (item.hItem && pidlCurrent) 1083 { 1084 LPITEMIDLIST pidlNeedle = ILCloneToDepth(pidlSelection, ++depth); 1085 if (_ILIsEmpty(pidlNeedle)) 1086 { 1087 ILFree(pidlNeedle); 1088 item.hItem = NULL; // Failure 1089 break; 1090 } 1091next: 1092 TreeView_GetItem(info->hwndTreeView, &item); 1093 const BrItemData *pItemData = (BrItemData *)item.lParam; 1094 if (ILIsEqual(pItemData->pidlFull, pidlNeedle)) 1095 { 1096 BOOL done = _ILGetDepth(pidlSelection) == _ILGetDepth(pidlNeedle); 1097 if (done) 1098 { 1099 pidlCurrent = NULL; // Success 1100 } 1101 else 1102 { 1103 TreeView_Expand(info->hwndTreeView, item.hItem, TVE_EXPAND); 1104 item.hItem = TreeView_GetChild(info->hwndTreeView, item.hItem); 1105 } 1106 } 1107 else 1108 { 1109 item.hItem = TreeView_GetNextSibling(info->hwndTreeView, item.hItem); 1110 if (item.hItem) 1111 goto next; 1112 } 1113 ILFree(pidlNeedle); 1114 } 1115 1116 if (phItem) 1117 *phItem = item.hItem; 1118 1119 return (_ILIsEmpty(pidlCurrent) && item.hItem); 1120} 1121 1122static BOOL 1123BrFolder_ExpandToString(BrFolder *info, LPWSTR pszString, HTREEITEM *phItem) 1124{ 1125 CComHeapPtr<ITEMIDLIST_ABSOLUTE> pidlSelection; 1126 HRESULT hr = SHParseDisplayName(pszString, NULL, &pidlSelection, 0, NULL); 1127 return SUCCEEDED(hr) && BrFolder_ExpandToPidl(info, pidlSelection, phItem); 1128} 1129 1130static BOOL 1131BrFolder_OnSetExpanded(BrFolder *info, LPITEMIDLIST pidlSelection, LPWSTR pszString) 1132{ 1133 HTREEITEM hItem; 1134 BOOL ret; 1135 if (pszString) 1136 ret = BrFolder_ExpandToString(info, pszString, &hItem); 1137 else 1138 ret = BrFolder_ExpandToPidl(info, pidlSelection, &hItem); 1139 1140 if (ret) 1141 TreeView_Expand(info->hwndTreeView, hItem, TVE_EXPAND); 1142 return ret; 1143} 1144 1145static BOOL 1146BrFolder_OnSetSelectionPidl(BrFolder *info, LPITEMIDLIST pidlSelection) 1147{ 1148 if (!pidlSelection) 1149 return FALSE; 1150 1151 HTREEITEM hItem; 1152 BOOL ret = BrFolder_ExpandToPidl(info, pidlSelection, &hItem); 1153 if (ret) 1154 TreeView_SelectItem(info->hwndTreeView, hItem); 1155 return ret; 1156} 1157 1158static BOOL 1159BrFolder_OnSetSelectionW(BrFolder *info, LPWSTR pszSelection) 1160{ 1161 if (!pszSelection) 1162 return FALSE; 1163 1164 HTREEITEM hItem; 1165 BOOL ret = BrFolder_ExpandToString(info, pszSelection, &hItem); 1166 if (ret) 1167 TreeView_SelectItem(info->hwndTreeView, hItem); 1168 return ret; 1169} 1170 1171static BOOL 1172BrFolder_OnSetSelectionA(BrFolder *info, LPSTR pszSelectionA) 1173{ 1174 if (!pszSelectionA) 1175 return FALSE; 1176 1177 CComHeapPtr<WCHAR> pszSelectionW; 1178 __SHCloneStrAtoW(&pszSelectionW, pszSelectionA); 1179 if (!pszSelectionW) 1180 return FALSE; 1181 1182 return BrFolder_OnSetSelectionW(info, pszSelectionW); 1183} 1184 1185static void 1186BrFolder_OnDestroy(BrFolder *info) 1187{ 1188 if (info->layout) 1189 { 1190 LayoutDestroy(info->layout); 1191 info->layout = NULL; 1192 } 1193 1194 SHChangeNotifyDeregister(info->hChangeNotify); 1195} 1196 1197static void 1198BrFolder_RefreshRecurse( 1199 _Inout_ BrFolder *info, 1200 _In_ HTREEITEM hTarget) 1201{ 1202 // Get enum 1203 CComPtr<IEnumIDList> pEnum; 1204 BrItemData *pItemData = BrFolder_GetItemData(info, hTarget); 1205 HRESULT hrEnum = BrFolder_GetChildrenEnum(info, pItemData, &pEnum); 1206 1207 // Insert new items 1208 if (SUCCEEDED(hrEnum)) 1209 { 1210 CComHeapPtr<ITEMIDLIST_RELATIVE> pidlTemp; 1211 while (S_OK == pEnum->Next(1, &pidlTemp, NULL)) 1212 { 1213 if (!BrFolder_TreeItemHasThisChild(info, hTarget, pidlTemp)) 1214 { 1215 BrFolder_InsertItem(info, pItemData->lpsfParent, pidlTemp, pItemData->pidlFull, 1216 hTarget); 1217 } 1218 pidlTemp.Free(); 1219 } 1220 } 1221 1222 // Delete zombie items and update items 1223 HTREEITEM hItem, hNextItem; 1224 for (hItem = TreeView_GetChild(info->hwndTreeView, hTarget); hItem; hItem = hNextItem) 1225 { 1226 hNextItem = TreeView_GetNextSibling(info->hwndTreeView, hItem); 1227 1228 if (FAILED(hrEnum) || !BrFolder_IsTreeItemInEnum(info, hItem, pEnum)) 1229 { 1230 TreeView_DeleteItem(info->hwndTreeView, hItem); 1231 hNextItem = TreeView_GetChild(info->hwndTreeView, hTarget); 1232 continue; 1233 } 1234 1235 BrFolder_UpdateItem(info, hItem); 1236 BrFolder_RefreshRecurse(info, hItem); 1237 } 1238 1239 if (SUCCEEDED(hrEnum)) 1240 TreeView_SortChildren(info->hwndTreeView, hTarget, FALSE); 1241} 1242 1243static void 1244BrFolder_Refresh(_Inout_ BrFolder *info) 1245{ 1246 HTREEITEM hRoot = TreeView_GetRoot(info->hwndTreeView); 1247 1248 SendMessageW(info->hwndTreeView, WM_SETREDRAW, FALSE, 0); 1249 1250 BrFolder_RefreshRecurse(info, hRoot); 1251 1252 SendMessageW(info->hwndTreeView, WM_SETREDRAW, TRUE, 0); 1253 InvalidateRect(info->hwndTreeView, NULL, TRUE); 1254} 1255 1256static void 1257BrFolder_OnChangeEx( 1258 _Inout_ BrFolder *info, 1259 _In_ PCIDLIST_ABSOLUTE pidl1, 1260 _In_ PCIDLIST_ABSOLUTE pidl2, 1261 _In_ LONG event) 1262{ 1263 TRACE("(%p)->(%p, %p, 0x%lX)\n", info, pidl1, pidl2, event); 1264 1265 switch (event) 1266 { 1267 case SHCNE_RENAMEFOLDER: 1268 case SHCNE_RENAMEITEM: 1269 case SHCNE_UPDATEITEM: 1270 { 1271 UINT UpdateFlags = (event == SHCNE_UPDATEITEM) ? (TVIF_IMAGE | TVIF_CHILDREN) : (TVIF_TEXT); 1272 if (HTREEITEM hTI = BrFolder_FindTreeItemOfAbsoluteItem(*info, pidl1)) 1273 { 1274 if (BrFolder_UpdateItemEx(*info, hTI, pidl2, UpdateFlags)) 1275 { 1276 if ((hTI = TreeView_GetParent(info->hwndTreeView, hTI)) != NULL) 1277 TreeView_SortChildren(info->hwndTreeView, hTI, FALSE); 1278 return; 1279 } 1280 } 1281 break; 1282 } 1283 } 1284 1285 switch (event) 1286 { 1287 case SHCNE_DRIVEADD: 1288 case SHCNE_MKDIR: 1289 case SHCNE_CREATE: 1290 case SHCNE_DRIVEREMOVED: 1291 case SHCNE_RMDIR: 1292 case SHCNE_DELETE: 1293 case SHCNE_RENAMEFOLDER: 1294 case SHCNE_RENAMEITEM: 1295 case SHCNE_UPDATEDIR: 1296 case SHCNE_UPDATEITEM: 1297 { 1298 // FIXME: Avoid full refresh and optimize for speed. Use pidl0 and pidl1 1299 BrFolder_Refresh(info); 1300 break; 1301 } 1302 } 1303} 1304 1305// SHV_CHANGE_NOTIFY 1306static void 1307BrFolder_OnChange(BrFolder *info, WPARAM wParam, LPARAM lParam) 1308{ 1309 // We use SHCNRF_NewDelivery method 1310 HANDLE hChange = (HANDLE)wParam; 1311 DWORD dwProcID = (DWORD)lParam; 1312 1313 PIDLIST_ABSOLUTE *ppidl = NULL; 1314 LONG event; 1315 HANDLE hLock = SHChangeNotification_Lock(hChange, dwProcID, &ppidl, &event); 1316 if (hLock == NULL) 1317 { 1318 ERR("hLock == NULL\n"); 1319 return; 1320 } 1321 1322 BrFolder_OnChangeEx(info, ppidl[0], ppidl[1], (event & ~SHCNE_INTERRUPT)); 1323 1324 SHChangeNotification_Unlock(hLock); 1325} 1326 1327/************************************************************************* 1328 * BrFolderDlgProc32 (not an exported API function) 1329 */ 1330static INT_PTR CALLBACK 1331BrFolderDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 1332{ 1333 if (uMsg == WM_INITDIALOG) 1334 return BrFolder_OnInitDialog(hWnd, (BrFolder *)lParam); 1335 1336 BrFolder *info = (BrFolder *)GetWindowLongPtrW(hWnd, DWLP_USER); 1337 if (!info) 1338 return 0; 1339 1340 if (info->pContextMenu && SHELL_IsContextMenuMsg(uMsg)) 1341 { 1342 LRESULT result; 1343 if (SHForwardContextMenuMsg(info->pContextMenu, uMsg, wParam, 1344 lParam, &result, TRUE) == S_OK) 1345 { 1346 SetWindowLongPtr(hWnd, DWLP_MSGRESULT, result); 1347 return TRUE; 1348 } 1349 } 1350 1351 switch (uMsg) 1352 { 1353 case WM_NOTIFY: 1354 SetWindowLongPtr(hWnd, DWLP_MSGRESULT, BrFolder_OnNotify(info, (UINT)wParam, (LPNMHDR)lParam)); 1355 return TRUE; 1356 1357 case WM_COMMAND: 1358 BrFolder_OnCommand(info, wParam); 1359 break; 1360 1361 case WM_CONTEXTMENU: 1362 if (info->hwndTreeView == (HWND)wParam) 1363 BrFolder_OnContextMenu(*info, lParam); 1364 break; 1365 1366 case WM_GETMINMAXINFO: 1367 ((LPMINMAXINFO)lParam)->ptMinTrackSize.x = info->szMin.cx; 1368 ((LPMINMAXINFO)lParam)->ptMinTrackSize.y = info->szMin.cy; 1369 break; 1370 1371 case WM_SIZE: 1372 if (info->layout) // New style dialogs 1373 LayoutUpdate(hWnd, info->layout, g_layout_info, _countof(g_layout_info)); 1374 break; 1375 1376 case BFFM_SETSTATUSTEXTA: 1377 SetDlgItemTextA(hWnd, IDC_BROWSE_FOR_FOLDER_STATUS, (LPSTR)lParam); 1378 break; 1379 1380 case BFFM_SETSTATUSTEXTW: 1381 SetDlgItemTextW(hWnd, IDC_BROWSE_FOR_FOLDER_STATUS, (LPWSTR)lParam); 1382 break; 1383 1384 case BFFM_ENABLEOK: 1385 EnableWindow(GetDlgItem(hWnd, IDOK), lParam != 0); 1386 break; 1387 1388 case BFFM_SETOKTEXT: // Unicode only 1389 SetDlgItemTextW(hWnd, IDOK, (LPWSTR)lParam); 1390 break; 1391 1392 case BFFM_SETSELECTIONA: 1393 if (wParam) // String 1394 return BrFolder_OnSetSelectionA(info, (LPSTR)lParam); 1395 else // PIDL 1396 return BrFolder_OnSetSelectionPidl(info, (LPITEMIDLIST)lParam); 1397 1398 case BFFM_SETSELECTIONW: 1399 if (wParam) // String 1400 return BrFolder_OnSetSelectionW(info, (LPWSTR)lParam); 1401 else // PIDL 1402 return BrFolder_OnSetSelectionPidl(info, (LPITEMIDLIST)lParam); 1403 1404 case BFFM_SETEXPANDED: // Unicode only 1405 if (wParam) // String 1406 return BrFolder_OnSetExpanded(info, NULL, (LPWSTR)lParam); 1407 else // PIDL 1408 return BrFolder_OnSetExpanded(info, (LPITEMIDLIST)lParam, NULL); 1409 1410 case SHV_CHANGE_NOTIFY: 1411 BrFolder_OnChange(info, wParam, lParam); 1412 break; 1413 1414 case WM_DESTROY: 1415 BrFolder_OnDestroy(info); 1416 break; 1417 } 1418 1419 return 0; 1420} 1421 1422/************************************************************************* 1423 * SHBrowseForFolderA [SHELL32.@] 1424 * SHBrowseForFolder [SHELL32.@] 1425 */ 1426EXTERN_C 1427LPITEMIDLIST WINAPI 1428SHBrowseForFolderA(LPBROWSEINFOA lpbi) 1429{ 1430 BROWSEINFOW bi; 1431 bi.hwndOwner = lpbi->hwndOwner; 1432 bi.pidlRoot = lpbi->pidlRoot; 1433 1434 WCHAR szName[MAX_PATH]; 1435 bi.pszDisplayName = (lpbi->pszDisplayName ? szName : NULL); 1436 1437 CComHeapPtr<WCHAR> pszTitle; 1438 if (lpbi->lpszTitle) 1439 __SHCloneStrAtoW(&pszTitle, lpbi->lpszTitle); 1440 bi.lpszTitle = pszTitle; 1441 1442 bi.ulFlags = lpbi->ulFlags; 1443 bi.lpfn = lpbi->lpfn; 1444 bi.lParam = lpbi->lParam; 1445 bi.iImage = lpbi->iImage; 1446 PIDLIST_ABSOLUTE pidl = SHBrowseForFolderW(&bi); 1447 1448 if (bi.pszDisplayName) 1449 SHUnicodeToAnsi(bi.pszDisplayName, lpbi->pszDisplayName, MAX_PATH); 1450 1451 lpbi->iImage = bi.iImage; 1452 return pidl; 1453} 1454 1455/************************************************************************* 1456 * SHBrowseForFolderW [SHELL32.@] 1457 */ 1458EXTERN_C 1459LPITEMIDLIST WINAPI 1460SHBrowseForFolderW(LPBROWSEINFOW lpbi) 1461{ 1462 TRACE("%p\n", lpbi); 1463 1464 // MSDN says the caller must initialize COM. We do it anyway in case the caller forgot. 1465 COleInit OleInit; 1466 BrFolder info = { lpbi }; 1467 1468 INT id = ((lpbi->ulFlags & BIF_USENEWUI) ? IDD_BROWSE_FOR_FOLDER_NEW : IDD_BROWSE_FOR_FOLDER); 1469 INT_PTR ret = DialogBoxParamW(shell32_hInstance, MAKEINTRESOURCEW(id), lpbi->hwndOwner, 1470 BrFolderDlgProc, (LPARAM)&info); 1471 if (ret == IDOK && !(lpbi->ulFlags & BIF_NOTRANSLATETARGETS) && 1472 RosGetProcessEffectiveVersion() >= _WIN32_WINNT_WINXP) 1473 { 1474 PIDLIST_ABSOLUTE pidlTarget; 1475 if (SHELL_GetIDListTarget(info.pidlRet, &pidlTarget) == S_OK) 1476 { 1477 ILFree(info.pidlRet); 1478 info.pidlRet = pidlTarget; 1479 } 1480 } 1481 if (ret != IDOK) 1482 { 1483 ILFree(info.pidlRet); 1484 return NULL; 1485 } 1486 1487 return info.pidlRet; 1488}