changeset 27623:698c8b08fe8c

New function uisetfont (bug #57130). * base-text-renderer.h (base_text_renderer::get_system_fonts): Add new pure virtual method. * text-renderer.h (text_renderer::get_system_fonts): Ditto. * text-renderer.cc (text_renderer::get_system_fonts): New function to call rep->get_system_fonts. * ft-text-renderer.[h,cc] (ft_manager::get_system_fonts): New static method. (ft_manager::do_get_system_fonts): Use fontconfig to build a map of installed fonts. (ft_text_renderer::get_system_fonts): Call ft_manager::get_system_fonts. * graphics.cc (F__get_system_fonts__): New function. * scripts/gui/uisetfont.m: New function. * scripts/gui/private/__ok_cancel_dlg.m: Base implementation of a 2-button dialog. * scripts/gui/module.mk: Add uisetfont.m, __ok_cancel_dlg__.m to build system. * gui.txi: Add uisetfont docstring to the manual.
author Pantxo Diribarne <pantxo.diribarne@gmail.com>
date Wed, 30 Oct 2019 00:24:23 +0100
parents df8942d19b7b
children d6b561842eeb
files NEWS doc/interpreter/gui.txi libinterp/corefcn/base-text-renderer.h libinterp/corefcn/ft-text-renderer.cc libinterp/corefcn/graphics.cc libinterp/corefcn/text-renderer.cc libinterp/corefcn/text-renderer.h scripts/gui/module.mk scripts/gui/private/__ok_cancel_dlg__.m scripts/gui/uisetfont.m
diffstat 10 files changed, 612 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Mon Sep 02 21:21:58 2019 +0200
+++ b/NEWS	Wed Oct 30 00:24:23 2019 +0100
@@ -131,6 +131,7 @@
 * `rotx`
 * `roty`
 * `rotz`
+* `uisetfont`
 * `verLessThan`
 * `web`
 * `weboptions`
--- a/doc/interpreter/gui.txi	Mon Sep 02 21:21:58 2019 +0200
+++ b/doc/interpreter/gui.txi	Wed Oct 30 00:24:23 2019 +0100
@@ -86,6 +86,9 @@
 @cindex dialog, displaying a warning dialog
 @DOCSTRING(warndlg)
 
+@cindex dialog, displaying a font selection dialog
+@DOCSTRING(uisetfont)
+
 For creating new dialog types, there is a dialog function.
 
 @cindex dialog, displaying a modal dialog
--- a/libinterp/corefcn/base-text-renderer.h	Mon Sep 02 21:21:58 2019 +0200
+++ b/libinterp/corefcn/base-text-renderer.h	Wed Oct 30 00:24:23 2019 +0100
@@ -30,6 +30,7 @@
 #include <string>
 
 #include "dMatrix.h"
+#include "oct-map.h"
 #include "uint8NDArray.h"
 
 #include "text-engine.h"
@@ -66,6 +67,9 @@
     set_font (const std::string& name, const std::string& weight,
               const std::string& angle, double size) = 0;
 
+    virtual octave_map
+    get_system_fonts (void) = 0;
+
     virtual void set_color (const Matrix& c) = 0;
 
     virtual void
--- a/libinterp/corefcn/ft-text-renderer.cc	Mon Sep 02 21:21:58 2019 +0200
+++ b/libinterp/corefcn/ft-text-renderer.cc	Wed Oct 30 00:24:23 2019 +0100
@@ -165,6 +165,13 @@
               : nullptr);
     }
 
