1 // Written by Christopher E. Miller 2 // See the included license.txt for copyright and license details. 3 4 5 6 module dfl.folderdialog; 7 8 private import dfl.internal.dlib, dfl.internal.clib; 9 10 private import dfl.commondialog, dfl.base, dfl.internal.winapi, dfl.internal.wincom; 11 private import dfl.internal.utf, dfl.application; 12 13 14 private extern(Windows) nothrow { 15 alias LPITEMIDLIST function(LPBROWSEINFOW lpbi) SHBrowseForFolderWProc; 16 alias BOOL function(LPCITEMIDLIST pidl, LPWSTR pszPath) SHGetPathFromIDListWProc; 17 } 18 19 20 21 class FolderBrowserDialog: CommonDialog { // docmain 22 this() { 23 // Flag BIF_NEWDIALOGSTYLE requires OleInitialize(). 24 //OleInitialize(null); 25 26 Application.ppin(cast(void*)this); 27 28 bi.ulFlags = INIT_FLAGS; 29 bi.lParam = cast(typeof(bi.lParam))cast(void*)this; 30 bi.lpfn = &fbdHookProc; 31 } 32 33 34 ~this() { 35 //OleUninitialize(); 36 } 37 38 39 override DialogResult showDialog() { 40 if(!runDialog(GetActiveWindow())) { 41 return DialogResult.CANCEL; 42 } 43 return DialogResult.OK; 44 } 45 46 47 override DialogResult showDialog(IWindow owner) { 48 if(!runDialog(owner ? owner.handle : GetActiveWindow())) { 49 return DialogResult.CANCEL; 50 } 51 return DialogResult.OK; 52 } 53 54 55 override void reset() { 56 bi.ulFlags = INIT_FLAGS; 57 _desc = null; 58 _selpath = null; 59 } 60 61 62 63 final @property void description(Dstring desc) { // setter 64 // lpszTitle 65 66 _desc = desc; 67 } 68 69 /// ditto 70 final @property Dstring description() { // getter 71 return _desc; 72 } 73 74 75 76 final @property void selectedPath(Dstring selpath) { // setter 77 // pszDisplayName 78 79 _selpath = selpath; 80 } 81 82 /// ditto 83 final @property Dstring selectedPath() { // getter 84 return _selpath; 85 } 86 87 88 // /// 89 // Currently only works for shell32.dll version 6.0+. 90 final @property void showNewFolderButton(bool byes) { // setter 91 // BIF_NONEWFOLDERBUTTON exists with shell 6.0+. 92 // Might need to enum child windows looking for window title 93 // "&New Folder" and hide it, then shift "OK" and "Cancel" over. 94 95 if(byes) { 96 bi.ulFlags &= ~BIF_NONEWFOLDERBUTTON; 97 } else { 98 bi.ulFlags |= BIF_NONEWFOLDERBUTTON; 99 } 100 } 101 102 // /// ditto 103 final @property bool showNewFolderButton() { // getter 104 return (bi.ulFlags & BIF_NONEWFOLDERBUTTON) == 0; 105 } 106 107 108 // /// 109 // Currently only works for shell32.dll version 6.0+. 110 final @property void showNewStyleDialog(bool byes) { // setter 111 // BIF_NONEWFOLDERBUTTON exists with shell 6.0+. 112 // Might need to enum child windows looking for window title 113 // "&New Folder" and hide it, then shift "OK" and "Cancel" over. 114 115 if(byes) { 116 bi.ulFlags |= BIF_NEWDIALOGSTYLE; 117 } else { 118 bi.ulFlags &= ~BIF_NEWDIALOGSTYLE; 119 } 120 } 121 122 // /// ditto 123 final @property bool showNewStyleDialog() { // getter 124 return (bi.ulFlags & BIF_NEWDIALOGSTYLE) != 0; 125 } 126 127 128 // /// 129 // Currently only works for shell32.dll version 6.0+. 130 final @property void showTextBox(bool byes) { // setter 131 // BIF_NONEWFOLDERBUTTON exists with shell 6.0+. 132 // Might need to enum child windows looking for window title 133 // "&New Folder" and hide it, then shift "OK" and "Cancel" over. 134 135 if(byes) { 136 bi.ulFlags |= BIF_EDITBOX; 137 } else { 138 bi.ulFlags &= ~BIF_EDITBOX; 139 } 140 } 141 142 // /// ditto 143 final @property bool showTextBox() { // getter 144 return (bi.ulFlags & BIF_EDITBOX) != 0; 145 } 146 147 148 private void _errPathTooLong() { 149 throw new DflException("Path name is too long"); 150 } 151 152 153 private void _errNoGetPath() { 154 throw new DflException("Unable to obtain path"); 155 } 156 157 158 private void _errNoShMalloc() { 159 throw new DflException("Unable to get shell memory allocator"); 160 } 161 162 163 protected override bool runDialog(HWND owner) { 164 IMalloc shmalloc; 165 166 bi.hwndOwner = owner; 167 168 // Using size of wchar so that the buffer works for ansi and unicode. 169 //void* pdescz = dfl.internal.clib.alloca(wchar.sizeof * MAX_PATH); 170 //if(!pdescz) 171 // throw new DflException("Out of memory"); // Stack overflow ? 172 //wchar[MAX_PATH] pdescz = void; 173 wchar[MAX_PATH] pdescz; // Initialize because SHBrowseForFolder() is modal. 174 175 if(dfl.internal.utf.useUnicode) { 176 enum BROWSE_NAME = "SHBrowseForFolderW"; 177 enum PATH_NAME = "SHGetPathFromIDListW"; 178 static SHBrowseForFolderWProc browseproc = null; 179 static SHGetPathFromIDListWProc pathproc = null; 180 181 if(!browseproc) { 182 HMODULE hmod; 183 hmod = GetModuleHandleA("shell32.dll"); 184 185 browseproc = cast(SHBrowseForFolderWProc)GetProcAddress(hmod, BROWSE_NAME.ptr); 186 if(!browseproc) { 187 throw new Exception("Unable to load procedure " ~ BROWSE_NAME); 188 } 189 190 pathproc = cast(SHGetPathFromIDListWProc)GetProcAddress(hmod, PATH_NAME.ptr); 191 if(!pathproc) { 192 throw new Exception("Unable to load procedure " ~ PATH_NAME); 193 } 194 } 195 196 biw.lpszTitle = dfl.internal.utf.toUnicodez(_desc); 197 198 biw.pszDisplayName = cast(wchar*)pdescz; 199 if(_desc.length) { 200 Dwstring tmp; 201 tmp = dfl.internal.utf.toUnicode(_desc); 202 if(tmp.length >= MAX_PATH) { 203 _errPathTooLong(); 204 } 205 biw.pszDisplayName[0 .. tmp.length] = tmp[]; 206 biw.pszDisplayName[tmp.length] = 0; 207 } else { 208 biw.pszDisplayName[0] = 0; 209 } 210 211 // Show the dialog! 212 LPITEMIDLIST result; 213 result = browseproc(&biw); 214 215 if(!result) { 216 biw.lpszTitle = null; 217 return false; 218 } 219 220 if(NOERROR != SHGetMalloc(&shmalloc)) { 221 _errNoShMalloc(); 222 } 223 224 //wchar* wbuf = cast(wchar*)dfl.internal.clib.alloca(wchar.sizeof * MAX_PATH); 225 wchar[MAX_PATH] wbuf = void; 226 if(!pathproc(result, wbuf.ptr)) { 227 shmalloc.Free(result); 228 shmalloc.Release(); 229 _errNoGetPath(); 230 assert(0); 231 } 232 233 _selpath = dfl.internal.utf.fromUnicodez(wbuf.ptr); // Assumes fromUnicodez() copies. 234 235 shmalloc.Free(result); 236 shmalloc.Release(); 237 238 biw.lpszTitle = null; 239 } else { 240 bia.lpszTitle = dfl.internal.utf.toAnsiz(_desc); 241 242 bia.pszDisplayName = cast(char*)pdescz; 243 if(_desc.length) { 244 Dstring tmp; // ansi. 245 tmp = dfl.internal.utf.toAnsi(_desc); 246 if(tmp.length >= MAX_PATH) { 247 _errPathTooLong(); 248 } 249 bia.pszDisplayName[0 .. tmp.length] = tmp[]; 250 bia.pszDisplayName[tmp.length] = 0; 251 } else { 252 bia.pszDisplayName[0] = 0; 253 } 254 255 // Show the dialog! 256 LPITEMIDLIST result; 257 result = SHBrowseForFolderA(&bia); 258 259 if(!result) { 260 bia.lpszTitle = null; 261 return false; 262 } 263 264 if(NOERROR != SHGetMalloc(&shmalloc)) { 265 _errNoShMalloc(); 266 } 267 268 //char* abuf = cast(char*)dfl.internal.clib.alloca(char.sizeof * MAX_PATH); 269 char[MAX_PATH] abuf = void; 270 if(!SHGetPathFromIDListA(result, abuf.ptr)) { 271 shmalloc.Free(result); 272 shmalloc.Release(); 273 _errNoGetPath(); 274 assert(0); 275 } 276 277 _selpath = dfl.internal.utf.fromAnsiz(abuf.ptr); // Assumes fromAnsiz() copies. 278 279 shmalloc.Free(result); 280 shmalloc.Release(); 281 282 bia.lpszTitle = null; 283 } 284 285 return true; 286 } 287 288 289 protected: 290 291 /+ 292 override LRESULT hookProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { 293 switch(msg) { 294 case WM_NOTIFY: { 295 NMHDR* nmhdr; 296 nmhdr = cast(NMHDR*)lparam; 297 switch(nmhdr.code) { 298 /+ 299 case CDN_FILEOK: 300 break; 301 +/ 302 303 default: 304 } 305 } 306 break; 307 308 default: 309 } 310 311 return super.hookProc(hwnd, msg, wparam, lparam); 312 } 313 +/ 314 315 316 private: 317 318 union { 319 BROWSEINFOW biw; 320 BROWSEINFOA bia; 321 alias biw bi; 322 323 static assert(BROWSEINFOW.sizeof == BROWSEINFOA.sizeof); 324 static assert(BROWSEINFOW.ulFlags.offsetof == BROWSEINFOA.ulFlags.offsetof); 325 } 326 327 Dstring _desc; 328 Dstring _selpath; 329 330 331 enum UINT INIT_FLAGS = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE; 332 } 333 334 335 private: 336 337 private extern(Windows) int fbdHookProc(HWND hwnd, UINT msg, LPARAM lparam, LPARAM lpData) nothrow { 338 FolderBrowserDialog fd; 339 int result = 0; 340 341 try 342 { 343 fd = cast(FolderBrowserDialog)cast(void*)lpData; 344 if(fd) { 345 Dstring s; 346 switch(msg) { 347 case BFFM_INITIALIZED: 348 s = fd.selectedPath; 349 if(s.length) { 350 if(dfl.internal.utf.useUnicode) { 351 SendMessageA(hwnd, BFFM_SETSELECTIONW, TRUE, cast(LPARAM)dfl.internal.utf.toUnicodez(s)); 352 } else { 353 SendMessageA(hwnd, BFFM_SETSELECTIONA, TRUE, cast(LPARAM)dfl.internal.utf.toAnsiz(s)); 354 } 355 } 356 break; 357 358 default: 359 } 360 } 361 } catch(DThrowable e) { 362 Application.onThreadException(e); 363 } 364 365 return result; 366 } 367