changeset 31663:a74935a6cc75

move gh_manager class out of graphics.in.h and graphics.cc to separate files * gh-manager.h, gh-manager.cc: New files. Move gh_manager class here from graphics.in.h and graphics.cc. * gl-render.cc, gl2ps-print.cc, interpreter.h, graphics.cc, graphics-utils.cc, graphics-toolkit.cc: Include gh-manager.h. * graphics.in.h, graphics.cc (make_graphics_object_from_type): Declare extern. * libinterp/corefcn/module.mk: Update.
author John W. Eaton <jwe@octave.org>
date Sat, 10 Dec 2022 00:30:20 -0500
parents a36035adefeb
children a1e7e0b62765
files libinterp/corefcn/gh-manager.cc libinterp/corefcn/gh-manager.h libinterp/corefcn/gl-render.cc libinterp/corefcn/gl2ps-print.cc libinterp/corefcn/graphics-toolkit.cc libinterp/corefcn/graphics-utils.cc libinterp/corefcn/graphics.cc libinterp/corefcn/graphics.in.h libinterp/corefcn/interpreter.h libinterp/corefcn/module.mk
diffstat 10 files changed, 1009 insertions(+), 916 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libinterp/corefcn/gh-manager.cc	Sat Dec 10 00:30:20 2022 -0500
@@ -0,0 +1,706 @@
+////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2007-2022 The Octave Project Developers
+//
+// See the file COPYRIGHT.md in the top-level directory of this
+// distribution or <https://octave.org/copyright/>.
+//
+// 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/>.
+//
+////////////////////////////////////////////////////////////////////////
+
+#if defined (HAVE_CONFIG_H)
+#  include "config.h"
+#endif
+
+#include "cmd-edit.h"
+
+#include "builtin-defun-decls.h"
+#include "gh-manager.h"
+#include "graphics-utils.h"
+#include "input.h"
+#include "interpreter-private.h"
+#include "interpreter.h"
+#include "parse.h"
+
+OCTAVE_BEGIN_NAMESPACE(octave)
+
+static double
+make_handle_fraction (void)
+{
+  static double maxrand = RAND_MAX + 2.0;
+
+  return (rand () + 1.0) / maxrand;
+}
+
+graphics_handle
+gh_manager::get_handle (bool integer_figure_handle)
+{
+  graphics_handle retval;
+
+  if (integer_figure_handle)
+    {
+      // Figure handles are positive integers corresponding
+      // to the figure number.
+
+      // We always want the lowest unused figure number.
+
+      retval = 1;
+
+      while (m_handle_map.find (retval) != m_handle_map.end ())
+        retval++;
+    }
+  else
+    {
+      // Other graphics handles are negative integers plus some random
+      // fractional part.  To avoid running out of integers, we recycle the
+      // integer part but tack on a new random part each time.
+
+      auto p = m_handle_free_list.begin ();
+
+      if (p != m_handle_free_list.end ())
+        {
+          retval = *p;
+          m_handle_free_list.erase (p);
+        }
+      else
+        {
+          retval = graphics_handle (m_next_handle);
+
+          m_next_handle = std::ceil (m_next_handle) - 1.0 - make_handle_fraction ();
+        }
+    }
+
+  return retval;
+}
+
+void
+gh_manager::free (const graphics_handle& h, bool from_root)
+{
+  if (h.ok ())
+    {
+      if (h.value () == 0)
+        error ("graphics_handle::free: can't delete root object");
+
+      auto p = m_handle_map.find (h);
+
+      if (p == m_handle_map.end ())
+        error ("graphics_handle::free: invalid object %g", h.value ());
+
+      base_properties& bp = p->second.get_properties ();
+
+      if (! p->second.valid_object () || bp.is_beingdeleted ())
+        return;
+
+      graphics_handle parent_h = p->second.get_parent ();
+      graphics_object parent_go = nullptr;
+      if (! from_root || isfigure (h.value ()))
+        parent_go = get_object (parent_h);
+
+      bp.set_beingdeleted (true);
+
+      // delete listeners before invalidating object
+      p->second.remove_all_listeners ();
+
+      bp.delete_children (true, from_root);
+
+      // NOTE: Call the delete function while the object's state is still valid.
+      octave_value val = bp.get_deletefcn ();
+
+      bp.execute_deletefcn ();
+
+      // Notify graphics toolkit.
+      p->second.finalize ();
+
+
+      // NOTE: Call remove_child before erasing the go from the map if not
+      // removing from groot.
+      // A callback function might have already deleted the parent
+      if ((! from_root || isfigure (h.value ())) && parent_go.valid_object ()
+          && h.ok ())
+        parent_go.remove_child (h);
+
+      // Note: this will be valid only for first explicitly deleted
+      // object.  All its children will then have an
+      // unknown graphics toolkit.
+
+      // Graphics handles for non-figure objects are negative
+      // integers plus some random fractional part.  To avoid
+      // running out of integers, we recycle the integer part
+      // but tack on a new random part each time.
+
+      m_handle_map.erase (p);
+
+      if (h.value () < 0)
+        m_handle_free_list.insert
+        (std::ceil (h.value ()) - make_handle_fraction ());
+    }
+}
+
+void
+gh_manager::renumber_figure (const graphics_handle& old_gh,
+                             const graphics_handle& new_gh)
+{
+  auto p = m_handle_map.find (old_gh);
+
+  if (p == m_handle_map.end ())
+    error ("graphics_handle::free: invalid object %g", old_gh.value ());
+
+  graphics_object go = p->second;
+
+  m_handle_map.erase (p);
+
+  m_handle_map[new_gh] = go;
+
+  if (old_gh.value () < 0)
+    m_handle_free_list.insert (std::ceil (old_gh.value ())
+                               - make_handle_fraction ());
+
+  for (auto& hfig : m_figure_list)
+    {
+      if (hfig == old_gh)
+        {
+          hfig = new_gh;
+          break;
+        }
+    }
+}
+
+void
+gh_manager::close_all_figures (void)
+{
+  // FIXME: should we process or discard pending events?
+
+  m_event_queue.clear ();
+
+  // Don't use m_figure_list_iterator because we'll be removing elements
+  // from the list elsewhere.
+
+  Matrix hlist = figure_handle_list (true);
+
+  for (octave_idx_type i = 0; i < hlist.numel (); i++)
+    {
+      graphics_handle h = lookup (hlist(i));
+
+      if (h.ok ())
+        close_figure (h);
+    }
+
+  // They should all be closed now.  If not, force them to close.
+
+  hlist = figure_handle_list (true);
+
+  for (octave_idx_type i = 0; i < hlist.numel (); i++)
+    {
+      graphics_handle h = lookup (hlist(i));
+
+      if (h.ok ())
+        force_close_figure (h);
+    }
+
+  // None left now, right?
+
+  hlist = figure_handle_list (true);
+
+  if (hlist.numel () != 0)
+    warning ("gh_manager::close_all_figures: some graphics elements failed to close");
+
+  // Clear all callback objects from our list.
+
+  m_callback_objects.clear ();
+}
+
+// We use a random value for the handle to avoid issues with plots and
+// scalar values for the first argument.
+gh_manager::gh_manager (octave::interpreter& interp)
+  : m_interpreter (interp), m_handle_map (), m_handle_free_list (),
+    m_next_handle (-1.0 - (rand () + 1.0) / (RAND_MAX + 2.0)),
+    m_figure_list (), m_graphics_lock (),  m_event_queue (),
+    m_callback_objects (), m_event_processing (0)
+{
+  m_handle_map[0] = graphics_object (new root_figure ());
+
+  octave::gtk_manager& gtk_mgr = octave::__get_gtk_manager__ ();
+
+  // Make sure the default graphics toolkit is registered.
+  gtk_mgr.default_toolkit ();
+}
+
+graphics_handle
+gh_manager::make_graphics_handle (const std::string& go_name,
+                                  const graphics_handle& p,
+                                  bool integer_figure_handle,
+                                  bool call_createfcn, bool notify_toolkit)
+{
+  graphics_handle h = get_handle (integer_figure_handle);
+
+  base_graphics_object *bgo = make_graphics_object_from_type (go_name, h, p);
+
+  if (! bgo)
+    error ("gh_manager::make_graphics_handle: invalid object type '%s'",
+           go_name.c_str ());
+
+  graphics_object go (bgo);
+
+  m_handle_map[h] = go;
+
+  if (go_name == "axes")
+    {
+      // Handle defaults for labels since overriding defaults for
+      // them can't work before the axes object is fully
+      // constructed.
+
+      axes::properties& props
+        = dynamic_cast<axes::properties&> (go.get_properties ());
+
+      graphics_object tgo;
+
+      tgo = get_object (props.get_xlabel ());
+      tgo.override_defaults ();
+
+      tgo = get_object (props.get_ylabel ());
+      tgo.override_defaults ();
+
+      tgo = get_object (props.get_zlabel ());
+      tgo.override_defaults ();
+
+      tgo = get_object (props.get_title ());
+      tgo.override_defaults ();
+    }
+
+  // Overriding defaults will work now because the handle is valid
+  // and we can find parent objects (not just handles).
+  go.override_defaults ();
+
+  if (call_createfcn)
+    bgo->get_properties ().execute_createfcn ();
+
+  // Notify graphics toolkit.
+  if (notify_toolkit)
+    go.initialize ();
+
+  return h;
+}
+
+graphics_handle
+gh_manager::make_figure_handle (double val, bool notify_toolkit)
+{
+  graphics_handle h = val;
+
+  base_graphics_object *bgo = new figure (h, 0);
+  graphics_object go (bgo);
+
+  m_handle_map[h] = go;
+
+  // Notify graphics toolkit.
+  if (notify_toolkit)
+    go.initialize ();
+
+  go.override_defaults ();
+
+  return h;
+}
+
+void
+gh_manager::push_figure (const graphics_handle& h)
+{
+  pop_figure (h);
+
+  m_figure_list.push_front (h);
+}
+
+void
+gh_manager::pop_figure (const graphics_handle& h)
+{
+  for (auto it = m_figure_list.begin (); it != m_figure_list.end (); it++)
+    {
+      if (*it == h)
+        {
+          m_figure_list.erase (it);
+          break;
+        }
+    }
+}
+
+static void
+xset_gcbo (const graphics_handle& h)
+{
+  gh_manager& gh_mgr = octave::__get_gh_manager__ ();
+
+  graphics_object go = gh_mgr.get_object (0);
+
+  root_figure::properties& props
+    = dynamic_cast<root_figure::properties&> (go.get_properties ());
+
+  props.set_callbackobject (h.as_octave_value ());
+}
+
+void
+gh_manager::restore_gcbo (void)
+{
+  octave::autolock guard (m_graphics_lock);
+
+  m_callback_objects.pop_front ();
+
+  xset_gcbo (m_callback_objects.empty ()
+             ? graphics_handle () : m_callback_objects.front ().get_handle ());
+}
+
+void
+gh_manager::execute_listener (const graphics_handle& h, const octave_value& l)
+{
+  if (octave::thread::is_thread ())
+    execute_callback (h, l, octave_value ());
+  else
+    {
+      octave::autolock guard (m_graphics_lock);
+
+      post_event (graphics_event::create_callback_event (h, l));
+    }
+}
+
+void
+gh_manager::execute_callback (const graphics_handle& h,
+                              const octave_value& cb_arg,
+                              const octave_value& data)
+{
+  if (cb_arg.is_defined () && ! cb_arg.isempty ())
+    {
+      octave_value_list args;
+      octave_value ov_fcn;
+      octave_function *fcn = nullptr;
+
+      args(0) = h.as_octave_value ();
+      if (data.is_defined ())
+        args(1) = data;
+      else
+        args(1) = Matrix ();
+
+      octave::unwind_action_safe restore_gcbo_action
+      (&gh_manager::restore_gcbo, this);
+
+      graphics_object go (get_object (h));
+      if (go)
+        {
+          // FIXME: Is the lock necessary when we're only calling a
+          //        const "get" method?
+          octave::autolock guard (m_graphics_lock);
+          m_callback_objects.push_front (go);
+          xset_gcbo (h);
+        }
+
+      // Copy CB because "function_value" method is non-const.
+      octave_value cb = cb_arg;
+
+      if (cb.is_function ())
+        fcn = cb.function_value ();
+      else if (cb.is_function_handle ())
+        ov_fcn = cb;
+      else if (cb.is_string ())
+        {
+          int status;
+          std::string s = cb.string_value ();
+
+          try
+            {
+              m_interpreter.eval_string (s, false, status, 0);
+            }
+          catch (const octave::execution_exception& ee)
+            {
+              m_interpreter.handle_exception (ee);
+            }
+        }
+      else if (cb.iscell () && cb.length () > 0
+               && (cb.rows () == 1 || cb.columns () == 1)
+               && (cb.cell_value ()(0).is_function ()
+                   || cb.cell_value ()(0).is_function_handle ()))
+        {
+          Cell c = cb.cell_value ();
+
+          ov_fcn = c(0);
+
+          for (int i = 1; i < c.numel () ; i++)
+            args(1+i) = c(i);
+        }
+      else
+        {
+          std::string nm = cb.class_name ();
+          error ("trying to execute non-executable object (class = %s)",
+                 nm.c_str ());
+        }
+
+      if (fcn || ov_fcn.is_defined ())
+        try
+          {
+            if (ov_fcn.is_defined ())
+              octave::feval (ov_fcn, args);
+            else
+              octave::feval (fcn, args);
+          }
+        catch (const octave::execution_exception& ee)
+          {
+            m_interpreter.handle_exception (ee);
+          }
+
+      // Redraw after interacting with a user-interface (ui*) object.
+      if (Vdrawnow_requested)
+        {
+          if (go)
+            {
+              std::string go_name
+                = go.get_properties ().graphics_object_name ();
+
+              if (go_name.length () > 1
+                  && go_name[0] == 'u' && go_name[1] == 'i')
+                {
+                  Fdrawnow (m_interpreter);
+                  Vdrawnow_requested = false;
+                }
+            }
+        }
+    }
+}
+
+static int
+process_graphics_events (void)
+{
+  gh_manager& gh_mgr = octave::__get_gh_manager__ ();
+
+  return gh_mgr.process_events ();
+}
+
+void
+gh_manager::post_event (const graphics_event& e)
+{
+  m_event_queue.push_back (e);
+
+  octave::command_editor::add_event_hook (process_graphics_events);
+}
+
+void
+gh_manager::post_callback (const graphics_handle& h, const std::string& name,
+                           const octave_value& data)
+{
+  octave::autolock guard (m_graphics_lock);
+
+  graphics_object go = get_object (h);
+
+  if (go.valid_object ())
+    {
+      caseless_str cname (name);
+      int busyaction = base_graphics_event::QUEUE;
+
+      if (cname == "deletefcn" || cname == "createfcn"
+          || cname == "closerequestfcn"
+          || ((go.isa ("figure") || go.isa ("uipanel")
+               || go.isa ("uibuttongroup"))
+              && (cname == "resizefcn" || cname == "sizechangedfcn")))
+        busyaction = base_graphics_event::INTERRUPT;
+      else if (go.get_properties ().get_busyaction () == "cancel")
+        busyaction = base_graphics_event::CANCEL;
+
+      // The "closerequestfcn" callback must be executed once the figure has
+      // been made current.  Let "close" do the job.
+      if (cname == "closerequestfcn")
+        {
+          std::string cmd ("close (gcbf ());");
+          post_event (graphics_event::create_mcode_event (h, cmd, busyaction));
+        }
+      else
+        post_event (graphics_event::create_callback_event (h, name, data,
+                    busyaction));
+    }
+}
+
+void
+gh_manager::post_function (graphics_event::event_fcn fcn, void *fcn_data)
+{
+  octave::autolock guard (m_graphics_lock);
+
+  post_event (graphics_event::create_function_event (fcn, fcn_data));
+}
+
+void
+gh_manager::post_set (const graphics_handle& h, const std::string& name,
+                      const octave_value& value, bool notify_toolkit,
+                      bool redraw_figure)
+{
+  octave::autolock guard (m_graphics_lock);
+
+  post_event (graphics_event::create_set_event (h, name, value, notify_toolkit,
+              redraw_figure));
+}
+
+int
+gh_manager::process_events (bool force)
+{
+  graphics_event e;
+  bool old_Vdrawnow_requested = Vdrawnow_requested;
+  bool events_executed = false;
+
+  do
+    {
+      e = graphics_event ();
+
+      {
+        octave::autolock guard (m_graphics_lock);
+
+        if (! m_event_queue.empty ())
+          {
+            if (m_callback_objects.empty () || force)
+              {
+                e = m_event_queue.front ();
+
+                m_event_queue.pop_front ();
+              }
+            else
+              {
+                const graphics_object& go = m_callback_objects.front ();
+
+                if (go.get_properties ().is_interruptible ())
+                  {
+                    e = m_event_queue.front ();
+
+                    m_event_queue.pop_front ();
+                  }
+                else
+                  {
+                    std::list<graphics_event>::iterator p = m_event_queue.begin ();
+
+                    while (p != m_event_queue.end ())
+                      if (p->get_busyaction () == base_graphics_event::CANCEL)
+                        {
+                          p = m_event_queue.erase (p);
+                        }
+                      else if (p->get_busyaction ()
+                               == base_graphics_event::INTERRUPT)
+                        {
+                          e = (*p);
+                          m_event_queue.erase (p);
+                          break;
+                        }
+                      else
+                        p++;
+                  }
+              }
+          }
+      }
+
+      if (e.ok ())
+        {
+          e.execute ();
+          events_executed = true;
+        }
+    }
+  while (e.ok ());
+
+  {
+    octave::autolock guard (m_graphics_lock);
+
+    if (m_event_queue.empty () && m_event_processing == 0)
+      octave::command_editor::remove_event_hook (process_graphics_events);
+  }
+
+  if (events_executed)
+    octave::flush_stdout ();
+
+  if (Vdrawnow_requested && ! old_Vdrawnow_requested)
+    {
+      Fdrawnow (m_interpreter);
+
+      Vdrawnow_requested = false;
+    }
+
+  return 0;
+}
+
+
+/*
+## Test interruptible/busyaction properties
+%!function cb (h, ~)
+%! setappdata (gcbf (), "cb_exec", [getappdata(gcbf (), "cb_exec") h]);
+%! drawnow ();
+%! setappdata (gcbf (), "cb_exec", [getappdata(gcbf (), "cb_exec") h]);
+%!endfunction
+%!
+%!testif HAVE_OPENGL, HAVE_QT; have_window_system () && any (strcmp ("qt", available_graphics_toolkits ()))
+%! hf = figure ("visible", "off", "resizefcn", @cb);
+%! graphics_toolkit (hf, "qt");
+%! unwind_protect
+%!   ## Default
+%!   hui1 = uicontrol ("parent", hf, "interruptible", "on", "callback", @cb);
+%!   hui2 = uicontrol ("parent", hf, "busyaction", "queue", "callback", @cb);
+%!   hui3 = uicontrol ("parent", hf, "busyaction", "queue", "callback", @cb);
+%!   __go_post_callback__ (hui1, "callback");
+%!   __go_post_callback__ (hui2, "callback");
+%!   __go_post_callback__ (hui3, "callback");
+%!
+%!   assert (getappdata (hf, "cb_exec"), []);
+%!   drawnow ();
+%!   assert (getappdata (hf, "cb_exec"), [hui1 hui2 hui3 hui3 hui2 hui1]);
+%!
+%!   ## Interruptible off
+%!   setappdata (hf, "cb_exec", []);
+%!   set (hui1, "interruptible", "off");
+%!   __go_post_callback__ (hui1, "callback");
+%!   __go_post_callback__ (hui2, "callback");
+%!   __go_post_callback__ (hui3, "callback");
+%!   drawnow ();
+%!   assert (getappdata (hf, "cb_exec"), [hui1 hui1 hui2 hui3 hui3 hui2]);
+%!
+%!   ## "resizefcn" callback interrupts regardless of interruptible property
+%!   setappdata (hf, "cb_exec", []);
+%!   __go_post_callback__ (hui1, "callback");
+%!   __go_post_callback__ (hf, "resizefcn");
+%!   drawnow ();
+%!   assert (getappdata (hf, "cb_exec"), [hui1 hf hf hui1]);
+%!
+%!   ## test "busyaction" "cancel"
+%!   setappdata (hf, "cb_exec", []);
+%!   set (hui2, "busyaction", "cancel");
+%!   __go_post_callback__ (hui1, "callback");
+%!   __go_post_callback__ (hui2, "callback");
+%!   __go_post_callback__ (hui3, "callback");
+%!   __go_post_callback__ (hf, "resizefcn");
+%!   drawnow ();
+%!   assert (getappdata (hf, "cb_exec"), [hui1 hf hui3 hui3 hf hui1]);
+%! unwind_protect_cleanup
+%!   close (hf)
+%! end_unwind_protect
+*/
+
+void
+gh_manager::enable_event_processing (bool enable)
+{
+  octave::autolock guard (m_graphics_lock);
+
+  if (enable)
+    {
+      m_event_processing++;
+
+      octave::command_editor::add_event_hook (process_graphics_events);
+    }
+  else
+    {
+      m_event_processing--;
+
+      if (m_event_queue.empty () && m_event_processing == 0)
+        octave::command_editor::remove_event_hook (process_graphics_events);
+    }
+}
+
+OCTAVE_END_NAMESPACE(octave)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libinterp/corefcn/gh-manager.h	Sat Dec 10 00:30:20 2022 -0500
@@ -0,0 +1,288 @@
+////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2007-2022 The Octave Project Developers
+//
+// See the file COPYRIGHT.md in the top-level directory of this
+// distribution or <https://octave.org/copyright/>.
+//
+// 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/>.
+//
+////////////////////////////////////////////////////////////////////////
+
+#if ! defined (octave_gh_manager_h)
+#define octave_gh_manager_h 1
+
+#include "octave-config.h"
+
+#include "graphics.h"
+#include "gtk-manager.h"
+
+OCTAVE_BEGIN_NAMESPACE(octave)
+
+class OCTINTERP_API gh_manager
+{
+public:
+
+  typedef std::pair<uint8NDArray /*pixels*/, std::string /*svg*/> latex_data;
+
+  OCTINTERP_API gh_manager (octave::interpreter& interp);
+
+  // FIXME: eventually eliminate these static functions and access
+  // gh_manager object through the interpreter.
+
+  OCTINTERP_API graphics_handle get_handle (bool integer_figure_handle);
+
+  OCTINTERP_API void free (const graphics_handle& h, bool from_root = false);
+
+  OCTINTERP_API void renumber_figure (const graphics_handle& old_gh,
+                                      const graphics_handle& new_gh);
+
+  graphics_handle lookup (double val) const
+  {
+    const_iterator p = (octave::math::isnan (val)
+                        ? m_handle_map.end () : m_handle_map.find (val));
+
+    return (p != m_handle_map.end ()) ? p->first : graphics_handle ();
+  }
+
+  graphics_handle lookup (const octave_value& val) const
+  {
+    return (val.is_real_scalar ()
+            ? lookup (val.double_value ()) : graphics_handle ());
+  }
+
+  graphics_object get_object (double val) const
+  {
+    return get_object (lookup (val));
+  }
+
+  graphics_object get_object (const graphics_handle& h) const
+  {
+    const_iterator p = (h.ok () ? m_handle_map.find (h) : m_handle_map.end ());
+
+    return (p != m_handle_map.end ()) ? p->second : graphics_object ();
+  }
+
+  OCTINTERP_API graphics_handle
+  make_graphics_handle (const std::string& go_name,
+                        const graphics_handle& p,
+                        bool integer_figure_handle = false,
+                        bool call_createfcn = true,
+                        bool notify_toolkit = true);
+
+  OCTINTERP_API graphics_handle
+  make_figure_handle (double val, bool notify_toolkit = true);
+
+  OCTINTERP_API void push_figure (const graphics_handle& h);
+
+  OCTINTERP_API void pop_figure (const graphics_handle& h);
+
+  graphics_handle current_figure (void) const
+  {
+    graphics_handle retval;
+
+    for (const auto& hfig : m_figure_list)
+      {
+        if (is_handle_visible (hfig))
+          retval = hfig;
+      }
+
+    return retval;
+  }
+
+  Matrix handle_list (bool show_hidden = false)
+  {
+    Matrix retval (1, m_handle_map.size ());
+
+    octave_idx_type i = 0;
+    for (const auto& h_iter : m_handle_map)
+      {
+        graphics_handle h = h_iter.first;
+
+        if (show_hidden || is_handle_visible (h))
+          retval(i++) = h.value ();
+      }
+
+    retval.resize (1, i);
+
+    return retval;
+  }
+
+  void lock (void) { m_graphics_lock.lock (); }
+
+  bool try_lock (void) { return m_graphics_lock.try_lock (); }
+
+  void unlock (void) { m_graphics_lock.unlock (); }
+
+  Matrix figure_handle_list (bool show_hidden = false)
+  {
+    Matrix retval (1, m_figure_list.size ());
+
+    octave_idx_type i = 0;
+    for (const auto& hfig : m_figure_list)
+      {
+        if (show_hidden || is_handle_visible (hfig))
+          retval(i++) = hfig.value ();
+      }
+
+    retval.resize (1, i);
+
+    return retval;
+  }
+
+  OCTINTERP_API void
+  execute_listener (const graphics_handle& h, const octave_value& l);
+
+  void execute_callback (const graphics_handle& h,
+                         const std::string& name,
+                         const octave_value& data = Matrix ())
+  {
+    octave_value cb;
+
+    if (true)
+      {
+        octave::autolock guard (graphics_lock ());
+
+        graphics_object go = get_object (h);
+
+        if (go.valid_object ())
+          cb = go.get (name);
+      }
+
+    execute_callback (h, cb, data);
+  }
+
+  OCTINTERP_API void
+  execute_callback (const graphics_handle& h, const octave_value& cb,
+                    const octave_value& data = Matrix ());
+
+  OCTINTERP_API void
+  post_callback (const graphics_handle& h, const std::string& name,
+                 const octave_value& data = Matrix ());
+
+  OCTINTERP_API void
+  post_function (graphics_event::event_fcn fcn, void *fcn_data = nullptr);
+
+  OCTINTERP_API void
+  post_set (const graphics_handle& h, const std::string& name,
+            const octave_value& value, bool notify_toolkit = true,
+            bool redraw_figure = false);
+
+  OCTINTERP_API int process_events (bool force = false);
+
+  OCTINTERP_API void enable_event_processing (bool enable = true);
+
+  bool is_handle_visible (const graphics_handle& h) const
+  {
+    bool retval = false;
+
+    graphics_object go = get_object (h);
+
+    if (go.valid_object ())
+      retval = go.is_handle_visible ();
+
+    return retval;
+  }
+
+  OCTINTERP_API void close_all_figures (void);
+
+  OCTINTERP_API void restore_gcbo (void);
+
+  OCTINTERP_API void post_event (const graphics_event& e);
+
+  octave::mutex graphics_lock (void)
+  {
+    return m_graphics_lock;
+  }
+
+  latex_data get_latex_data (const std::string& key) const
+  {
+    latex_data retval;
+
+    const auto it = m_latex_cache.find (key);
+
+    if (it != m_latex_cache.end ())
+      retval = it->second;
+
+    return retval;
+  }
+
+  void set_latex_data (const std::string& key, latex_data val)
+  {
+    // Limit the number of cache entries to 500
+    if (m_latex_keys.size () >= 500)
+      {
+        auto it = m_latex_cache.find (m_latex_keys.front ());
+
+        if (it != m_latex_cache.end ())
+          m_latex_cache.erase (it);
+
+        m_latex_keys.pop_front ();
+      }
+
+    m_latex_cache[key] = val;
+    m_latex_keys.push_back (key);
+  }
+
+private:
+
+  typedef std::map<graphics_handle, graphics_object>::iterator iterator;
+  typedef std::map<graphics_handle, graphics_object>::const_iterator
+    const_iterator;
+
+  typedef std::set<graphics_handle>::iterator free_list_iterator;
+  typedef std::set<graphics_handle>::const_iterator const_free_list_iterator;
+
+  typedef std::list<graphics_handle>::iterator figure_list_iterator;
+  typedef std::list<graphics_handle>::const_iterator const_figure_list_iterator;
+
+  octave::interpreter& m_interpreter;
+
+  // A map of handles to graphics objects.
+  std::map<graphics_handle, graphics_object> m_handle_map;
+
+  // The available graphics handles.
+  std::set<graphics_handle> m_handle_free_list;
+
+  // The next handle available if m_handle_free_list is empty.
+  double m_next_handle;
+
+  // The allocated figure handles.  Top of the stack is most recently
+  // created.
+  std::list<graphics_handle> m_figure_list;
+
+  // The lock for accessing the graphics sytsem.
+  octave::mutex m_graphics_lock;
+
+  // The list of events queued by graphics toolkits.
+  std::list<graphics_event> m_event_queue;
+
+  // The stack of callback objects.
+  std::list<graphics_object> m_callback_objects;
+
+  // A flag telling whether event processing must be constantly on.
+  int m_event_processing;
+
+  // Cache of already parsed latex strings. Store a separate list of keys
+  // to allow for erasing oldest entries if cache size becomes too large.
+  std::unordered_map<std::string, latex_data> m_latex_cache;
+  std::list<std::string> m_latex_keys;
+};
+
+OCTAVE_END_NAMESPACE(octave)
+
+#endif
--- a/libinterp/corefcn/gl-render.cc	Sat Dec 10 00:11:20 2022 -0500
+++ b/libinterp/corefcn/gl-render.cc	Sat Dec 10 00:30:20 2022 -0500
@@ -40,6 +40,7 @@
 #include "oct-locbuf.h"
 
 #include "errwarn.h"
