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