# HG changeset patch
# User Pantxo Diribarne <pantxo.diribarne@gmail.com>
# Date 1572391463 -3600
#      Wed Oct 30 00:24:23 2019 +0100
# Node ID 9e5bad4173be1e345570d3414311f55f6be890c3
# Parent  5aad387784aa2c94575dfa3b2c5838b07b79ce51
Implement 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.
* 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.
  
* scripts/gui/uisetfont.m: New file.
* scripts/gui/private/__ok_cancel_dlg.m: Base implementation of a 2 buttons
  dialog.
* gui.txi: add uisetfont docstring to the manual
  
diff -r 5aad387784aa -r 9e5bad4173be doc/interpreter/gui.txi
--- a/doc/interpreter/gui.txi	Mon Oct 28 12:05:06 2019 -0400
+++ 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
diff -r 5aad387784aa -r 9e5bad4173be libinterp/corefcn/base-text-renderer.h
--- a/libinterp/corefcn/base-text-renderer.h	Mon Oct 28 12:05:06 2019 -0400
+++ 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
diff -r 5aad387784aa -r 9e5bad4173be libinterp/corefcn/ft-text-renderer.cc
--- a/libinterp/corefcn/ft-text-renderer.cc	Mon Oct 28 12:05:06 2019 -0400
+++ 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,87 @@
     // 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];
+                  families(i)
+                    = ((FcPatternGetString (font, FC_FAMILY, 0, &family)
+                        == FcResultMatch)
+                       ? std::string (reinterpret_cast<char*> (family))
+                       : "unknown");
+
+                  angles(i)
+                    = ((FcPatternGetInteger (font, FC_SLANT, 0, &val)
+                        == FcResultMatch)
+                       ? ((val == FC_SLANT_ITALIC || val == FC_SLANT_OBLIQUE)
+                          ? "italic" : "normal")
+                       : "unknown");
+
+                  weights(i)
+                    = ((FcPatternGetInteger (font, FC_WEIGHT, 0, &val)
+                        == FcResultMatch)
+                       ? ((val == FC_WEIGHT_BOLD || val == FC_WEIGHT_DEMIBOLD)
+                          ? "bold" : "normal")
+                       : "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 +506,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);
@@ -554,6 +644,13 @@
     font = ft_font (name, weight, angle, size, nullptr);
   }
 
+
+  octave_map
+  ft_text_renderer::get_system_fonts (void)
+  {
+    return ft_manager::get_system_fonts ();
+  }
+
   void
   ft_text_renderer::push_new_line (void)
   {
@@ -920,7 +1017,7 @@
                 mblen = 1;
                 u32_c = 0xFFFD;
               }
-                
+
             n -= mblen;
 
             if (m_do_strlist && mode == MODE_RENDER)
