1########################################################################
2##
3## Copyright (C) 2019-2021 The Octave Project Developers
4##
5## See the file COPYRIGHT.md in the top-level directory of this
6## distribution or <https://octave.org/copyright/>.
7##
8## This file is part of Octave.
9##
10## Octave is free software: you can redistribute it and/or modify it
11## under the terms of the GNU General Public License as published by
12## the Free Software Foundation, either version 3 of the License, or
13## (at your option) any later version.
14##
15## Octave is distributed in the hope that it will be useful, but
16## WITHOUT ANY WARRANTY; without even the implied warranty of
17## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18## GNU General Public License for more details.
19##
20## You should have received a copy of the GNU General Public License
21## along with Octave; see the file COPYING.  If not, see
22## <https://www.gnu.org/licenses/>.
23##
24########################################################################
25
26## -*- texinfo -*-
27## @deftypefn  {} { } uisetfont ()
28## @deftypefnx {} { } uisetfont (@var{h})
29## @deftypefnx {} { } uisetfont (@var{fontstruct})
30## @deftypefnx {} { } uisetfont (@dots{}, @var{title})
31## @deftypefnx {} {@var{fontstruct} =} uisetfont (@dots{})
32## Open a font selection dialog.
33##
34## If the first argument is a handle to a text, axes, or uicontrol object,
35## pressing the OK button will change the font properties of the object.
36##
37## The first argument may also be a structure with fields @code{FontName},
38## @code{FontWeight}, @code{FontAngle}, @code{FontUnits}, and @code{FontSize},
39## indicating the initially selected font.
40##
41## The title of the dialog window can be changed using the last argument
42## @var{title}.
43##
44## If an output argument @var{fontstruct} is requested, the selected font
45## structure is returned.  Otherwise, the font information is displayed
46## onscreen.
47##
48## @seealso{text, axes, uicontrol}
49## @end deftypefn
50
51function varargout = uisetfont (varargin)
52
53  persistent sysfonts = build_fontstruct ();
54  persistent fontfields = {"FontName", "FontWeight", "FontAngle", ...
55                           "FontUnits", "FontSize"};
56
57  do_display = true;
58  h = [];
59  fontstruct = [];
60  ttl = "Font";
61  nargin = numel (varargin);
62
63  ## Input checking
64  if (nargin > 2)
65    print_usage ();
66  elseif (nargin == 0)
67    ## Do nothing
68  elseif (ishghandle (varargin{1}))
69
70    h = varargin{1};
71    typ = get (h, "type");
72    if (! any (strcmp (typ, {"axes", "text", "uicontrol"})))
73      error ("Octave:uisetfont:bad-object",
74             'uisetfont: unhandled object type "%s"', typ);
75    endif
76    nargin--;
77    varargin(1) = [];
78    do_display = false;
79
80  elseif (isstruct (varargin{1}))
81
82    fontstruct = varargin{1};
83    fields = fieldnames (fontstruct);
84    if (isempty (fields)
85        || ! all (cellfun (@(s) any (strcmp (s, fontfields)), fields)))
86      error ("Octave:uisetfont:bad-fontstruct",
87             "uisetfont: FONTSTRUCT structure must have fields %s",
88             strjoin (fontfields, ", "));
89    endif
90    nargin--;
91    varargin(1) = [];
92
93  endif
94
95  ## Trailing TITLE argument
96  if (nargin == 1)
97    ttl = varargin{1};
98    if (! (ischar (ttl) && isrow (ttl)))
99      error ("Octave:uisetfont:bad-title",
100             "uisetfont: TITLE must be a character vector");
101    endif
102  elseif (nargin == 2)
103    print_usage ();
104  endif
105
106  ## Populate fontstruct
107  persistent defstruct = [];
108  if (isempty (defstruct))
109    factory_fields = strcat ("factorytext", tolower (fontfields));
110    values = get (0, factory_fields);
111    defstruct = struct ([fontfields; values]{:});
112  endif
113
114  if (isempty (fontstruct))
115    if (isempty (h))
116      fontstruct = defstruct;
117    else
118      values = get (h, fontfields);
119      fontstruct = struct ([fontfields; values]{:});
120      names = {sysfonts.name};
121      if (! any (strcmpi (fontstruct.FontName, {sysfonts.name})))
122        warning ("Octave:uisefont:unknown-font",
123                 "uisetfont: unknown font %s", fontstruct.FontName);
124        fontstruct = defstruct;
125      endif
126    endif
127  endif
128
129  ## Sample string
130  persistent str = {"Portez ce vieux whisky";
131                    "au juge blond qui fume";
132                    "0123456789";
133                 ['\alpha, \beta, \gamma, \delta, \epsilon, \zeta, \eta, ' ...
134                  '\theta, \vartheta, \iota, \kappa, \lambda, \mu, \nu, '];
135                 ['\xi, \o, \pi, \varpi, \rho, \sigma, \varsigma, \tau, ' ...
136                  '\upsilon, \phi, \chi, \psi, \omega']};
137
138  ## Run the dialog
139  warning ("off", "Octave:missing-glyph", "local");
140  hf = run_fontdialog (sysfonts, h, fontstruct, ttl, str);
141
142  ## Now wait for a button to be pressed or the figure to be closed
143  uiwait (hf);
144
145  fontstruct = [];
146  if (ishghandle (hf))
147    fontstruct = getappdata (hf, "__uisetfont_struct__");
148    if (! isempty (h) && ! isempty (fontstruct))
149      set (h, fontstruct);
150    endif
151    close (hf);
152  endif
153
154  if (nargout > 0)
155    varargout{1} = fontstruct;
156  elseif (do_display && ! isempty (fontstruct))
157    disp (fontstruct);
158  endif
159
160endfunction
161
162function fonts = build_fontstruct ()
163
164  fontfiles = __get_system_fonts__ ();
165  families = unique ({fontfiles.family});
166
167  fonts(numel (families)+1) = struct ("name", "",
168                                      "has_regular", false,
169                                      "has_bold", false,
170                                      "has_italic", false,
171                                      "has_bold_italic", false);
172
173  fonts(1) = struct ("name", "*",
174                     "has_regular", true,
175                     "has_bold", true,
176                     "has_italic", true,
177                     "has_bold_italic", true);
178
179  for i = 1:numel (families)
180    ii = i + 1;
181    fonts(ii).name = families{i};
182    idx = strcmp ({fontfiles.family}, families{i});
183
184    isbold = strcmp ({fontfiles(idx).weight}, "bold");
185    isitalic = strcmp ({fontfiles(idx).angle}, "italic");
186
187    fonts(ii).has_regular = any (! isbold & ! isitalic);
188    fonts(ii).has_bold = any (isbold & ! isitalic);
189    fonts(ii).has_italic = any (isitalic & ! isbold);
190    fonts(ii).has_bold_italic = any (isbold & isitalic);
191  endfor
192
193endfunction
194
195function hf = run_fontdialog (sysfonts, hobj, fontstruct, ttl, str)
196
197  [hf, hok, hcancel, hp] = __ok_cancel_dlg__ (ttl,
198                                              "position", [200 200 400 400],
199                                              "windowstyle", "modal",
200                                              "resize", "on");
201
202  ## List controls
203  htmp = uipanel (hp, "title", "Font Name",
204                      "units", "normalized", "position", [0.04 0.35 0.5 0.6]);
205  hnames = uicontrol (htmp, "style", "listbox", "string", {sysfonts.name},
206                            "units", "normalized",
207                            "position", [0.02 0.01 0.96 .95]);
208
209  htmp = uipanel (hp, "title", "Style",
210                      "units", "normalized", "position", [0.56 0.35 0.25 0.6]);
211  hstyle = uicontrol (htmp, "style", "listbox",
212                      "units", "normalized",
213                      "position", [0.02 0.01 0.96 .95]);
214
215  htmp = uipanel (hp, "title", "Size",
216                      "units", "normalized", "position", [0.83 0.35 0.13 0.6]);
217  hsize = uicontrol (htmp, "style", "listbox",
218                           "string", arrayfun (@num2str, (8:30), "uni", false),
219                           "units", "normalized",
220                           "position", [0.02 0.01 0.96 .95]);
221
222  fcn = @(h) set (hstyle, "string",
223                          getstylestring (sysfonts(get (h, "value"))));
224  set (hnames, "callback", fcn);
225
226  ## Axes to display samples
227  htmp = uipanel (hp, "title", "Sample",
228                      "units", "normalized", "position", [0.04 0 0.92 0.33]);
229  hax = axes ("parent", htmp, "visible", "off", "units", "normalized",
230              "position", [0 0 1 0.95], "xlim", [0 1], "ylim", [0 1]);
231  ht = text (hax, 0.5, 0.5, str, "horizontalalignment", "center");
232
233  hlists = [hnames, hstyle, hsize];
234
235  ## Update text and uicontrol objects according to the input fontstruct
236  struct_to_lists (fontstruct, sysfonts, hlists);
237  set (ht, fontstruct);
238
239  ## Setup callbacks
240  set (hlists, "callback", {@cb_list_value_changed, hlists, ht, sysfonts});
241
242  set (hok, "callback", {@cb_button, hlists, "ok"});
243  set (hcancel, "callback", {@cb_button, hlists, "cancel"});
244
245  ## Give focus to the OK button
246  uicontrol (hok);
247
248endfunction
249
250function str = getstylestring (fontitem)
251
252  styles = {"Plain", "Bold", "Italic", "Bold Italic"};
253  if (fontitem.has_bold_italic)
254    str = styles;
255  elseif (fontitem.has_bold && fontitem.has_italic)
256    str = styles(1:3);
257  elseif (fontitem.has_bold)
258    str = styles(1:2);
259  elseif (fontitem.has_italic)
260    str = styles(1:2:3);
261  else
262    str = styles{1};
263  endif
264
265endfunction
266
267function fontstruct = struct_from_lists (hlists)
268
269  name = get (hlists(1), "string");
270  if (iscell (name))
271    name = name{get(hlists(1), "value")};
272  endif
273
274  szstr = get (hlists(3), "string");
275  sz = str2num (szstr{get(hlists(3), "value")});
276
277  fontstruct = struct ("FontName", name, "FontWeight", "normal",
278                       "FontAngle", "normal", "FontUnits", "points",
279                       "FontSize", sz);
280
281  style = get (hlists(2), "string");
282  if (iscell (style))
283    style = style{get(hlists(2), "value")};
284  endif
285
286  if (strcmp (style, "Bold"))
287    fontstruct.FontWeight = "bold";
288  elseif (strcmp (style, "Bold Italic"))
289    fontstruct.FontWeight = "bold";
290    fontstruct.FontAngle = "italic";
291  elseif (strcmp (style, "Italic"))
292    fontstruct.FontAngle = "italic";
293  endif
294
295endfunction
296
297function struct_to_lists (fontstruct, sysfonts, hlists)
298
299  ## Match font name
300  names = get (hlists(1), "string");
301  idx = find (strcmpi (fontstruct.FontName, names));
302  if (isempty (idx))
303    idx = 1;
304  endif
305  set (hlists(1), "value", idx);
306  styles = getstylestring (sysfonts(idx));
307  set (hlists(2), "string", styles);
308
309  ## Match style
310  style = "Plain";
311  if (strcmp (fontstruct.FontWeight, "bold")
312      && strcmp (fontstruct.FontAngle, "italic"))
313    style = "Bold Italic";
314  elseif (strcmp (fontstruct.FontWeight, "bold"))
315    style = "Bold";
316  elseif (strcmp (fontstruct.FontAngle, "italic"))
317    style = "Italic";
318  endif
319
320  idx = find (strcmpi (style, styles));
321  if (isempty (idx))
322    idx = 1;
323  endif
324  set (hlists(2), "value", idx);
325
326  ## Match size
327  szs = (8:30);
328  idx = find (round (fontstruct.FontSize) == szs);
329  if (isempty (idx))
330    idx = 1;
331  endif
332  set (hlists(3), "value", idx);
333
334endfunction
335
336function cb_button (h, evt, hlists, role)
337
338  fontstruct = [];
339  if (strcmp (role, "ok"))
340    fontstruct = struct_from_lists (hlists);
341  endif
342
343  setappdata (gcbf (), "__uisetfont_struct__", fontstruct);
344  uiresume (gcbf ());
345
346endfunction
347
348function cb_list_value_changed (h, evt, hlists, htext, sysfonts)
349
350  if (h == hlists(1))
351    set (hlists(2), "string", getstylestring (sysfonts(get (h, "value"))),
352                    "value", 1);
353  endif
354  fontstruct = struct_from_lists (hlists);
355  set (htext, fontstruct);
356
357endfunction
358
359
360## Test input validation
361%!testif HAVE_FONTCONFIG
362%! fail ("uisetfont (1, 2, 3)", "Invalid call");
363%!testif HAVE_FONTCONFIG
364%! fail ("uisetfont (110, struct ())", "Invalid call");
365%!testif HAVE_FONTCONFIG
366%! fail ("uisetfont (groot ())", "unhandled object type");
367%!testif HAVE_FONTCONFIG
368%! fail ("uisetfont (struct ())", "FONTSTRUCT .* must have fields FontName,.*");
369%!testif HAVE_FONTCONFIG
370%! fail ("uisetfont ({'Title'})", "TITLE must be a character vector");
371