1 // Written by Christopher E. Miller
2 // See the included license.txt for copyright and license details.
3 
4 
5 ///
6 module dfl.filedialog;
7 
8 private import dfl.internal.dlib;
9 
10 private import dfl.control, dfl.internal.winapi, dfl.base, dfl.drawing;
11 private import dfl.application, dfl.commondialog, dfl.event, dfl.internal.utf;
12 
13 
14 ///
15 abstract class FileDialog: CommonDialog // docmain
16 {
17 	private this()
18 	{
19 		Application.ppin(cast(void*)this);
20 		
21 		ofn.lStructSize = ofn.sizeof;
22 		ofn.lCustData = cast(typeof(ofn.lCustData))cast(void*)this;
23 		ofn.Flags = INIT_FLAGS;
24 		ofn.nFilterIndex = INIT_FILTER_INDEX;
25 		initInstance();
26 		ofn.lpfnHook = cast(typeof(ofn.lpfnHook))&ofnHookProc;
27 	}
28 	
29 	
30 	override DialogResult showDialog()
31 	{
32 		return runDialog(GetActiveWindow()) ?
33 			DialogResult.OK : DialogResult.CANCEL;
34 	}
35 	
36 	override DialogResult showDialog(IWindow owner)
37 	{
38 		return runDialog(owner ? owner.handle : GetActiveWindow()) ?
39 			DialogResult.OK : DialogResult.CANCEL;
40 	}
41 	
42 	
43 	override void reset()
44 	{
45 		ofn.Flags = INIT_FLAGS;
46 		ofn.lpstrFilter = null;
47 		ofn.nFilterIndex = INIT_FILTER_INDEX;
48 		ofn.lpstrDefExt = null;
49 		_defext = null;
50 		_fileNames = null;
51 		needRebuildFiles = false;
52 		_filter = null;
53 		ofn.lpstrInitialDir = null;
54 		_initDir = null;
55 		ofn.lpstrTitle = null;
56 		_title = null;
57 		initInstance();
58 	}
59 	
60 	
61 	private void initInstance()
62 	{
63 		//ofn.hInstance = ?; // Should this be initialized?
64 	}
65 	
66 	
67 	/+
68 	final @property void addExtension(bool byes) // setter
69 	{
70 		addext = byes;
71 	}
72 	
73 	
74 	final @property bool addExtension() // getter
75 	{
76 		return addext;
77 	}
78 	+/
79 	
80 	
81 	///
82 	@property void checkFileExists(bool byes) // setter
83 	{
84 		if(byes)
85 			ofn.Flags |= OFN_FILEMUSTEXIST;
86 		else
87 			ofn.Flags &= ~OFN_FILEMUSTEXIST;
88 	}
89 	
90 	/// ditto
91 	@property bool checkFileExists() // getter
92 	{
93 		return (ofn.Flags & OFN_FILEMUSTEXIST) != 0;
94 	}
95 	
96 	
97 	///
98 	final @property void checkPathExists(bool byes) // setter
99 	{
100 		if(byes)
101 			ofn.Flags |= OFN_PATHMUSTEXIST;
102 		else
103 			ofn.Flags &= ~OFN_PATHMUSTEXIST;
104 	}
105 	
106 	/// ditto
107 	final @property bool checkPathExists() // getter
108 	{
109 		return (ofn.Flags & OFN_PATHMUSTEXIST) != 0;
110 	}
111 	
112 	
113 	///
114 	final @property void defaultExt(Dstring ext) // setter
115 	{
116 		if(!ext.length)
117 		{
118 			ofn.lpstrDefExt = null;
119 			_defext = null;
120 		}
121 		else
122 		{
123 			if(ext.length && ext[0] == '.')
124 				ext = ext[1 .. ext.length];
125 			
126 			if(dfl.internal.utf.useUnicode)
127 			{
128 				ofnw.lpstrDefExt = dfl.internal.utf.toUnicodez(ext);
129 			}
130 			else
131 			{
132 				ofna.lpstrDefExt = dfl.internal.utf.toAnsiz(ext);
133 			}
134 			_defext = ext;
135 		}
136 	}
137 	
138 	/// ditto
139 	final @property Dstring defaultExt() // getter
140 	{
141 		return _defext;
142 	}
143 	
144 	
145 	///
146 	final @property void dereferenceLinks(bool byes) // setter
147 	{
148 		if(byes)
149 			ofn.Flags &= ~OFN_NODEREFERENCELINKS;
150 		else
151 			ofn.Flags |= OFN_NODEREFERENCELINKS;
152 	}
153 	
154 	/// ditto
155 	final @property bool dereferenceLinks() // getter
156 	{
157 		return (ofn.Flags & OFN_NODEREFERENCELINKS) == 0;
158 	}
159 	
160 	
161 	///
162 	final @property void fileName(Dstring fn) // setter
163 	{
164 		// TODO: check if correct implementation.
165 		
166 		if(fn.length > MAX_PATH)
167 			throw new DflException("Invalid file name");
168 		
169 		if(fileNames.length)
170 		{
171 			_fileNames = (&fn)[0 .. 1] ~ _fileNames[1 .. _fileNames.length];
172 		}
173 		else
174 		{
175 			_fileNames = new Dstring[1];
176 			_fileNames[0] = fn;
177 		}
178 	}
179 	
180 	/// ditto
181 	final @property Dstring fileName() // getter
182 	{
183 		if(fileNames.length)
184 			return fileNames[0];
185 		return null;
186 	}
187 	
188 	
189 	///
190 	final @property Dstring[] fileNames() // getter
191 	{
192 		if(needRebuildFiles)
193 			populateFiles();
194 		
195 		return _fileNames;
196 	}
197 	
198 	
199 	///
200 	// The format string is like "Text files (*.txt)|*.txt|All files (*.*)|*.*".
201 	final @property void filter(Dstring filterString) // setter
202 	{
203 		if(!filterString.length)
204 		{
205 			ofn.lpstrFilter = null;
206 			_filter = null;
207 		}
208 		else
209 		{
210 			struct _Str
211 			{
212 				union
213 				{
214 					wchar[] sw;
215 					char[] sa;
216 				}
217 			}
218 			_Str str;
219 			
220 			size_t i, starti;
221 			size_t nitems = 0;
222 			
223 			if(dfl.internal.utf.useUnicode)
224 			{
225 				str.sw = new wchar[filterString.length + 2];
226 				str.sw = str.sw[0 .. 0];
227 			}
228 			else
229 			{
230 				str.sa = new char[filterString.length + 2];
231 				str.sa = str.sa[0 .. 0];
232 			}
233 			
234 			
235 			for(i = starti = 0; i != filterString.length; i++)
236 			{
237 				switch(filterString[i])
238 				{
239 					case '|':
240 						if(starti == i)
241 							goto bad_filter;
242 						
243 						if(dfl.internal.utf.useUnicode)
244 						{
245 							str.sw ~= dfl.internal.utf.toUnicode(filterString[starti .. i]);
246 							str.sw ~= "\0"w;
247 						}
248 						else
249 						{
250 							str.sa ~= dfl.internal.utf.unsafeAnsi(filterString[starti .. i]);
251 							str.sa ~= "\0";
252 						}
253 						
254 						starti = i + 1;
255 						nitems++;
256 						break;
257 					
258 					case 0:
259 					case '\r', '\n':
260 						goto bad_filter;
261 					
262 					default:
263 				}
264 			}
265 			if(starti == i || !(nitems % 2))
266 				goto bad_filter;
267 			if(dfl.internal.utf.useUnicode)
268 			{
269 				str.sw ~=dfl.internal.utf.toUnicode(filterString[starti .. i]);
270 				str.sw ~= "\0\0"w;
271 				
272 				ofnw.lpstrFilter = str.sw.ptr;
273 			}
274 			else
275 			{
276 				str.sa ~= dfl.internal.utf.unsafeAnsi(filterString[starti .. i]);
277 				str.sa ~= "\0\0";
278 				
279 				ofna.lpstrFilter = str.sa.ptr;
280 			}
281 			
282 			_filter = filterString;
283 			return;
284 			
285 			bad_filter:
286 			throw new DflException("Invalid file filter string");
287 		}
288 	}
289 	
290 	/// ditto
291 	final @property Dstring filter() // getter
292 	{
293 		return _filter;
294 	}
295 	
296 	
297 	///
298 	// Note: index is 1-based.
299 	final @property void filterIndex(int index) // setter
300 	{
301 		ofn.nFilterIndex = (index > 0) ? index : 1;
302 	}
303 	
304 	/// ditto
305 	final @property int filterIndex() // getter
306 	{
307 		return ofn.nFilterIndex;
308 	}
309 	
310 	
311 	///
312 	final @property void initialDirectory(Dstring dir) // setter
313 	{
314 		if(!dir.length)
315 		{
316 			ofn.lpstrInitialDir = null;
317 			_initDir = null;
318 		}
319 		else
320 		{
321 			if(dfl.internal.utf.useUnicode)
322 			{
323 				ofnw.lpstrInitialDir = dfl.internal.utf.toUnicodez(dir);
324 			}
325 			else
326 			{
327 				ofna.lpstrInitialDir = dfl.internal.utf.toAnsiz(dir);
328 			}
329 			_initDir = dir;
330 		}
331 	}
332 	
333 	/// ditto
334 	final @property Dstring initialDirectory() // getter
335 	{
336 		return _initDir;
337 	}
338 	
339 	
340 	// Should be instance(), but conflicts with D's old keyword.
341 	
342 	///
343 	protected @property void inst(HINSTANCE hinst) // setter
344 	{
345 		ofn.hInstance = hinst;
346 	}
347 	
348 	/// ditto
349 	protected @property HINSTANCE inst() // getter
350 	{
351 		return ofn.hInstance;
352 	}
353 	
354 	
355 	///
356 	protected @property DWORD options() // getter
357 	{
358 		return ofn.Flags;
359 	}
360 	
361 	
362 	///
363 	final @property void restoreDirectory(bool byes) // setter
364 	{
365 		if(byes)
366 			ofn.Flags |= OFN_NOCHANGEDIR;
367 		else
368 			ofn.Flags &= ~OFN_NOCHANGEDIR;
369 	}
370 	
371 	/// ditto
372 	final @property bool restoreDirectory() // getter
373 	{
374 		return (ofn.Flags & OFN_NOCHANGEDIR) != 0;
375 	}
376 	
377 	
378 	///
379 	final @property void showHelp(bool byes) // setter
380 	{
381 		if(byes)
382 			ofn.Flags |= OFN_SHOWHELP;
383 		else
384 			ofn.Flags &= ~OFN_SHOWHELP;
385 	}
386 	
387 	/// ditto
388 	final @property bool showHelp() // getter
389 	{
390 		return (ofn.Flags & OFN_SHOWHELP) != 0;
391 	}
392 	
393 	
394 	///
395 	final @property void title(Dstring newTitle) // setter
396 	{
397 		if(!newTitle.length)
398 		{
399 			ofn.lpstrTitle = null;
400 			_title = null;
401 		}
402 		else
403 		{
404 			if(dfl.internal.utf.useUnicode)
405 			{
406 				ofnw.lpstrTitle = dfl.internal.utf.toUnicodez(newTitle);
407 			}
408 			else
409 			{
410 				ofna.lpstrTitle = dfl.internal.utf.toAnsiz(newTitle);
411 			}
412 			_title = newTitle;
413 		}
414 	}
415 	
416 	/// ditto
417 	final @property Dstring title() // getter
418 	{
419 		return _title;
420 	}
421 	
422 	
423 	///
424 	final @property void validateNames(bool byes) // setter
425 	{
426 		if(byes)
427 			ofn.Flags &= ~OFN_NOVALIDATE;
428 		else
429 			ofn.Flags |= OFN_NOVALIDATE;
430 	}
431 	
432 	/// ditto
433 	final @property bool validateNames() // getter
434 	{
435 		return(ofn.Flags & OFN_NOVALIDATE) == 0;
436 	}
437 	
438 	
439 	///
440 	Event!(FileDialog, CancelEventArgs) fileOk;
441 	
442 	
443 	protected:
444 	
445 	override bool runDialog(HWND owner)
446 	{
447 		assert(0);
448 	}
449 	
450 	
451 	///
452 	void onFileOk(CancelEventArgs ea)
453 	{
454 		fileOk(this, ea);
455 	}
456 	
457 	
458 	override LRESULT hookProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
459 	{
460 		switch(msg)
461 		{
462 			case WM_NOTIFY:
463 				{
464 					NMHDR* nmhdr;
465 					nmhdr = cast(NMHDR*)lparam;
466 					switch(nmhdr.code)
467 					{
468 						case CDN_FILEOK:
469 							{
470 								CancelEventArgs cea;
471 								cea = new CancelEventArgs;
472 								onFileOk(cea);
473 								if(cea.cancel)
474 								{
475 									SetWindowLongA(hwnd, DWL_MSGRESULT, 1);
476 									return 1;
477 								}
478 							}
479 							break;
480 						
481 						default:
482 							//cprintf("   nmhdr.code = %d/0x%X\n", nmhdr.code, nmhdr.code);
483 					}
484 				}
485 				break;
486 			
487 			default:
488 		}
489 		
490 		return super.hookProc(hwnd, msg, wparam, lparam);
491 	}
492 	
493 	
494 	private:
495 	union
496 	{
497 		OPENFILENAMEW ofnw;
498 		OPENFILENAMEA ofna;
499 		alias ofnw ofn;
500 		
501 		static assert(OPENFILENAMEW.sizeof == OPENFILENAMEA.sizeof);
502 		static assert(OPENFILENAMEW.Flags.offsetof == OPENFILENAMEA.Flags.offsetof);
503 	}
504 	Dstring[] _fileNames;
505 	Dstring _filter;
506 	Dstring _initDir;
507 	Dstring _defext;
508 	Dstring _title;
509 	//bool addext = true;
510 	bool needRebuildFiles = false;
511 	
512 	enum DWORD INIT_FLAGS = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY |
513 		OFN_ENABLEHOOK | OFN_ENABLESIZING;
514 	enum INIT_FILTER_INDEX = 0;
515 	enum FILE_BUF_LEN = 4096; // ? 12288 ? 12800 ?
516 	
517 	
518 	void beginOfn(HWND owner)
519 	{
520 		if(dfl.internal.utf.useUnicode)
521 		{
522 			auto buf = new wchar[(ofn.Flags & OFN_ALLOWMULTISELECT) ? FILE_BUF_LEN : MAX_PATH];
523 			buf[0] = 0;
524 			
525 			if(fileNames.length)
526 			{
527 				Dwstring ts;
528 				ts = dfl.internal.utf.toUnicode(_fileNames[0]);
529 				buf[0 .. ts.length] = ts[];
530 				buf[ts.length] = 0;
531 			}
532 			
533 			ofnw.nMaxFile =cast(uint)buf.length;
534 			ofnw.lpstrFile = buf.ptr;
535 		}
536 		else
537 		{
538 			auto buf = new char[(ofn.Flags & OFN_ALLOWMULTISELECT) ? FILE_BUF_LEN : MAX_PATH];
539 			buf[0] = 0;
540 			
541 			if(fileNames.length)
542 			{
543 				Dstring ts;
544 				ts = dfl.internal.utf.unsafeAnsi(_fileNames[0]);
545 				buf[0 .. ts.length] = ts[];
546 				buf[ts.length] = 0;
547 			}
548 			
549 			ofna.nMaxFile = cast(uint)buf.length;
550 			ofna.lpstrFile = buf.ptr;
551 		}
552 		
553 		ofn.hwndOwner = owner;
554 	}
555 	
556 	
557 	// Populate -_fileNames- from -ofn.lpstrFile-.
558 	void populateFiles()
559 	in
560 	{
561 		assert(ofn.lpstrFile !is null);
562 	}
563 	body
564 	{
565 		if(ofn.Flags & OFN_ALLOWMULTISELECT)
566 		{
567 			// Nonstandard reserve.
568 			_fileNames = new Dstring[4];
569 			_fileNames = _fileNames[0 .. 0];
570 			
571 			if(dfl.internal.utf.useUnicode)
572 			{
573 				wchar* startp, p;
574 				p = startp = ofnw.lpstrFile;
575 				for(;;)
576 				{
577 					if(!*p)
578 					{
579 						_fileNames ~= dfl.internal.utf.fromUnicode(startp, p - startp); // dup later.
580 						
581 						p++;
582 						if(!*p)
583 							break;
584 						
585 						startp = p;
586 						continue;
587 					}
588 					
589 					p++;
590 				}
591 			}
592 			else
593 			{
594 				char* startp, p;
595 				p = startp = ofna.lpstrFile;
596 				for(;;)
597 				{
598 					if(!*p)
599 					{
600 						_fileNames ~= dfl.internal.utf.fromAnsi(startp, p - startp); // dup later.
601 						
602 						p++;
603 						if(!*p)
604 							break;
605 						
606 						startp = p;
607 						continue;
608 					}
609 					
610 					p++;
611 				}
612 			}
613 			
614 			assert(_fileNames.length);
615 			if(_fileNames.length == 1)
616 			{
617 				//_fileNames[0] = _fileNames[0].dup;
618 				//_fileNames[0] = _fileNames[0].idup; // Needed in D2. Doesn't work in D1.
619 				_fileNames[0] = cast(Dstring)_fileNames[0].dup; // Needed in D2.
620 			}
621 			else
622 			{
623 				Dstring s;
624 				size_t i;
625 				s = _fileNames[0];
626 				
627 				// Not sure which of these 2 is better...
628 				/+
629 				for(i = 1; i != _fileNames.length; i++)
630 				{
631 					_fileNames[i - 1] = pathJoin(s, _fileNames[i]);
632 				}
633 				_fileNames = _fileNames[0 .. _fileNames.length - 1];
634 				+/
635 				for(i = 1; i != _fileNames.length; i++)
636 				{
637 					_fileNames[i] = pathJoin(s, _fileNames[i]);
638 				}
639 				_fileNames = _fileNames[1 .. _fileNames.length];
640 			}
641 		}
642 		else
643 		{
644 			_fileNames = new Dstring[1];
645 			if(dfl.internal.utf.useUnicode)
646 			{
647 				_fileNames[0] = dfl.internal.utf.fromUnicodez(ofnw.lpstrFile);
648 			}
649 			else
650 			{
651 				_fileNames[0] = dfl.internal.utf.fromAnsiz(ofna.lpstrFile);
652 			}
653 			
654 			/+
655 			if(addext && checkFileExists() && ofn.nFilterIndex)
656 			{
657 				if(!ofn.nFileExtension || ofn.nFileExtension == _fileNames[0].length)
658 				{
659 					Dstring s;
660 					typeof(ofn.nFilterIndex) onidx;
661 					int i;
662 					Dstring[] exts;
663 					
664 					s = _filter;
665 					onidx = ofn.nFilterIndex << 1;
666 					do
667 					{
668 						i = charFindInString(s, '|');
669 						if(i == -1)
670 							goto no_such_filter;
671 						
672 						s = s[i + 1 .. s.length];
673 						
674 						onidx--;
675 					}
676 					while(onidx != 1);
677 					
678 					i = charFindInString(s, '|');
679 					if(i != -1)
680 						s = s[0 .. i];
681 					
682 					exts = stringSplit(s, ";");
683 					foreach(Dstring ext; exts)
684 					{
685 						cprintf("sel ext:  %.*s\n", ext);
686 					}
687 					
688 					// ...
689 					
690 					no_such_filter: ;
691 				}
692 			}
693 			+/
694 		}
695 		
696 		needRebuildFiles = false;
697 	}
698 	
699 	
700 	// Call only if the dialog succeeded.
701 	void finishOfn()
702 	{
703 		if(needRebuildFiles)
704 			populateFiles();
705 		
706 		ofn.lpstrFile = null;
707 	}
708 	
709 	
710 	// Call only if dialog fail or cancel.
711 	void cancelOfn()
712 	{
713 		needRebuildFiles = false;
714 		
715 		ofn.lpstrFile = null;
716 		_fileNames = null;
717 	}
718 }
719 
720 
721 private extern(Windows) nothrow
722 {
723 	alias BOOL function(LPOPENFILENAMEW lpofn) GetOpenFileNameWProc;
724 	alias BOOL function(LPOPENFILENAMEW lpofn) GetSaveFileNameWProc;
725 }
726 
727 
728 ///
729 class OpenFileDialog: FileDialog // docmain
730 {
731 	this()
732 	{
733 		super();
734 		ofn.Flags |= OFN_FILEMUSTEXIST;
735 	}
736 	
737 	
738 	override void reset()
739 	{
740 		super.reset();
741 		ofn.Flags |= OFN_FILEMUSTEXIST;
742 	}
743 	
744 	
745 	///
746 	final @property void multiselect(bool byes) // setter
747 	{
748 		if(byes)
749 			ofn.Flags |= OFN_ALLOWMULTISELECT;
750 		else
751 			ofn.Flags &= ~OFN_ALLOWMULTISELECT;
752 	}
753 	
754 	/// ditto
755 	final @property bool multiselect() // getter
756 	{
757 		return (ofn.Flags & OFN_ALLOWMULTISELECT) != 0;
758 	}
759 	
760 	
761 	///
762 	final @property void readOnlyChecked(bool byes) // setter
763 	{
764 		if(byes)
765 			ofn.Flags |= OFN_READONLY;
766 		else
767 			ofn.Flags &= ~OFN_READONLY;
768 	}
769 	
770 	/// ditto
771 	final @property bool readOnlyChecked() // getter
772 	{
773 		return (ofn.Flags & OFN_READONLY) != 0;
774 	}
775 	
776 	
777 	///
778 	final @property void showReadOnly(bool byes) // setter
779 	{
780 		if(byes)
781 			ofn.Flags &= ~OFN_HIDEREADONLY;
782 		else
783 			ofn.Flags |= OFN_HIDEREADONLY;
784 	}
785 	
786 	/// ditto
787 	final @property bool showReadOnly() // getter
788 	{
789 		return (ofn.Flags & OFN_HIDEREADONLY) == 0;
790 	}
791 	
792 	
793 	private import std.stream; // TO-DO: remove this import; use dfl.internal.dlib.
794 	
795 	///
796 	final Stream openFile()
797 	{
798 		return new File(fileName(), FileMode.In);
799 	}
800 	
801 	
802 	protected:
803 	
804 	override bool runDialog(HWND owner)
805 	{
806 		if(!_runDialog(owner))
807 		{
808 			if(!CommDlgExtendedError())
809 				return false;
810 			_cantrun();
811 		}
812 		return true;
813 	}
814 	
815 	
816 	private BOOL _runDialog(HWND owner)
817 	{
818 		BOOL result = 0;
819 		
820 		beginOfn(owner);
821 		
822 		//synchronized(typeid(dfl.internal.utf.CurDirLockType))
823 		{
824 			if(dfl.internal.utf.useUnicode)
825 			{
826 				enum NAME = "GetOpenFileNameW";
827 				static GetOpenFileNameWProc proc = null;
828 				
829 				if(!proc)
830 				{
831 					proc = cast(GetOpenFileNameWProc)GetProcAddress(GetModuleHandleA("comdlg32.dll"), NAME.ptr);
832 					if(!proc)
833 						throw new Exception("Unable to load procedure " ~ NAME ~ "");
834 				}
835 				
836 				result = proc(&ofnw);
837 			}
838 			else
839 			{
840 				result = GetOpenFileNameA(&ofna);
841 			}
842 		}
843 		
844 		if(result)
845 		{
846 			finishOfn();
847 			return result;
848 		}
849 		
850 		cancelOfn();
851 		return result;
852 	}
853 }
854 
855 
856 ///
857 class SaveFileDialog: FileDialog // docmain
858 {
859 	this()
860 	{
861 		super();
862 		ofn.Flags |= OFN_OVERWRITEPROMPT;
863 	}
864 	
865 	
866 	override void reset()
867 	{
868 		super.reset();
869 		ofn.Flags |= OFN_OVERWRITEPROMPT;
870 	}
871 	
872 	
873 	///
874 	final @property void createPrompt(bool byes) // setter
875 	{
876 		if(byes)
877 			ofn.Flags |= OFN_CREATEPROMPT;
878 		else
879 			ofn.Flags &= ~OFN_CREATEPROMPT;
880 	}
881 	
882 	/// ditto
883 	final @property bool createPrompt() // getter
884 	{
885 		return (ofn.Flags & OFN_CREATEPROMPT) != 0;
886 	}
887 	
888 	
889 	///
890 	final @property void overwritePrompt(bool byes) // setter
891 	{
892 		if(byes)
893 			ofn.Flags |= OFN_OVERWRITEPROMPT;
894 		else
895 			ofn.Flags &= ~OFN_OVERWRITEPROMPT;
896 	}
897 	
898 	/// ditto
899 	final @property bool overwritePrompt() // getter
900 	{
901 		return (ofn.Flags & OFN_OVERWRITEPROMPT) != 0;
902 	}
903 	
904 	
905 	private import std.stream; // TO-DO: remove this import; use dfl.internal.dlib.
906 		
907 	///
908 	// Opens and creates with read and write access.
909 	// Warning: if file exists, it's truncated.
910 	final Stream openFile()
911 	{
912 		return new File(fileName(), FileMode.OutNew | FileMode.Out | FileMode.In);
913 	}
914 	
915 	
916 	protected:
917 	
918 	override bool runDialog(HWND owner)
919 	{
920 		beginOfn(owner);
921 		
922 		//synchronized(typeid(dfl.internal.utf.CurDirLockType))
923 		{
924 			if(dfl.internal.utf.useUnicode)
925 			{
926 				enum NAME = "GetSaveFileNameW";
927 				static GetSaveFileNameWProc proc = null;
928 				
929 				if(!proc)
930 				{
931 					proc = cast(GetSaveFileNameWProc)GetProcAddress(GetModuleHandleA("comdlg32.dll"), NAME.ptr);
932 					if(!proc)
933 						throw new Exception("Unable to load procedure " ~ NAME ~ "");
934 				}
935 				
936 				if(proc(&ofnw))
937 				{
938 					finishOfn();
939 					return true;
940 				}
941 			}
942 			else
943 			{
944 				if(GetSaveFileNameA(&ofna))
945 				{
946 					finishOfn();
947 					return true;
948 				}
949 			}
950 		}
951 		
952 		cancelOfn();
953 		return false;
954 	}
955 }
956 
957 
958 private extern(Windows) LRESULT ofnHookProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) nothrow
959 {
960 	alias dfl.internal.winapi.HANDLE HANDLE; // Otherwise, odd conflict with wine.
961 	
962 	enum PROP_STR = "DFL_FileDialog";
963 	FileDialog fd;
964 	LRESULT result = 0;
965 	
966 	try
967 	{
968 		if(msg == WM_INITDIALOG)
969 		{
970 			OPENFILENAMEA* ofn;
971 			ofn = cast(OPENFILENAMEA*)lparam;
972 			SetPropA(hwnd, PROP_STR.ptr, cast(HANDLE)ofn.lCustData);
973 			fd = cast(FileDialog)cast(void*)ofn.lCustData;
974 		}
975 		else
976 		{
977 			fd = cast(FileDialog)cast(void*)GetPropA(hwnd, PROP_STR.ptr);
978 		}
979 		
980 		//cprintf("hook msg(%d/0x%X) to obj %p\n", msg, msg, fd);
981 		if(fd)
982 		{
983 			fd.needRebuildFiles = true;
984 			result = fd.hookProc(hwnd, msg, wparam, lparam);
985 		}
986 	}
987 	catch(DThrowable e)
988 	{
989 		Application.onThreadException(e);
990 	}
991 	
992 	return result;
993 }
994