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