+    static octave_map get_system_fonts (void)
+    {
+      return (instance_ok ()
+              ? instance->do_get_system_fonts ()
+              : octave_map ());
+    }
+
     static void font_destroyed (FT_Face face)
     {
       if (instance_ok ())
@@ -183,6 +190,88 @@
     // in class text_renderer.
     ft_cache cache;
 
+    static octave_map do_get_system_fonts (void)
+    {
+      static octave_map font_map;
+
+      if (font_map.isempty ())
+        {
+          FcConfig *config = FcConfigGetCurrent();
+          FcPattern *pat = FcPatternCreate ();
+          FcObjectSet *os = FcObjectSetBuild (FC_FAMILY, FC_SLANT, FC_WEIGHT,
+                                              FC_CHARSET, nullptr);
+          FcFontSet *fs = FcFontList (config, pat, os);
+
+          if (fs->nfont > 0)
+            {
+              // Mark fonts that have at least all printable ASCII chars
+              FcCharSet *minimal_charset =  FcCharSetCreate ();
+              for (int i = 32; i < 127; i++)
+                FcCharSetAddChar (minimal_charset, static_cast<FcChar32> (i));
+
+              string_vector fields (4);
+              fields(0) = "family";
+              fields(1) = "angle";
+              fields(2) = "weight";
+              fields(3) = "suitable";
+
+              dim_vector dv (1, fs->nfont);
+              Cell families (dv);
+              Cell angles (dv);
+              Cell weights (dv);
+              Cell suitable (dv);
+
+              unsigned char *family;
+              int val;
+              for (int i = 0; fs && i < fs->nfont; i++)
+                {
+                  FcPattern *font = fs->fonts[i];
+                  if (FcPatternGetString (font, FC_FAMILY, 0, &family)
+                      == FcResultMatch)
+                    families(i) = std::string (reinterpret_cast<char*> (family));
+                  else
+                    families(i) = "unknown";
+
+                  if (FcPatternGetInteger (font, FC_SLANT, 0, &val)
+                      == FcResultMatch)
+                    angles(i) = (val == FC_SLANT_ITALIC
+                                 || val == FC_SLANT_OBLIQUE)
+                                ? "italic" : "normal";
+                  else
+                    angles(i) = "unknown";
+
+                  if (FcPatternGetInteger (font, FC_WEIGHT, 0, &val)
+                      == FcResultMatch)
+                    weights(i) = (val == FC_WEIGHT_BOLD
+                                  || val == FC_WEIGHT_DEMIBOLD)
+                                 ? "bold" : "normal";
+                  else
+                    weights(i) = "unknown";
+
+                  FcCharSet *cset;
+                  if (FcPatternGetCharSet (font, FC_CHARSET, 0, &cset)
+                      == FcResultMatch)
+                    suitable(i) = (FcCharSetIsSubset (minimal_charset, cset)
+                                   ? true : false);
+                  else
+                    suitable(i) = false;
+                }
+
+              font_map = octave_map (dv, fields);
+
+              font_map.assign ("family", families);
+              font_map.assign ("angle", angles);
+              font_map.assign ("weight", weights);
+              font_map.assign ("suitable", suitable);
+
+              if (fs)
+                FcFontSetDestroy (fs);
+            }
+        }
+
+      return font_map;
+    }
+
     FT_Face do_get_font (const std::string& name, const std::string& weight,
                          const std::string& angle, double size)
     {
@@ -418,6 +507,8 @@
     void set_font (const std::string& name, const std::string& weight,
                    const std::string& angle, double size);
 
+    octave_map get_system_fonts (void);
+
     void set_color (const Matrix& c);
 
     void set_mode (int m);
@@ -546,12 +637,18 @@
   };
 
   void
-  ft_text_renderer::set_font (const std::string& name, const std::string& weight,
+  ft_text_renderer::set_font (const std::string& name,
+                              const std::string& weight,
                               const std::string& angle, double size)
   {
     // FIXME: take "fontunits" into account
+    font = ft_font (name, weight, angle, size, nullptr);
+  }
 
-    font = ft_font (name, weight, angle, size, nullptr);
+  octave_map
+  ft_text_renderer::get_system_fonts (void)
+  {
+    return ft_manager::get_system_fonts ();
   }
 
   void
@@ -920,7 +1017,7 @@
                 mblen = 1;
                 u32_c = 0xFFFD;
               }
-                
+
             n -= mblen;
 
             if (m_do_strlist && mode == MODE_RENDER)
--- a/libinterp/corefcn/graphics.cc	Mon Sep 02 21:21:58 2019 +0200
+++ b/libinterp/corefcn/graphics.cc	Wed Oct 30 00:24:23 2019 +0100
@@ -14479,3 +14479,17 @@
 
   return ovl (go.get_toolkit ().get_pixels (go));
 }
