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