xref: /reactos/dll/win32/shell32/brfolder.cpp (revision 32d615fc)
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