+#include "gh-manager.h"
 #include "gl-render.h"
 #include "interpreter-private.h"
 #include "oct-opengl.h"
--- a/libinterp/corefcn/gl2ps-print.cc	Sat Dec 10 00:11:20 2022 -0500
+++ b/libinterp/corefcn/gl2ps-print.cc	Sat Dec 10 00:30:20 2022 -0500
@@ -47,6 +47,7 @@
 #include "unistr-wrappers.h"
 #include "unwind-prot.h"
 
+#include "gh-manager.h"
 #include "gl-render.h"
 #include "interpreter-private.h"
 #include "oct-opengl.h"
--- a/libinterp/corefcn/graphics-toolkit.cc	Sat Dec 10 00:11:20 2022 -0500
+++ b/libinterp/corefcn/graphics-toolkit.cc	Sat Dec 10 00:30:20 2022 -0500
@@ -27,6 +27,7 @@
 #  include "config.h"
 #endif
 
+#include "gh-manager.h"
 #include "graphics.h"
 #include "gtk-manager.h"
 #include "interpreter-private.h"
--- a/libinterp/corefcn/graphics-utils.cc	Sat Dec 10 00:11:20 2022 -0500
+++ b/libinterp/corefcn/graphics-utils.cc	Sat Dec 10 00:30:20 2022 -0500
@@ -29,6 +29,7 @@
 
 #include "caseless-str.h"
 
