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