1 // Written by Christopher E. Miller
2 // See the included license.txt for copyright and license details.
3 
4 module dfl.folderdialog;
5 
6 import std.utf: toUTFz, toUTF8;
7 import core.sys.windows.windows;
8 import core.sys.windows.shlobj; // Fot ITEMIDLIST
9 import core.sys.windows.objidl; // Fot IMALLC
10 
11 import dfl.application;
12 import dfl.base;
13 import dfl.commondialog;
14 import dfl.exception;
15 import dfl.internal.clib;
16 import dfl.internal.dlib;
17 import dfl.internal.utf;
18 
19 private extern (Windows) nothrow {
20    alias SHBrowseForFolderWProc = LPITEMIDLIST function(LPBROWSEINFOW lpbi);
21    alias SHGetPathFromIDListWProc = BOOL function(LPCITEMIDLIST pidl, LPWSTR pszPath);
22 }
23 
24 class FolderBrowserDialog : CommonDialog {
25    this() {
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    ~this() {
37       //OleUninitialize();
38    }
39 
40    override DialogResult showDialog() {
41       if (!runDialog(GetActiveWindow())) {
42          return DialogResult.CANCEL;
43       }
44       return DialogResult.OK;
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    override void reset() {
55       bi.ulFlags = INIT_FLAGS;
56       _desc = null;
57       _selpath = null;
58    }
59 
60    final @property void description(Dstring desc) {
61       // lpszTitle
62       _desc = desc;
63    }
64 
65    final @property Dstring description() {
66       return _desc;
67    }
68 
69    final @property void selectedPath(Dstring selpath) {
70       // pszDisplayName
71       _selpath = selpath;
72    }
73 
74    final @property Dstring selectedPath() {
75       return _selpath;
76    }
77 
78    // FIX:
79    /+
80    // Currently only works for shell32.dll version 6.0+.
81    final @property void showNewFolderButton(bool byes) {
82       // BIF_NONEWFOLDERBUTTON exists with shell 6.0+.
83       // Might need to enum child windows looking for window title
84       // "&New Folder" and hide it, then shift "OK" and "Cancel" over.
85 
86       if (byes) {
87          bi.ulFlags &= ~BIF_NONEWFOLDERBUTTON;
88       } else {
89          bi.ulFlags |= BIF_NONEWFOLDERBUTTON;
90       }
91    }
92    //
93    final @property bool showNewFolderButton() {
94       return (bi.ulFlags & BIF_NONEWFOLDERBUTTON) == 0;
95    }
96 +/
97 
98    // Currently only works for shell32.dll version 6.0+.
99    final @property void showNewStyleDialog(bool byes) {
100       // BIF_NONEWFOLDERBUTTON exists with shell 6.0+.
101       // Might need to enum child windows looking for window title
102       // "&New Folder" and hide it, then shift "OK" and "Cancel" over.
103 
104       if (byes) {
105          bi.ulFlags |= BIF_NEWDIALOGSTYLE;
106       } else {
107          bi.ulFlags &= ~BIF_NEWDIALOGSTYLE;
108       }
109    }
110 
111    final @property bool showNewStyleDialog() {
112       return (bi.ulFlags & BIF_NEWDIALOGSTYLE) != 0;
113    }
114 
115    // Currently only works for shell32.dll version 6.0+.
116    final @property void showTextBox(bool byes) {
117       // BIF_NONEWFOLDERBUTTON exists with shell 6.0+.
118       // Might need to enum child windows looking for window title
119       // "&New Folder" and hide it, then shift "OK" and "Cancel" over.
120 
121       if (byes) {
122          bi.ulFlags |= BIF_EDITBOX;
123       } else {
124          bi.ulFlags &= ~BIF_EDITBOX;
125       }
126    }
127 
128    final @property bool showTextBox() {
129       return (bi.ulFlags & BIF_EDITBOX) != 0;
130    }
131 
132    private void _errPathTooLong() {
133       throw new DflException("Path name is too long");
134    }
135 
136    private void _errNoGetPath() {
137       throw new DflException("Unable to obtain path");
138    }
139 
140    private void _errNoShMalloc() {
141       throw new DflException("Unable to get shell memory allocator");
142    }
143 
144    protected override bool runDialog(HWND owner) {
145 		wchar[MAX_PATH + 1] buffer;
146 		buffer[] = '\0';
147       BROWSEINFOW dlgStruct;
148 		dlgStruct.hwndOwner = owner;
149 		dlgStruct.pszDisplayName = buffer.ptr;
150 		dlgStruct.ulFlags = BIF_RETURNONLYFSDIRS;
151 		dlgStruct.lpszTitle = toUTFz!(wchar*)(_desc);
152 
153 		ITEMIDLIST* pidl = SHBrowseForFolderW(&dlgStruct);
154 
155 		if (pidl) {
156 			SHGetPathFromIDListW(pidl, buffer.ptr); //Get Full Path.
157 			this.selectedPath = toUTF8(buffer);
158 			return true;
159 		}
160 		return false;
161    }
162 
163 protected:
164 
165    /+
166          override LRESULT hookProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
167             switch(msg) {
168                case WM_NOTIFY: {
169                                   NMHDR* nmhdr;
170                                   nmhdr = cast(NMHDR*)lparam;
171                                   switch(nmhdr.code) {
172                                      /+
173                                      case CDN_FILEOK:
174                                         break;
175                                         +/
176 
177                                      default:
178                                   }
179                                }
180                                break;
181 
182                default:
183             }
184 
185             return super.hookProc(hwnd, msg, wparam, lparam);
186          }
187       +/
188 
189 private:
190 
191    union {
192       BROWSEINFOW biw;
193       BROWSEINFOA bia;
194       alias bi = biw;
195 
196       static assert(BROWSEINFOW.sizeof == BROWSEINFOA.sizeof);
197       static assert(BROWSEINFOW.ulFlags.offsetof == BROWSEINFOA.ulFlags.offsetof);
198    }
199 
200    Dstring _desc;
201    Dstring _selpath;
202 
203    enum UINT INIT_FLAGS = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
204 }
205 
206 private:
207 
208 // FIX:
209 private  /*extern (Windows)*/ int fbdHookProc(HWND hwnd, UINT msg, LPARAM lparam, LPARAM lpData) {
210    FolderBrowserDialog fd;
211    int result = 0;
212 
213    try {
214       fd = cast(FolderBrowserDialog) cast(void*) lpData;
215       if (fd) {
216          Dstring s;
217          switch (msg) {
218          case BFFM_INITIALIZED:
219             s = fd.selectedPath;
220             if (s.length) {
221                if (dfl.internal.utf.useUnicode) {
222                   SendMessageA(hwnd, BFFM_SETSELECTIONW, TRUE,
223                      cast(LPARAM) dfl.internal.utf.toUnicodez(s));
224                } else {
225                   SendMessageA(hwnd, BFFM_SETSELECTIONA, TRUE,
226                      cast(LPARAM) dfl.internal.utf.toAnsiz(s));
227                }
228             }
229             break;
230 
231          default:
232          }
233       }
234    }
235    catch (DThrowable e) {
236       Application.onThreadException(e);
237    }
238 
239    return result;
240 }