1 // Written by Christopher E. Miller
2 // See the included license.txt for copyright and license details.
3 
4 module dfl.fontdialog;
5 
6 import core.sys.windows.windows;
7 
8 import dfl.application;
9 import dfl.base;
10 import dfl.commondialog;
11 import dfl.control;
12 import dfl.drawing;
13 import dfl.event;
14 import dfl.exception;
15 import dfl.internal.dlib;
16 import dfl.internal.utf;
17 
18 private extern (Windows) nothrow {
19    alias ChooseFontWProc = BOOL function(LPCHOOSEFONTW lpcf);
20 }
21 
22 class FontDialog : CommonDialog {
23    this() {
24       Application.ppin(cast(void*) this);
25 
26       cf.lStructSize = cf.sizeof;
27       cf.Flags = INIT_FLAGS;
28       cf.lpLogFont = cast(typeof(cf.lpLogFont))&lfw;
29       cf.lCustData = cast(typeof(cf.lCustData)) cast(void*) this;
30       cf.lpfnHook = &fondHookProc;
31       cf.rgbColors = 0;
32    }
33 
34    override void reset() {
35       _fon = null;
36       cf.Flags = INIT_FLAGS;
37       cf.rgbColors = 0;
38       cf.nSizeMin = 0;
39       cf.nSizeMax = 0;
40    }
41 
42    final @property void allowSimulations(bool byes) {
43       if (byes) {
44          cf.Flags &= ~CF_NOSIMULATIONS;
45       } else {
46          cf.Flags |= CF_NOSIMULATIONS;
47       }
48    }
49 
50    final @property bool allowSimulations() {
51       if (cf.Flags & CF_NOSIMULATIONS) {
52          return false;
53       }
54       return true;
55    }
56 
57    final @property void allowVectorFonts(bool byes) {
58       if (byes) {
59          cf.Flags &= ~CF_NOVECTORFONTS;
60       } else {
61          cf.Flags |= CF_NOVECTORFONTS;
62       }
63    }
64 
65    final bool allowVectorFonts() {
66       if (cf.Flags & CF_NOVECTORFONTS) {
67          return false;
68       }
69       return true;
70    }
71 
72    final @property void allowVerticalFonts(bool byes) {
73       if (byes) {
74          cf.Flags &= ~CF_NOVERTFONTS;
75       } else {
76          cf.Flags |= CF_NOVERTFONTS;
77       }
78    }
79 
80    final @property bool allowVerticalFonts() {
81       if (cf.Flags & CF_NOVERTFONTS) {
82          return false;
83       }
84       return true;
85    }
86 
87    final @property void color(Color c) {
88       cf.rgbColors = c.toRgb();
89    }
90 
91    final @property Color color() {
92       return Color.fromRgb(cf.rgbColors);
93    }
94 
95    final @property void fixedPitchOnly(bool byes) {
96       if (byes) {
97          cf.Flags |= CF_FIXEDPITCHONLY;
98       } else {
99          cf.Flags &= ~CF_FIXEDPITCHONLY;
100       }
101    }
102 
103    final @property bool fixedPitchOnly() {
104       if (cf.Flags & CF_FIXEDPITCHONLY) {
105          return true;
106       }
107       return false;
108    }
109 
110    final @property void font(Font f) {
111       _fon = f;
112    }
113 
114    final @property Font font() {
115       if (!_fon) {
116          _fon = Control.defaultFont; // ?
117       }
118       return _fon;
119    }
120 
121    final @property void fontMustExist(bool byes) {
122       if (byes) {
123          cf.Flags |= CF_FORCEFONTEXIST;
124       } else {
125          cf.Flags &= ~CF_FORCEFONTEXIST;
126       }
127    }
128 
129    final @property bool fontMustExist() {
130       if (cf.Flags & CF_FORCEFONTEXIST) {
131          return true;
132       }
133       return false;
134    }
135 
136    final @property void maxSize(int max) {
137       if (max > 0) {
138          if (max > cf.nSizeMin) {
139             cf.nSizeMax = max;
140          }
141          cf.Flags |= CF_LIMITSIZE;
142       } else {
143          cf.Flags &= ~CF_LIMITSIZE;
144          cf.nSizeMax = 0;
145          cf.nSizeMin = 0;
146       }
147    }
148 
149    final @property int maxSize() {
150       if (cf.Flags & CF_LIMITSIZE) {
151          return cf.nSizeMax;
152       }
153       return 0;
154    }
155 
156    final @property void minSize(int min) {
157       if (min > cf.nSizeMax) {
158          cf.nSizeMax = min;
159       }
160       cf.nSizeMin = min;
161       cf.Flags |= CF_LIMITSIZE;
162    }
163 
164    final @property int minSize() {
165       if (cf.Flags & CF_LIMITSIZE) {
166          return cf.nSizeMin;
167       }
168       return 0;
169    }
170 
171    final @property void scriptsOnly(bool byes) {
172       if (byes) {
173          cf.Flags |= CF_SCRIPTSONLY;
174       } else {
175          cf.Flags &= ~CF_SCRIPTSONLY;
176       }
177    }
178 
179    final @property bool scriptsOnly() {
180       if (cf.Flags & CF_SCRIPTSONLY) {
181          return true;
182       }
183       return false;
184    }
185 
186    final @property void showApply(bool byes) {
187       if (byes) {
188          cf.Flags |= CF_APPLY;
189       } else {
190          cf.Flags &= ~CF_APPLY;
191       }
192    }
193 
194    final @property bool showApply() {
195       if (cf.Flags & CF_APPLY) {
196          return true;
197       }
198       return false;
199    }
200 
201    final @property void showHelp(bool byes) {
202       if (byes) {
203          cf.Flags |= CF_SHOWHELP;
204       } else {
205          cf.Flags &= ~CF_SHOWHELP;
206       }
207    }
208 
209    final @property bool showHelp() {
210       if (cf.Flags & CF_SHOWHELP) {
211          return true;
212       }
213       return false;
214    }
215 
216    final @property void showEffects(bool byes) {
217       if (byes) {
218          cf.Flags |= CF_EFFECTS;
219       } else {
220          cf.Flags &= ~CF_EFFECTS;
221       }
222    }
223 
224    final @property bool showEffects() {
225       if (cf.Flags & CF_EFFECTS) {
226          return true;
227       }
228       return false;
229    }
230 
231    override DialogResult showDialog() {
232       return runDialog(GetActiveWindow()) ? DialogResult.OK : DialogResult.CANCEL;
233    }
234 
235    override DialogResult showDialog(IWindow owner) {
236       return runDialog(owner ? owner.handle : GetActiveWindow()) ? DialogResult.OK
237          : DialogResult.CANCEL;
238    }
239 
240    EventHandler apply;
241 
242    protected override LRESULT hookProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
243       switch (msg) {
244          case WM_COMMAND:
245             switch (LOWORD(wparam)) {
246                case CF_APPLY: // ?
247                   _update();
248                   onApply(EventArgs.empty);
249                   break;
250 
251                default:
252             }
253             break;
254 
255          default:
256       }
257 
258       return super.hookProc(hwnd, msg, wparam, lparam);
259    }
260 
261    protected override bool runDialog(HWND owner) {
262       if (!_runDialog(owner)) {
263          if (!CommDlgExtendedError()) {
264             return false;
265          }
266          _cantrun();
267       }
268       return true;
269    }
270 
271    private BOOL _runDialog(HWND owner) {
272       BOOL result = FALSE;
273 
274       cf.hwndOwner = owner;
275 
276       if (dfl.internal.utf.useUnicode) {
277          font._info(&lfw); // -font- gets default font if not set.
278 
279          enum NAME = "ChooseFontW";
280          static ChooseFontWProc proc = null;
281 
282          if (!proc) {
283             proc = cast(ChooseFontWProc) GetProcAddress(GetModuleHandleA("comdlg32.dll"),
284                   NAME.ptr);
285             if (!proc) {
286                throw new Exception("Unable to load procedure " ~ NAME ~ ".");
287             }
288          }
289 
290          result = proc(&cfw);
291       } else {
292          font._info(&lfa); // -font- gets default font if not set.
293 
294          result = ChooseFontA(&cfa);
295       }
296 
297       if (result) {
298          _update();
299          return result;
300       }
301       return FALSE;
302    }
303 
304    private void _update() {
305       LogFont lf;
306 
307       if (dfl.internal.utf.useUnicode) {
308          Font.LOGFONTWtoLogFont(lf, &lfw);
309       } else {
310          Font.LOGFONTAtoLogFont(lf, &lfa);
311       }
312 
313       _fon = new Font(Font._create(lf), true);
314    }
315 
316    protected void onApply(EventArgs ea) {
317       apply(this, ea);
318    }
319 
320    private:
321 
322    union {
323       CHOOSEFONTW cfw;
324       CHOOSEFONTA cfa;
325       alias cf = cfw;
326 
327       static assert(CHOOSEFONTW.sizeof == CHOOSEFONTA.sizeof);
328       static assert(CHOOSEFONTW.Flags.offsetof == CHOOSEFONTA.Flags.offsetof);
329       static assert(CHOOSEFONTW.nSizeMax.offsetof == CHOOSEFONTA.nSizeMax.offsetof);
330    }
331 
332    union {
333       LOGFONTW lfw;
334       LOGFONTA lfa;
335 
336       static assert(LOGFONTW.lfFaceName.offsetof == LOGFONTA.lfFaceName.offsetof);
337    }
338 
339    Font _fon;
340 
341    enum UINT INIT_FLAGS = CF_EFFECTS | CF_ENABLEHOOK | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS;
342 }
343 
344 // WM_CHOOSEFONT_SETFLAGS to update flags after dialog creation ... ?
345 
346 private extern (Windows) UINT fondHookProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) nothrow {
347    enum PROP_STR = "DFL_FontDialog";
348    FontDialog fd;
349    LRESULT result = 0;
350 
351    try {
352       if (msg == WM_INITDIALOG) {
353          CHOOSEFONTA* cf;
354          cf = cast(CHOOSEFONTA*) lparam;
355          SetPropA(hwnd, PROP_STR.ptr, cast(HANDLE) cf.lCustData);
356          fd = cast(FontDialog) cast(void*) cf.lCustData;
357       } else {
358          fd = cast(FontDialog) cast(void*) GetPropA(hwnd, PROP_STR.ptr);
359       }
360 
361       if (fd) {
362          result = fd.hookProc(hwnd, msg, wparam, lparam);
363       }
364    }
365    catch (DThrowable e) {
366       Application.onThreadException(e);
367    }
368 
369    return result;
370 }