+#include "gh-manager.h"
 #include "graphics-utils.h"
 #include "graphics.h"
 #include "input.h"
--- a/libinterp/corefcn/graphics.cc	Sat Dec 10 00:11:20 2022 -0500
+++ b/libinterp/corefcn/graphics.cc	Sat Dec 10 00:30:20 2022 -0500
@@ -51,6 +51,7 @@
 #include "defun.h"
 #include "display.h"
 #include "error.h"
+#include "gh-manager.h"
 #include "graphics-utils.h"
 #include "graphics.h"
 #include "input.h"
@@ -1200,10 +1201,10 @@
   return result;
 }
 
-static base_graphics_object *
+base_graphics_object *
 make_graphics_object_from_type (const caseless_str& type,
-                                const graphics_handle& h = graphics_handle (),
-                                const graphics_handle& p = graphics_handle ())
+                                const graphics_handle& h,
+                                const graphics_handle& p)
 {
   base_graphics_object *go = nullptr;
 
@@ -2816,147 +2817,6 @@
 %! end_unwind_protect
 */
 
-static double
-make_handle_fraction (void)
-{
-  static double maxrand = RAND_MAX + 2.0;
-
-  return (rand () + 1.0) / maxrand;
-}
-
-graphics_handle
-gh_manager::get_handle (bool integer_figure_handle)
-{
-  graphics_handle retval;
-
-  if (integer_figure_handle)
-    {
-      // Figure handles are positive integers corresponding
-      // to the figure number.
-
-      // We always want the lowest unused figure number.
-
-      retval = 1;
-
-      while (m_handle_map.find (retval) != m_handle_map.end ())
-        retval++;
-    }
-  else
-    {
-      // Other graphics handles are negative integers plus some random
-      // fractional part.  To avoid running out of integers, we recycle the
-      // integer part but tack on a new random part each time.
-
-      auto p = m_handle_free_list.begin ();
-
-      if (p != m_handle_free_list.end ())
-        {
-          retval = *p;
-          m_handle_free_list.erase (p);
-        }
-      else
-        {
-          retval = graphics_handle (m_next_handle);
-
-          m_next_handle = std::ceil (m_next_handle) - 1.0 - make_handle_fraction ();
-        }
-    }
-
-  return retval;
-}
-
-void
-gh_manager::free (const graphics_handle& h, bool from_root)
-{
-  if (h.ok ())
-    {
-      if (h.value () == 0)
-        error ("graphics_handle::free: can't delete root object");
-
-      auto p = m_handle_map.find (h);
-
-      if (p == m_handle_map.end ())
-        error ("graphics_handle::free: invalid object %g", h.value ());
-
-      base_properties& bp = p->second.get_properties ();
-
-      if (! p->second.valid_object () || bp.is_beingdeleted ())
-        return;
-
-      graphics_handle parent_h = p->second.get_parent ();
-      graphics_object parent_go = nullptr;
-      if (! from_root || isfigure (h.value ()))
-        parent_go = get_object (parent_h);
-
-      bp.set_beingdeleted (true);
-
-      // delete listeners before invalidating object
-      p->second.remove_all_listeners ();
-
-      bp.delete_children (true, from_root);
-
-      // NOTE: Call the delete function while the object's state is still valid.
-      octave_value val = bp.get_deletefcn ();
-
-      bp.execute_deletefcn ();
-
-      // Notify graphics toolkit.
-      p->second.finalize ();
-
-
-      // NOTE: Call remove_child before erasing the go from the map if not
-      // removing from groot.
-      // A callback function might have already deleted the parent
-      if ((! from_root || isfigure (h.value ())) && parent_go.valid_object ()
-          && h.ok ())
-        parent_go.remove_child (h);
-
-      // Note: this will be valid only for first explicitly deleted
-      // object.  All its children will then have an
-      // unknown graphics toolkit.
-
-      // Graphics handles for non-figure objects are negative
-      // integers plus some random fractional part.  To avoid
-      // running out of integers, we recycle the integer part
-      // but tack on a new random part each time.
-
-      m_handle_map.erase (p);
-
-      if (h.value () < 0)
-        m_handle_free_list.insert
-        (std::ceil (h.value ()) - make_handle_fraction ());
-    }
-}
-
-void
-gh_manager::renumber_figure (const graphics_handle& old_gh,
-                             const graphics_handle& new_gh)
-{
-  auto p = m_handle_map.find (old_gh);
-
-  if (p == m_handle_map.end ())
-    error ("graphics_handle::free: invalid object %g", old_gh.value ());
-
-  graphics_object go = p->second;
-
-  m_handle_map.erase (p);
-
-  m_handle_map[new_gh] = go;
-
-  if (old_gh.value () < 0)
-    m_handle_free_list.insert (std::ceil (old_gh.value ())
-                               - make_handle_fraction ());
-
-  for (auto& hfig : m_figure_list)
-    {
-      if (hfig == old_gh)
-        {
-          hfig = new_gh;
-          break;
-        }
-    }
-}
-
 // This function is NOT equivalent to the scripting language function gcf.
 graphics_handle
 gcf (void)
@@ -2977,50 +2837,6 @@
          : val.double_value ();
 }
 
