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