diff -r 5aad387784aa -r 9e5bad4173be libinterp/corefcn/graphics.cc
--- a/libinterp/corefcn/graphics.cc	Mon Oct 28 12:05:06 2019 -0400
+++ 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 ());
+}
diff -r 5aad387784aa -r 9e5bad4173be libinterp/corefcn/text-renderer.cc
--- a/libinterp/corefcn/text-renderer.cc	Mon Oct 28 12:05:06 2019 -0400
+++ 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)
diff -r 5aad387784aa -r 9e5bad4173be libinterp/corefcn/text-renderer.h
--- a/libinterp/corefcn/text-renderer.h	Mon Oct 28 12:05:06 2019 -0400
+++ 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,
diff -r 5aad387784aa -r 9e5bad4173be scripts/gui/module.mk
--- a/scripts/gui/module.mk	Mon Oct 28 12:05:06 2019 -0400
+++ 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 \
diff -r 5aad387784aa -r 9e5bad4173be scripts/gui/private/__ok_cancel_dlg__.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,55 @@
+## Copyright (C) 2019 Pantxo Diribarne
+##
+## This program 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.
+##
+## This program 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 this program.  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__ (tit, varargin)
+
+  hf = dialog ("name", tit, varargin{:});
+  setappdata (hf, "__ok_cancel_btn__", "cancel");
+
+  hpanel = uipanel (hf, "units", "pixels", "bordertype", "none");
+
+  hcancel = uicontrol (hf, "style", "pushbutton", "string", "Cancel");
+
+  hok = uicontrol (hf, "style", "pushbutton", "string", "Ok");
+
+  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) - margin - wd), margin, wd, hgt]);
+    set (hcancel, "position", ...
+         [(pos(3) - 2 * margin - 2 * 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
diff -r 5aad387784aa -r 9e5bad4173be scripts/gui/uisetfont.m
--- /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,418 @@
+## Copyright (C) 2019 Pantxo Diribarne
+##
+## This program 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.
+##
+## This program 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 this program.  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, an axes, or an uicontrol
+## object, pressing the OK button will change the font properties of the
+## object.
+##
+## The first argument may also be a structure with fields "FontName",
+## "FontWeight", "FontAngle", "FontUnits", and "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 are
+## printed to stdout.
+##
+## @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 = [];
+  tit = "Font";
+  nargin = numel (varargin);
+
+  ## Input checking
+  if (nargin > 2)
+    print_usage ()
+  elseif (nargin == 0)
+    ## Do nothing
+  elseif (ishghandle (varargin{1}))
+
+    h = varargin{1};
+    if (! any (strcmp ({"axes", "text", "uicontrol"}, get (h ,"type"))))
+      error ("Octave:uisetfont:bad-object", ...
+             "uisetfont: unhandled object type \"%s\"", get (h, "type"));
+    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: structure must have fields %s", ...
+             strjoin (fontfields, ", "));
+    endif
+    nargin--;
+    varargin(1) = [];
+
+  endif
+
+  ## Trailing TITLE argument
+  if (nargin == 1)
+    tit = varargin{1};
+    if (! ischar (tit) && isrow (tit))
+      error ("Octave:uisetfont:bad-title", ...
+             "uisetfont: TITLE must be a character verctor");
+    endif
+  elseif (nargin == 2)
+    print_usage ();
+  endif
+
+  ## Populate fontstruct
+  persistent defstruct = [];
+  if (isempty (defstruct))
+    factory_fields = cellfun (@(s) ["factorytext" s], ...
+                              tolower (fontfields), ...
+                              "uni", false);
+    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", ...
+         "123456789", ...
+         ["\\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, tit, 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__ ();
+  allfamilies = {fontfiles.family};
+  families = unique (allfamilies);
+
+  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 ii = 1:numel (families)
+    fonts(ii+1).name = families{ii};
+    idx = strcmp ({fontfiles.family},families{ii});
+
+    isbold = strcmp ({fontfiles(idx).weight}, "bold");
+    isitalic = strcmp ({fontfiles(idx).angle}, "italic");
+
+    fonts(ii+1).has_regular = any (! isbold & ! isitalic);
+
+    fonts(ii+1).has_bold = any (isbold & ! isitalic);
+
+    fonts(ii+1).has_italic = any (isitalic & ! isbold);
+
+    fonts(ii+1).has_bold_italic = any (isbold & isitalic);
+  endfor
+
+endfunction
+
+function hf = run_fontdialog (sysfonts, hobj, fontstruct, tit, str)
+
+  [hf, hok, hcancel, hp] = __ok_cancel_dlg__ (tit, ...
+                                              "position", [200 200 400 400], ...
+                                              "windowstyle", "normal", ...
+                                              "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], ...
+              "units", "normalized");
+  ht = text (0.5, 0.5, str, "horizontalalignment", "center", "parent",
+             hax);
+
+  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)
+
+  tmp = {"Plain", "Bold", "Italic", "Bold Italic"};
+  if (fontitem.has_bold_italic)
+    str = tmp;
+  elseif (fontitem.has_bold && fontitem.has_italic)
+    str = tmp(1:3);
+  elseif (fontitem.has_bold)
+    str = tmp(1:2);
+  elseif (fontitem.has_italic)
+    str = tmp(1:2:3);
+  else
+    str = tmp{1};
+  endif
+
+endfunction
+
+function fontstruct = struct_from_lists (hlists)
+
+  name = get (hlists(1), "string");
+  if (iscell (name))
+    name = name{get(hlists(1), "value")};
+  endif
+
+  szs = get (hlists(3), "string");
+  sz = str2num (szs{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
+
+  tmp = {"Plain", "Bold", "Italic", "Bold Italic"};
+  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)
+
+  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 ())