-void
-gh_manager::close_all_figures (void)
-{
-  // FIXME: should we process or discard pending events?
-
-  m_event_queue.clear ();
-
-  // Don't use m_figure_list_iterator because we'll be removing elements
-  // from the list elsewhere.
-
-  Matrix hlist = figure_handle_list (true);
-
-  for (octave_idx_type i = 0; i < hlist.numel (); i++)
-    {
-      graphics_handle h = lookup (hlist(i));
-
-      if (h.ok ())
-        close_figure (h);
-    }
-
-  // They should all be closed now.  If not, force them to close.
-
-  hlist = figure_handle_list (true);
-
-  for (octave_idx_type i = 0; i < hlist.numel (); i++)
-    {
-      graphics_handle h = lookup (hlist(i));
-
-      if (h.ok ())
-        force_close_figure (h);
-    }
-
-  // None left now, right?
-
-  hlist = figure_handle_list (true);
-
-  if (hlist.numel () != 0)
-    warning ("gh_manager::close_all_figures: some graphics elements failed to close");
-
-  // Clear all callback objects from our list.
-
-  m_callback_objects.clear ();
-}
-
 static void
 adopt (const graphics_handle& parent_h, const graphics_handle& h)
 {
@@ -11753,118 +11569,6 @@
   return parent_go.get_factory_default (type () + name);
 }
 
