Declare LPCITEMIDLIST as 'const' pointer and adjust shell32
[wine] / dlls / shell32 / brsfolder.c
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  *
18  * FIXME:
19  *  - many memory leaks
20  *  - many flags unimplemented
21  */
22
23 #include <stdlib.h>
24 #include <string.h>
25
26 #define NONAMELESSUNION
27 #define NONAMELESSSTRUCT
28 #include "wine/debug.h"
29 #include "undocshell.h"
30 #include "shlguid.h"
31 #include "pidl.h"
32 #include "shell32_main.h"
33 #include "shellapi.h"
34 #include "shresdef.h"
35
36 WINE_DEFAULT_DEBUG_CHANNEL(shell);
37
38 static HWND             hwndTreeView;
39 static LPBROWSEINFOW    lpBrowseInfo;
40 static LPITEMIDLIST     pidlRet;
41
42 static void FillTreeView(LPSHELLFOLDER lpsf, LPITEMIDLIST  lpifq, HTREEITEM hParent, IEnumIDList* lpe);
43 static HTREEITEM InsertTreeViewItem(IShellFolder * lpsf, LPCITEMIDLIST pidl, LPCITEMIDLIST pidlParent, IEnumIDList* pEnumIL, HTREEITEM hParent);
44
45 #define SUPPORTEDFLAGS (BIF_STATUSTEXT | \
46                         BIF_BROWSEFORCOMPUTER | \
47                         BIF_RETURNFSANCESTORS | \
48                         BIF_RETURNONLYFSDIRS | \
49                         BIF_BROWSEINCLUDEFILES)
50
51 static inline DWORD BrowseFlagsToSHCONTF(UINT ulFlags)
52 {
53     return SHCONTF_FOLDERS | (ulFlags & BIF_BROWSEINCLUDEFILES ? SHCONTF_NONFOLDERS : 0);
54 }
55
56 static void InitializeTreeView(HWND hwndParent, LPCITEMIDLIST root)
57 {
58         HIMAGELIST      hImageList;
59         IShellFolder *  lpsf;
60         HRESULT hr;
61         IEnumIDList * pEnumIL = NULL;
62         LPITEMIDLIST parentofroot;
63         parentofroot = ILClone(root);
64         ILRemoveLastID(parentofroot);
65
66         hwndTreeView = GetDlgItem (hwndParent, IDD_TREEVIEW);
67         Shell_GetImageList(NULL, &hImageList);
68
69         TRACE("dlg=%p tree=%p\n", hwndParent, hwndTreeView );
70
71         if (hImageList && hwndTreeView)
72           TreeView_SetImageList(hwndTreeView, hImageList, 0);
73
74         if (_ILIsDesktop (root)) {
75            hr = SHGetDesktopFolder(&lpsf);
76         } else {
77            IShellFolder *       lpsfdesktop;
78
79            hr = SHGetDesktopFolder(&lpsfdesktop);
80            if (SUCCEEDED(hr)) {
81               hr = IShellFolder_BindToObject(lpsfdesktop, parentofroot, 0,(REFIID)&IID_IShellFolder,(LPVOID *)&lpsf);
82               IShellFolder_Release(lpsfdesktop);
83            }
84         }
85         if (SUCCEEDED(hr))
86         {
87             IShellFolder * pSFRoot;
88             if (_ILIsPidlSimple(root))
89             {
90                 pSFRoot = lpsf;
91                 IShellFolder_AddRef(pSFRoot);
92             }
93             else
94                 hr = IShellFolder_BindToObject(lpsf,ILFindLastID(root),0,&IID_IShellFolder,(LPVOID *)&pSFRoot);
95             if (SUCCEEDED(hr))
96             {
97                 hr = IShellFolder_EnumObjects(
98                     pSFRoot,
99                     hwndParent,
100                     BrowseFlagsToSHCONTF(lpBrowseInfo->ulFlags),
101                     &pEnumIL);
102                 IShellFolder_Release(pSFRoot);
103             }
104         }
105
106         if (SUCCEEDED(hr) && hwndTreeView)
107         {
108           TreeView_DeleteAllItems(hwndTreeView);
109           TreeView_Expand(hwndTreeView,
110                           InsertTreeViewItem(lpsf, _ILIsPidlSimple(root) ? root : ILFindLastID(root), parentofroot, pEnumIL,  TVI_ROOT),
111                           TVE_EXPAND);
112         }
113
114         if (SUCCEEDED(hr))
115           IShellFolder_Release(lpsf);
116
117         TRACE("done\n");
118 }
119
120 static int GetIcon(LPITEMIDLIST lpi, UINT uFlags)
121 {
122         SHFILEINFOW    sfi;
123         SHGetFileInfoW((LPCWSTR)lpi, 0 ,&sfi, sizeof(SHFILEINFOW), uFlags);
124         return sfi.iIcon;
125 }
126
127 static void GetNormalAndSelectedIcons(LPITEMIDLIST lpifq, LPTVITEMW lpTV_ITEM)
128 {
129         LPITEMIDLIST pidlDesktop = NULL;
130
131         TRACE("%p %p\n",lpifq, lpTV_ITEM);
132
133         if (!lpifq)
134         {
135             pidlDesktop = _ILCreateDesktop();
136             lpifq = pidlDesktop;
137         }
138
139         lpTV_ITEM->iImage = GetIcon(lpifq, SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_SMALLICON);
140         lpTV_ITEM->iSelectedImage = GetIcon(lpifq, SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_SMALLICON | SHGFI_OPENICON);
141
142         if (pidlDesktop)
143             ILFree(pidlDesktop);
144
145         return;
146 }
147
148 typedef struct tagID
149 {
150    LPSHELLFOLDER lpsfParent;
151    LPITEMIDLIST  lpi;
152    LPITEMIDLIST  lpifq;
153    IEnumIDList*  pEnumIL;
154 } TV_ITEMDATA, *LPTV_ITEMDATA;
155
156 static BOOL GetName(LPSHELLFOLDER lpsf, LPCITEMIDLIST lpi, DWORD dwFlags, LPWSTR lpFriendlyName)
157 {
158         BOOL   bSuccess=TRUE;
159         STRRET str;
160
161         TRACE("%p %p %lx %p\n", lpsf, lpi, dwFlags, lpFriendlyName);
162         if (SUCCEEDED(IShellFolder_GetDisplayNameOf(lpsf, lpi, dwFlags, &str)))
163         {
164           if (FAILED(StrRetToStrNW(lpFriendlyName, MAX_PATH, &str, lpi)))
165           {
166               bSuccess = FALSE;
167           }
168         }
169         else
170           bSuccess = FALSE;
171
172         TRACE("-- %s\n", debugstr_w(lpFriendlyName));
173         return bSuccess;
174 }
175
176 static HTREEITEM InsertTreeViewItem(IShellFolder * lpsf, LPCITEMIDLIST pidl, LPCITEMIDLIST pidlParent, IEnumIDList* pEnumIL, HTREEITEM hParent)
177 {
178         TVITEMW         tvi;
179         TVINSERTSTRUCTW tvins;
180         WCHAR           szBuff[MAX_PATH];
181         LPTV_ITEMDATA   lptvid=0;
182
183         tvi.mask  = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM;
184
185         tvi.cChildren= pEnumIL ? 1 : 0;
186         tvi.mask |= TVIF_CHILDREN;
187
188         if (!(lptvid = (LPTV_ITEMDATA)SHAlloc(sizeof(TV_ITEMDATA))))
189             return NULL;
190
191         if (!GetName(lpsf, pidl, SHGDN_NORMAL, szBuff))
192             return NULL;
193
194         tvi.pszText    = szBuff;
195         tvi.cchTextMax = MAX_PATH;
196         tvi.lParam = (LPARAM)lptvid;
197
198         IShellFolder_AddRef(lpsf);
199         lptvid->lpsfParent = lpsf;
200         lptvid->lpi     = ILClone(pidl);
201         lptvid->lpifq   = pidlParent ? ILCombine(pidlParent, pidl) : ILClone(pidl);
202         lptvid->pEnumIL = pEnumIL;
203         GetNormalAndSelectedIcons(lptvid->lpifq, &tvi);
204
205         tvins.DUMMYUNIONNAME.item         = tvi;
206         tvins.hInsertAfter = NULL;
207         tvins.hParent      = hParent;
208
209         return (HTREEITEM)TreeView_InsertItemW(hwndTreeView, &tvins);
210 }
211
212 static void FillTreeView(IShellFolder * lpsf, LPITEMIDLIST  pidl, HTREEITEM hParent, IEnumIDList* lpe)
213 {
214         HTREEITEM       hPrev = 0;
215         LPITEMIDLIST    pidlTemp = 0;
216         ULONG           ulFetched;
217         HRESULT         hr;
218         HWND            hwnd=GetParent(hwndTreeView);
219
220         TRACE("%p %p %x\n",lpsf, pidl, (INT)hParent);
221         SetCapture(GetParent(hwndTreeView));
222         SetCursor(LoadCursorA(0, IDC_WAITA));
223
224         while (NOERROR == IEnumIDList_Next(lpe,1,&pidlTemp,&ulFetched))
225         {
226             ULONG ulAttrs = SFGAO_HASSUBFOLDER | SFGAO_FOLDER;
227             IEnumIDList* pEnumIL = NULL;
228             IShellFolder* pSFChild = NULL;
229             IShellFolder_GetAttributesOf(lpsf, 1, (LPCITEMIDLIST*)&pidlTemp, &ulAttrs);
230             if (ulAttrs & SFGAO_FOLDER)
231             {
232                 hr = IShellFolder_BindToObject(lpsf,pidlTemp,NULL,&IID_IShellFolder,(LPVOID*)&pSFChild);
233                 if (SUCCEEDED(hr))
234                 {
235                     hr = IShellFolder_EnumObjects(pSFChild, hwnd, BrowseFlagsToSHCONTF(lpBrowseInfo->ulFlags), &pEnumIL);
236                     if (SUCCEEDED(hr))
237                     {
238                         if ((IEnumIDList_Skip(pEnumIL, 1) != S_OK) || FAILED(IEnumIDList_Reset(pEnumIL)))
239                         {
240                             IEnumIDList_Release(pEnumIL);
241                             pEnumIL = NULL;
242                         }
243                     }
244                     IShellFolder_Release(pSFChild);
245                 }
246             }
247
248             if (!(hPrev = InsertTreeViewItem(lpsf, pidlTemp, pidl, pEnumIL, hParent)))
249                 goto Done;
250             SHFree(pidlTemp);  /* Finally, free the pidl that the shell gave us... */
251             pidlTemp=NULL;
252         }
253
254 Done:
255         ReleaseCapture();
256         SetCursor(LoadCursorW(0, IDC_ARROWW));
257
258         if (pidlTemp)
259           SHFree(pidlTemp);
260 }
261
262 static inline BOOL PIDLIsType(LPCITEMIDLIST pidl, PIDLTYPE type)
263 {
264     LPPIDLDATA data = _ILGetDataPointer(pidl);
265     if (!data)
266         return FALSE;
267     return (data->type == type);
268 }
269
270 static void BrsFolder_CheckValidSelection(HWND hWndTree, LPTV_ITEMDATA lptvid)
271 {
272     LPCITEMIDLIST pidl = lptvid->lpi;
273     BOOL bEnabled = TRUE;
274     DWORD dwAttributes;
275     if ((lpBrowseInfo->ulFlags & BIF_BROWSEFORCOMPUTER) &&
276         !PIDLIsType(pidl, PT_COMP))
277         bEnabled = FALSE;
278     if (lpBrowseInfo->ulFlags & BIF_RETURNFSANCESTORS)
279     {
280         dwAttributes = SFGAO_FILESYSANCESTOR | SFGAO_FILESYSTEM;
281         if (FAILED(IShellFolder_GetAttributesOf(lptvid->lpsfParent, 1, (LPCITEMIDLIST*)&lptvid->lpi, &dwAttributes)) ||
282             !dwAttributes)
283             bEnabled = FALSE;
284     }
285     if (lpBrowseInfo->ulFlags & BIF_RETURNONLYFSDIRS)
286     {
287         dwAttributes = SFGAO_FOLDER | SFGAO_FILESYSTEM;
288         if (FAILED(IShellFolder_GetAttributesOf(lptvid->lpsfParent, 1, (LPCITEMIDLIST*)&lptvid->lpi, &dwAttributes)) ||
289             (dwAttributes != (SFGAO_FOLDER | SFGAO_FILESYSTEM)))
290             bEnabled = FALSE;
291     }
292     SendMessageW(hWndTree, BFFM_ENABLEOK, 0, (LPARAM)bEnabled);
293 }
294
295 static LRESULT MsgNotify(HWND hWnd,  UINT CtlID, LPNMHDR lpnmh)
296 {
297         NMTREEVIEWW     *pnmtv   = (NMTREEVIEWW *)lpnmh;
298         LPTV_ITEMDATA   lptvid;  /* Long pointer to TreeView item data */
299         IShellFolder *  lpsf2=0;
300
301
302         TRACE("%p %x %p msg=%x\n", hWnd,  CtlID, lpnmh, pnmtv->hdr.code);
303
304         switch (pnmtv->hdr.idFrom)
305         { case IDD_TREEVIEW:
306             switch (pnmtv->hdr.code)
307             {
308               case TVN_DELETEITEMA:
309               case TVN_DELETEITEMW:
310                 TRACE("TVN_DELETEITEMA/W\n");
311                 lptvid=(LPTV_ITEMDATA)pnmtv->itemOld.lParam;
312                 IShellFolder_Release(lptvid->lpsfParent);
313                 if (lptvid->pEnumIL)
314                   IEnumIDList_Release(lptvid->pEnumIL);
315                 SHFree(lptvid->lpi);
316                 SHFree(lptvid->lpifq);
317                 SHFree(lptvid);
318                 break;
319
320               case TVN_ITEMEXPANDINGA:
321               case TVN_ITEMEXPANDINGW:
322                 {
323                   TRACE("TVN_ITEMEXPANDINGA/W\n");
324                   if ((pnmtv->itemNew.state & TVIS_EXPANDEDONCE))
325                     break;
326
327                   lptvid=(LPTV_ITEMDATA)pnmtv->itemNew.lParam;
328                   if (SUCCEEDED(IShellFolder_BindToObject(lptvid->lpsfParent, lptvid->lpi,0,(REFIID)&IID_IShellFolder,(LPVOID *)&lpsf2)))
329                   { FillTreeView( lpsf2, lptvid->lpifq, pnmtv->itemNew.hItem, lptvid->pEnumIL);
330                   }
331                   TreeView_SortChildren(hwndTreeView, pnmtv->itemNew.hItem, FALSE);
332                 }
333                 break;
334               case TVN_SELCHANGEDA:
335               case TVN_SELCHANGEDW:
336                 lptvid=(LPTV_ITEMDATA)pnmtv->itemNew.lParam;
337                 pidlRet = lptvid->lpifq;
338                 if (lpBrowseInfo->lpfn)
339                    (lpBrowseInfo->lpfn)(hWnd, BFFM_SELCHANGED, (LPARAM)pidlRet, lpBrowseInfo->lParam);
340                 BrsFolder_CheckValidSelection(hWnd, lptvid);
341                 break;
342
343               default:
344                 WARN("unhandled (%d)\n", pnmtv->hdr.code);
345                 break;
346             }
347             break;
348
349           default:
350             break;
351         }
352
353         return 0;
354 }
355
356
357 /*************************************************************************
358  *             BrsFolderDlgProc32  (not an exported API function)
359  */
360 static INT_PTR CALLBACK BrsFolderDlgProc(HWND hWnd, UINT msg, WPARAM wParam,
361                                      LPARAM lParam )
362 {
363         TRACE("hwnd=%p msg=%04x 0x%08x 0x%08lx\n", hWnd,  msg, wParam, lParam );
364
365         switch(msg)
366         { case WM_INITDIALOG:
367             pidlRet = NULL;
368             lpBrowseInfo = (LPBROWSEINFOW) lParam;
369             if (lpBrowseInfo->ulFlags & ~SUPPORTEDFLAGS)
370               FIXME("flags %x not implemented\n", lpBrowseInfo->ulFlags & ~SUPPORTEDFLAGS);
371             if (lpBrowseInfo->lpszTitle) {
372                SetWindowTextW(GetDlgItem(hWnd, IDD_TITLE), lpBrowseInfo->lpszTitle);
373             } else {
374                ShowWindow(GetDlgItem(hWnd, IDD_TITLE), SW_HIDE);
375             }
376             if (!(lpBrowseInfo->ulFlags & BIF_STATUSTEXT))
377                ShowWindow(GetDlgItem(hWnd, IDD_STATUS), SW_HIDE);
378
379             InitializeTreeView(hWnd, lpBrowseInfo->pidlRoot);
380
381             if (lpBrowseInfo->lpfn)
382                (lpBrowseInfo->lpfn)(hWnd, BFFM_INITIALIZED, 0, lpBrowseInfo->lParam);
383
384             return TRUE;
385
386           case WM_NOTIFY:
387             MsgNotify( hWnd, (UINT)wParam, (LPNMHDR)lParam);
388             break;
389
390           case WM_COMMAND:
391             switch (wParam)
392             { case IDOK:
393                 pdump ( pidlRet );
394                 SHGetPathFromIDListW(pidlRet, lpBrowseInfo->pszDisplayName);
395                 EndDialog(hWnd, (DWORD) ILClone(pidlRet));
396                 return TRUE;
397
398               case IDCANCEL:
399                 EndDialog(hWnd, 0);
400                 return TRUE;
401             }
402             break;
403         case BFFM_SETSTATUSTEXTA:
404            TRACE("Set status %s\n", debugstr_a((LPSTR)lParam));
405            SetWindowTextA(GetDlgItem(hWnd, IDD_STATUS), (LPSTR)lParam);
406            break;
407         case BFFM_SETSTATUSTEXTW:
408            TRACE("Set status %s\n", debugstr_w((LPWSTR)lParam));
409            SetWindowTextW(GetDlgItem(hWnd, IDD_STATUS), (LPWSTR)lParam);
410            break;
411         case BFFM_ENABLEOK:
412            TRACE("Enable %ld\n", lParam);
413            EnableWindow(GetDlgItem(hWnd, 1), (lParam)?TRUE:FALSE);
414            break;
415         case BFFM_SETOKTEXT: /* unicode only */
416            TRACE("Set OK text %s\n", debugstr_w((LPWSTR)wParam));
417            SetWindowTextW(GetDlgItem(hWnd, 1), (LPWSTR)wParam);
418            break;
419         case BFFM_SETSELECTIONA:
420            if (wParam)
421               FIXME("Set selection %s\n", debugstr_a((LPSTR)lParam));
422            else
423               FIXME("Set selection %p\n", (void*)lParam);
424            break;
425         case BFFM_SETSELECTIONW:
426            if (wParam)
427               FIXME("Set selection %s\n", debugstr_w((LPWSTR)lParam));
428            else
429               FIXME("Set selection %p\n", (void*)lParam);
430            break;
431         case BFFM_SETEXPANDED: /* unicode only */
432            if (wParam)
433               FIXME("Set expanded %s\n", debugstr_w((LPWSTR)lParam));
434            else
435               FIXME("Set expanded %p\n", (void*)lParam);
436            break;
437         }
438         return FALSE;
439 }
440
441 static WCHAR swBrowseTempName[] = {'S','H','B','R','S','F','O','R','F','O','L','D','E','R','_','M','S','G','B','O','X',0};
442
443 /*************************************************************************
444  * SHBrowseForFolderA [SHELL32.@]
445  * SHBrowseForFolder  [SHELL32.@]
446  */
447 LPITEMIDLIST WINAPI SHBrowseForFolderA (LPBROWSEINFOA lpbi)
448 {
449         BROWSEINFOW bi;
450         LPITEMIDLIST lpid;
451         INT len;
452         
453         TRACE("(%p{lpszTitle=%s,owner=%p})\n", lpbi,
454             lpbi ? debugstr_a(lpbi->lpszTitle) : NULL, lpbi ? lpbi->hwndOwner : NULL);
455
456         if (!lpbi)
457           return NULL;
458
459         bi.hwndOwner = lpbi->hwndOwner;
460         bi.pidlRoot = lpbi->pidlRoot;
461         if (lpbi->pszDisplayName)
462         {
463           len = MultiByteToWideChar(CP_ACP, 0, lpbi->pszDisplayName, -1, NULL, 0);
464           bi.pszDisplayName = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
465           MultiByteToWideChar(CP_ACP, 0, lpbi->pszDisplayName, -1, bi.pszDisplayName, len);
466         }
467         else
468           bi.pszDisplayName = NULL;
469
470         if (lpbi->lpszTitle)
471         {
472           len = MultiByteToWideChar(CP_ACP, 0, lpbi->lpszTitle, -1, NULL, 0);
473           bi.lpszTitle = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
474           MultiByteToWideChar(CP_ACP, 0, lpbi->lpszTitle, -1, (LPWSTR)bi.lpszTitle, len);
475         }
476         else
477           bi.lpszTitle = NULL;
478
479         bi.ulFlags = lpbi->ulFlags;
480         bi.lpfn = lpbi->lpfn;
481         bi.lParam = lpbi->lParam;
482         bi.iImage = lpbi->iImage;
483         lpid = (LPITEMIDLIST) DialogBoxParamW(shell32_hInstance,
484                                               swBrowseTempName, lpbi->hwndOwner,
485                                               BrsFolderDlgProc, (INT)&bi);
486         if (bi.pszDisplayName)
487         {
488           WideCharToMultiByte(CP_ACP, 0, bi.pszDisplayName, -1, lpbi->pszDisplayName, MAX_PATH, 0, NULL);
489           HeapFree(GetProcessHeap(), 0, bi.pszDisplayName);
490         }
491         if (bi.lpszTitle)
492         {
493           HeapFree(GetProcessHeap(), 0, (LPVOID)bi.lpszTitle);
494         }
495         lpbi->iImage = bi.iImage;
496         return lpid;
497 }
498
499
500 /*************************************************************************
501  * SHBrowseForFolderW [SHELL32.@]
502  */
503 LPITEMIDLIST WINAPI SHBrowseForFolderW (LPBROWSEINFOW lpbi)
504 {
505         TRACE("((%p->{lpszTitle=%s,owner=%p})\n", lpbi,
506             lpbi ? debugstr_w(lpbi->lpszTitle) : NULL, lpbi ? lpbi->hwndOwner : 0);
507
508         if (!lpbi)
509           return NULL;
510
511         return (LPITEMIDLIST) DialogBoxParamW(shell32_hInstance,
512                                               swBrowseTempName, lpbi->hwndOwner,
513                                               BrsFolderDlgProc, (INT)lpbi);
514 }