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