-// We use a random value for the handle to avoid issues with plots and
-// scalar values for the first argument.
-gh_manager::gh_manager (octave::interpreter& interp)
-  : m_interpreter (interp), m_handle_map (), m_handle_free_list (),
-    m_next_handle (-1.0 - (rand () + 1.0) / (RAND_MAX + 2.0)),
-    m_figure_list (), m_graphics_lock (),  m_event_queue (),
-    m_callback_objects (), m_event_processing (0)
-{
-  m_handle_map[0] = graphics_object (new root_figure ());
-
-  octave::gtk_manager& gtk_mgr = octave::__get_gtk_manager__ ();
-
-  // Make sure the default graphics toolkit is registered.
-  gtk_mgr.default_toolkit ();
-}
-
-graphics_handle
-gh_manager::make_graphics_handle (const std::string& go_name,
-                                  const graphics_handle& p,
-                                  bool integer_figure_handle,
-                                  bool call_createfcn, bool notify_toolkit)
-{
-  graphics_handle h = get_handle (integer_figure_handle);
-
-  base_graphics_object *bgo = make_graphics_object_from_type (go_name, h, p);
-
-  if (! bgo)
-    error ("gh_manager::make_graphics_handle: invalid object type '%s'",
-           go_name.c_str ());
-
-  graphics_object go (bgo);
-
-  m_handle_map[h] = go;
-
-  if (go_name == "axes")
-    {
-      // Handle defaults for labels since overriding defaults for
-      // them can't work before the axes object is fully
-      // constructed.
-
-      axes::properties& props
-        = dynamic_cast<axes::properties&> (go.get_properties ());
-
-      graphics_object tgo;
-
-      tgo = get_object (props.get_xlabel ());
-      tgo.override_defaults ();
-
-      tgo = get_object (props.get_ylabel ());
-      tgo.override_defaults ();
-
-      tgo = get_object (props.get_zlabel ());
-      tgo.override_defaults ();
-
-      tgo = get_object (props.get_title ());
-      tgo.override_defaults ();
-    }
-
-  // Overriding defaults will work now because the handle is valid
-  // and we can find parent objects (not just handles).
-  go.override_defaults ();
-
-  if (call_createfcn)
-    bgo->get_properties ().execute_createfcn ();
-
-  // Notify graphics toolkit.
-  if (notify_toolkit)
-    go.initialize ();
-
-  return h;
-}
-
-graphics_handle
-gh_manager::make_figure_handle (double val, bool notify_toolkit)
-{
-  graphics_handle h = val;
-
-  base_graphics_object *bgo = new figure (h, 0);
-  graphics_object go (bgo);
-
-  m_handle_map[h] = go;
-
-  // Notify graphics toolkit.
-  if (notify_toolkit)
-    go.initialize ();
-
-  go.override_defaults ();
-
-  return h;
-}
-
-void
-gh_manager::push_figure (const graphics_handle& h)
-{
-  pop_figure (h);
-
-  m_figure_list.push_front (h);
-}
-
-void
-gh_manager::pop_figure (const graphics_handle& h)
-{
-  for (auto it = m_figure_list.begin (); it != m_figure_list.end (); it++)
-    {
-      if (*it == h)
-        {
-          m_figure_list.erase (it);
-          break;
-        }
-    }
-}
-
 class
 callback_event : public base_graphics_event
 {
@@ -12089,373 +11793,6 @@
                                         redraw_figure));
 }
 
