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