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