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