+
+DEFUN (__get_system_fonts__, args, ,
+       doc: /* -*- texinfo -*-
+@deftypefn {} {@var{font_struct} =} __get_system_fonts__ ()
+Internal function.
+@end deftypefn */)
+{
+  if (args.length () != 0)
+    print_usage ();
+
+  octave::text_renderer txt_renderer;
+
+  return ovl (txt_renderer.get_system_fonts ());
+}
--- a/libinterp/corefcn/text-renderer.cc	Mon Sep 02 21:21:58 2019 +0200
+++ b/libinterp/corefcn/text-renderer.cc	Wed Oct 30 00:24:23 2019 +0100
@@ -93,6 +93,17 @@
       rep->set_anti_aliasing (val);
   }
 
+  octave_map
+  text_renderer::get_system_fonts (void)
+  {
+    octave_map retval;
+
+    if (ok ())
+      retval = rep->get_system_fonts ();
+
+    return retval;
+  }
+
   void
   text_renderer::set_font (const std::string& name, const std::string& weight,
                            const std::string& angle, double size)
--- a/libinterp/corefcn/text-renderer.h	Mon Sep 02 21:21:58 2019 +0200
+++ b/libinterp/corefcn/text-renderer.h	Wed Oct 30 00:24:23 2019 +0100
@@ -67,6 +67,8 @@
     void set_font (const std::string& name, const std::string& weight,
                    const std::string& angle, double size);
 
