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