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
ILCloneToDepth(LPCITEMIDLIST pidlSrc,UINT depth)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
GetIconIndex(PCIDLIST_ABSOLUTE pidl,UINT uFlags)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
BrowseFlagsToSHCONTF(UINT ulFlags)87 BrowseFlagsToSHCONTF(UINT ulFlags)
88 {
89 return SHCONTF_FOLDERS | ((ulFlags & BIF_BROWSEINCLUDEFILES) ? SHCONTF_NONFOLDERS : 0);
90 }
91
92 static void
BrFolder_Callback(LPBROWSEINFOW lpBrowseInfo,HWND hWnd,UINT uMsg,LPARAM lParam)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 *
BrFolder_GetItemData(BrFolder * info,HTREEITEM hItem)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
BrFolder_GetItemAttributes(BrFolder * info,HTREEITEM hItem,SFGAOF Att)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
BrFolder_GetChildrenEnum(_In_ BrFolder * info,_In_ BrItemData * pItemData,_Outptr_opt_ IEnumIDList ** ppEnum)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
BrFolder_InitTreeView(BrFolder * info)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
BrFolder_GetIconPair(PCIDLIST_ABSOLUTE pidl,LPTVITEMW pItem)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
BrFolder_GetName(IShellFolder * lpsf,PCIDLIST_RELATIVE pidlChild,DWORD dwFlags,LPWSTR lpFriendlyName)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
BrFolder_IsTreeItemInEnum(_Inout_ BrFolder * info,_In_ HTREEITEM hItem,_Inout_ IEnumIDList * pEnum)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
BrFolder_TreeItemHasThisChild(_In_ BrFolder * info,_In_ HTREEITEM hItem,_Outptr_opt_ PITEMID_CHILD pidlChild)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
BrFolder_UpdateItem(_In_ BrFolder * info,_In_ HTREEITEM hItem)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
BrFolder_InsertItem(_Inout_ BrFolder * info,_Inout_ IShellFolder * lpsf,_In_ PCUITEMID_CHILD pidlChild,_In_ PCIDLIST_ABSOLUTE pidlParent,_In_ HTREEITEM hParent)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
BrFolder_Expand(_In_ BrFolder * info,_In_ IShellFolder * lpsf,_In_ PCIDLIST_ABSOLUTE pidlFull,_In_ HTREEITEM hParent)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 /* Ignore return value of BrFolder_InsertItem to avoid incomplete folder listing */
412 BrFolder_InsertItem(info, lpsf, pidlTemp, pidlFull, hParent);
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
PIDLIsType(LPCITEMIDLIST pidl,PIDLTYPE type)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
BrFolder_CheckValidSelection(BrFolder * info,BrItemData * pItemData)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
BrFolder_Treeview_Delete(BrFolder * info,NMTREEVIEWW * pnmtv)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
BrFolder_Treeview_Expand(BrFolder * info,NMTREEVIEWW * pnmtv)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
BrFolder_Treeview_Changed(BrFolder * info,NMTREEVIEWW * pnmtv)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
BrFolder_Treeview_Rename(BrFolder * info,NMTVDISPINFOW * pnmtv)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
BrFolder_Rename(BrFolder * info,HTREEITEM hItem)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
BrFolder_Delete(BrFolder * info,HTREEITEM hItem)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
BrFolder_Treeview_Keydown(BrFolder * info,LPNMTVKEYDOWN keydown)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
BrFolder_OnNotify(BrFolder * info,UINT CtlID,LPNMHDR lpnmh)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
BrFolder_OnInitDialog(HWND hWnd,BrFolder * info)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
BrFolder_NewFolder(BrFolder * info)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
BrFolder_OnOK(BrFolder * info)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
BrFolder_OnCommand(BrFolder * info,UINT id)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
GetTreeViewItemContextMenuPos(HWND hWnd,HTREEITEM hItem,POINT * ppt)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
BrFolder_OnContextMenu(BrFolder & info,LPARAM lParam)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
BrFolder_ExpandToPidl(BrFolder * info,LPITEMIDLIST pidlSelection,HTREEITEM * phItem)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
BrFolder_ExpandToString(BrFolder * info,LPWSTR pszString,HTREEITEM * phItem)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
BrFolder_OnSetExpanded(BrFolder * info,LPITEMIDLIST pidlSelection,LPWSTR pszString)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
BrFolder_OnSetSelectionPidl(BrFolder * info,LPITEMIDLIST pidlSelection)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
BrFolder_OnSetSelectionW(BrFolder * info,LPWSTR pszSelection)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
BrFolder_OnSetSelectionA(BrFolder * info,LPSTR pszSelectionA)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
BrFolder_OnDestroy(BrFolder * info)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
BrFolder_RefreshRecurse(_Inout_ BrFolder * info,_In_ HTREEITEM hTarget)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
BrFolder_Refresh(_Inout_ BrFolder * info)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
BrFolder_OnChangeEx(_Inout_ BrFolder * info,_In_ PCIDLIST_ABSOLUTE pidl0,_In_ PCIDLIST_ABSOLUTE pidl1,_In_ LONG event)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
BrFolder_OnChange(BrFolder * info,WPARAM wParam,LPARAM lParam)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
BrFolderDlgProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam)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
SHBrowseForFolderA(LPBROWSEINFOA lpbi)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
SHBrowseForFolderW(LPBROWSEINFOW lpbi)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