-static void
-xset_gcbo (const graphics_handle& h)
-{
-  gh_manager& gh_mgr = octave::__get_gh_manager__ ();
-
-  graphics_object go = gh_mgr.get_object (0);
-
-  root_figure::properties& props
-    = dynamic_cast<root_figure::properties&> (go.get_properties ());
-
-  props.set_callbackobject (h.as_octave_value ());
-}
-
-void
-gh_manager::restore_gcbo (void)
-{
-  octave::autolock guard (m_graphics_lock);
-
-  m_callback_objects.pop_front ();
-
-  xset_gcbo (m_callback_objects.empty ()
-             ? graphics_handle () : m_callback_objects.front ().get_handle ());
-}
-
-void
-gh_manager::execute_listener (const graphics_handle& h, const octave_value& l)
-{
-  if (octave::thread::is_thread ())
-    execute_callback (h, l, octave_value ());
-  else
-    {
-      octave::autolock guard (m_graphics_lock);
-
-      post_event (graphics_event::create_callback_event (h, l));
-    }
-}
-
-void
-gh_manager::execute_callback (const graphics_handle& h,
-                              const octave_value& cb_arg,
-                              const octave_value& data)
-{
-  if (cb_arg.is_defined () && ! cb_arg.isempty ())
-    {
-      octave_value_list args;
-      octave_value ov_fcn;
-      octave_function *fcn = nullptr;
-
-      args(0) = h.as_octave_value ();
-      if (data.is_defined ())
-        args(1) = data;
-      else
-        args(1) = Matrix ();
-
-      octave::unwind_action_safe restore_gcbo_action
-      (&gh_manager::restore_gcbo, this);
-
-      graphics_object go (get_object (h));
-      if (go)
-        {
-          // FIXME: Is the lock necessary when we're only calling a
-          //        const "get" method?
-          octave::autolock guard (m_graphics_lock);
-          m_callback_objects.push_front (go);
-          xset_gcbo (h);
-        }
-
-      // Copy CB because "function_value" method is non-const.
-      octave_value cb = cb_arg;
-
-      if (cb.is_function ())
-        fcn = cb.function_value ();
-      else if (cb.is_function_handle ())
-        ov_fcn = cb;
-      else if (cb.is_string ())
-        {
-          int status;
-          std::string s = cb.string_value ();
-
-          try
-            {
-              m_interpreter.eval_string (s, false, status, 0);
-            }
-          catch (const octave::execution_exception& ee)
-            {
-              m_interpreter.handle_exception (ee);
-            }
-        }
-      else if (cb.iscell () && cb.length () > 0
-               && (cb.rows () == 1 || cb.columns () == 1)
-               && (cb.cell_value ()(0).is_function ()
-                   || cb.cell_value ()(0).is_function_handle ()))
-        {
-          Cell c = cb.cell_value ();
-
-          ov_fcn = c(0);
-
-          for (int i = 1; i < c.numel () ; i++)
-            args(1+i) = c(i);
-        }
-      else
-        {
-          std::string nm = cb.class_name ();
-          error ("trying to execute non-executable object (class = %s)",
-                 nm.c_str ());
-        }
-
-      if (fcn || ov_fcn.is_defined ())
-        try
-          {
-            if (ov_fcn.is_defined ())
-              octave::feval (ov_fcn, args);
-            else
-              octave::feval (fcn, args);
-          }
-        catch (const octave::execution_exception& ee)
-          {
-            m_interpreter.handle_exception (ee);
-          }
-
-      // Redraw after interacting with a user-interface (ui*) object.
-      if (Vdrawnow_requested)
-        {
-          if (go)
-            {
-              std::string go_name
-                = go.get_properties ().graphics_object_name ();
-
-              if (go_name.length () > 1
-                  && go_name[0] == 'u' && go_name[1] == 'i')
-                {
-                  Fdrawnow (m_interpreter);
-                  Vdrawnow_requested = false;
-                }
-            }
-        }
-    }
-}
-
-static int
-process_graphics_events (void)
-{
-  gh_manager& gh_mgr = octave::__get_gh_manager__ ();
-
-  return gh_mgr.process_events ();
-}
-
-void
-gh_manager::post_event (const graphics_event& e)
-{
-  m_event_queue.push_back (e);
-
-  octave::command_editor::add_event_hook (process_graphics_events);
-}
-
-void
-gh_manager::post_callback (const graphics_handle& h, const std::string& name,
-                           const octave_value& data)
-{
-  octave::autolock guard (m_graphics_lock);
-
-  graphics_object go = get_object (h);
-
-  if (go.valid_object ())
-    {
-      caseless_str cname (name);
-      int busyaction = base_graphics_event::QUEUE;
-
-      if (cname == "deletefcn" || cname == "createfcn"
-          || cname == "closerequestfcn"
-          || ((go.isa ("figure") || go.isa ("uipanel")
-               || go.isa ("uibuttongroup"))
-              && (cname == "resizefcn" || cname == "sizechangedfcn")))
-        busyaction = base_graphics_event::INTERRUPT;
-      else if (go.get_properties ().get_busyaction () == "cancel")
-        busyaction = base_graphics_event::CANCEL;
-
-      // The "closerequestfcn" callback must be executed once the figure has
-      // been made current.  Let "close" do the job.
-      if (cname == "closerequestfcn")
-        {
-          std::string cmd ("close (gcbf ());");
-          post_event (graphics_event::create_mcode_event (h, cmd, busyaction));
-        }
-      else
-        post_event (graphics_event::create_callback_event (h, name, data,
-                    busyaction));
-    }
-}
-
-void
-gh_manager::post_function (graphics_event::event_fcn fcn, void *fcn_data)
-{
-  octave::autolock guard (m_graphics_lock);
-
-  post_event (graphics_event::create_function_event (fcn, fcn_data));
-}
-
-void
-gh_manager::post_set (const graphics_handle& h, const std::string& name,
-                      const octave_value& value, bool notify_toolkit,
-                      bool redraw_figure)
-{
-  octave::autolock guard (m_graphics_lock);
-
-  post_event (graphics_event::create_set_event (h, name, value, notify_toolkit,
-              redraw_figure));
-}
-
-int
-gh_manager::process_events (bool force)
-{
-  graphics_event e;
-  bool old_Vdrawnow_requested = Vdrawnow_requested;
-  bool events_executed = false;
-
-  do
-    {
-      e = graphics_event ();
-
-      {
-        octave::autolock guard (m_graphics_lock);
-
-        if (! m_event_queue.empty ())
-          {
-            if (m_callback_objects.empty () || force)
-              {
-                e = m_event_queue.front ();
-
-                m_event_queue.pop_front ();
-              }
-            else
-              {
-                const graphics_object& go = m_callback_objects.front ();
-
-                if (go.get_properties ().is_interruptible ())
-                  {
-                    e = m_event_queue.front ();
-
-                    m_event_queue.pop_front ();
-                  }
-                else
-                  {
-                    std::list<graphics_event>::iterator p = m_event_queue.begin ();
-
-                    while (p != m_event_queue.end ())
-                      if (p->get_busyaction () == base_graphics_event::CANCEL)
-                        {
-                          p = m_event_queue.erase (p);
-                        }
-                      else if (p->get_busyaction ()
-                               == base_graphics_event::INTERRUPT)
-                        {
-                          e = (*p);
-                          m_event_queue.erase (p);
-                          break;
-                        }
-                      else
-                        p++;
-                  }
-              }
-          }
-      }
-
-      if (e.ok ())
-        {
-          e.execute ();
-          events_executed = true;
-        }
-    }
-  while (e.ok ());
-
-  {
-    octave::autolock guard (m_graphics_lock);
-
-    if (m_event_queue.empty () && m_event_processing == 0)
-      octave::command_editor::remove_event_hook (process_graphics_events);
-  }
-
-  if (events_executed)
-    octave::flush_stdout ();
-
-  if (Vdrawnow_requested && ! old_Vdrawnow_requested)
-    {
-      Fdrawnow (m_interpreter);
-
-      Vdrawnow_requested = false;
-    }
-
-  return 0;
-}
-
-
-/*
-## Test interruptible/busyaction properties
-%!function cb (h, ~)
-%! setappdata (gcbf (), "cb_exec", [getappdata(gcbf (), "cb_exec") h]);
-%! drawnow ();
-%! setappdata (gcbf (), "cb_exec", [getappdata(gcbf (), "cb_exec") h]);
-%!endfunction
-%!
-%!testif HAVE_OPENGL, HAVE_QT; have_window_system () && any (strcmp ("qt", available_graphics_toolkits ()))
-%! hf = figure ("visible", "off", "resizefcn", @cb);
-%! graphics_toolkit (hf, "qt");
-%! unwind_protect
-%!   ## Default
-%!   hui1 = uicontrol ("parent", hf, "interruptible", "on", "callback", @cb);
-%!   hui2 = uicontrol ("parent", hf, "busyaction", "queue", "callback", @cb);
-%!   hui3 = uicontrol ("parent", hf, "busyaction", "queue", "callback", @cb);
-%!   __go_post_callback__ (hui1, "callback");
-%!   __go_post_callback__ (hui2, "callback");
-%!   __go_post_callback__ (hui3, "callback");
-%!
-%!   assert (getappdata (hf, "cb_exec"), []);
-%!   drawnow ();
-%!   assert (getappdata (hf, "cb_exec"), [hui1 hui2 hui3 hui3 hui2 hui1]);
-%!
-%!   ## Interruptible off
-%!   setappdata (hf, "cb_exec", []);
-%!   set (hui1, "interruptible", "off");
-%!   __go_post_callback__ (hui1, "callback");
-%!   __go_post_callback__ (hui2, "callback");
-%!   __go_post_callback__ (hui3, "callback");
-%!   drawnow ();
-%!   assert (getappdata (hf, "cb_exec"), [hui1 hui1 hui2 hui3 hui3 hui2]);
-%!
-%!   ## "resizefcn" callback interrupts regardless of interruptible property
-%!   setappdata (hf, "cb_exec", []);
-%!   __go_post_callback__ (hui1, "callback");
-%!   __go_post_callback__ (hf, "resizefcn");
-%!   drawnow ();
-%!   assert (getappdata (hf, "cb_exec"), [hui1 hf hf hui1]);
-%!
-%!   ## test "busyaction" "cancel"
-%!   setappdata (hf, "cb_exec", []);
-%!   set (hui2, "busyaction", "cancel");
-%!   __go_post_callback__ (hui1, "callback");
-%!   __go_post_callback__ (hui2, "callback");
-%!   __go_post_callback__ (hui3, "callback");
-%!   __go_post_callback__ (hf, "resizefcn");
-%!   drawnow ();
-%!   assert (getappdata (hf, "cb_exec"), [hui1 hf hui3 hui3 hf hui1]);
-%! unwind_protect_cleanup
-%!   close (hf)
-%! end_unwind_protect
-*/
-
-void
-gh_manager::enable_event_processing (bool enable)
-{
-  octave::autolock guard (m_graphics_lock);
-
-  if (enable)
-    {
-      m_event_processing++;
-
-      octave::command_editor::add_event_hook (process_graphics_events);
-    }
-  else
-    {
-      m_event_processing--;
-
-      if (m_event_queue.empty () && m_event_processing == 0)
-        octave::command_editor::remove_event_hook (process_graphics_events);
-    }
-}
-
 property_list::plist_map_type
 root_figure::init_factory_properties (void)
 {
--- a/libinterp/corefcn/graphics.in.h	Sat Dec 10 00:11:20 2022 -0500
+++ b/libinterp/corefcn/graphics.in.h	Sat Dec 10 00:30:20 2022 -0500
@@ -6691,255 +6691,10 @@
   std::shared_ptr <base_graphics_event> m_rep;
 };
 
