[SHELL32]
[reactos.git] / dll / win32 / shell32 / brsfolder.cpp
1 /*
2 * Copyright 1999 Juergen Schmied
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17 *
18 * FIXME:
19 * - many memory leaks
20 * - many flags unimplemented
21 * - implement new dialog style "make new folder" button
22 * - implement editbox
23 * - implement new dialog style resizing
24 */
25
26 #include "precomp.h"
27
28 WINE_DEFAULT_DEBUG_CHANNEL(shell);
29
30 /* original margins and control size */
31 typedef struct tagLAYOUT_DATA
32 {
33 LONG left, width, right;
34 LONG top, height, bottom;
35 } LAYOUT_DATA;
36
37 typedef struct tagbrowse_info
38 {
39 HWND hWnd;
40 HWND hwndTreeView;
41 LPBROWSEINFOW lpBrowseInfo;
42 LPITEMIDLIST pidlRet;
43 LAYOUT_DATA *layout; /* filled by LayoutInit, used by LayoutUpdate */
44 SIZE szMin;
45 } browse_info;
46
47 typedef struct tagTV_ITEMDATA
48 {
49 IShellFolder* lpsfParent; /* IShellFolder of the parent */
50 LPITEMIDLIST lpi; /* PIDL relative to parent */
51 LPITEMIDLIST lpifq; /* Fully qualified PIDL */
52 IEnumIDList* pEnumIL; /* Children iterator */
53 } TV_ITEMDATA, *LPTV_ITEMDATA;
54
55 typedef struct tagLAYOUT_INFO
56 {
57 int iItemId; /* control id */
58 DWORD dwAnchor; /* BF_* flags specifying which margins should remain constant */
59 } LAYOUT_INFO;
60
61 static const LAYOUT_INFO g_layout_info[] =
62 {
63 {IDC_BROWSE_FOR_FOLDER_TITLE, BF_TOP|BF_LEFT|BF_RIGHT},
64 {IDC_BROWSE_FOR_FOLDER_STATUS, BF_TOP|BF_LEFT|BF_RIGHT},
65 {IDC_BROWSE_FOR_FOLDER_FOLDER, BF_TOP|BF_LEFT|BF_RIGHT},
66 {IDC_BROWSE_FOR_FOLDER_TREEVIEW, BF_TOP|BF_BOTTOM|BF_LEFT|BF_RIGHT},
67 {IDC_BROWSE_FOR_FOLDER_FOLDER, BF_BOTTOM|BF_LEFT},
68 {IDC_BROWSE_FOR_FOLDER_FOLDER_TEXT, BF_BOTTOM|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 LAYOUT_INFO_COUNT (sizeof(g_layout_info)/sizeof(g_layout_info[0]))
75
76 #define SUPPORTEDFLAGS (BIF_STATUSTEXT | \
77 BIF_BROWSEFORCOMPUTER | \
78 BIF_RETURNFSANCESTORS | \
79 BIF_RETURNONLYFSDIRS | \
80 BIF_NONEWFOLDERBUTTON | \
81 BIF_NEWDIALOGSTYLE | \
82 BIF_BROWSEINCLUDEFILES)
83
84 static void FillTreeView(browse_info*, IShellFolder*,
85 LPITEMIDLIST, HTREEITEM, IEnumIDList*);
86 static HTREEITEM InsertTreeViewItem( browse_info*, IShellFolder*,
87 LPCITEMIDLIST, LPCITEMIDLIST, IEnumIDList*, HTREEITEM);
88
89 static const WCHAR szBrowseFolderInfo[] = L"__WINE_BRSFOLDERDLG_INFO";
90
91 static DWORD __inline BrowseFlagsToSHCONTF(UINT ulFlags)
92 {
93 return SHCONTF_FOLDERS | (ulFlags & BIF_BROWSEINCLUDEFILES ? SHCONTF_NONFOLDERS : 0);
94 }
95
96 static void browsefolder_callback( LPBROWSEINFOW lpBrowseInfo, HWND hWnd,
97 UINT msg, LPARAM param )
98 {
99 if (!lpBrowseInfo->lpfn)
100 return;
101 lpBrowseInfo->lpfn( hWnd, msg, param, lpBrowseInfo->lParam );
102 }
103
104 static LAYOUT_DATA *LayoutInit(HWND hwnd, const LAYOUT_INFO *layout_info, int layout_count)
105 {
106 LAYOUT_DATA *data;
107 RECT rcWnd;
108 int i;
109
110 GetClientRect(hwnd, &rcWnd);
111 data = (LAYOUT_DATA *)SHAlloc(sizeof(LAYOUT_DATA)*layout_count);
112 for (i = 0; i < layout_count; i++)
113 {
114 RECT r;
115 HWND hItem = GetDlgItem(hwnd, layout_info[i].iItemId);
116
117 if (hItem == NULL)
118 ERR("Item %d not found\n", i);
119 GetWindowRect(hItem, &r);
120 MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT)&r, 2);
121
122 data[i].left = r.left;
123 data[i].right = rcWnd.right - r.right;
124 data[i].width = r.right - r.left;
125
126 data[i].top = r.top;
127 data[i].bottom = rcWnd.bottom - r.bottom;
128 data[i].height = r.bottom - r.top;
129 }
130 return data;
131 }
132
133 static void LayoutUpdate(HWND hwnd, LAYOUT_DATA *data, const LAYOUT_INFO *layout_info, int layout_count)
134 {
135 RECT rcWnd;
136 int i;
137
138 GetClientRect(hwnd, &rcWnd);
139 for (i = 0; i < layout_count; i++)
140 {
141 RECT r;
142 HWND hItem = GetDlgItem(hwnd, layout_info[i].iItemId);
143
144 GetWindowRect(hItem, &r);
145 MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT)&r, 2);
146
147 if (layout_info[i].dwAnchor & BF_RIGHT)
148 {
149 r.right = rcWnd.right - data[i].right;
150 if (!(layout_info[i].dwAnchor & BF_LEFT))
151 r.left = r.right - data[i].width;
152 }
153
154 if (layout_info[i].dwAnchor & BF_BOTTOM)
155 {
156 r.bottom = rcWnd.bottom - data[i].bottom;
157 if (!(layout_info[i].dwAnchor & BF_TOP))
158 r.top = r.bottom - data[i].height;
159 }
160
161 SetWindowPos(hItem, NULL, r.left, r.top, r.right - r.left, r.bottom - r.top, SWP_NOZORDER);
162 }
163 }
164
165
166 /******************************************************************************
167 * InitializeTreeView [Internal]
168 *
169 * Called from WM_INITDIALOG handler.
170 *
171 * PARAMS
172 * hwndParent [I] The BrowseForFolder dialog
173 * root [I] ITEMIDLIST of the root shell folder
174 */
175 static void InitializeTreeView( browse_info *info )
176 {
177 LPITEMIDLIST pidlParent, pidlChild;
178 HIMAGELIST hImageList;
179 HRESULT hr;
180 CComPtr<IShellFolder> lpsfParent;
181 CComPtr<IShellFolder> lpsfRoot;
182 CComPtr<IEnumIDList> pEnumChildren;
183 HTREEITEM item;
184 DWORD flags;
185 LPCITEMIDLIST root = info->lpBrowseInfo->pidlRoot;
186
187 TRACE("%p\n", info );
188
189 Shell_GetImageLists(NULL, &hImageList);
190
191 if (hImageList)
192 SendMessageW( info->hwndTreeView, TVM_SETIMAGELIST, 0, (LPARAM)hImageList );
193
194 /* We want to call InsertTreeViewItem down the code, in order to insert
195 * the root item of the treeview. Due to InsertTreeViewItem's signature,
196 * we need the following to do this:
197 *
198 * + An ITEMIDLIST corresponding to _the parent_ of root.
199 * + An ITEMIDLIST, which is a relative path from root's parent to root
200 * (containing a single SHITEMID).
201 * + An IShellFolder interface pointer of root's parent folder.
202 *
203 * If root is 'Desktop', then root's parent is also 'Desktop'.
204 */
205
206 pidlParent = ILClone(root);
207 ILRemoveLastID(pidlParent);
208 pidlChild = ILClone(ILFindLastID(root));
209
210 if (_ILIsDesktop(pidlParent))
211 {
212 hr = SHGetDesktopFolder(&lpsfParent);
213 } else {
214 CComPtr<IShellFolder> lpsfDesktop;
215 hr = SHGetDesktopFolder(&lpsfDesktop);
216 if (FAILED(hr)) {
217 WARN("SHGetDesktopFolder failed! hr = %08x\n", hr);
218 ILFree(pidlChild);
219 ILFree(pidlParent);
220 return;
221 }
222 hr = lpsfDesktop->BindToObject(pidlParent, 0, IID_PPV_ARG(IShellFolder, &lpsfParent));
223 }
224
225 if (FAILED(hr)) {
226 WARN("Could not bind to parent shell folder! hr = %08x\n", hr);
227 ILFree(pidlChild);
228 ILFree(pidlParent);
229 return;
230 }
231
232 if (pidlChild && pidlChild->mkid.cb) {
233 hr = lpsfParent->BindToObject(pidlChild, 0, IID_PPV_ARG(IShellFolder, &lpsfRoot));
234 } else {
235 lpsfRoot = lpsfParent;
236 }
237
238 if (FAILED(hr)) {
239 WARN("Could not bind to root shell folder! hr = %08x\n", hr);
240 ILFree(pidlChild);
241 ILFree(pidlParent);
242 return;
243 }
244
245 flags = BrowseFlagsToSHCONTF( info->lpBrowseInfo->ulFlags );
246 hr = lpsfRoot->EnumObjects(info->hWnd, flags, &pEnumChildren );
247 if (FAILED(hr)) {
248 WARN("Could not get child iterator! hr = %08x\n", hr);
249 ILFree(pidlChild);
250 ILFree(pidlParent);
251 return;
252 }
253
254 SendMessageW( info->hwndTreeView, TVM_DELETEITEM, 0, (LPARAM)TVI_ROOT );
255 item = InsertTreeViewItem( info, lpsfParent, pidlChild,
256 pidlParent, pEnumChildren, TVI_ROOT );
257 SendMessageW( info->hwndTreeView, TVM_EXPAND, TVE_EXPAND, (LPARAM)item );
258
259 ILFree(pidlChild);
260 ILFree(pidlParent);
261 }
262
263 static int GetIcon(LPCITEMIDLIST lpi, UINT uFlags)
264 {
265 SHFILEINFOW sfi;
266 SHGetFileInfoW((LPCWSTR)lpi, 0 ,&sfi, sizeof(SHFILEINFOW), uFlags);
267 return sfi.iIcon;
268 }
269
270 static void GetNormalAndSelectedIcons(LPITEMIDLIST lpifq, LPTVITEMW lpTV_ITEM)
271 {
272 LPITEMIDLIST pidlDesktop = NULL;
273 DWORD flags;
274
275 TRACE("%p %p\n",lpifq, lpTV_ITEM);
276
277 if (!lpifq)
278 {
279 pidlDesktop = _ILCreateDesktop();
280 lpifq = pidlDesktop;
281 }
282
283 flags = SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_SMALLICON;
284 lpTV_ITEM->iImage = GetIcon( lpifq, flags );
285
286 flags = SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_SMALLICON | SHGFI_OPENICON;
287 lpTV_ITEM->iSelectedImage = GetIcon( lpifq, flags );
288
289 if (pidlDesktop)
290 ILFree( pidlDesktop );
291 }
292
293 /******************************************************************************
294 * GetName [Internal]
295 *
296 * Query a shell folder for the display name of one of it's children
297 *
298 * PARAMS
299 * lpsf [I] IShellFolder interface of the folder to be queried.
300 * lpi [I] ITEMIDLIST of the child, relative to parent
301 * dwFlags [I] as in IShellFolder::GetDisplayNameOf
302 * lpFriendlyName [O] The desired display name in unicode
303 *
304 * RETURNS
305 * Success: TRUE
306 * Failure: FALSE
307 */
308 static BOOL GetName(IShellFolder * lpsf, LPCITEMIDLIST lpi, DWORD dwFlags, LPWSTR lpFriendlyName)
309 {
310 BOOL bSuccess=TRUE;
311 STRRET str;
312
313 TRACE("%p %p %x %p\n", lpsf, lpi, dwFlags, lpFriendlyName);
314 if (SUCCEEDED(lpsf->GetDisplayNameOf(lpi, dwFlags, &str)))
315 bSuccess = StrRetToStrNW(lpFriendlyName, MAX_PATH, &str, lpi);
316 else
317 bSuccess = FALSE;
318
319 TRACE("-- %s\n", debugstr_w(lpFriendlyName));
320 return bSuccess;
321 }
322
323 /******************************************************************************
324 * InsertTreeViewItem [Internal]
325 *
326 * PARAMS
327 * info [I] data for the dialog
328 * lpsf [I] IShellFolder interface of the item's parent shell folder
329 * pidl [I] ITEMIDLIST of the child to insert, relative to parent
330 * pidlParent [I] ITEMIDLIST of the parent shell folder
331 * pEnumIL [I] Iterator for the children of the item to be inserted
332 * hParent [I] The treeview-item that represents the parent shell folder
333 *
334 * RETURNS
335 * Success: Handle to the created and inserted treeview-item
336 * Failure: NULL
337 */
338 static HTREEITEM InsertTreeViewItem( browse_info *info, IShellFolder * lpsf,
339 LPCITEMIDLIST pidl, LPCITEMIDLIST pidlParent, IEnumIDList* pEnumIL,
340 HTREEITEM hParent)
341 {
342 TVITEMW tvi;
343 TVINSERTSTRUCTW tvins;
344 WCHAR szBuff[MAX_PATH];
345 LPTV_ITEMDATA lptvid=0;
346
347 tvi.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM;
348
349 tvi.cChildren= pEnumIL ? 1 : 0;
350 tvi.mask |= TVIF_CHILDREN;
351
352 lptvid = (TV_ITEMDATA *)SHAlloc( sizeof(TV_ITEMDATA) );
353 if (!lptvid)
354 return NULL;
355
356 if (!GetName(lpsf, pidl, SHGDN_NORMAL, szBuff))
357 return NULL;
358
359 tvi.pszText = szBuff;
360 tvi.cchTextMax = MAX_PATH;
361 tvi.lParam = (LPARAM)lptvid;
362
363 lpsf->AddRef();
364 pEnumIL->AddRef();
365 lptvid->lpsfParent = lpsf;
366 lptvid->lpi = ILClone(pidl);
367 lptvid->lpifq = pidlParent ? ILCombine(pidlParent, pidl) : ILClone(pidl);
368 lptvid->pEnumIL = pEnumIL;
369 GetNormalAndSelectedIcons(lptvid->lpifq, &tvi);
370
371 tvins.item = tvi;
372 tvins.hInsertAfter = NULL;
373 tvins.hParent = hParent;
374
375 return TreeView_InsertItem( info->hwndTreeView, &tvins );
376 }
377
378 /******************************************************************************
379 * FillTreeView [Internal]
380 *
381 * For each child (given by lpe) of the parent shell folder, which is given by
382 * lpsf and whose PIDL is pidl, insert a treeview-item right under hParent
383 *
384 * PARAMS
385 * info [I] data for the dialog
386 * lpsf [I] IShellFolder interface of the parent shell folder
387 * pidl [I] ITEMIDLIST of the parent shell folder
388 * hParent [I] The treeview item that represents the parent shell folder
389 * lpe [I] An iterator for the children of the parent shell folder
390 */
391 static void FillTreeView( browse_info *info, IShellFolder * lpsf,
392 LPITEMIDLIST pidl, HTREEITEM hParent, IEnumIDList* lpe)
393 {
394 LPITEMIDLIST pidlTemp = 0;
395 ULONG ulFetched;
396 HRESULT hr;
397 HWND hwnd = GetParent( info->hwndTreeView );
398
399 TRACE("%p %p %p %p\n",lpsf, pidl, hParent, lpe);
400
401 /* No IEnumIDList -> No children */
402 if (!lpe) return;
403
404 SetCapture( hwnd );
405 SetCursor( LoadCursorA( 0, (LPSTR)IDC_WAIT ) );
406
407 while (S_OK == lpe->Next(1,&pidlTemp,&ulFetched))
408 {
409 ULONG ulAttrs = SFGAO_HASSUBFOLDER | SFGAO_FOLDER;
410 CComPtr<IEnumIDList> pEnumIL;
411 CComPtr<IShellFolder> pSFChild;
412 lpsf->GetAttributesOf(1, (LPCITEMIDLIST*)&pidlTemp, &ulAttrs);
413 if (ulAttrs & SFGAO_FOLDER)
414 {
415 hr = lpsf->BindToObject(pidlTemp, NULL, IID_PPV_ARG(IShellFolder, &pSFChild));
416 if (SUCCEEDED(hr))
417 {
418 DWORD flags = BrowseFlagsToSHCONTF(info->lpBrowseInfo->ulFlags);
419 hr = pSFChild->EnumObjects(hwnd, flags, &pEnumIL);
420 if (hr == S_OK)
421 {
422 if ((pEnumIL->Skip(1) != S_OK) ||
423 FAILED(pEnumIL->Reset()))
424 {
425 pEnumIL = NULL;
426 }
427 }
428 }
429 }
430
431 if (!InsertTreeViewItem(info, lpsf, pidlTemp, pidl, pEnumIL, hParent))
432 goto done;
433 SHFree(pidlTemp); /* Finally, free the pidl that the shell gave us... */
434 pidlTemp=NULL;
435 }
436
437 done:
438 ReleaseCapture();
439 SetCursor(LoadCursorW(0, (LPWSTR)IDC_ARROW));
440 SHFree(pidlTemp);
441 }
442
443 static BOOL __inline PIDLIsType(LPCITEMIDLIST pidl, PIDLTYPE type)
444 {
445 LPPIDLDATA data = _ILGetDataPointer(pidl);
446 if (!data)
447 return FALSE;
448 return (data->type == type);
449 }
450
451 static void BrsFolder_CheckValidSelection( browse_info *info, LPTV_ITEMDATA lptvid )
452 {
453 LPBROWSEINFOW lpBrowseInfo = info->lpBrowseInfo;
454 LPCITEMIDLIST pidl = lptvid->lpi;
455 BOOL bEnabled = TRUE;
456 DWORD dwAttributes;
457 HRESULT r;
458
459 if ((lpBrowseInfo->ulFlags & BIF_BROWSEFORCOMPUTER) &&
460 !PIDLIsType(pidl, PT_COMP))
461 bEnabled = FALSE;
462 if (lpBrowseInfo->ulFlags & BIF_RETURNFSANCESTORS)
463 {
464 dwAttributes = SFGAO_FILESYSANCESTOR | SFGAO_FILESYSTEM;
465 r = lptvid->lpsfParent->GetAttributesOf(1,
466 (LPCITEMIDLIST*)&lptvid->lpi, &dwAttributes);
467 if (FAILED(r) || !(dwAttributes & (SFGAO_FILESYSANCESTOR|SFGAO_FILESYSTEM)))
468 bEnabled = FALSE;
469 }
470
471 dwAttributes = SFGAO_FOLDER | SFGAO_FILESYSTEM;
472 r = lptvid->lpsfParent->GetAttributesOf(1,
473 (LPCITEMIDLIST*)&lptvid->lpi,
474 &dwAttributes);
475 if (FAILED(r) ||
476 ((dwAttributes & (SFGAO_FOLDER|SFGAO_FILESYSTEM)) != (SFGAO_FOLDER|SFGAO_FILESYSTEM)))
477 {
478 if (lpBrowseInfo->ulFlags & BIF_RETURNONLYFSDIRS)
479 bEnabled = FALSE;
480 EnableWindow(GetDlgItem(info->hWnd, IDC_BROWSE_FOR_FOLDER_NEW_FOLDER), FALSE);
481 }
482 else
483 EnableWindow(GetDlgItem(info->hWnd, IDC_BROWSE_FOR_FOLDER_NEW_FOLDER), TRUE);
484
485 SendMessageW(info->hWnd, BFFM_ENABLEOK, 0, bEnabled);
486 }
487
488 static LRESULT BrsFolder_Treeview_Delete( browse_info *info, NMTREEVIEWW *pnmtv )
489 {
490 LPTV_ITEMDATA lptvid = (LPTV_ITEMDATA)pnmtv->itemOld.lParam;
491
492 TRACE("TVN_DELETEITEMA/W %p\n", lptvid);
493
494 lptvid->lpsfParent->Release();
495 if (lptvid->pEnumIL)
496 lptvid->pEnumIL->Release();
497 SHFree(lptvid->lpi);
498 SHFree(lptvid->lpifq);
499 SHFree(lptvid);
500 return 0;
501 }
502
503 static LRESULT BrsFolder_Treeview_Expand( browse_info *info, NMTREEVIEWW *pnmtv )
504 {
505 CComPtr<IShellFolder> lpsf2;
506 LPTV_ITEMDATA lptvid = (LPTV_ITEMDATA) pnmtv->itemNew.lParam;
507 HRESULT r;
508
509 TRACE("TVN_ITEMEXPANDINGA/W\n");
510
511 if ((pnmtv->itemNew.state & TVIS_EXPANDEDONCE))
512 return 0;
513
514 if (lptvid->lpi && lptvid->lpi->mkid.cb) {
515 r = lptvid->lpsfParent->BindToObject(lptvid->lpi, 0, IID_PPV_ARG(IShellFolder, &lpsf2));
516 } else {
517 lpsf2 = lptvid->lpsfParent;
518 r = lpsf2->AddRef();
519 }
520
521 if (SUCCEEDED(r))
522 {
523 FillTreeView( info, lpsf2, lptvid->lpifq, pnmtv->itemNew.hItem, lptvid->pEnumIL);
524 lpsf2->Release();
525 }
526
527 /* My Computer is already sorted and trying to do a simple text
528 * sort will only mess things up */
529 if (!_ILIsMyComputer(lptvid->lpi))
530 SendMessageW( info->hwndTreeView, TVM_SORTCHILDREN,
531 FALSE, (LPARAM)pnmtv->itemNew.hItem );
532
533 return 0;
534 }
535
536 static HRESULT BrsFolder_Treeview_Changed( browse_info *info, NMTREEVIEWW *pnmtv )
537 {
538 LPTV_ITEMDATA lptvid = (LPTV_ITEMDATA) pnmtv->itemNew.lParam;
539 WCHAR name[MAX_PATH];
540
541 ILFree(info->pidlRet);
542 info->pidlRet = ILClone(lptvid->lpifq);
543
544 if (GetName(lptvid->lpsfParent, lptvid->lpi, SHGDN_NORMAL, name))
545 SetWindowTextW( GetDlgItem(info->hWnd, IDC_BROWSE_FOR_FOLDER_FOLDER_TEXT), name );
546
547 browsefolder_callback( info->lpBrowseInfo, info->hWnd, BFFM_SELCHANGED,
548 (LPARAM)info->pidlRet );
549 BrsFolder_CheckValidSelection( info, lptvid );
550 return 0;
551 }
552
553 static LRESULT BrsFolder_Treeview_Rename(browse_info *info, NMTVDISPINFOW *pnmtv)
554 {
555 LPTV_ITEMDATA item_data;
556 WCHAR old_path[MAX_PATH], new_path[MAX_PATH], *p;
557 NMTREEVIEWW nmtv;
558 TVITEMW item;
559
560 if(!pnmtv->item.pszText)
561 return 0;
562
563 item.mask = TVIF_HANDLE|TVIF_PARAM;
564 item.hItem = (HTREEITEM)SendMessageW(info->hwndTreeView, TVM_GETNEXTITEM, TVGN_CARET, 0);
565 SendMessageW(info->hwndTreeView, TVM_GETITEMW, 0, (LPARAM)&item);
566 item_data = (LPTV_ITEMDATA)item.lParam;
567
568 SHGetPathFromIDListW(item_data->lpifq, old_path);
569 if(!(p = strrchrW(old_path, '\\')))
570 return 0;
571 p = new_path+(p-old_path+1);
572 memcpy(new_path, old_path, (p-new_path)*sizeof(WCHAR));
573 strcpyW(p, pnmtv->item.pszText);
574
575 if(!MoveFileW(old_path, new_path))
576 return 0;
577
578 SHFree(item_data->lpifq);
579 SHFree(item_data->lpi);
580 item_data->lpifq = SHSimpleIDListFromPathW(new_path);
581 item_data->lpsfParent->ParseDisplayName(NULL, NULL, pnmtv->item.pszText,
582 NULL, &item_data->lpi, NULL);
583
584 item.mask = TVIF_HANDLE|TVIF_TEXT;
585 item.pszText = pnmtv->item.pszText;
586 SendMessageW(info->hwndTreeView, TVM_SETITEMW, 0, (LPARAM)&item);
587
588 nmtv.itemNew.lParam = item.lParam;
589 BrsFolder_Treeview_Changed(info, &nmtv);
590 return 0;
591 }
592
593 static LRESULT BrsFolder_OnNotify( browse_info *info, UINT CtlID, LPNMHDR lpnmh )
594 {
595 NMTREEVIEWW *pnmtv = (NMTREEVIEWW *)lpnmh;
596
597 TRACE("%p %x %p msg=%x\n", info, CtlID, lpnmh, pnmtv->hdr.code);
598
599 if (pnmtv->hdr.idFrom != IDC_BROWSE_FOR_FOLDER_TREEVIEW)
600 return 0;
601
602 switch (pnmtv->hdr.code)
603 {
604 case TVN_DELETEITEMA:
605 case TVN_DELETEITEMW:
606 return BrsFolder_Treeview_Delete( info, pnmtv );
607
608 case TVN_ITEMEXPANDINGA:
609 case TVN_ITEMEXPANDINGW:
610 return BrsFolder_Treeview_Expand( info, pnmtv );
611
612 case TVN_SELCHANGEDA:
613 case TVN_SELCHANGEDW:
614 return BrsFolder_Treeview_Changed( info, pnmtv );
615
616 case TVN_ENDLABELEDITA:
617 case TVN_ENDLABELEDITW:
618 return BrsFolder_Treeview_Rename( info, (LPNMTVDISPINFOW)pnmtv );
619
620 default:
621 WARN("unhandled (%d)\n", pnmtv->hdr.code);
622 break;
623 }
624
625 return 0;
626 }
627
628
629 static BOOL BrsFolder_OnCreate( HWND hWnd, browse_info *info )
630 {
631 LPBROWSEINFOW lpBrowseInfo = info->lpBrowseInfo;
632
633 info->hWnd = hWnd;
634 SetPropW( hWnd, szBrowseFolderInfo, info );
635
636 if (lpBrowseInfo->ulFlags & BIF_NEWDIALOGSTYLE)
637 FIXME("flags BIF_NEWDIALOGSTYLE partially implemented\n");
638 if (lpBrowseInfo->ulFlags & ~SUPPORTEDFLAGS)
639 FIXME("flags %x not implemented\n", lpBrowseInfo->ulFlags & ~SUPPORTEDFLAGS);
640
641 if (lpBrowseInfo->ulFlags & BIF_NEWDIALOGSTYLE)
642 {
643 RECT rcWnd;
644
645 info->layout = LayoutInit(hWnd, g_layout_info, LAYOUT_INFO_COUNT);
646
647 /* TODO: Windows allows shrinking the windows a bit */
648 GetWindowRect(hWnd, &rcWnd);
649 info->szMin.cx = rcWnd.right - rcWnd.left;
650 info->szMin.cy = rcWnd.bottom - rcWnd.top;
651 }
652 else
653 {
654 info->layout = NULL;
655 }
656
657 if (lpBrowseInfo->lpszTitle)
658 SetWindowTextW( GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_TITLE), lpBrowseInfo->lpszTitle );
659 else
660 ShowWindow( GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_TITLE), SW_HIDE );
661
662 if (!(lpBrowseInfo->ulFlags & BIF_STATUSTEXT)
663 || (lpBrowseInfo->ulFlags & BIF_NEWDIALOGSTYLE))
664 ShowWindow( GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_STATUS), SW_HIDE );
665
666 /* Hide "Make New Folder" Button? */
667 if ((lpBrowseInfo->ulFlags & BIF_NONEWFOLDERBUTTON)
668 || !(lpBrowseInfo->ulFlags & BIF_NEWDIALOGSTYLE))
669 ShowWindow( GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_NEW_FOLDER), SW_HIDE );
670
671 /* Hide the editbox? */
672 if (!(lpBrowseInfo->ulFlags & BIF_EDITBOX))
673 {
674 ShowWindow( GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_FOLDER), SW_HIDE );
675 ShowWindow( GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_FOLDER_TEXT), SW_HIDE );
676 }
677
678 info->hwndTreeView = GetDlgItem( hWnd, IDC_BROWSE_FOR_FOLDER_TREEVIEW );
679 if (info->hwndTreeView)
680 {
681 InitializeTreeView( info );
682
683 /* Resize the treeview if there's not editbox */
684 if ((lpBrowseInfo->ulFlags & BIF_NEWDIALOGSTYLE)
685 && !(lpBrowseInfo->ulFlags & BIF_EDITBOX))
686 {
687 RECT rc;
688 GetClientRect(info->hwndTreeView, &rc);
689 SetWindowPos(info->hwndTreeView, HWND_TOP, 0, 0,
690 rc.right, rc.bottom + 40, SWP_NOMOVE);
691 }
692 }
693 else
694 ERR("treeview control missing!\n");
695
696 browsefolder_callback( info->lpBrowseInfo, hWnd, BFFM_INITIALIZED, 0 );
697
698 return TRUE;
699 }
700
701 static HRESULT BrsFolder_Rename(browse_info *info, HTREEITEM rename)
702 {
703 SendMessageW(info->hwndTreeView, TVM_SELECTITEM, TVGN_CARET, (LPARAM)rename);
704 SendMessageW(info->hwndTreeView, TVM_EDITLABELW, 0, (LPARAM)rename);
705 return S_OK;
706 }
707
708 static HRESULT BrsFolder_NewFolder(browse_info *info)
709 {
710 DWORD flags = BrowseFlagsToSHCONTF(info->lpBrowseInfo->ulFlags);
711 CComPtr<IShellFolder> desktop;
712 CComPtr<IShellFolder> cur;
713 CComPtr<ISFHelper> sfhelper;
714 WCHAR name[MAX_PATH];
715 HTREEITEM parent, added;
716 LPTV_ITEMDATA item_data;
717 LPITEMIDLIST new_item;
718 TVITEMW item;
719 HRESULT hr;
720 int len;
721
722 if(!info->pidlRet) {
723 ERR("Make new folder button should be disabled\n");
724 return E_FAIL;
725 }
726
727 /* Create new directory */
728 hr = SHGetDesktopFolder(&desktop);
729 if(FAILED(hr))
730 return hr;
731 hr = desktop->BindToObject(info->pidlRet, 0, IID_PPV_ARG(IShellFolder, &cur));
732 if(FAILED(hr))
733 return hr;
734
735 hr = cur->QueryInterface(IID_PPV_ARG(ISFHelper, &sfhelper));
736 if(FAILED(hr))
737 return hr;
738
739 hr = SHGetPathFromIDListW(info->pidlRet, name);
740 if(FAILED(hr))
741 goto cleanup;
742
743 len = strlenW(name);
744 if(len<MAX_PATH)
745 name[len++] = '\\';
746 hr = sfhelper->GetUniqueName(&name[len], MAX_PATH-len);
747 if(FAILED(hr))
748 goto cleanup;
749
750 hr = E_FAIL;
751 if(!CreateDirectoryW(name, NULL))
752 goto cleanup;
753
754 /* Update parent of newly created directory */
755 parent = (HTREEITEM)SendMessageW(info->hwndTreeView, TVM_GETNEXTITEM, TVGN_CARET, 0);
756 if(!parent)
757 goto cleanup;
758
759 SendMessageW(info->hwndTreeView, TVM_EXPAND, TVE_EXPAND, (LPARAM)parent);
760
761 memset(&item, 0, sizeof(TVITEMW));
762 item.mask = TVIF_PARAM|TVIF_STATE;
763 item.hItem = parent;
764 SendMessageW(info->hwndTreeView, TVM_GETITEMW, 0, (LPARAM)&item);
765 item_data = (LPTV_ITEMDATA)item.lParam;
766 if(!item_data)
767 goto cleanup;
768
769 if(item_data->pEnumIL)
770 item_data->pEnumIL->Release();
771 hr = cur->EnumObjects(info->hwndTreeView, flags, &item_data->pEnumIL);
772 if(FAILED(hr))
773 goto cleanup;
774
775 /* Update treeview */
776 if(!(item.state&TVIS_EXPANDEDONCE)) {
777 item.mask = TVIF_STATE;
778 item.state = TVIS_EXPANDEDONCE;
779 item.stateMask = TVIS_EXPANDEDONCE;
780 SendMessageW(info->hwndTreeView, TVM_SETITEMW, 0, (LPARAM)&item);
781 }
782
783 hr = cur->ParseDisplayName(NULL, NULL, name+len, NULL, &new_item, NULL);
784 if(FAILED(hr))
785 goto cleanup;
786
787 added = InsertTreeViewItem(info, cur, new_item, item_data->lpifq, NULL, parent);
788 SHFree(new_item);
789
790 SendMessageW(info->hwndTreeView, TVM_SORTCHILDREN, FALSE, (LPARAM)parent);
791 return BrsFolder_Rename(info, added);
792
793 cleanup:
794 return hr;
795 }
796
797 static BOOL BrsFolder_OnCommand( browse_info *info, UINT id )
798 {
799 LPBROWSEINFOW lpBrowseInfo = info->lpBrowseInfo;
800
801 switch (id)
802 {
803 case IDOK:
804 /* The original pidl is owned by the treeview and will be free'd. */
805 info->pidlRet = ILClone(info->pidlRet);
806 if (info->pidlRet == NULL) /* A null pidl would mean a cancel */
807 info->pidlRet = _ILCreateDesktop();
808 pdump( info->pidlRet );
809 if (lpBrowseInfo->pszDisplayName)
810 SHGetPathFromIDListW( info->pidlRet, lpBrowseInfo->pszDisplayName );
811 EndDialog( info->hWnd, 1 );
812 return TRUE;
813
814 case IDCANCEL:
815 EndDialog( info->hWnd, 0 );
816 return TRUE;
817
818 case IDC_BROWSE_FOR_FOLDER_NEW_FOLDER:
819 BrsFolder_NewFolder(info);
820 return TRUE;
821 }
822 return FALSE;
823 }
824
825 static BOOL BrsFolder_OnSetExpanded(browse_info *info, LPVOID selection,
826 BOOL is_str, HTREEITEM *pItem)
827 {
828 LPITEMIDLIST pidlSelection = (LPITEMIDLIST)selection;
829 LPCITEMIDLIST pidlCurrent, pidlRoot;
830 TVITEMEXW item;
831 BOOL bResult = FALSE;
832
833 memset(&item, 0, sizeof(item));
834
835 /* If 'selection' is a string, convert to a Shell ID List. */
836 if (is_str) {
837 CComPtr<IShellFolder> psfDesktop;
838 HRESULT hr;
839
840 hr = SHGetDesktopFolder(&psfDesktop);
841 if (FAILED(hr))
842 goto done;
843
844 hr = psfDesktop->ParseDisplayName(NULL, NULL, (LPOLESTR)selection,
845 NULL, &pidlSelection, NULL);
846 if (FAILED(hr))
847 goto done;
848 }
849
850 /* Move pidlCurrent behind the SHITEMIDs in pidlSelection, which are the root of
851 * the sub-tree currently displayed. */
852 pidlRoot = info->lpBrowseInfo->pidlRoot;
853 pidlCurrent = pidlSelection;
854 while (!_ILIsEmpty(pidlRoot) && _ILIsEqualSimple(pidlRoot, pidlCurrent)) {
855 pidlRoot = ILGetNext(pidlRoot);
856 pidlCurrent = ILGetNext(pidlCurrent);
857 }
858
859 /* The given ID List is not part of the SHBrowseForFolder's current sub-tree. */
860 if (!_ILIsEmpty(pidlRoot))
861 goto done;
862
863 /* Initialize item to point to the first child of the root folder. */
864 item.mask = TVIF_PARAM;
865 item.hItem = (HTREEITEM)SendMessageW(info->hwndTreeView, TVM_GETNEXTITEM, TVGN_ROOT, 0);
866
867 if (item.hItem)
868 item.hItem = (HTREEITEM)SendMessageW(info->hwndTreeView, TVM_GETNEXTITEM, TVGN_CHILD,
869 (LPARAM)item.hItem);
870
871 /* Walk the tree along the nodes corresponding to the remaining ITEMIDLIST */
872 while (item.hItem && !_ILIsEmpty(pidlCurrent)) {
873 LPTV_ITEMDATA pItemData;
874
875 SendMessageW(info->hwndTreeView, TVM_GETITEMW, 0, (LPARAM)&item);
876 pItemData = (LPTV_ITEMDATA)item.lParam;
877
878 if (_ILIsEqualSimple(pItemData->lpi, pidlCurrent)) {
879 pidlCurrent = ILGetNext(pidlCurrent);
880 if (!_ILIsEmpty(pidlCurrent)) {
881 /* Only expand current node and move on to it's first child,
882 * if we didn't already reach the last SHITEMID */
883 SendMessageW(info->hwndTreeView, TVM_EXPAND, TVE_EXPAND, (LPARAM)item.hItem);
884 item.hItem = (HTREEITEM)SendMessageW(info->hwndTreeView, TVM_GETNEXTITEM, TVGN_CHILD,
885 (LPARAM)item.hItem);
886 }
887 } else {
888 item.hItem = (HTREEITEM)SendMessageW(info->hwndTreeView, TVM_GETNEXTITEM, TVGN_NEXT,
889 (LPARAM)item.hItem);
890 }
891 }
892
893 if (_ILIsEmpty(pidlCurrent) && item.hItem)
894 bResult = TRUE;
895
896 done:
897 if (pidlSelection && pidlSelection != (LPITEMIDLIST)selection)
898 ILFree(pidlSelection);
899
900 if (pItem)
901 *pItem = item.hItem;
902
903 return bResult;
904 }
905
906 static BOOL BrsFolder_OnSetSelectionW(browse_info *info, LPVOID selection, BOOL is_str) {
907 HTREEITEM hItem;
908 BOOL bResult;
909
910 if (!selection) return FALSE;
911
912 bResult = BrsFolder_OnSetExpanded(info, selection, is_str, &hItem);
913 if (bResult)
914 SendMessageW(info->hwndTreeView, TVM_SELECTITEM, TVGN_CARET, (LPARAM)hItem );
915 return bResult;
916 }
917
918 static BOOL BrsFolder_OnSetSelectionA(browse_info *info, LPVOID selection, BOOL is_str) {
919 LPWSTR selectionW = NULL;
920 BOOL result = FALSE;
921 int length;
922
923 if (!is_str)
924 return BrsFolder_OnSetSelectionW(info, selection, is_str);
925
926 if ((length = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)selection, -1, NULL, 0)) &&
927 (selectionW = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, length * sizeof(WCHAR))) &&
928 MultiByteToWideChar(CP_ACP, 0, (LPCSTR)selection, -1, selectionW, length))
929 {
930 result = BrsFolder_OnSetSelectionW(info, selectionW, is_str);
931 }
932
933 HeapFree(GetProcessHeap(), 0, selectionW);
934 return result;
935 }
936
937 static BOOL BrsFolder_OnWindowPosChanging(browse_info *info, WINDOWPOS *pos)
938 {
939 if ((info->lpBrowseInfo->ulFlags & BIF_NEWDIALOGSTYLE) && !(pos->flags & SWP_NOSIZE))
940 {
941 if (pos->cx < info->szMin.cx)
942 pos->cx = info->szMin.cx;
943 if (pos->cy < info->szMin.cy)
944 pos->cy = info->szMin.cy;
945 }
946 return 0;
947 }
948
949 static INT BrsFolder_OnDestroy(browse_info *info)
950 {
951 if (info->layout)
952 {
953 SHFree(info->layout);
954 info->layout = NULL;
955 }
956
957 return 0;
958 }
959
960 /*************************************************************************
961 * BrsFolderDlgProc32 (not an exported API function)
962 */
963 static INT_PTR CALLBACK BrsFolderDlgProc( HWND hWnd, UINT msg, WPARAM wParam,
964 LPARAM lParam )
965 {
966 browse_info *info;
967
968 TRACE("hwnd=%p msg=%04x 0x%08lx 0x%08lx\n", hWnd, msg, wParam, lParam );
969
970 if (msg == WM_INITDIALOG)
971 return BrsFolder_OnCreate( hWnd, (browse_info*) lParam );
972
973 info = (browse_info*) GetPropW( hWnd, szBrowseFolderInfo );
974
975 switch (msg)
976 {
977 case WM_NOTIFY:
978 return BrsFolder_OnNotify( info, (UINT)wParam, (LPNMHDR)lParam);
979
980 case WM_COMMAND:
981 return BrsFolder_OnCommand( info, wParam );
982
983 case WM_WINDOWPOSCHANGING:
984 return BrsFolder_OnWindowPosChanging( info, (WINDOWPOS *)lParam);
985
986 case WM_SIZE:
987 if (info->layout) /* new style dialogs */
988 LayoutUpdate(hWnd, info->layout, g_layout_info, LAYOUT_INFO_COUNT);
989 return 0;
990
991 case BFFM_SETSTATUSTEXTA:
992 TRACE("Set status %s\n", debugstr_a((LPSTR)lParam));
993 SetWindowTextA(GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_STATUS), (LPSTR)lParam);
994 break;
995
996 case BFFM_SETSTATUSTEXTW:
997 TRACE("Set status %s\n", debugstr_w((LPWSTR)lParam));
998 SetWindowTextW(GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_STATUS), (LPWSTR)lParam);
999 break;
1000
1001 case BFFM_ENABLEOK:
1002 TRACE("Enable %ld\n", lParam);
1003 EnableWindow(GetDlgItem(hWnd, 1), (lParam)?TRUE:FALSE);
1004 break;
1005
1006 case BFFM_SETOKTEXT: /* unicode only */
1007 TRACE("Set OK text %s\n", debugstr_w((LPWSTR)wParam));
1008 SetWindowTextW(GetDlgItem(hWnd, 1), (LPWSTR)wParam);
1009 break;
1010
1011 case BFFM_SETSELECTIONA:
1012 return BrsFolder_OnSetSelectionA(info, (LPVOID)lParam, (BOOL)wParam);
1013
1014 case BFFM_SETSELECTIONW:
1015 return BrsFolder_OnSetSelectionW(info, (LPVOID)lParam, (BOOL)wParam);
1016
1017 case BFFM_SETEXPANDED: /* unicode only */
1018 return BrsFolder_OnSetExpanded(info, (LPVOID)lParam, (BOOL)wParam, NULL);
1019
1020 case WM_DESTROY:
1021 return BrsFolder_OnDestroy(info);
1022 }
1023 return FALSE;
1024 }
1025
1026
1027
1028
1029 /*************************************************************************
1030 * SHBrowseForFolderA [SHELL32.@]
1031 * SHBrowseForFolder [SHELL32.@]
1032 */
1033 LPITEMIDLIST WINAPI SHBrowseForFolderA (LPBROWSEINFOA lpbi)
1034 {
1035 BROWSEINFOW bi;
1036 LPITEMIDLIST lpid;
1037 INT len;
1038 LPWSTR title;
1039
1040 TRACE("%p\n", lpbi);
1041
1042 bi.hwndOwner = lpbi->hwndOwner;
1043 bi.pidlRoot = lpbi->pidlRoot;
1044 if (lpbi->pszDisplayName)
1045 {
1046 bi.pszDisplayName = (WCHAR *)HeapAlloc( GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR) );
1047 MultiByteToWideChar( CP_ACP, 0, lpbi->pszDisplayName, -1, bi.pszDisplayName, MAX_PATH );
1048 }
1049 else
1050 bi.pszDisplayName = NULL;
1051
1052 if (lpbi->lpszTitle)
1053 {
1054 len = MultiByteToWideChar( CP_ACP, 0, lpbi->lpszTitle, -1, NULL, 0 );
1055 title = (WCHAR *)HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) );
1056 MultiByteToWideChar( CP_ACP, 0, lpbi->lpszTitle, -1, title, len );
1057 }
1058 else
1059 title = NULL;
1060
1061 bi.lpszTitle = title;
1062 bi.ulFlags = lpbi->ulFlags;
1063 bi.lpfn = lpbi->lpfn;
1064 bi.lParam = lpbi->lParam;
1065 bi.iImage = lpbi->iImage;
1066 lpid = SHBrowseForFolderW( &bi );
1067 if (bi.pszDisplayName)
1068 {
1069 WideCharToMultiByte( CP_ACP, 0, bi.pszDisplayName, -1,
1070 lpbi->pszDisplayName, MAX_PATH, 0, NULL);
1071 HeapFree( GetProcessHeap(), 0, bi.pszDisplayName );
1072 }
1073 HeapFree(GetProcessHeap(), 0, title);
1074 lpbi->iImage = bi.iImage;
1075 return lpid;
1076 }
1077
1078
1079 /*************************************************************************
1080 * SHBrowseForFolderW [SHELL32.@]
1081 *
1082 * NOTES
1083 * crashes when passed a null pointer
1084 */
1085 LPITEMIDLIST WINAPI SHBrowseForFolderW (LPBROWSEINFOW lpbi)
1086 {
1087 browse_info info;
1088 DWORD r;
1089 HRESULT hr;
1090 WORD wDlgId;
1091
1092 info.hWnd = 0;
1093 info.pidlRet = NULL;
1094 info.lpBrowseInfo = lpbi;
1095 info.hwndTreeView = NULL;
1096
1097 hr = OleInitialize(NULL);
1098
1099 if (lpbi->ulFlags & BIF_NEWDIALOGSTYLE)
1100 wDlgId = IDD_BROWSE_FOR_FOLDER_NEW;
1101 else
1102 wDlgId = IDD_BROWSE_FOR_FOLDER;
1103 r = DialogBoxParamW( shell32_hInstance, MAKEINTRESOURCEW(wDlgId), lpbi->hwndOwner,
1104 BrsFolderDlgProc, (LPARAM)&info );
1105 if (SUCCEEDED(hr))
1106 OleUninitialize();
1107 if (!r)
1108 {
1109 ILFree(info.pidlRet);
1110 return NULL;
1111 }
1112
1113 return info.pidlRet;
1114 }