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