-class OCTINTERP_API gh_manager
-{
-public:
-
-  typedef std::pair<uint8NDArray /*pixels*/, std::string /*svg*/> latex_data;
-
-  OCTINTERP_API gh_manager (octave::interpreter& interp);
-
-  // FIXME: eventually eliminate these static functions and access
-  // gh_manager object through the interpreter.
-
-  OCTINTERP_API graphics_handle get_handle (bool integer_figure_handle);
-
-  OCTINTERP_API void free (const graphics_handle& h, bool from_root = false);
-
-  OCTINTERP_API void renumber_figure (const graphics_handle& old_gh,
-                                      const graphics_handle& new_gh);
-
-  graphics_handle lookup (double val) const
-  {
-    const_iterator p = (octave::math::isnan (val)
-                        ? m_handle_map.end () : m_handle_map.find (val));
-
-    return (p != m_handle_map.end ()) ? p->first : graphics_handle ();
-  }
-
-  graphics_handle lookup (const octave_value& val) const
-  {
-    return (val.is_real_scalar ()
-            ? lookup (val.double_value ()) : graphics_handle ());
-  }
-
-  graphics_object get_object (double val) const
-  {
-    return get_object (lookup (val));
-  }
-
-  graphics_object get_object (const graphics_handle& h) const
-  {
-    const_iterator p = (h.ok () ? m_handle_map.find (h) : m_handle_map.end ());
-
-    return (p != m_handle_map.end ()) ? p->second : graphics_object ();
-  }
-
-  OCTINTERP_API graphics_handle
-  make_graphics_handle (const std::string& go_name,
-                        const graphics_handle& p,
-                        bool integer_figure_handle = false,
-                        bool call_createfcn = true,
-                        bool notify_toolkit = true);
-
-  OCTINTERP_API graphics_handle
-  make_figure_handle (double val, bool notify_toolkit = true);
-
-  OCTINTERP_API void push_figure (const graphics_handle& h);
-
-  OCTINTERP_API void pop_figure (const graphics_handle& h);
-
-  graphics_handle current_figure (void) const
-  {
-    graphics_handle retval;
-
-    for (const auto& hfig : m_figure_list)
-      {
-        if (is_handle_visible (hfig))
-          retval = hfig;
-      }
-
-    return retval;
-  }
-
-  Matrix handle_list (bool show_hidden = false)
-  {
-    Matrix retval (1, m_handle_map.size ());
-
-    octave_idx_type i = 0;
-    for (const auto& h_iter : m_handle_map)
-      {
-        graphics_handle h = h_iter.first;
-
-        if (show_hidden || is_handle_visible (h))
-          retval(i++) = h.value ();
-      }
-
-    retval.resize (1, i);
-
-    return retval;
-  }
-
-  void lock (void) { m_graphics_lock.lock (); }
-
-  bool try_lock (void) { return m_graphics_lock.try_lock (); }
-
-  void unlock (void) { m_graphics_lock.unlock (); }
-
-  Matrix figure_handle_list (bool show_hidden = false)
-  {
-    Matrix retval (1, m_figure_list.size ());
-
-    octave_idx_type i = 0;
-    for (const auto& hfig : m_figure_list)
-      {
-        if (show_hidden || is_handle_visible (hfig))
-          retval(i++) = hfig.value ();
-      }
-
-    retval.resize (1, i);
-
-    return retval;
-  }
-
-  OCTINTERP_API void
-  execute_listener (const graphics_handle& h, const octave_value& l);
-
-  void execute_callback (const graphics_handle& h,
-                         const std::string& name,
-                         const octave_value& data = Matrix ())
-  {
-    octave_value cb;
-
-    if (true)
-      {
-        octave::autolock guard (graphics_lock ());
-
-        graphics_object go = get_object (h);
-
-        if (go.valid_object ())
-          cb = go.get (name);
-      }
-
-    execute_callback (h, cb, data);
-  }
-
-  OCTINTERP_API void
-  execute_callback (const graphics_handle& h, const octave_value& cb,
-                    const octave_value& data = Matrix ());
-
-  OCTINTERP_API void
-  post_callback (const graphics_handle& h, const std::string& name,
-                 const octave_value& data = Matrix ());
-
-  OCTINTERP_API void
-  post_function (graphics_event::event_fcn fcn, void *fcn_data = nullptr);
-
-  OCTINTERP_API void
-  post_set (const graphics_handle& h, const std::string& name,
-            const octave_value& value, bool notify_toolkit = true,
-            bool redraw_figure = false);
-
-  OCTINTERP_API int process_events (bool force = false);
-
-  OCTINTERP_API void enable_event_processing (bool enable = true);
-
-  bool is_handle_visible (const graphics_handle& h) const
-  {
-    bool retval = false;
-
-    graphics_object go = get_object (h);
-
-    if (go.valid_object ())
-      retval = go.is_handle_visible ();
-
-    return retval;
-  }
-
-  OCTINTERP_API void close_all_figures (void);
-
-  OCTINTERP_API void restore_gcbo (void);
-
-  OCTINTERP_API void post_event (const graphics_event& e);
-
-  octave::mutex graphics_lock (void)
-  {
-    return m_graphics_lock;
-  }
-
-  latex_data get_latex_data (const std::string& key) const
-  {
-    latex_data retval;
-
-    const auto it = m_latex_cache.find (key);
-
-    if (it != m_latex_cache.end ())
-      retval = it->second;
-
-    return retval;
-  }
-
-  void set_latex_data (const std::string& key, latex_data val)
-  {
-    // Limit the number of cache entries to 500
-    if (m_latex_keys.size () >= 500)
-      {
-        auto it = m_latex_cache.find (m_latex_keys.front ());
-
-        if (it != m_latex_cache.end ())
-          m_latex_cache.erase (it);
-
-        m_latex_keys.pop_front ();
-      }
-
-    m_latex_cache[key] = val;
-    m_latex_keys.push_back (key);
-  }
-
-private:
-
-  typedef std::map<graphics_handle, graphics_object>::iterator iterator;
-  typedef std::map<graphics_handle, graphics_object>::const_iterator
-    const_iterator;
-
-  typedef std::set<graphics_handle>::iterator free_list_iterator;
-  typedef std::set<graphics_handle>::const_iterator const_free_list_iterator;
-
-  typedef std::list<graphics_handle>::iterator figure_list_iterator;
-  typedef std::list<graphics_handle>::const_iterator const_figure_list_iterator;
-
-  octave::interpreter& m_interpreter;
-
-  // A map of handles to graphics objects.
-  std::map<graphics_handle, graphics_object> m_handle_map;
-
-  // The available graphics handles.
-  std::set<graphics_handle> m_handle_free_list;
-
-  // The next handle available if m_handle_free_list is empty.
-  double m_next_handle;
-
-  // The allocated figure handles.  Top of the stack is most recently
-  // created.
-  std::list<graphics_handle> m_figure_list;
-
-  // The lock for accessing the graphics sytsem.
-  octave::mutex m_graphics_lock;
-
-  // The list of events queued by graphics toolkits.
-  std::list<graphics_event> m_event_queue;
-
-  // The stack of callback objects.
-  std::list<graphics_object> m_callback_objects;
-
-  // A flag telling whether event processing must be constantly on.
-  int m_event_processing;
-
-  // Cache of already parsed latex strings. Store a separate list of keys
-  // to allow for erasing oldest entries if cache size becomes too large.
-  std::unordered_map<std::string, latex_data> m_latex_cache;
-  std::list<std::string> m_latex_keys;
-};
+OCTINTERP_API base_graphics_object *
+make_graphics_object_from_type (const caseless_str& type,
+                                const graphics_handle& h = graphics_handle (),
+                                const graphics_handle& p = graphics_handle ());
 
 OCTINTERP_API void
 get_children_limits (double& min_val, double& max_val,
--- a/libinterp/corefcn/interpreter.h	Sat Dec 10 00:11:20 2022 -0500
+++ b/libinterp/corefcn/interpreter.h	Sat Dec 10 00:30:20 2022 -0500
@@ -44,6 +44,7 @@
 #include "environment.h"
 #include "error.h"
 #include "event-manager.h"
+#include "gh-manager.h"
 #include "graphics.h"
 #include "gtk-manager.h"
 #include "help.h"
--- a/libinterp/corefcn/module.mk	Sat Dec 10 00:11:20 2022 -0500
+++ b/libinterp/corefcn/module.mk	Sat Dec 10 00:30:20 2022 -0500
@@ -38,6 +38,7 @@
   %reldir%/fcn-info.h \
   %reldir%/file-io.h \
   %reldir%/ft-text-renderer.h \
+  %reldir%/gh-manager.h \
   %reldir%/gl-render.h \
   %reldir%/gl2ps-print.h \
   %reldir%/graphics-handle.h \
@@ -177,6 +178,7 @@
   %reldir%/getgrent.cc \
   %reldir%/getpwent.cc \
   %reldir%/getrusage.cc \
+  %reldir%/gh-manager.cc \
   %reldir%/givens.cc \
   %reldir%/gl-render.cc \
   %reldir%/gl2ps-print.cc \