1 /*
2  * Unit test of the SHBrowseForFolder function.
3  *
4  * Copyright 2009-2010 Michael Mc Donnell
5  * Copyright 2011 André Hentschel
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21 
22 #define COBJMACROS
23 
24 #include <windows.h>
25 #include <shlobj.h>
26 #include <shobjidl.h>
27 #include <string.h>
28 #include "shellapi.h"
29 
30 #include "wine/test.h"
31 #define IDD_MAKENEWFOLDER 0x3746 /* From "../shresdef.h" */
32 #define TIMER_WAIT_MS 50 /* Should be long enough for slow systems */
33 
34 static const char new_folder_name[] = "foo";
35 static LPITEMIDLIST selected_folder_pidl;
36 
37 /*
38  * Returns the number of folders in a folder.
39  */
40 static int get_number_of_folders(LPCSTR path)
41 {
42     int number_of_folders = 0;
43     char path_search_string[MAX_PATH];
44     WIN32_FIND_DATAA find_data;
45     HANDLE find_handle;
46 
47     lstrcpynA(path_search_string, path, MAX_PATH - 1);
48     strcat(path_search_string, "*");
49 
50     find_handle = FindFirstFileA(path_search_string, &find_data);
51     if (find_handle == INVALID_HANDLE_VALUE)
52         return -1;
53 
54     do
55     {
56         if ((find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
57             strcmp(find_data.cFileName, ".") != 0 &&
58             strcmp(find_data.cFileName, "..") != 0)
59         {
60             number_of_folders++;
61         }
62     }
63     while (FindNextFileA(find_handle, &find_data) != 0);
64 
65     FindClose(find_handle);
66     return number_of_folders;
67 }
68 
69 static BOOL does_folder_or_file_exist(LPCSTR folder_path)
70 {
71     DWORD file_attributes = GetFileAttributesA(folder_path);
72     return !(file_attributes == INVALID_FILE_ATTRIBUTES);
73 }
74 
75 /*
76  * Timer callback used by test_click_make_new_folder_button. It simulates a user
77  * making a new folder and calling it "foo".
78  */
79 static void CALLBACK make_new_folder_timer_callback(HWND hwnd, UINT uMsg,
80                                                     UINT_PTR idEvent, DWORD dwTime)
81 {
82     static int step = 0;
83 
84     switch (step++)
85     {
86     case 0:
87         /* Click "Make New Folder" button */
88         PostMessageA(hwnd, WM_COMMAND, IDD_MAKENEWFOLDER, 0);
89         break;
90     case 1:
91         /* Set the new folder name to foo by replacing text in edit control */
92         SendMessageA(GetFocus(), EM_REPLACESEL, 0, (LPARAM) new_folder_name);
93         SetFocus(hwnd);
94         break;
95     case 2:
96         /*
97          * The test does not trigger the correct state on Windows. This results
98          * in the new folder pidl not being returned. The result is as
99          * expected if the same steps are done manually.
100          * Sending the down key selects the new folder again which sets the
101          * correct state. This ensures that the correct pidl is returned.
102          */
103         keybd_event(VK_DOWN, 0, 0, 0);
104         break;
105     case 3:
106         keybd_event(VK_DOWN, 0, KEYEVENTF_KEYUP, 0);
107         break;
108     case 4:
109         KillTimer(hwnd, idEvent);
110         /* Close dialog box */
111         SendMessageA(hwnd, WM_COMMAND, IDOK, 0);
112         break;
113     default:
114         break;
115     }
116 }
117 
118 /*
119  * Callback used by test_click_make_new_folder_button. It sets up a timer to
120  * simulate user input.
121  */
122 static int CALLBACK create_new_folder_callback(HWND hwnd, UINT uMsg,
123                                                LPARAM lParam, LPARAM lpData)
124 {
125     switch (uMsg)
126     {
127     case BFFM_INITIALIZED:
128         /* User input is simulated in timer callback */
129         SetTimer(hwnd, 0, TIMER_WAIT_MS, make_new_folder_timer_callback);
130         return TRUE;
131     default:
132         return FALSE;
133     }
134 }
135 
136 /*
137  * Tests if clicking the "Make New Folder" button in a SHBrowseForFolder
138  * dialog box creates a new folder. (Bug 17986).
139  *
140  * Here follows a description of what happens on W2K,Vista, W2K8, W7:
141  * When the "Make New Folder" button is clicked a new folder is created and
142  * inserted into the tree. The folder is given a default name that depends on
143  * the locale (e.g. "New Folder"). The folder name is selected and the dialog
144  * waits for the user to type in a new name. The folder is renamed when the user
145  * types in a name and presses enter.
146  *
147  * Note that XP and W2K3 do not select the folder name or wait for the user
148  * to type in a new folder name. This behavior is considered broken as most
149  * users would like to give the folder a name after creating it. The fact that
150  * it originally waited for the user to type in a new folder name(W2K), and then
151  * again was changed back wait for the new folder name(Vista, W2K8, W7),
152  * indicates that MS also believes that it was broken in XP and W2K3.
153  */
154 static void test_click_make_new_folder_button(void)
155 {
156     HRESULT resCoInit, hr;
157     BROWSEINFOA bi;
158     LPITEMIDLIST pidl = NULL;
159     LPITEMIDLIST test_folder_pidl;
160     IShellFolder *test_folder_object;
161     char test_folder_path[MAX_PATH];
162     WCHAR test_folder_pathW[MAX_PATH];
163     CHAR new_folder_path[MAX_PATH];
164     CHAR new_folder_pidl_path[MAX_PATH];
165     char selected_folder[MAX_PATH];
166     const CHAR title[] = "test_click_make_new_folder_button";
167     int number_of_folders = -1;
168     SHFILEOPSTRUCTA shfileop;
169 
170     if (does_folder_or_file_exist(title))
171     {
172         skip("The test folder already exists.\n");
173         return;
174     }
175 
176     /* Must initialize COM if using the NEWDIAlOGSTYLE according to MSDN. */
177     resCoInit = CoInitialize(NULL);
178     if(!(resCoInit == S_OK || resCoInit == S_FALSE))
179     {
180         skip("COM could not be initialized %u\n", GetLastError());
181         return;
182     }
183 
184     /* Leave room for concatenating title, two backslashes, and an extra NULL. */
185     if (!GetCurrentDirectoryA(MAX_PATH-strlen(title)-3, test_folder_path))
186     {
187         skip("GetCurrentDirectoryA failed %u\n", GetLastError());
188     }
189     strcat(test_folder_path, "\\");
190     strcat(test_folder_path, title);
191     strcat(test_folder_path, "\\");
192 
193     /* Avoid conflicts by creating a test folder. */
194     if (!CreateDirectoryA(title, NULL))
195     {
196         skip("CreateDirectoryA failed %u\n", GetLastError());
197         return;
198     }
199 
200     /* Initialize browse info struct for SHBrowseForFolder */
201     bi.hwndOwner = NULL;
202     bi.pszDisplayName = selected_folder;
203     bi.lpszTitle = title;
204     bi.ulFlags = BIF_NEWDIALOGSTYLE;
205     bi.lpfn = create_new_folder_callback;
206     /* Use test folder as the root folder for dialog box */
207     MultiByteToWideChar(CP_UTF8, 0, test_folder_path, -1,
208         test_folder_pathW, MAX_PATH);
209     hr = SHGetDesktopFolder(&test_folder_object);
210     ok (SUCCEEDED(hr), "SHGetDesktopFolder failed with hr 0x%08x\n", hr);
211     if (FAILED(hr)) {
212         skip("SHGetDesktopFolder failed - skipping\n");
213         return;
214     }
215     test_folder_object->lpVtbl->ParseDisplayName(test_folder_object, NULL, NULL,
216         test_folder_pathW, 0UL, &test_folder_pidl, 0UL);
217     bi.pidlRoot = test_folder_pidl;
218 
219     /* Display dialog box and let callback click the buttons */
220     pidl = SHBrowseForFolderA(&bi);
221 
222     number_of_folders = get_number_of_folders(test_folder_path);
223     ok(number_of_folders == 1 || broken(number_of_folders == 0) /* W95, W98 */,
224         "Clicking \"Make New Folder\" button did not result in a new folder.\n");
225 
226     /* There should be a new folder foo inside the test folder */
227     strcpy(new_folder_path, test_folder_path);
228     strcat(new_folder_path, new_folder_name);
229     ok(does_folder_or_file_exist(new_folder_path)
230         || broken(!does_folder_or_file_exist(new_folder_path)) /* W95, W98, XP, W2K3 */,
231         "The new folder did not get the name %s\n", new_folder_name);
232 
233     /* Dialog should return a pidl pointing to the new folder */
234     ok(SHGetPathFromIDListA(pidl, new_folder_pidl_path),
235         "SHGetPathFromIDList failed for new folder.\n");
236     ok(strcmp(new_folder_path, new_folder_pidl_path) == 0
237         || broken(strcmp(new_folder_path, new_folder_pidl_path) != 0) /* earlier than Vista */,
238         "SHBrowseForFolder did not return the pidl for the new folder. "
239         "Expected '%s' got '%s'\n", new_folder_path, new_folder_pidl_path);
240 
241     /* Remove test folder and any subfolders created in this test */
242     shfileop.hwnd = NULL;
243     shfileop.wFunc = FO_DELETE;
244     /* Path must be double NULL terminated */
245     test_folder_path[strlen(test_folder_path)+1] = '\0';
246     shfileop.pFrom = test_folder_path;
247     shfileop.pTo = NULL;
248     shfileop.fFlags = FOF_NOCONFIRMATION|FOF_NOERRORUI|FOF_SILENT;
249     SHFileOperationA(&shfileop);
250 
251     CoTaskMemFree(pidl);
252     CoTaskMemFree(test_folder_pidl);
253     test_folder_object->lpVtbl->Release(test_folder_object);
254 
255     CoUninitialize();
256 }
257 
258 
259 /*
260  * Callback used by test_selection.
261  */
262 static int CALLBACK selection_callback(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
263 {
264     DWORD ret;
265 
266     switch (uMsg)
267     {
268     case BFFM_INITIALIZED:
269         /* test with zero values */
270         ret = SendMessageA(hwnd, BFFM_SETSELECTIONA, 0, 0);
271         ok(!ret, "SendMessage returned: %u\n", ret);
272         ret = SendMessageA(hwnd, BFFM_SETSELECTIONW, 0, 0);
273         ok(!ret, "SendMessage returned: %u\n", ret);
274 
275         ret = SendMessageA(hwnd, BFFM_SETSELECTIONA, 1, 0);
276         ok(!ret, "SendMessage returned: %u\n", ret);
277 
278         if(0)
279         {
280             /* Crashes on NT4 */
281             ret = SendMessageA(hwnd, BFFM_SETSELECTIONW, 1, 0);
282             ok(!ret, "SendMessage returned: %u\n", ret);
283         }
284 
285         ret = SendMessageA(hwnd, BFFM_SETSELECTIONA, 0, (LPARAM)selected_folder_pidl);
286         ok(!ret, "SendMessage returned: %u\n", ret);
287         ret = SendMessageW(hwnd, BFFM_SETSELECTIONW, 0, (LPARAM)selected_folder_pidl);
288         ok(!ret, "SendMessage returned: %u\n", ret);
289 
290         ret = SendMessageA(hwnd, BFFM_SETSELECTIONA, 1, (LPARAM)selected_folder_pidl);
291         ok(!ret, "SendMessage returned: %u\n", ret);
292         ret = SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, (LPARAM)selected_folder_pidl);
293         ok(!ret, "SendMessage returned: %u\n", ret);
294 
295         ret = SendMessageA(hwnd, BFFM_SETSELECTIONA, 1, (LPARAM)new_folder_name);
296         ok(!ret, "SendMessage returned: %u\n", ret);
297         ret = SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, (LPARAM)new_folder_name);
298         ok(!ret, "SendMessage returned: %u\n", ret);
299 
300         SendMessageA(hwnd, WM_COMMAND, IDOK, 0);
301         return 1;
302     default:
303         return 0;
304     }
305 }
306 
307 static void test_selection(void)
308 {
309     HRESULT resCoInit, hr;
310     BROWSEINFOA bi;
311     LPITEMIDLIST pidl = NULL;
312     IShellFolder *desktop_object;
313     WCHAR selected_folderW[MAX_PATH];
314     const CHAR title[] = "test_selection";
315 
316     resCoInit = CoInitialize(NULL);
317     if(!(resCoInit == S_OK || resCoInit == S_FALSE))
318     {
319         skip("COM could not be initialized %u\n", GetLastError());
320         return;
321     }
322 
323     if (!GetCurrentDirectoryW(MAX_PATH, selected_folderW))
324     {
325         skip("GetCurrentDirectoryW failed %u\n", GetLastError());
326     }
327 
328     /* Initialize browse info struct for SHBrowseForFolder */
329     bi.hwndOwner = NULL;
330     bi.pszDisplayName = NULL;
331     bi.lpszTitle = title;
332     bi.lpfn = selection_callback;
333 
334     hr = SHGetDesktopFolder(&desktop_object);
335     ok (SUCCEEDED(hr), "SHGetDesktopFolder failed with hr 0x%08x\n", hr);
336     if (FAILED(hr)) {
337         skip("SHGetDesktopFolder failed - skipping\n");
338         return;
339     }
340     desktop_object->lpVtbl->ParseDisplayName(desktop_object, NULL, NULL,
341         selected_folderW, 0UL, &selected_folder_pidl, 0UL);
342     bi.pidlRoot = selected_folder_pidl;
343 
344     /* test without flags */
345     bi.ulFlags = 0;
346     pidl = SHBrowseForFolderA(&bi);
347     CoTaskMemFree(pidl);
348 
349     /* test with flag */
350     bi.ulFlags = BIF_NEWDIALOGSTYLE;
351     pidl = SHBrowseForFolderA(&bi);
352     CoTaskMemFree(pidl);
353 
354     IShellFolder_Release(desktop_object);
355 
356     CoUninitialize();
357 }
358 
359 START_TEST(brsfolder)
360 {
361     test_click_make_new_folder_button();
362     test_selection();
363 }
364