+    octave_map get_system_fonts (void);
+
     void set_color (const Matrix& c);
 
     void text_to_pixels (const std::string& txt,
--- a/scripts/gui/module.mk	Mon Sep 02 21:21:58 2019 +0200
+++ b/scripts/gui/module.mk	Wed Oct 30 00:24:23 2019 +0100
@@ -7,6 +7,7 @@
   %reldir%/private/__fltk_file_filter__.m \
   %reldir%/private/__get_funcname__.m \
   %reldir%/private/__is_function__.m \
+  %reldir%/private/__ok_cancel_dlg__.m \
   %reldir%/private/__uigetdir_fltk__.m \
   %reldir%/private/__uigetfile_fltk__.m \
   %reldir%/private/__uiobject_split_args__.m \
@@ -37,6 +38,7 @@
   %reldir%/uipushtool.m \
   %reldir%/uiputfile.m \
   %reldir%/uiresume.m \
+  %reldir%/uisetfont.m \
   %reldir%/uitable.m \
   %reldir%/uitoggletool.m \
   %reldir%/uitoolbar.m \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/gui/private/__ok_cancel_dlg__.m	Wed Oct 30 00:24:23 2019 +0100
@@ -0,0 +1,58 @@
+## Copyright (C) 2019 Pantxo Diribarne
+##
+## This file is part of Octave.
+##
+## Octave is free software: you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## Octave is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Octave; see the file COPYING.  If not, see
+## <https://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {} {[@var{hf}, @var{hok}, @var{hcancel}] =} __ok_cancel_dlg__ (@var{title})
+## Undocumented internal function.
+## @seealso{}
+## @end deftypefn
+
+function [hf, hok, hcancel, hpanel] = __ok_cancel_dlg__ (ttl, varargin)
+
+  hf = dialog ("name", ttl, varargin{:});
+  setappdata (hf, "__ok_cancel_btn__", "cancel");
+
+  hpanel = uipanel (hf, "units", "pixels", "bordertype", "none");
+
+  hok = uicontrol (hf, "style", "pushbutton", "string", "Ok");
+
+  hcancel = uicontrol (hf, "style", "pushbutton", "string", "Cancel");
+
+  cb_fix_button_position (hf, [], hcancel, hok, hpanel);
+  set (hf, "sizechangedfcn", {@cb_fix_button_position, hcancel, hok, hpanel});
+
+endfunction
+
+function  cb_fix_button_position (hf, evt, hcancel, hok, hpanel)
+  persistent margin = 20;
+  persistent hgt = 30;
+  persistent wd = 70;
+
+  units = get (hf, "units");
+  unwind_protect
+    set (hf, "units", "pixels");
+    pos = get (hf, "position");
+    set (hok, "position",
+         [(pos(3) - 2 * margin - 2 * wd), margin, wd, hgt]);
+    set (hcancel, "position",
+         [(pos(3) - margin - wd), margin, wd, hgt]);
+    set (hpanel, "position", [0 60 pos(3) pos(4)-60]);
+  unwind_protect_cleanup
+    set (hf, "units", units);
+  end_unwind_protect
+endfunction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/gui/uisetfont.m	Wed Oct 30 00:24:23 2019 +0100
@@ -0,0 +1,417 @@
+## Copyright (C) 2019 Pantxo Diribarne
+##
+## This file is part of Octave.
+##
+## Octave is free software: you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## Octave is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Octave; see the file COPYING.  If not, see
+## <https://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn  {} { } uisetfont ()
+## @deftypefnx {} { } uisetfont (@var{h})
+## @deftypefnx {} { } uisetfont (@var{fontstruct})
+## @deftypefnx {} { } uisetfont (@dots{}, @var{title})
+## @deftypefnx {} {@var{fontstruct} =} uisetfont (@dots{})
+## Open a font selection dialog.
+##
+## If the first argument is a handle to a text, axes, or uicontrol object,
+## pressing the OK button will change the font properties of the object.
+##
+## The first argument may also be a structure with fields @code{FontName},
+## @code{FontWeight}, @code{FontAngle}, @code{FontUnits}, and @code{FontSize},
+## indicating the initially selected font.
+##
+## The title of the dialog window can be changed using the last argument
+## @var{title}.
+##
+## If an output argument @var{fontstruct} is requested, the selected font
+## structure is returned.  Otherwise, the font information is displayed
+## onscreen.
+##
+## @seealso{text, axes, uicontrol}
+## @end deftypefn
+
+function varargout = uisetfont (varargin)
+
+  persistent sysfonts = build_fontstruct ();
+  persistent fontfields = {"FontName", "FontWeight", "FontAngle", ...
+                           "FontUnits", "FontSize"};
+
+  do_display = true;
+  h = [];
+  fontstruct = [];
+  ttl = "Font";
+  nargin = numel (varargin);
+
+  ## Input checking
+  if (nargin > 2)
+    print_usage ();
+  elseif (nargin == 0)
+    ## Do nothing
+  elseif (ishghandle (varargin{1}))
+
+    h = varargin{1};
+    typ = get (h, "type");
+    if (! any (strcmp (typ, {"axes", "text", "uicontrol"})))
+      error ("Octave:uisetfont:bad-object",
+             'uisetfont: unhandled object type "%s"', typ);
+    endif
+    nargin--;
+    varargin(1) = [];
+    do_display = false;
+
+  elseif (isstruct (varargin{1}))
+
+    fontstruct = varargin{1};
+    fields = fieldnames (fontstruct);
+    if (isempty (fields)
+        || ! all (cellfun (@(s) any (strcmp (s, fontfields)), fields)))
+      error ("Octave:uisetfont:bad-fontstruct",
+             "uisetfont: FONTSTRUCT structure must have fields %s",
+             strjoin (fontfields, ", "));
+    endif
+    nargin--;
+    varargin(1) = [];
+
+  endif
+
+  ## Trailing TITLE argument
+  if (nargin == 1)
+    ttl = varargin{1};
+    if (! (ischar (ttl) && isrow (ttl)))
+      error ("Octave:uisetfont:bad-title",
+             "uisetfont: TITLE must be a character vector");
+    endif
+  elseif (nargin == 2)
+    print_usage ();
+  endif
+
+  ## Populate fontstruct
+  persistent defstruct = [];
+  if (isempty (defstruct))
+    factory_fields = strcat ("factorytext", tolower (fontfields));
+    values = get (0, factory_fields);
+    defstruct = struct ([fontfields; values]{:});
+  endif
+
+  if (isempty (fontstruct))
+    if (isempty (h))
+      fontstruct = defstruct;
+    else
+      values = get (h, fontfields);
+      fontstruct = struct ([fontfields; values]{:});
+      names = {sysfonts.name};
+      if (! any (strcmpi (fontstruct.FontName, {sysfonts.name})))
+        warning ("Octave:uisefont:unknown-font",
+                 "uisetfont: unknown font %s", fontstruct.FontName);
+        fontstruct = defstruct;
+      endif
+    endif
+  endif
+
+  ## Sample string
+  persistent str = {"Portez ce vieux whisky";
+                    "au juge blond qui fume";
+                    "0123456789";
+                 ['\alpha, \beta, \gamma, \delta, \epsilon, \zeta, \eta, ' ...
+                  '\theta, \vartheta, \iota, \kappa, \lambda, \mu, \nu, '];
+                 ['\xi, \o, \pi, \varpi, \rho, \sigma, \varsigma, \tau, ' ...
+                  '\upsilon, \phi, \chi, \psi, \omega']};
+
+  ## Run the dialog
+  warning ("off", "Octave:missing-glyph", "local");
+  hf = run_fontdialog (sysfonts, h, fontstruct, ttl, str);
+
+  ## Now wait for a button to be pressed or the figure to be closed
+  uiwait (hf);
+
+  fontstruct = [];
+  if (ishghandle (hf))
+    fontstruct = getappdata (hf, "__uisetfont_struct__");
+    if (! isempty (h) && ! isempty (fontstruct))
+      set (h, fontstruct);
+    endif
+    close (hf);
+  endif
+
+  if (nargout > 0)
+    varargout{1} = fontstruct;
+  elseif (do_display && ! isempty (fontstruct))
+    disp (fontstruct);
+  endif
+
+endfunction
+
+function fonts = build_fontstruct ()
+
+  fontfiles = __get_system_fonts__ ();
+  families = unique ({fontfiles.family});
+
+  fonts(numel (families)+1) = struct ("name", "",
+                                      "has_regular", false,
+                                      "has_bold", false,
+                                      "has_italic", false,
+                                      "has_bold_italic", false);
+
+  fonts(1) = struct ("name", "*",
+                     "has_regular", true,
+                     "has_bold", true,
+                     "has_italic", true,
+                     "has_bold_italic", true);
+
+  for i = 1:numel (families)
+    ii = i + 1;
+    fonts(ii).name = families{i};
+    idx = strcmp ({fontfiles.family}, families{i});
+
+    isbold = strcmp ({fontfiles(idx).weight}, "bold");
+    isitalic = strcmp ({fontfiles(idx).angle}, "italic");
+
+    fonts(ii).has_regular = any (! isbold & ! isitalic);
+    fonts(ii).has_bold = any (isbold & ! isitalic);
+    fonts(ii).has_italic = any (isitalic & ! isbold);
+    fonts(ii).has_bold_italic = any (isbold & isitalic);
+  endfor
+
+endfunction
+
+function hf = run_fontdialog (sysfonts, hobj, fontstruct, ttl, str)
+
+  [hf, hok, hcancel, hp] = __ok_cancel_dlg__ (ttl,
+                                              "position", [200 200 400 400],
+                                              "windowstyle", "modal",
+                                              "resize", "on");
+
+  ## List controls
+  htmp = uipanel (hp, "title", "Font Name",
+                      "units", "normalized", "position", [0.04 0.35 0.5 0.6]);
+  hnames = uicontrol (htmp, "style", "listbox", "string", {sysfonts.name},
+                            "units", "normalized",
+                            "position", [0.02 0.01 0.96 .95]);
+
+  htmp = uipanel (hp, "title", "Style",
+                      "units", "normalized", "position", [0.56 0.35 0.25 0.6]);
+  hstyle = uicontrol (htmp, "style", "listbox",
+                      "units", "normalized",
+                      "position", [0.02 0.01 0.96 .95]);
+
+  htmp = uipanel (hp, "title", "Size",
+                      "units", "normalized", "position", [0.83 0.35 0.13 0.6]);
+  hsize = uicontrol (htmp, "style", "listbox",
+                           "string", arrayfun (@num2str, (8:30), "uni", false),
+                           "units", "normalized",
+                           "position", [0.02 0.01 0.96 .95]);
+
+  fcn = @(h) set (hstyle, "string",
+                          getstylestring (sysfonts(get (h, "value"))));
+  set (hnames, "callback", fcn);
+
+  ## Axes to display samples
+  htmp = uipanel (hp, "title", "Sample",
+                      "units", "normalized", "position", [0.04 0 0.92 0.33]);
+  hax = axes ("parent", htmp, "visible", "off", "units", "normalized",
+              "position", [0 0 1 0.95], "xlim", [0 1], "ylim", [0 1]);
+  ht = text (hax, 0.5, 0.5, str, "horizontalalignment", "center");
+
+  hlists = [hnames, hstyle, hsize];
+
+  ## Update text and uicontrol objects according to the input fontstruct
+  struct_to_lists (fontstruct, sysfonts, hlists);
+  set (ht, fontstruct);
+
+  ## Setup callbacks
+  set (hlists, "callback", {@cb_list_value_changed, hlists, ht, sysfonts});
+
+  set (hok, "callback", {@cb_button, hlists, "ok"});
+  set (hcancel, "callback", {@cb_button, hlists, "cancel"});
+
+  ## Give focus to the OK button
+  uicontrol (hok);
+
+endfunction
+
+function str = getstylestring (fontitem)
+
+  styles = {"Plain", "Bold", "Italic", "Bold Italic"};
+  if (fontitem.has_bold_italic)
+    str = styles;
+  elseif (fontitem.has_bold && fontitem.has_italic)
+    str = styles(1:3);
+  elseif (fontitem.has_bold)
+    str = styles(1:2);
+  elseif (fontitem.has_italic)
+    str = styles(1:2:3);
+  else
+    str = styles{1};
+  endif
+
+endfunction
+
+function fontstruct = struct_from_lists (hlists)
+
+  name = get (hlists(1), "string");
+  if (iscell (name))
+    name = name{get(hlists(1), "value")};
+  endif
+
+  szstr = get (hlists(3), "string");
+  sz = str2num (szstr{get(hlists(3), "value")});
+
+  fontstruct = struct ("FontName", name, "FontWeight", "normal",
+                       "FontAngle", "normal", "FontUnits", "points",
+                       "FontSize", sz);
+
+  style = get (hlists(2), "string");
+  if (iscell (style))
+    style = style{get(hlists(2), "value")};
+  endif
+
+  if (strcmp (style, "Bold"))
+    fontstruct.FontWeight = "bold";
+  elseif (strcmp (style, "Bold Italic"))
+    fontstruct.FontWeight = "bold";
+    fontstruct.FontAngle = "italic";
+  elseif (strcmp (style, "Italic"))
+    fontstruct.FontAngle = "italic";
+  endif
+
+endfunction
+
+function struct_to_lists (fontstruct, sysfonts, hlists)
+
+  ## Match font name
+  names = get (hlists(1), "string");
+  idx = find (strcmpi (fontstruct.FontName, names));
+  if (isempty (idx))
+    idx = 1;
+  endif
+  set (hlists(1), "value", idx);
+  styles = getstylestring (sysfonts(idx));
+  set (hlists(2), "string", styles);
+
+  ## Match style
+  style = "Plain";
+  if (strcmp (fontstruct.FontWeight, "bold")
+      && strcmp (fontstruct.FontAngle, "italic"))
+    style = "Bold Italic";
+  elseif (strcmp (fontstruct.FontWeight, "bold"))
+    style = "Bold";
+  elseif (strcmp (fontstruct.FontAngle, "italic"))
+    style = "Italic";
+  endif
+
+  idx = find (strcmpi (style, styles));
+  if (isempty (idx))
+    idx = 1;
+  endif
+  set (hlists(2), "value", idx);
+
+  ## Match size
+  szs = (8:30);
+  idx = find (round (fontstruct.FontSize) == szs);
+  if (isempty (idx))
+    idx = 1;
+  endif
+  set (hlists(3), "value", idx);
+
+endfunction
+
+function cb_button (h, evt, hlists, role)
+
+  fontstruct = [];
+  if (strcmp (role, "ok"))
+    fontstruct = struct_from_lists (hlists);
+  endif
+
+  setappdata (gcbf (), "__uisetfont_struct__", fontstruct);
+  uiresume (gcbf ());
+
+endfunction
+
+function cb_list_value_changed (h, evt, hlists, htext, sysfonts)
+
+  keyboard;
+  if (h == hlists(1))
+    set (hlists(2), "string", getstylestring (sysfonts(get (h, "value"))),
+                    "value", 1);
+  endif
+  fontstruct = struct_from_lists (hlists);
+  set (htext, fontstruct);
+
+endfunction
+
+
+## Test input validation
+%!test
+%! [msg, id] = lasterr ();
+%! unwind_protect
+%!   lasterr ("", "");
+%!   try
+%!     uisetfont (1, 2, 3);
+%!   catch
+%!   end_try_catch
+%!   [~, id] = lasterr ();
+%!   assert (id, "Octave:invalid-fun-call");
+%! unwind_protect_cleanup
+%!   lasterr (msg, id);
+%! end_unwind_protect
+
+%!test
+%! [msg, id] = lasterr ();
+%! unwind_protect
+%!   lasterr ("", "");
+%!   try
+%!     uisetfont (110, struct ());
+%!   catch
+%!   end_try_catch
+%!   [~, id] = lasterr ();
+%!   assert (id, "Octave:invalid-fun-call");
+%! unwind_protect_cleanup
+%!   lasterr (msg, id);
+%! end_unwind_protect
+
+%!test
+%! [msg, id] = lasterr ();
+%! unwind_protect
+%!   lasterr ("", "");
+%!   hf = figure ("visible", "off");
+%!   try
+%!     uisetfont (hf);
+%!   catch
+%!   end_try_catch
+%!   [~, id] = lasterr ();
+%!   assert (id, "Octave:uisetfont:bad-object");
+%! unwind_protect_cleanup
+%!   lasterr (msg, id);
+%!   close (hf);
+%! end_unwind_protect
+
+%!test
+%! [msg, id] = lasterr ();
+%! unwind_protect
+%!   lasterr ("", "");
+%!   hf = figure ("visible", "off");
+%!   hax = axes ();
+%!   try
+%!     uisetfont (hax, 1);
+%!   catch
+%!   end_try_catch
+%!   [~, id] = lasterr ();
+%!   assert (id, "Octave:uisetfont:bad-title");
+%! unwind_protect_cleanup
+%!   lasterr (msg, id);
+%!   close (hf);
+%! end_unwind_protect
+
+%!error <structure must have fields FontName, FontWeight, FontAngle, FontUnits, FontSize>
+%!  uisetfont (struct ());