SHGetFileInfo should tolerate null pointers.
[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, LPITEMIDLIST pidl, LPITEMIDLIST 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, LPITEMIDLIST 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, LPITEMIDLIST pidl, LPITEMIDLIST 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, &pidlTemp, &ulAttrs);
230             if (ulAttrs & SFGAO_FOLDER)
231             {
232                 hr = IShellFolder_BindToObject(lpsf,pidlTemp,NULL,&IID_IShellFolder,(LPVOID*)&pSFChild);
233                 if (SUCCEEDED(hr))
234                     hr = IShellFolder_EnumObjects(pSFChild, hwnd, BrowseFlagsToSHCONTF(lpBrowseInfo->ulFlags), &pEnumIL);
235                 if (SUCCEEDED(hr))
236                 {
237                     if ((IEnumIDList_Skip(pEnumIL, 1) != S_OK) || FAILED(IEnumIDList_Reset(pEnumIL)))
238                     {
239                         IEnumIDList_Release(pEnumIL);
240                         pEnumIL = NULL;
241                     }
242                 }
243                 IShellFolder_Release(pSFChild);
244             }
245
246             if (!(hPrev = InsertTreeViewItem(lpsf, pidlTemp, pidl, pEnumIL, hParent)))
247                 goto Done;
248             SHFree(pidlTemp);  /* Finally, free the pidl that the shell gave us... */
249             pidlTemp=NULL;
250         }
251
252 Done:
253         ReleaseCapture();
254         SetCursor(LoadCursorW(0, IDC_ARROWW));
255
256         if (pidlTemp)
257           SHFree(pidlTemp);
258 }
259
260 static inline BOOL PIDLIsType(LPCITEMIDLIST pidl, PIDLTYPE type)
261 {
262     LPPIDLDATA data = _ILGetDataPointer(pidl);
263     if (!data)
264         return FALSE;
265     return (data->type == type);
266 }
267
268 static void BrsFolder_CheckValidSelection(HWND hWndTree, LPTV_ITEMDATA lptvid)
269 {
270     LPCITEMIDLIST pidl = lptvid->lpi;
271     BOOL bEnabled = TRUE;
272     DWORD dwAttributes;
273     if ((lpBrowseInfo->ulFlags & BIF_BROWSEFORCOMPUTER) &&
274         !PIDLIsType(pidl, PT_COMP))
275         bEnabled = FALSE;
276     if (lpBrowseInfo->ulFlags & BIF_RETURNFSANCESTORS)
277     {
278         dwAttributes = SFGAO_FILESYSANCESTOR | SFGAO_FILESYSTEM;
279         if (FAILED(IShellFolder_GetAttributesOf(lptvid->lpsfParent, 1, &lptvid->lpi, &dwAttributes)) ||
280             !dwAttributes)
281             bEnabled = FALSE;
282     }
283     if (lpBrowseInfo->ulFlags & BIF_RETURNONLYFSDIRS)
284     {
285         dwAttributes = SFGAO_FOLDER | SFGAO_FILESYSTEM;
286         if (FAILED(IShellFolder_GetAttributesOf(lptvid->lpsfParent, 1, &lptvid->lpi, &dwAttributes)) ||
287             (dwAttributes != (SFGAO_FOLDER | SFGAO_FILESYSTEM)))
288             bEnabled = FALSE;
289     }
290     SendMessageW(hWndTree, BFFM_ENABLEOK, 0, (LPARAM)bEnabled);
291 }
292
293 static LRESULT MsgNotify(HWND hWnd,  UINT CtlID, LPNMHDR lpnmh)
294 {
295         NMTREEVIEWW     *pnmtv   = (NMTREEVIEWW *)lpnmh;
296         LPTV_ITEMDATA   lptvid;  /* Long pointer to TreeView item data */
297         IShellFolder *  lpsf2=0;
298
299
300         TRACE("%p %x %p msg=%x\n", hWnd,  CtlID, lpnmh, pnmtv->hdr.code);
301
302         switch (pnmtv->hdr.idFrom)
303         { case IDD_TREEVIEW:
304             switch (pnmtv->hdr.code)
305             {
306               case TVN_DELETEITEMA:
307               case TVN_DELETEITEMW:
308                 TRACE("TVN_DELETEITEMA/W\n");
309                 lptvid=(LPTV_ITEMDATA)pnmtv->itemOld.lParam;
310                 IShellFolder_Release(lptvid->lpsfParent);
311                 if (lptvid->pEnumIL)
312                   IEnumIDList_Release(lptvid->pEnumIL);
313                 SHFree(lptvid->lpi);
314                 SHFree(lptvid->lpifq);
315                 SHFree(lptvid);
316                 break;
317
318               case TVN_ITEMEXPANDINGA:
319               case TVN_ITEMEXPANDINGW:
320                 {
321                   TRACE("TVN_ITEMEXPANDINGA/W\n");
322                   if ((pnmtv->itemNew.state & TVIS_EXPANDEDONCE))
323                     break;
324
325                   lptvid=(LPTV_ITEMDATA)pnmtv->itemNew.lParam;
326                   if (SUCCEEDED(IShellFolder_BindToObject(lptvid->lpsfParent, lptvid->lpi,0,(REFIID)&IID_IShellFolder,(LPVOID *)&lpsf2)))
327                   { FillTreeView( lpsf2, lptvid->lpifq, pnmtv->itemNew.hItem, lptvid->pEnumIL);
328                   }
329                   TreeView_SortChildren(hwndTreeView, pnmtv->itemNew.hItem, FALSE);
330                 }
331                 break;
332               case TVN_SELCHANGEDA:
333               case TVN_SELCHANGEDW:
334                 lptvid=(LPTV_ITEMDATA)pnmtv->itemNew.lParam;
335                 pidlRet = lptvid->lpifq;
336                 if (lpBrowseInfo->lpfn)
337                    (lpBrowseInfo->lpfn)(hWnd, BFFM_SELCHANGED, (LPARAM)pidlRet, lpBrowseInfo->lParam);
338                 BrsFolder_CheckValidSelection(hWnd, lptvid);
339                 break;
340
341               default:
342                 WARN("unhandled (%d)\n", pnmtv->hdr.code);
343                 break;
344             }
345             break;
346
347           default:
348             break;
349         }
350
351         return 0;
352 }
353
354
355 /*************************************************************************
356  *             BrsFolderDlgProc32  (not an exported API function)
357  */
358 static INT_PTR CALLBACK BrsFolderDlgProc(HWND hWnd, UINT msg, WPARAM wParam,
359                                      LPARAM lParam )
360 {
361         TRACE("hwnd=%p msg=%04x 0x%08x 0x%08lx\n", hWnd,  msg, wParam, lParam );
362
363         switch(msg)
364         { case WM_INITDIALOG:
365             pidlRet = NULL;
366             lpBrowseInfo = (LPBROWSEINFOW) lParam;
367             if (lpBrowseInfo->ulFlags & ~SUPPORTEDFLAGS)
368               FIXME("flags %x not implemented\n", lpBrowseInfo->ulFlags & ~SUPPORTEDFLAGS);
369             if (lpBrowseInfo->lpszTitle) {
370                SetWindowTextW(GetDlgItem(hWnd, IDD_TITLE), lpBrowseInfo->lpszTitle);
371             } else {
372                ShowWindow(GetDlgItem(hWnd, IDD_TITLE), SW_HIDE);
373             }
374             if (!(lpBrowseInfo->ulFlags & BIF_STATUSTEXT))
375                ShowWindow(GetDlgItem(hWnd, IDD_STATUS), SW_HIDE);
376
377             InitializeTreeView(hWnd, lpBrowseInfo->pidlRoot);
378
379             if (lpBrowseInfo->lpfn)
380                (lpBrowseInfo->lpfn)(hWnd, BFFM_INITIALIZED, 0, lpBrowseInfo->lParam);
381
382             return TRUE;
383
384           case WM_NOTIFY:
385             MsgNotify( hWnd, (UINT)wParam, (LPNMHDR)lParam);
386             break;
387
388           case WM_COMMAND:
389             switch (wParam)
390             { case IDOK:
391                 pdump ( pidlRet );
392                 SHGetPathFromIDListW(pidlRet, lpBrowseInfo->pszDisplayName);
393                 EndDialog(hWnd, (DWORD) ILClone(pidlRet));
394                 return TRUE;
395
396               case IDCANCEL:
397                 EndDialog(hWnd, 0);
398                 return TRUE;
399             }
400             break;
401         case BFFM_SETSTATUSTEXTA:
402            TRACE("Set status %s\n", debugstr_a((LPSTR)lParam));
403            SetWindowTextA(GetDlgItem(hWnd, IDD_STATUS), (LPSTR)lParam);
404            break;
405         case BFFM_SETSTATUSTEXTW:
406            TRACE("Set status %s\n", debugstr_w((LPWSTR)lParam));
407            SetWindowTextW(GetDlgItem(hWnd, IDD_STATUS), (LPWSTR)lParam);
408            break;
409         case BFFM_ENABLEOK:
410            TRACE("Enable %ld\n", lParam);
411            EnableWindow(GetDlgItem(hWnd, 1), (lParam)?TRUE:FALSE);
412            break;
413         case BFFM_SETOKTEXT: /* unicode only */
414            TRACE("Set OK text %s\n", debugstr_w((LPWSTR)wParam));
415            SetWindowTextW(GetDlgItem(hWnd, 1), (LPWSTR)wParam);
416            break;
417         case BFFM_SETSELECTIONA:
418            if (wParam)
419               FIXME("Set selection %s\n", debugstr_a((LPSTR)lParam));
420            else
421               FIXME("Set selection %p\n", (void*)lParam);
422            break;
423         case BFFM_SETSELECTIONW:
424            if (wParam)
425               FIXME("Set selection %s\n", debugstr_w((LPWSTR)lParam));
426            else
427               FIXME("Set selection %p\n", (void*)lParam);
428            break;
429         case BFFM_SETEXPANDED: /* unicode only */
430            if (wParam)
431               FIXME("Set expanded %s\n", debugstr_w((LPWSTR)lParam));
432            else
433               FIXME("Set expanded %p\n", (void*)lParam);
434            break;
435         }
436         return FALSE;
437 }
438
439 static WCHAR swBrowseTempName[] = {'S','H','B','R','S','F','O','R','F','O','L','D','E','R','_','M','S','G','B','O','X',0};
440
441 /*************************************************************************
442  * SHBrowseForFolderA [SHELL32.@]
443  * SHBrowseForFolder  [SHELL32.@]
444  */
445 LPITEMIDLIST WINAPI SHBrowseForFolderA (LPBROWSEINFOA lpbi)
446 {
447         BROWSEINFOW bi;
448         LPITEMIDLIST lpid;
449         INT len;
450         
451         TRACE("(%p{lpszTitle=%s,owner=%p})\n", lpbi,
452             lpbi ? debugstr_a(lpbi->lpszTitle) : NULL, lpbi ? lpbi->hwndOwner : NULL);
453
454         if (!lpbi)
455           return NULL;
456
457         bi.hwndOwner = lpbi->hwndOwner;
458         bi.pidlRoot = lpbi->pidlRoot;
459         if (lpbi->pszDisplayName)
460         {
461           len = MultiByteToWideChar(CP_ACP, 0, lpbi->pszDisplayName, -1, NULL, 0);
462           bi.pszDisplayName = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
463           MultiByteToWideChar(CP_ACP, 0, lpbi->pszDisplayName, -1, bi.pszDisplayName, len);
464         }
465         else
466           bi.pszDisplayName = NULL;
467
468         if (lpbi->lpszTitle)
469         {
470           len = MultiByteToWideChar(CP_ACP, 0, lpbi->lpszTitle, -1, NULL, 0);
471           bi.lpszTitle = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
472           MultiByteToWideChar(CP_ACP, 0, lpbi->lpszTitle, -1, (LPWSTR)bi.lpszTitle, len);
473         }
474         else
475           bi.lpszTitle = NULL;
476
477         bi.ulFlags = lpbi->ulFlags;
478         bi.lpfn = lpbi->lpfn;
479         bi.lParam = lpbi->lParam;
480         bi.iImage = lpbi->iImage;
481         lpid = (LPITEMIDLIST) DialogBoxParamW(shell32_hInstance,
482                                               swBrowseTempName, lpbi->hwndOwner,
483                                               BrsFolderDlgProc, (INT)&bi);
484         if (bi.pszDisplayName)
485         {
486           WideCharToMultiByte(CP_ACP, 0, bi.pszDisplayName, -1, lpbi->pszDisplayName, MAX_PATH, 0, NULL);
487           HeapFree(GetProcessHeap(), 0, bi.pszDisplayName);
488         }
489         if (bi.lpszTitle)
490         {
491           HeapFree(GetProcessHeap(), 0, (LPVOID)bi.lpszTitle);
492         }
493         lpbi->iImage = bi.iImage;
494         return lpid;
495 }
496
497
498 /*************************************************************************
499  * SHBrowseForFolderW [SHELL32.@]
500  */
501 LPITEMIDLIST WINAPI SHBrowseForFolderW (LPBROWSEINFOW lpbi)
502 {
503         TRACE("((%p->{lpszTitle=%s,owner=%p})\n", lpbi,
504             lpbi ? debugstr_w(lpbi->lpszTitle) : NULL, lpbi ? lpbi->hwndOwner : 0);
505
506         if (!lpbi)
507           return NULL;
508
509         return (LPITEMIDLIST) DialogBoxParamW(shell32_hInstance,
510                                               swBrowseTempName, lpbi->hwndOwner,
511                                               BrsFolderDlgProc, (INT)lpbi);
512 }