# HG changeset patch
# User Remi Thebault <remi.thebault@gmail.com>
# Date 1607255478 -3600
#      Sun Dec 06 12:51:18 2020 +0100
# Node ID d6cea9acecdf869862b62e6599857c6cccb95d02
# Parent  95ec5841c9d6f52ce28c167a7d501078a4123965
Implementation of Property Inspector

HIGHLIGHTS

  * a widget with two main views
    - a tree to display and interact with graphics object tree
    - a table, displaying the properties of the object selected in the
      tree

  * the tree receive notifications of graphics object creation/deletion
    to update itself

  * the table provides display and edition of most types of properties


SHORTCOMINGS

  * Not all property types have yet proper editors/painting

  * Updating properties does not always reflect in the figures.
    - updating line color for example not always trigger redraw.
    - when redraw happens, all previous property editions are visible

  * Currently not usable outside the GUI. It is wished to have the PI usable
    from the terminal through the missing 'inspect' command.

DETAILED CHANGELOG

  * liboctave/util/oct-mutex.h
    - adding unique_lock template class that encapsulate a critical resource
      providing lock/unlock methods. unique_lock is moveable but non-copiable
      and also acts like a smart pointer.
      - not a strictly needed addition, but is used to provide safely the
        gh_manager instance to the GUI

  * libinterp/corefcn/graphics.in.h
    - adding some introspection of graphics properties
      - prop_type() method (returning new property_type enum)
      - as_base_property() method to retrieve a pointer that can be casted
      - as_[type]_property() that dynamic_cast the base property to derived
        type (not used - compiled out at the moment)

  * libinterp/corefcn/event-manager.h
    - adding lifetime events of graphics objects in the interpreter events
      interface. Needed to track creation and removal of graphics objects in
      the tree.

  * libinterp/corefcn/graphics.cc
   - fire the graphics objects lifetime events

  * libui/src/qt-interpreter-events.h
  * libui/src/qt-interpreter-events.cc
    - adding signals for the new graphics objects

  * libgui/src/interpreter-qobject.h
  * libgui/src/interpreter-qobject.cc
    - provide access to the gh_manager through a unique_lock
    - the GUI cannot use the gh_manager without locking it
    - Not very useful today because the gh_manager is not consistently locked
      in the interpreter thread (e.g. when setting/reading properties).
      This could lead to race conditions (although I didn't observe any).
    - May be a better strategy is to have fine grain (per property) locking.

  * libgui/src/item-hyperlink-delegate.h
  * libgui/src/item-hyperlink-delegate.cc
    - class that makes easy having hyperlinks in item models, without having
      to instantiate one widget per cell.

  * libgui/src/property-inspector.h
  * libgui/src/property-inspector.cc
    - implementation of the property inspector
      - simple widget that instantiate the more complex views and models and
        make them talk together

  * libgui/src/property-inspector-tree.h
  * libgui/src/property-inspector-tree.cc
    - implementation of the graphics handle tree model

  * libgui/src/property-inspector-table.h
  * libgui/src/property-inspector-table.cc
    - implementation of the graphics properties table model and item delegate.
    - to keep code flexible, I tried to take the best decisions based on the
      octave_value of the property rather than based on the derived property
      type, but this is not always possible. (hence the properties
      introspection)
    - there (at the moment) two specific widgets used as cell editors:
      - matrix_widget
      - color_editor_widget (this one use a *.ui designer file)

  * libgui/src/property-inspector-color-editor.ui
    - Qt Designer file for the color_editor_widget

  * libgui/src/main-window.h
  * libgui/src/main-window.cc
  * libgui/src/gui-preferences-sc.h
    - instantiate the property inspector and setup shortcuts

  * libgui/src/module.mk
    - adding new property inspector files

diff --git a/libgui/src/gui-preferences-all.h b/libgui/src/gui-preferences-all.h
--- a/libgui/src/gui-preferences-all.h
+++ b/libgui/src/gui-preferences-all.h
@@ -37,6 +37,7 @@
 #include "gui-preferences-mw.h"
 #include "gui-preferences-nr.h"
 #include "gui-preferences-pd.h"
+#include "gui-preferences-pi.h"
 #include "gui-preferences-ve.h"
 #include "gui-preferences-sc.h"
 #include "gui-preferences-sd.h"
diff --git a/libgui/src/gui-preferences-pi.h b/libgui/src/gui-preferences-pi.h
new file mode 100644
--- /dev/null
+++ b/libgui/src/gui-preferences-pi.h
@@ -0,0 +1,45 @@
+////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2017-2020 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_gui_preferences_pi_h)
+#define octave_gui_preferences_pi_h 1
+
+#include "gui-preferences.h"
+
+#include <QApplication>
+
+// Variable Editor preferences
+
+const gui_pref
+pi_tree_alternate_rows ("property_inspector/tree_alternate_rows", QVariant (false));
+
+const gui_pref
+pi_table_row_height ("property_inspector/table_row_height", QVariant ());
+
+const gui_pref
+pi_table_alternate_rows ("property_inspector/table_alternate_rows", QVariant (false));
+
+
+#endif // octave_gui_preferences_pi_h
diff --git a/libgui/src/gui-preferences-sc.h b/libgui/src/gui-preferences-sc.h
--- a/libgui/src/gui-preferences-sc.h
+++ b/libgui/src/gui-preferences-sc.h
@@ -112,6 +112,7 @@ const sc_pref sc_main_window_show_worksp
 const sc_pref sc_main_window_show_editor (sc_main_window + ":show_editor", PRE + CTRL_SHIFT + Qt::Key_4);
 const sc_pref sc_main_window_show_doc (sc_main_window + ":show_doc", PRE + CTRL_SHIFT + Qt::Key_5);
 const sc_pref sc_main_window_show_variable_editor (sc_main_window + ":show_variable_editor", PRE + CTRL_SHIFT + Qt::Key_6);
+const sc_pref sc_main_window_show_property_inspector (sc_main_window + ":show_property_inspector", PRE + CTRL_SHIFT + Qt::Key_7);
 const sc_pref sc_main_window_command (sc_main_window + ":command", PRE + CTRL + Qt::Key_0);
 const sc_pref sc_main_window_history (sc_main_window + ":history", PRE + CTRL + Qt::Key_1);
 const sc_pref sc_main_window_file_browser (sc_main_window + ":file_browser", PRE + CTRL + Qt::Key_2);
@@ -119,6 +120,7 @@ const sc_pref sc_main_window_workspace (
 const sc_pref sc_main_window_editor (sc_main_window + ":editor", PRE + CTRL + Qt::Key_4);
 const sc_pref sc_main_window_doc (sc_main_window + ":doc", PRE + CTRL + Qt::Key_5);
 const sc_pref sc_main_window_variable_editor (sc_main_window + ":variable_editor", PRE + CTRL + Qt::Key_6);
+const sc_pref sc_main_window_property_inspector (sc_main_window + ":property_inspector", PRE + CTRL + Qt::Key_7);
 const sc_pref sc_main_window_previous_dock (sc_main_window + ":previous_widget", PRE + CTRL_ALT + Qt::Key_P);
 const sc_pref sc_main_window_reset (sc_main_window + ":reset", QKeySequence::UnknownKey);
 
diff --git a/libgui/src/icons/hsplitter-handle.png b/libgui/src/icons/hsplitter-handle.png
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..3247c8d0a32ec0d2218c47458f2dab026310e61f
GIT binary patch
diff --git a/libgui/src/icons/hsplitter-handle.svg b/libgui/src/icons/hsplitter-handle.svg
new file mode 100644
--- /dev/null
+++ b/libgui/src/icons/hsplitter-handle.svg
@@ -0,0 +1,1900 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="7"
+   height="75"
+   viewBox="0 0 1.8520833 19.843752"
+   version="1.1"
+   id="svg8"
+   inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)"
+   sodipodi:docname="hsplitter-handle.svg"
+   inkscape:export-filename="/home/remi/dev/octave/libgui/src/icons/hsplitter-handle.png"
+   inkscape:export-xdpi="96"
+   inkscape:export-ydpi="96">
+  <defs
+     id="defs2" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="7.9195959"
+     inkscape:cx="15.056921"
+     inkscape:cy="43.499151"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     inkscape:document-rotation="0"
+     showgrid="false"
+     units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="1016"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata5">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Calque 1"
+     inkscape:groupmode="layer"
+     id="layer1">
+    <rect
+       style="fill:#808080;stroke-width:0.381027"
+       id="rect841"
+       width="0.26458335"
+       height="0.26458335"
+       x="0"
+       y="0" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="-2.7755576e-17" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583334"
+       y="0" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875001"
+       y="0" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458335"
+       y="0.26458335" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375005"
+       y="0.26458335" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.3229167"
+       y="0.26458335" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-1"
+       width="0.26458335"
+       height="0.26458335"
+       x="0"
+       y="0.5291667" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="0.5291667" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-7"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583334"
+       y="0.5291667" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-0"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875001"
+       y="0.5291667" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458335"
+       y="0.79375005" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-6"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="0.79375005" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-0"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.322917"
+       y="0.79375005" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="0"
+       y="1.0583334" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-61"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="1.0583334" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-8"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583334"
+       y="1.0583334" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-7"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875003"
+       y="1.0583334" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-0"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458335"
+       y="1.3229167" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375005"
+       y="1.3229167" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.3229167"
+       y="1.3229167" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-1-5"
+       width="0.26458335"
+       height="0.26458335"
+       x="0"
+       y="1.5875001" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-2-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="1.5875001" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-7-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583334"
+       y="1.5875001" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-0-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875003"
+       y="1.5875001" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-3-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458335"
+       y="1.8520834" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-6-7"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="1.8520834" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-0-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.322917"
+       y="1.8520834" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-12"
+       width="0.26458335"
+       height="0.26458335"
+       x="0"
+       y="2.1166668" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="2.1166668" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583336"
+       y="2.1166668" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-1"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875003"
+       y="2.1166668" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-7"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458335"
+       y="2.3812501" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-8"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375005"
+       y="2.3812501" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-4"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.3229167"
+       y="2.3812501" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-1-0"
+       width="0.26458335"
+       height="0.26458335"
+       x="0"
+       y="2.6458335" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-2-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="2.6458335" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-7-6"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583336"
+       y="2.6458335" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-0-1"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875003"
+       y="2.6458335" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-3-6"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458335"
+       y="2.9104168" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-6-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="2.9104168" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-0-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.3229172"
+       y="2.9104168" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-2-6"
+       width="0.26458335"
+       height="0.26458335"
+       x="0"
+       y="3.1750002" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-61-1"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="3.1750002" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-8-5"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583336"
+       y="3.1750002" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-7-5"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875003"
+       y="3.1750002" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-0-7"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458335"
+       y="3.4395833" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-2-6"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375005"
+       y="3.4395833" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-3-5"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.3229167"
+       y="3.4395833" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-1-5-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="0"
+       y="3.7041669" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-2-9-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="3.7041669" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-7-2-7"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583336"
+       y="3.7041669" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-0-2-4"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875003"
+       y="3.7041669" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-3-9-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458335"
+       y="3.96875" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-6-7-5"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="3.96875" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-0-3-4"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.3229172"
+       y="3.96875" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-4"
+       width="0.26458335"
+       height="0.26458335"
+       x="0"
+       y="4.2333336" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-4"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="4.2333336" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-30"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583334"
+       y="4.2333336" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-78"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875001"
+       y="4.2333336" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-8"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458359"
+       y="4.4979167" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-84"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="4.4979167" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-31"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.322917"
+       y="4.4979167" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-1-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="0"
+       y="4.7625003" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-2-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="4.7625003" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-7-0"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583334"
+       y="4.7625003" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-0-6"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875001"
+       y="4.7625003" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-3-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458359"
+       y="5.0270839" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-6-6"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="5.0270839" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-0-6"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.322917"
+       y="5.0270839" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-2-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="0"
+       y="5.291667" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-61-5"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="5.291667" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-8-0"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583334"
+       y="5.291667" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-7-4"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875006"
+       y="5.291667" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-0-71"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458359"
+       y="5.5562501" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-2-7"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="5.5562501" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-3-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.322917"
+       y="5.5562501" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-1-5-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="0"
+       y="5.8208337" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-2-9-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="5.8208337" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-7-2-6"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583334"
+       y="5.8208337" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-0-2-1"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875006"
+       y="5.8208337" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-3-9-6"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458359"
+       y="6.0854168" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-6-7-1"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="6.0854168" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-0-3-5"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.322917"
+       y="6.0854168" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-12-4"
+       width="0.26458335"
+       height="0.26458335"
+       x="0"
+       y="6.3500004" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-9-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="6.3500004" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-3-0"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583339"
+       y="6.3500004" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-1-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875006"
+       y="6.3500004" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-7-7"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458359"
+       y="6.6145835" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-8-7"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="6.6145835" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-4-1"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.322917"
+       y="6.6145835" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-1-0-5"
+       width="0.26458335"
+       height="0.26458335"
+       x="0"
+       y="6.8791671" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-2-3-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="6.8791671" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-7-6-7"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583339"
+       y="6.8791671" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-0-1-7"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875006"
+       y="6.8791671" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-3-6-7"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458359"
+       y="7.1437507" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-6-3-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="7.1437507" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-0-2-6"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.3229175"
+       y="7.1437507" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-2-6-6"
+       width="0.26458335"
+       height="0.26458335"
+       x="0"
+       y="7.4083338" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-61-1-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="7.4083338" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-8-5-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583339"
+       y="7.4083338" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-7-5-4"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875006"
+       y="7.4083338" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-0-7-1"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458359"
+       y="7.6729169" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-2-6-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="7.6729169" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-3-5-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.322917"
+       y="7.6729169" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-1-5-9-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="0"
+       y="7.9375005" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-2-9-3-0"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="7.9375005" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-7-2-7-8"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583339"
+       y="7.9375005" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-0-2-4-8"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875006"
+       y="7.9375005" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-3-9-2-0"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458359"
+       y="8.2020836" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-6-7-5-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="8.2020836" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-0-3-4-6"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.3229175"
+       y="8.2020836" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-8"
+       width="0.26458335"
+       height="0.26458335"
+       x="0"
+       y="8.4666672" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-5"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="8.4666672" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-6"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583334"
+       y="8.4666672" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-11"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875006"
+       y="8.4666672" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458359"
+       y="8.7312498" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-848"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="8.7312498" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-1"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.322917"
+       y="8.7312498" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-1-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="0"
+       y="8.9958334" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-2-0"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="8.9958334" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-7-4"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583334"
+       y="8.9958334" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-0-4"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875006"
+       y="8.9958334" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-3-4"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458359"
+       y="9.260417" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-6-76"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="9.260417" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-0-31"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.322917"
+       y="9.260417" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-2-5"
+       width="0.26458335"
+       height="0.26458335"
+       x="0"
+       y="9.5250006" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-61-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="9.5250006" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-8-6"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583334"
+       y="9.5250006" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-7-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875006"
+       y="9.5250006" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-0-78"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458359"
+       y="9.7895832" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-2-5"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="9.7895832" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-3-7"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.322917"
+       y="9.7895832" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-1-5-1"
+       width="0.26458335"
+       height="0.26458335"
+       x="0"
+       y="10.054167" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-2-9-8"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="10.054167" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-7-2-5"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583334"
+       y="10.054167" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-0-2-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875006"
+       y="10.054167" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-3-9-5"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458359"
+       y="10.31875" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-6-7-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="10.31875" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-0-3-8"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.322917"
+       y="10.31875" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-12-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="0"
+       y="10.583334" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-9-1"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="10.583334" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-3-8"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583339"
+       y="10.583334" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-1-96"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875006"
+       y="10.583334" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-7-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458359"
+       y="10.847917" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-8-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="10.847917" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-4-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.322917"
+       y="10.847917" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-1-0-6"
+       width="0.26458335"
+       height="0.26458335"
+       x="0"
+       y="11.1125" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-2-3-0"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="11.1125" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-7-6-4"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583339"
+       y="11.1125" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-0-1-8"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875006"
+       y="11.1125" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-3-6-8"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458359"
+       y="11.377084" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-6-3-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="11.377084" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-0-2-7"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.3229175"
+       y="11.377084" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-2-6-64"
+       width="0.26458335"
+       height="0.26458335"
+       x="0"
+       y="11.641667" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-61-1-30"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="11.641667" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-8-5-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583339"
+       y="11.641667" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-7-5-0"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875006"
+       y="11.641667" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458335"
+       y="11.906251" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375005"
+       y="11.906251" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.3229167"
+       y="11.906251" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-1-6"
+       width="0.26458335"
+       height="0.26458335"
+       x="2.220446e-16"
+       y="12.170835" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-2-02"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="12.170835" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-7-7"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583334"
+       y="12.170835" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-0-61"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875001"
+       y="12.170835" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-3-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458335"
+       y="12.435418" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-6-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="12.435418" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-0-1"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.322917"
+       y="12.435418" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-2-59"
+       width="0.26458335"
+       height="0.26458335"
+       x="2.220446e-16"
+       y="12.700002" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-61-91"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="12.700002" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-8-4"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583334"
+       y="12.700002" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-7-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875003"
+       y="12.700002" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-0-1"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458335"
+       y="12.964585" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-2-0"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375005"
+       y="12.964585" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-3-75"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.3229167"
+       y="12.964585" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-1-5-8"
+       width="0.26458335"
+       height="0.26458335"
+       x="2.220446e-16"
+       y="13.229168" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-2-9-7"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="13.229168" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-7-2-0"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583334"
+       y="13.229168" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-0-2-48"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875003"
+       y="13.229168" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-3-9-0"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458335"
+       y="13.493752" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-6-7-4"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="13.493752" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-0-3-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.322917"
+       y="13.493752" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-12-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="2.220446e-16"
+       y="13.758335" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-9-6"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="13.758335" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-3-1"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583336"
+       y="13.758335" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-1-0"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875003"
+       y="13.758335" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-7-4"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458335"
+       y="14.022919" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-8-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375005"
+       y="14.022919" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-4-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.3229167"
+       y="14.022919" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-1-0-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="2.220446e-16"
+       y="14.287501" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-2-3-05"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="14.287501" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-7-6-5"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583336"
+       y="14.287501" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-0-1-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875003"
+       y="14.287501" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-3-6-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458335"
+       y="14.552085" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-6-3-0"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="14.552085" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-0-2-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.3229172"
+       y="14.552085" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-2-6-8"
+       width="0.26458335"
+       height="0.26458335"
+       x="2.220446e-16"
+       y="14.816669" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-61-1-38"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="14.816669" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-8-5-0"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583336"
+       y="14.816669" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-7-5-40"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875003"
+       y="14.816669" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-0-7-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458335"
+       y="15.081251" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-2-6-1"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375005"
+       y="15.081251" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-3-5-96"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.3229167"
+       y="15.081251" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-1-5-9-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="2.220446e-16"
+       y="15.345835" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-2-9-3-5"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="15.345835" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-7-2-7-4"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583336"
+       y="15.345835" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-0-2-4-4"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875003"
+       y="15.345835" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-3-9-2-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458335"
+       y="15.610418" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-6-7-5-93"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="15.610418" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-0-3-4-60"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.3229172"
+       y="15.610418" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-4-5"
+       width="0.26458335"
+       height="0.26458335"
+       x="2.220446e-16"
+       y="15.875001" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-4-0"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="15.875001" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-30-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583334"
+       y="15.875001" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-78-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875001"
+       y="15.875001" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-8-4"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458359"
+       y="16.139584" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-84-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="16.139584" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-31-5"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.322917"
+       y="16.139584" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-1-9-1"
+       width="0.26458335"
+       height="0.26458335"
+       x="2.220446e-16"
+       y="16.404167" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-2-2-7"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="16.404167" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-7-0-4"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583334"
+       y="16.404167" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-0-6-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875001"
+       y="16.404167" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-3-2-1"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458359"
+       y="16.668751" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-6-6-4"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="16.668751" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-0-6-6"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.322917"
+       y="16.668751" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-2-9-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="2.220446e-16"
+       y="16.933334" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-61-5-4"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="16.933334" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-8-0-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583334"
+       y="16.933334" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-7-4-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875006"
+       y="16.933334" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-0-71-6"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458359"
+       y="17.197916" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-2-7-4"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="17.197916" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-3-2-1"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.322917"
+       y="17.197916" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-1-5-2-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="2.220446e-16"
+       y="17.4625" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-2-9-2-8"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="17.4625" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-7-2-6-8"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583334"
+       y="17.4625" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-0-2-1-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875006"
+       y="17.4625" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-3-9-6-2"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458359"
+       y="17.727083" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-6-7-1-8"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="17.727083" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-0-3-5-8"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.322917"
+       y="17.727083" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-12-4-8"
+       width="0.26458335"
+       height="0.26458335"
+       x="2.220446e-16"
+       y="17.991667" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-9-9-6"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="17.991667" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-3-0-8"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583339"
+       y="17.991667" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-1-9-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875006"
+       y="17.991667" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-7-7-8"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458359"
+       y="18.25625" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-8-7-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="18.25625" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-4-1-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.322917"
+       y="18.25625" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-1-0-5-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="2.220446e-16"
+       y="18.520834" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-2-3-9-8"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="18.520834" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-7-6-7-0"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583339"
+       y="18.520834" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-0-1-7-4"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875006"
+       y="18.520834" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-3-6-7-7"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458359"
+       y="18.785418" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-6-3-3-6"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="18.785418" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-0-2-6-8"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.3229175"
+       y="18.785418" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-2-6-6-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="2.220446e-16"
+       y="19.050001" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-61-1-3-0"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="19.050001" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-8-5-9-6"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583339"
+       y="19.050001" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-7-5-4-8"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875006"
+       y="19.050001" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-35-0-7-1-7"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.26458359"
+       y="19.314583" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-6-2-6-2-9"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.79375029"
+       y="19.314583" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-2-3-5-9-0"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.322917"
+       y="19.314583" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-1-5-9-9-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="2.220446e-16"
+       y="19.579166" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-2-9-3-0-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="0.5291667"
+       y="19.579166" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-6-7-2-7-8-3"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.0583339"
+       y="19.579166" />
+    <rect
+       style="fill:#808080;stroke-width:0.381026"
+       id="rect841-3-7-0-2-4-8-7"
+       width="0.26458335"
+       height="0.26458335"
+       x="1.5875006"
+       y="19.579166" />
+  </g>
+</svg>
diff --git a/libgui/src/interpreter-qobject.cc b/libgui/src/interpreter-qobject.cc
--- a/libgui/src/interpreter-qobject.cc
+++ b/libgui/src/interpreter-qobject.cc
@@ -143,4 +143,13 @@ namespace octave
   {
     return m_octave_qobj.qt_link ();
   }
+
+  gh_manager_lock interpreter_qobject::get_gh_manager (void)
+  {
+    if (! m_interpreter)
+      throw std::logic_error ("interpreter_qobject::get_gh_manager called "
+                              "before initialization of interpreter");
+
+    return gh_manager_lock (m_interpreter->get_gh_manager ());
+  }
 }
diff --git a/libgui/src/interpreter-qobject.h b/libgui/src/interpreter-qobject.h
--- a/libgui/src/interpreter-qobject.h
+++ b/libgui/src/interpreter-qobject.h
@@ -28,14 +28,22 @@
 
 #include <QObject>
 
+#include "oct-mutex.h"
+
 #include "qt-interpreter-events.h"
 
+
+class gh_manager;
+
 namespace octave
 {
+
   class interpreter;
 
   class base_qobject;
 
+  using gh_manager_lock = ::octave::unique_lock<gh_manager>;
+
   class interpreter_qobject : public QObject
   {
     Q_OBJECT
@@ -52,6 +60,8 @@ namespace octave
 
     void interpreter_event (const meth_callback& meth);
 
+    gh_manager_lock get_gh_manager ();
+
   signals:
 
     void ready (void);
diff --git a/libgui/src/item-hyperlink-delegate.cc b/libgui/src/item-hyperlink-delegate.cc
new file mode 100644
--- /dev/null
+++ b/libgui/src/item-hyperlink-delegate.cc
@@ -0,0 +1,242 @@
+////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2013-2020 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 "item-hyperlink-delegate.h"
+
+#include <QAbstractItemModel>
+#include <QAbstractItemView>
+#include <QApplication>
+#include <QDesktopServices>
+#include <QFont>
+#include <QMouseEvent>
+#include <QPainter>
+#include <QSet>
+#include <QStyle>
+
+namespace octave
+{
+
+  constexpr int default_alignment = Qt::AlignCenter;
+
+  static const QColor unvisited_color = QColor (Qt::darkBlue).lighter();
+  static const QColor visited_color = QColor (Qt::darkMagenta);
+
+  static QSet<QUrl> visited_links;
+
+  static int item_alignment (const QModelIndex &index)
+  {
+    const auto model_alignment = index.data (Qt::TextAlignmentRole);
+    return model_alignment.isValid () ? model_alignment.toInt ()
+                                      : default_alignment;
+  }
+
+  item_hyperlink_delegate::item_hyperlink_delegate (QObject *parent,
+                                                    QAbstractItemView *view)
+      : QObject{ parent }, m_view{ view }, m_font_metrics{ QFont{} }
+  {
+  }
+
+  item_hyperlink_delegate::~item_hyperlink_delegate () = default;
+
+  void item_hyperlink_delegate::mouse_move_event (QMouseEvent *ev)
+  {
+    const auto *m = m_view->model ();
+    if (!m)
+      return;
+
+    if (m_clicked_hyperlink && !m_clicked_hl_rect.contains (ev->pos ()))
+      {
+        // handling mouse drag. Dragging out of hyperlink cancels the click
+        m_clicked_hyperlink = hyperlink{};
+        const auto ind = m_clicked_index;
+        m_clicked_index = QModelIndex{};
+        m_clicked_hl_rect = QRect{};
+
+        if (m_cursor_set)
+          {
+            m_view->setCursor (m_old_cursor);
+            m_cursor_set = false;
+          }
+
+        m_view->update (ind);
+        return;
+      }
+
+    const auto index = m_view->indexAt (ev->pos ());
+    if (!index.isValid ())
+      return;
+
+    const auto hl
+        = qvariant_cast<hyperlink> (m->data (index, Qt::DisplayRole));
+    if (hl.isEmpty ())
+      {
+        if (m_cursor_set)
+          {
+            m_view->setCursor (m_old_cursor);
+            m_cursor_set = false;
+          }
+        return;
+      }
+
+    const auto ir = m_view->visualRect (index);
+    const auto alignment = item_alignment (index);
+    const auto br = QApplication::style ()->itemTextRect (
+        m_font_metrics, ir, alignment, true, hl.title ());
+    const auto within = br.contains (ev->pos ());
+
+    if (within && !m_cursor_set)
+      {
+        m_old_cursor = m_view->cursor ();
+        m_view->setCursor (Qt::PointingHandCursor);
+        m_cursor_set = true;
+      }
+    else if (!within && m_cursor_set)
+      {
+        m_view->setCursor (m_old_cursor);
+        m_cursor_set = false;
+      }
+  }
+
+  bool item_hyperlink_delegate::mouse_press_event (QMouseEvent *ev)
+  {
+    const auto *m = m_view->model ();
+    if (!m)
+      return false;
+
+    const auto index = m_view->indexAt (ev->pos ());
+    if (!index.isValid ())
+      return false;
+
+    const auto hl
+        = qvariant_cast<hyperlink> (m->data (index, Qt::DisplayRole));
+    if (hl.isEmpty ())
+      return false;
+
+    const auto ir = m_view->visualRect (index);
+    const auto alignment = item_alignment (index);
+    const auto br = QApplication::style ()->itemTextRect (
+        m_font_metrics, ir, alignment, true, hl.title ());
+
+    if (br.contains (ev->pos ()))
+      {
+        m_click_pos = ev->pos ();
+        m_clicked_hyperlink = hl;
+        m_clicked_index = index;
+        m_clicked_hl_rect = br;
+        m_view->update (index);
+        return true;
+      }
+    return false;
+  }
+
+  void item_hyperlink_delegate::mouse_release_event (QMouseEvent *ev)
+  {
+    Q_UNUSED (ev)
+
+    if (m_clicked_hyperlink)
+      {
+        QDesktopServices::openUrl (m_clicked_hyperlink.url ());
+
+        visited_links.insert (m_clicked_hyperlink.url ());
+
+        m_clicked_hyperlink = hyperlink{};
+        const auto ind = m_clicked_index;
+        m_clicked_index = QModelIndex{};
+        m_clicked_hl_rect = QRect{};
+
+        m_view->update (ind);
+      }
+  }
+
+  QSize item_hyperlink_delegate::size_hint (const QStyleOptionViewItem &option,
+                                            const QModelIndex &index)
+  {
+    const auto hl = qvariant_cast<hyperlink> (index.data (Qt::DisplayRole));
+
+    if (hl.isEmpty ())
+      return QSize{};
+
+    if (!m_font_metrics_set)
+      {
+        set_font_metrics (option.fontMetrics);
+      }
+
+    return m_font_metrics.boundingRect (hl.title ()).size ();
+  }
+
+  void item_hyperlink_delegate::paint (QPainter *painter,
+                                       const QStyleOptionViewItem &option,
+                                       const QModelIndex &index)
+  {
+    const auto hl = qvariant_cast<hyperlink> (index.data (Qt::DisplayRole));
+
+    if (hl.isEmpty ())
+      return;
+
+    if (!m_font_metrics_set)
+      {
+        set_font_metrics (option.fontMetrics);
+      }
+
+    QFont font = option.font;
+    font.setUnderline (true);
+
+    QColor textColor = unvisited_color;
+    if (option.state.testFlag (QStyle::State_Selected))
+      {
+        // hard-code the color only if not highlighted
+        // (otherwise we can have blue over blue)
+        textColor = option.palette.color (QPalette::HighlightedText);
+      }
+    else if (visited_links.find (hl.url ()) != visited_links.end ())
+      {
+        textColor = visited_color;
+      }
+
+    const auto alignment = item_alignment (index);
+
+    auto rect = option.rect;
+
+    // animate the click
+    if (m_clicked_hyperlink)
+      {
+        rect.translate (QPoint (1, 2));
+      }
+
+    painter->save ();
+
+    painter->setFont (font);
+    painter->setPen (QPen (textColor, painter->pen ().widthF ()));
+
+    painter->drawText (option.rect, alignment, hl.title ());
+
+    painter->restore ();
+  }
+
+}
diff --git a/libgui/src/item-hyperlink-delegate.h b/libgui/src/item-hyperlink-delegate.h
new file mode 100644
--- /dev/null
+++ b/libgui/src/item-hyperlink-delegate.h
@@ -0,0 +1,150 @@
+////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2013-2020 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_item_hyperlink_delegate_h)
+#define octave_item_hyperlink_delegate_h 1
+
+#include <QCursor>
+#include <QFontMetrics>
+#include <QModelIndex>
+#include <QObject>
+#include <QRect>
+#include <QString>
+#include <QStyleOptionViewItem>
+#include <QUrl>
+#include <QVariant>
+#include <Qt>
+
+class QAbstractItemView;
+class QMouseEvent;
+class QObject;
+class QPainter;
+
+namespace octave
+{
+  //! Describes an hyperlink, that is a url with a title
+  class hyperlink
+  {
+  public:
+    hyperlink (const QUrl &url, const QString &title)
+        : m_url{ url }, m_title{ title }
+    {
+    }
+    hyperlink (const QString &url, const QString &title)
+        : m_url{ url }, m_title{ title }
+    {
+    }
+
+    hyperlink () = default;
+    ~hyperlink () = default;
+    hyperlink (const hyperlink &) = default;
+    hyperlink &operator= (const hyperlink &) = default;
+
+    operator bool () const { return !isEmpty (); }
+
+    bool isEmpty () const { return m_url.isEmpty () || m_title.isEmpty (); }
+
+    QUrl url () const { return m_url; }
+    QString title () const { return m_title; }
+
+  private:
+    QUrl m_url;
+    QString m_title;
+  };
+
+  //! This class allows to easily setup hyperlinks in item
+  //! views (such as QTreeView or QTableView).
+  //!
+  //! There is no additional widget instanciated, so it is much more efficient
+  //! than having a QLabel in each cell.
+  //!
+  //! The usage requirements are as follow:
+  //! 1. The view must forward its mouse events to this delegate
+  //!    (and have mouse tracking set)
+  //! 2. For all indices with hyperlink, the model must return an hyperlink
+  //!    object in data() when role is Qt::DisplayRole.
+  //! 3. For all indices with hyperlink, the item delegate must forward
+  //!    sizeHint to this delegate
+  //! 4. For all indices with hyperlink, the item delegate must forward paint
+  //!    to this delegate
+  class item_hyperlink_delegate : public QObject
+  {
+    Q_OBJECT
+
+  public:
+    item_hyperlink_delegate (QObject *parent, QAbstractItemView *view);
+    virtual ~item_hyperlink_delegate ();
+
+    //! The QFontMetrics used by this delegate to paint and compute
+    //! bounding rects
+    QFontMetrics font_metrics () const { return m_font_metrics; }
+
+    //! Set the QFontMetrics used by this delegate.
+    //! Calling this method is entirely optional.
+    //! The delegate will use the style option item passed to paint
+    //! if this method is not called.
+    void set_font_metrics (const QFontMetrics &fm)
+    {
+      m_font_metrics = fm;
+      m_font_metrics_set = true;
+    }
+
+    //! View must call this method to forward mouse move event
+    //! (mouse tracking must be enabled in the view)
+    void mouse_move_event (QMouseEvent *ev);
+    //! View must call this method to forward mouse press event
+    //! Returns whether the hyperlink was hit by the click.
+    //! Item view may want to accept the event to avoid further
+    //! process (like selecting the row) if the hyperlink is hit
+    bool mouse_press_event (QMouseEvent *ev);
+    //! View must call this method to forward mouse release event
+    void mouse_release_event (QMouseEvent *ev);
+
+    //! View delegate must call this method to estimate size of the hyperlink.
+    QSize size_hint (const QStyleOptionViewItem &option,
+                     const QModelIndex &index);
+
+    //! View delegate must call this method to paint the hyperlink.
+    //! The hyperlink will be painted different color if link was visited.
+    void paint (QPainter *painter, const QStyleOptionViewItem &option,
+                const QModelIndex &index);
+
+  private:
+    QAbstractItemView *m_view = nullptr;
+    QCursor m_old_cursor;
+    bool m_cursor_set = false;
+    QFontMetrics m_font_metrics;
+    bool m_font_metrics_set = false;
+    QPoint m_click_pos;
+    hyperlink m_clicked_hyperlink;
+    QModelIndex m_clicked_index;
+    QRect m_clicked_hl_rect; // to test dragging out easily
+  };
+
+}
+
+Q_DECLARE_METATYPE (octave::hyperlink);
+
+#endif // octave_item_hyperlink_delegate_h
diff --git a/libgui/src/main-window.cc b/libgui/src/main-window.cc
--- a/libgui/src/main-window.cc
+++ b/libgui/src/main-window.cc
@@ -107,13 +107,13 @@ namespace octave
 #endif
   }
 
-  main_window::main_window (base_qobject& oct_qobj)
-    : QMainWindow (), m_octave_qobj (oct_qobj),
-      m_workspace_model (nullptr),
+  main_window::main_window (base_qobject &oct_qobj)
+    : QMainWindow (), m_octave_qobj (oct_qobj), m_workspace_model (nullptr),
       m_status_bar (nullptr), m_command_window (nullptr),
       m_history_window (nullptr), m_file_browser_window (nullptr),
       m_doc_browser_window (nullptr), m_editor_window (nullptr),
       m_workspace_window (nullptr), m_variable_editor_window (nullptr),
+      m_property_inspector_window (nullptr),
       m_external_editor (new external_editor_interface (this, m_octave_qobj)),
       m_active_editor (m_external_editor), m_settings_dlg (nullptr),
       m_find_files_dlg (nullptr), m_set_path_dlg (nullptr),
@@ -181,6 +181,7 @@ namespace octave
     m_editor_window = create_default_editor (this, m_octave_qobj);
     m_variable_editor_window = new variable_editor (this, m_octave_qobj);
     m_workspace_window = new workspace_view (this, m_octave_qobj);
+    m_property_inspector_window = new property_inspector (this, m_octave_qobj);
 
     m_previous_dock = m_command_window;
 
@@ -258,6 +259,7 @@ namespace octave
     delete m_history_window;
     delete m_status_bar;
     delete m_variable_editor_window;
+    delete m_property_inspector_window;
 
     delete m_find_files_dlg;
     delete m_release_notes_window;
@@ -2094,6 +2096,9 @@ namespace octave
              m_variable_editor_window, SLOT (save_settings (void)));
 
     connect (qApp, SIGNAL (aboutToQuit (void)),
+             m_property_inspector_window, SLOT (save_settings (void)));
+
+    connect (qApp, SIGNAL (aboutToQuit (void)),
              this, SLOT (prepare_to_exit (void)));
 
     connect (qApp, SIGNAL (focusChanged (QWidget*, QWidget*)),
@@ -2161,6 +2166,8 @@ namespace octave
 #endif
     addDockWidget (Qt::RightDockWidgetArea, m_variable_editor_window);
     tabifyDockWidget (m_command_window, m_variable_editor_window);
+    addDockWidget(Qt::RightDockWidgetArea, m_property_inspector_window);
+    tabifyDockWidget (m_command_window, m_property_inspector_window);
 
     addDockWidget (Qt::LeftDockWidgetArea, m_file_browser_window);
     addDockWidget (Qt::LeftDockWidgetArea, m_workspace_window);
@@ -2703,8 +2710,13 @@ namespace octave
     m_show_documentation_action = construct_window_menu_item
       (window_menu, tr ("Show Documentation"), true, m_doc_browser_window);
 
-    m_show_variable_editor_action = construct_window_menu_item
-      (window_menu, tr ("Show Variable Editor"), true, m_variable_editor_window);
+    m_show_variable_editor_action
+        = construct_window_menu_item (window_menu, tr ("Show Variable Editor"),
+                                      true, m_variable_editor_window);
+
+    m_show_property_inspector_action = construct_window_menu_item (
+        window_menu, tr ("Show Property Inspector"), true,
+        m_property_inspector_window);
 
     window_menu->addSeparator ();
 
@@ -2729,6 +2741,10 @@ namespace octave
     m_variable_editor_action = construct_window_menu_item
       (window_menu, tr ("Variable Editor"), false, m_variable_editor_window);
 
+    m_property_inspector_action
+        = construct_window_menu_item (window_menu, tr ("Property Inspector"),
+                                      false, m_property_inspector_window);
+
     window_menu->addSeparator ();
 
     m_previous_dock_action = add_action (window_menu, QIcon (),
@@ -2900,6 +2916,7 @@ namespace octave
     scmgr.set_shortcut (m_show_editor_action, sc_main_window_show_editor);
     scmgr.set_shortcut (m_show_documentation_action, sc_main_window_show_doc);
     scmgr.set_shortcut (m_show_variable_editor_action, sc_main_window_show_variable_editor);
+    scmgr.set_shortcut (m_show_property_inspector_action, sc_main_window_show_property_inspector);
     scmgr.set_shortcut (m_command_window_action, sc_main_window_command);
     scmgr.set_shortcut (m_history_action, sc_main_window_history);
     scmgr.set_shortcut (m_workspace_action, sc_main_window_workspace);
@@ -2907,6 +2924,7 @@ namespace octave
     scmgr.set_shortcut (m_editor_action, sc_main_window_editor);
     scmgr.set_shortcut (m_documentation_action, sc_main_window_doc);
     scmgr.set_shortcut (m_variable_editor_action, sc_main_window_variable_editor);
+    scmgr.set_shortcut (m_property_inspector_action, sc_main_window_property_inspector);
     scmgr.set_shortcut (m_previous_dock_action, sc_main_window_previous_dock);
     scmgr.set_shortcut (m_reset_windows_action, sc_main_window_reset);
 
@@ -2936,6 +2954,8 @@ namespace octave
 #endif
     list.append (static_cast<octave_dock_widget *> (m_workspace_window));
     list.append (static_cast<octave_dock_widget *> (m_variable_editor_window));
+    list.append (
+        static_cast<octave_dock_widget *> (m_property_inspector_window));
     return list;
   }
 
diff --git a/libgui/src/main-window.h b/libgui/src/main-window.h
--- a/libgui/src/main-window.h
+++ b/libgui/src/main-window.h
@@ -56,6 +56,7 @@
 #include "led-indicator.h"
 #include "octave-dock-widget.h"
 #include "octave-qobject.h"
+#include "property-inspector.h"
 #include "qt-interpreter-events.h"
 #include "set-path-dialog.h"
 #include "terminal-dock-widget.h"
@@ -331,6 +332,7 @@ namespace octave
     file_editor_interface *m_editor_window;
     workspace_view *m_workspace_window;
     variable_editor *m_variable_editor_window;
+    property_inspector *m_property_inspector_window;
     //!@}
 
     external_editor_interface *m_external_editor;
@@ -383,6 +385,7 @@ namespace octave
     QAction *m_show_editor_action;
     QAction *m_show_documentation_action;
     QAction *m_show_variable_editor_action;
+    QAction *m_show_property_inspector_action;
     QAction *m_command_window_action;
     QAction *m_history_action;
     QAction *m_workspace_action;
@@ -390,6 +393,7 @@ namespace octave
     QAction *m_editor_action;
     QAction *m_documentation_action;
     QAction *m_variable_editor_action;
+    QAction *m_property_inspector_action;
     QAction *m_previous_dock_action;
     QAction *m_reset_windows_action;
 
diff --git a/libgui/src/module.mk b/libgui/src/module.mk
--- a/libgui/src/module.mk
+++ b/libgui/src/module.mk
@@ -67,6 +67,8 @@ octave_gui_ICONS = \
   %reldir%/icons/graphic_logo_ReleaseWidget.svg \
   %reldir%/icons/graphic_logo_VariableEditor.svg \
   %reldir%/icons/hand2.png \
+  %reldir%/icons/hsplitter-handle.svg \
+  %reldir%/icons/hsplitter-handle.png \
   %reldir%/icons/icons_license \
   %reldir%/icons/left_side.png \
   %reldir%/icons/letter_logo_DocumentationDockWidget.png \
@@ -140,10 +142,14 @@ OCTAVE_GUI_SRC_MOC = \
   %reldir%/moc-gui-settings.cc \
   %reldir%/moc-history-dock-widget.cc \
   %reldir%/moc-interpreter-qobject.cc \
+  %reldir%/moc-item-hyperlink-delegate.cc \
   %reldir%/moc-led-indicator.cc \
   %reldir%/moc-main-window.cc \
   %reldir%/moc-news-reader.cc \
   %reldir%/moc-octave-qobject.cc \
+  %reldir%/moc-property-inspector.cc \
+  %reldir%/moc-property-inspector-table.cc \
+  %reldir%/moc-property-inspector-tree.cc \
   %reldir%/moc-settings-dialog.cc \
   %reldir%/moc-terminal-dock-widget.cc \
   %reldir%/moc-color-picker.cc \
@@ -174,6 +180,7 @@ DIRSTAMP_FILES += \
   %reldir%/$(octave_dirstamp)
 
 octave_gui_UI = \
+  %reldir%/property-inspector-color-editor.ui \
   %reldir%/settings-dialog.ui
 
 octave_gui_UI_H = $(patsubst %reldir%/%.ui, %reldir%/ui-%.h, $(octave_gui_UI))
@@ -200,6 +207,7 @@ noinst_HEADERS += \
   %reldir%/gui-preferences-mw.h \
   %reldir%/gui-preferences-nr.h \
   %reldir%/gui-preferences-pd.h \
+  %reldir%/gui-preferences-pi.h \
   %reldir%/gui-preferences-sc.h \
   %reldir%/gui-preferences-sd.h \
   %reldir%/gui-preferences-ve.h \
@@ -211,6 +219,7 @@ noinst_HEADERS += \
   %reldir%/graphics-init.h \
   %reldir%/history-dock-widget.h \
   %reldir%/interpreter-qobject.h \
+  %reldir%/item-hyperlink-delegate.h \
   %reldir%/led-indicator.h \
   %reldir%/m-editor/file-editor-interface.h \
   %reldir%/m-editor/file-editor-tab.h \
@@ -222,6 +231,9 @@ noinst_HEADERS += \
   %reldir%/main-window.h \
   %reldir%/news-reader.h \
   %reldir%/octave-qobject.h \
+  %reldir%/property-inspector.h \
+  %reldir%/property-inspector-table.h \
+  %reldir%/property-inspector-tree.h \
   %reldir%/qt-application.h \
   %reldir%/qt-interpreter-events.h \
   %reldir%/qt-utils.h \
@@ -253,6 +265,7 @@ noinst_HEADERS += \
   %reldir%/gui-settings.cc \
   %reldir%/history-dock-widget.cc \
   %reldir%/interpreter-qobject.cc \
+  %reldir%/item-hyperlink-delegate.cc \
   %reldir%/led-indicator.cc \
   %reldir%/m-editor/file-editor-tab.cc \
   %reldir%/m-editor/file-editor.cc \
@@ -264,6 +277,9 @@ noinst_HEADERS += \
   %reldir%/news-reader.cc \
   %reldir%/octave-dock-widget.cc \
   %reldir%/octave-qobject.cc \
+  %reldir%/property-inspector.cc \
+  %reldir%/property-inspector-table.cc \
+  %reldir%/property-inspector-tree.cc \
   %reldir%/qt-interpreter-events.cc \
   %reldir%/qt-application.cc \
   %reldir%/resource-manager.cc \
diff --git a/libgui/src/property-inspector-color-editor.ui b/libgui/src/property-inspector-color-editor.ui
new file mode 100644
--- /dev/null
+++ b/libgui/src/property-inspector-color-editor.ui
@@ -0,0 +1,312 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>color_editor_widget</class>
+ <widget class="QWidget" name="color_editor_widget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>277</width>
+    <height>179</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_3">
+   <property name="leftMargin">
+    <number>4</number>
+   </property>
+   <property name="topMargin">
+    <number>4</number>
+   </property>
+   <property name="rightMargin">
+    <number>4</number>
+   </property>
+   <property name="bottomMargin">
+    <number>0</number>
+   </property>
+   <item>
+    <widget class="QWidget" name="radioWgt" native="true">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <layout class="QHBoxLayout" name="horizontalLayout_3">
+      <property name="leftMargin">
+       <number>0</number>
+      </property>
+      <property name="topMargin">
+       <number>0</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+      <item>
+       <widget class="QRadioButton" name="radioRad">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="text">
+         <string>preset</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QComboBox" name="radioBox"/>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="Line" name="line">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QWidget" name="widget_5" native="true">
+     <layout class="QGridLayout" name="gridLayout">
+      <property name="leftMargin">
+       <number>0</number>
+      </property>
+      <property name="topMargin">
+       <number>0</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+      <item row="0" column="1">
+       <widget class="QWidget" name="rgbWgt1" native="true">
+        <layout class="QHBoxLayout" name="horizontalLayout_5">
+         <property name="leftMargin">
+          <number>0</number>
+         </property>
+         <property name="topMargin">
+          <number>0</number>
+         </property>
+         <property name="rightMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>0</number>
+         </property>
+         <item>
+          <widget class="QLabel" name="rgbIcon">
+           <property name="minimumSize">
+            <size>
+             <width>80</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="alignment">
+            <set>Qt::AlignCenter</set>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="pickBtn">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="text">
+            <string>Pick</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+      </item>
+      <item row="0" column="0">
+       <widget class="QRadioButton" name="rgbValRad">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="text">
+         <string>RBG</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="0" colspan="2">
+       <widget class="QWidget" name="rgbWgt2" native="true">
+        <layout class="QFormLayout" name="formLayout">
+         <property name="leftMargin">
+          <number>0</number>
+         </property>
+         <property name="topMargin">
+          <number>0</number>
+         </property>
+         <property name="rightMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>0</number>
+         </property>
+         <item row="1" column="0">
+          <widget class="QLabel" name="label_2">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="text">
+            <string>Matrix</string>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="1">
+          <widget class="QWidget" name="widget" native="true">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <layout class="QHBoxLayout" name="horizontalLayout">
+            <property name="leftMargin">
+             <number>0</number>
+            </property>
+            <property name="topMargin">
+             <number>0</number>
+            </property>
+            <property name="rightMargin">
+             <number>0</number>
+            </property>
+            <property name="bottomMargin">
+             <number>0</number>
+            </property>
+            <item>
+             <widget class="QDoubleSpinBox" name="rSpn">
+              <property name="decimals">
+               <number>3</number>
+              </property>
+              <property name="maximum">
+               <double>1.000000000000000</double>
+              </property>
+              <property name="singleStep">
+               <double>0.100000000000000</double>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QDoubleSpinBox" name="gSpn">
+              <property name="decimals">
+               <number>3</number>
+              </property>
+              <property name="maximum">
+               <double>1.000000000000000</double>
+              </property>
+              <property name="singleStep">
+               <double>0.100000000000000</double>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QDoubleSpinBox" name="bSpn">
+              <property name="decimals">
+               <number>3</number>
+              </property>
+              <property name="maximum">
+               <double>1.000000000000000</double>
+              </property>
+              <property name="singleStep">
+               <double>0.100000000000000</double>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </widget>
+         </item>
+         <item row="3" column="0">
+          <widget class="QLabel" name="label">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="text">
+            <string>SVG</string>
+           </property>
+          </widget>
+         </item>
+         <item row="3" column="1">
+          <widget class="QComboBox" name="svgBox">
+           <property name="minimumSize">
+            <size>
+             <width>100</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="editable">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+         <item row="2" column="0">
+          <widget class="QLabel" name="label_3">
+           <property name="text">
+            <string>Octave</string>
+           </property>
+          </widget>
+         </item>
+         <item row="2" column="1">
+          <widget class="QComboBox" name="octaveBox"/>
+         </item>
+        </layout>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QWidget" name="widget_2" native="true">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <layout class="QHBoxLayout" name="horizontalLayout_2">
+      <property name="leftMargin">
+       <number>0</number>
+      </property>
+      <property name="topMargin">
+       <number>0</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+     </layout>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/libgui/src/property-inspector-table.cc b/libgui/src/property-inspector-table.cc
new file mode 100644
--- /dev/null
+++ b/libgui/src/property-inspector-table.cc
@@ -0,0 +1,1725 @@
+////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2013-2020 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/>.
+//
+////////////////////////////////////////////////////////////////////////
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <algorithm>
+#include <limits>
+#include <sstream>
+#include <vector>
+
+#include <QApplication>
+#include <QButtonGroup>
+#include <QColorDialog>
+#include <QComboBox>
+#include <QDataStream>
+#include <QDebug>
+#include <QGridLayout>
+#include <QHeaderView>
+#include <QPainter>
+#include <QStyle>
+#include <QTableView>
+
+#include "caseless-str.h"
+
+#include "graphics.h"
+#include "interpreter.h"
+
+#include "gui-preferences-pi.h"
+#include "gui-settings.h"
+#include "item-hyperlink-delegate.h"
+#include "octave-qobject.h"
+#include "property-inspector-table.h"
+#include "qt-application.h"
+#include "ui-property-inspector-color-editor.h"
+
+namespace octave
+{
+
+  enum Column
+  {
+    // COL_NAME,
+    COL_VALUE,
+    COL_TYPE,
+    COL_DOCLINK,
+
+    COL_COUNT,
+  };
+
+  // max number of displayed items
+  constexpr int array_disp_limit = 4;
+  // max number of items to allow edition
+  // (TODO: forward to variable editor for bigger data)
+  constexpr int matrix_edit_row_limit = 4;
+  constexpr int matrix_edit_col_limit = 4;
+
+  go_properties_table_view::go_properties_table_view (QWidget *parent)
+      : QTableView (parent)
+  {
+    setMouseTracking (true);
+  }
+
+  go_properties_table_view::~go_properties_table_view () = default;
+
+  void go_properties_table_view::mouseMoveEvent (QMouseEvent *ev)
+  {
+    if (m_hl_dg)
+      {
+        m_hl_dg->mouse_move_event (ev);
+      }
+    QTableView::mouseMoveEvent (ev);
+  }
+
+  void go_properties_table_view::mousePressEvent (QMouseEvent *ev)
+  {
+    if (m_hl_dg && m_hl_dg->mouse_press_event (ev))
+      {
+        ev->accept ();
+        return;
+      }
+    QTableView::mousePressEvent (ev);
+  }
+
+  void go_properties_table_view::mouseReleaseEvent (QMouseEvent *ev)
+  {
+    if (m_hl_dg)
+      {
+        m_hl_dg->mouse_release_event (ev);
+      }
+    QTableView::mouseReleaseEvent (ev);
+  }
+
+  //! turn "figure" or "FIGURE" into "Figure"
+  inline QString titlecased (const QString &input)
+  {
+    if (input.size () <= 1)
+      return input;
+    return input[0].toUpper () + input.right (input.size () - 1).toLower ();
+  }
+
+  inline QUrl go_property_doc_url (const QString &obj_type,
+                                   const QString &prop_name,
+                                   const QString &octave_version = "latest")
+  {
+    return QUrl (
+        QString ("https://octave.org/doc/%1/%2-Properties.html#XREF%3%4")
+            .arg (octave_version, titlecased (obj_type), obj_type.toLower (),
+                  prop_name.toLower ()));
+  }
+
+  template <class prop_type>
+  inline QString property_string_value (const base_property *prop)
+  {
+    const auto *p = static_cast<const prop_type *> (prop);
+    return QString::fromStdString (p->string_value ());
+  }
+
+  static QString function_display_text (octave_function *of)
+  {
+    if (of->is_user_function () && of->is_anonymous_function ())
+      {
+        const auto uf = of->user_function_value ();
+        return QString ("(anonymous at %1:%2)")
+            .arg (QString::fromStdString (uf->src_file_name ()),
+                  QString::number (uf->beginning_line ()));
+      }
+
+    return QString ("(%1)").arg (
+        QString::fromStdString (of->canonical_name ()));
+  }
+
+  static bool specific_matrix_item (const base_property *prop)
+  {
+    if (prop->prop_type () == PROP_COLOR)
+      {
+        // color have matrix values but deserve special treatment
+        return false;
+      }
+
+    auto val = prop->get ();
+
+    if (val.is_char_matrix ())
+      return false;
+    if (!val.is_real_matrix ())
+      return false;
+
+    if (val.ndims () != 2)
+      return false;
+
+    const auto matrix = val.matrix_value ();
+
+    return matrix.numel () > 1 && matrix.rows () <= matrix_edit_row_limit
+           && matrix.cols () <= matrix_edit_col_limit;
+  }
+
+  static QString array_display_string (const Matrix &m,
+                                       int lim = array_disp_limit)
+  {
+    const auto dims = m.dims ();
+    if (dims.ndims () == 0)
+      return "[]";
+    if (dims.ndims () == 1)
+      {
+        return "[...]"; // can't be
+      }
+    int count = 0;
+    QStringList rows;
+    for (int r = 0; r < dims.elem (0); ++r)
+      {
+        QStringList cols;
+        for (int c = 0; c < dims.elem (1); ++c)
+          {
+            // to avoid confusion between "," column separator
+            // and "," decimal separator on some locales, we use explicit
+            // C locale conversion (what QString::number does)
+            cols.push_back (QString::number (m (r, c)));
+            if (++count >= lim)
+              {
+                break;
+              }
+          }
+        if (cols.size ())
+          {
+            rows.push_back (cols.join (" , "));
+          }
+        if (count >= lim)
+          break;
+      }
+    QString data = rows.join (" ; ");
+    QString closing = dims.numel () > lim ? "..." : "]";
+    return QString ("[ %1 %2").arg (data, closing);
+  }
+
+  static QVariant property_display_data (const base_property *prop,
+                                         base_qobject &oct_qobj)
+  {
+    // some types require special handling, the other are checked from
+    // the octave_value
+    switch (prop->prop_type ())
+      {
+      case PROP_HANDLE:
+        {
+          const auto *p = static_cast<const handle_property *> (prop);
+          auto gh = p->handle_value ();
+
+          auto gh_mgr = oct_qobj.interpreter_qobj ()->get_gh_manager ();
+          auto obj = gh_mgr->get_object (gh);
+
+          if (!obj)
+            return "(undefined)";
+
+          return QString ("%1 (%2 handle)")
+              .arg (QString::number (gh.value ()),
+                    QString::fromStdString (obj.type ()));
+        }
+      case PROP_CALLBACK:
+        {
+          auto val = prop->get ();
+          if (!val.is_defined () || val.isempty ())
+            {
+              return "(undefined)";
+            }
+          // handling through regular value
+          break;
+        }
+      default:
+        break;
+      }
+
+    auto value = prop->get ();
+    if (!value.is_defined ())
+      {
+        return "(undefined)";
+      }
+    if (value.is_string ())
+      {
+        return QString::fromStdString (value.string_value ());
+      }
+    if (value.is_real_scalar ())
+      {
+        return value.scalar_value ();
+      }
+    if (value.is_function ())
+      {
+        return function_display_text (value.function_value ());
+      }
+    if (value.is_real_matrix ())
+      {
+        return array_display_string (value.matrix_value ());
+      }
+
+    return "";
+  }
+
+  static QString dims_string (const dim_vector &dv)
+  {
+    QStringList dims;
+    for (int i = 0; i < dv.ndims (); ++i)
+      {
+        dims.push_back (QString::number (dv.elem (i)));
+      }
+    return dims.join ('x');
+  }
+
+  static QString property_type_string (base_property *prop)
+  {
+    if (!prop)
+      return "(null)";
+
+    switch (prop->prop_type ())
+      {
+      case PROP_INVALID:
+        return "invalid";
+      case PROP_ROW_VECTOR:
+      case PROP_ARRAY:
+        return QString ("%1 matrix").arg (dims_string (prop->get ().dims ()));
+      case PROP_BOOL:
+        return "boolean";
+      case PROP_CALLBACK:
+        return "callback";
+      case PROP_CHILDREN:
+        return "children";
+      case PROP_COLOR:
+        return "color";
+      case PROP_DOUBLE:
+        return "double";
+      case PROP_DOUBLE_RADIO:
+        return "double radio";
+      case PROP_HANDLE:
+        return "handle";
+      case PROP_RADIO:
+        return "radio";
+      case PROP_STRING:
+        return "string";
+      case PROP_STRING_ARRAY:
+        return "string array";
+      case PROP_TEXT_LABEL:
+        return "text label";
+      case PROP_ANY:
+        return "any";
+      }
+    return "";
+  }
+
+  static bool property_editable (const base_property *prop)
+  {
+    if (!prop)
+      return false;
+
+    const auto value = prop->get ();
+
+    switch (prop->prop_type ())
+      {
+      case PROP_INVALID:
+      case PROP_STRING_ARRAY:
+      case PROP_DOUBLE_RADIO:
+      case PROP_CHILDREN:
+      case PROP_HANDLE:
+      case PROP_CALLBACK:
+      case PROP_ANY:
+        return false;
+      default:
+        break;
+      }
+
+    if (prop->prop_type () == PROP_COLOR)
+      return true;
+
+    return prop->is_radio () || value.is_string () || value.is_real_scalar ()
+           || specific_matrix_item (prop);
+  }
+
+  go_properties_table_model::go_properties_table_model (
+      QObject *p, base_qobject &oct_qobj, const graphics_object &go)
+      : QAbstractTableModel (p), m_oct_qobj{ oct_qobj }, m_go{ go }
+  {
+    if (!go)
+      return;
+
+    const auto name_set = go.get_properties ().all_property_names ();
+
+    static const auto parent = caseless_str ("parent");
+    static const auto children = caseless_str ("children");
+
+    for (const auto &name : name_set)
+      {
+        if (parent.compare (name) || children.compare (name))
+          continue;
+
+        if (name.size () > 1 && name[0] == '_' && name[1] == '_')
+          continue;
+
+        const auto prop = m_go.get_properties ().get_property (name);
+
+        if (prop.is_hidden ())
+          continue;
+
+        prop_index pi;
+        pi.name = name;
+        pi.id = prop.get_id ();
+        m_props.push_back (pi);
+      }
+    std::sort (m_props.begin (), m_props.end (),
+               [] (const prop_index &a, const prop_index &b) -> bool {
+                 return a.name < b.name;
+               });
+
+    auto events = m_oct_qobj.get_qt_interpreter_events ();
+
+    QObject::connect (events.get (), &qt_interpreter_events::go_updated, this,
+                      &go_properties_table_model::handle_go_update);
+  }
+
+  go_properties_table_model::~go_properties_table_model () {}
+
+  int go_properties_table_model::columnCount (const QModelIndex &) const
+  {
+    return COL_COUNT;
+  }
+
+  int go_properties_table_model::rowCount (const QModelIndex &) const
+  {
+    return static_cast<int> (m_props.size ());
+  }
+
+  QVariant go_properties_table_model::headerData (int section,
+                                                  Qt::Orientation orientation,
+                                                  int role) const
+  {
+    if (orientation == Qt::Vertical)
+      {
+        switch (role)
+          {
+          case Qt::DisplayRole:
+            {
+              if (section < 0 || section > static_cast<int> (m_props.size ()))
+                return QVariant{};
+              return QString::fromStdString (m_props[section].name);
+            }
+          case Qt::TextAlignmentRole:
+            return QVariant (Qt::AlignLeft | Qt::AlignVCenter);
+          }
+      }
+    else
+      {
+        switch (role)
+          {
+          case Qt::DisplayRole:
+            switch (section)
+              {
+              // case COL_NAME:
+              //   return tr ("Name");
+              case COL_VALUE:
+                return tr ("Value");
+              case COL_TYPE:
+                return tr ("Type");
+              case COL_DOCLINK:
+                return tr ("Doc");
+              default:
+                return QVariant{};
+              }
+          case Qt::TextAlignmentRole:
+            switch (section)
+              {
+              case COL_DOCLINK:
+                return QVariant (Qt::AlignCenter);
+              // case COL_NAME:
+              case COL_VALUE:
+              case COL_TYPE:
+              default:
+                return QVariant (Qt::AlignVCenter | Qt::AlignLeft);
+              }
+          }
+      }
+
+    return QVariant{};
+  }
+
+  QModelIndex go_properties_table_model::index (int row, int column,
+                                                const QModelIndex &) const
+  {
+    // we place the pointer to the base_property in the index
+    // this is safe because the model keep a reference to the graphics object
+    if (row < 0 || row >= static_cast<int> (m_props.size ()))
+      return QModelIndex{};
+
+    graphics_object go = m_go; // remove constness
+
+    const auto &prop_name = m_props[row].name;
+    base_property &prop
+        = go.get_properties ().get_property (prop_name).as_base_property ();
+
+    return createIndex (row, column, &prop);
+  }
+
+  Qt::ItemFlags
+  go_properties_table_model::flags (const QModelIndex &index) const
+  {
+    auto f = QAbstractTableModel::flags (index);
+    if (index.column () == COL_VALUE)
+      {
+        const auto *prop
+            = static_cast<base_property *> (index.internalPointer ());
+
+        f.setFlag (Qt::ItemIsEditable, property_editable (prop));
+      }
+    return f;
+  }
+
+  QVariant go_properties_table_model::data (const QModelIndex &index,
+                                            int role) const
+  {
+    auto *prop = static_cast<base_property *> (index.internalPointer ());
+    if (!prop)
+      return QVariant{};
+
+    if (index.row () < 0 || index.row () >= static_cast<int> (m_props.size ()))
+      return QVariant{};
+
+    if (index.column () == COL_VALUE && role == Qt::TextColorRole)
+      {
+        if (!prop->get ().is_defined ())
+          {
+            return QColor (Qt::gray);
+          }
+      }
+
+    if (role == Qt::TextAlignmentRole)
+      {
+        switch (index.column ())
+          {
+          // case COL_NAME:
+          //   return QVariant (Qt::AlignVCenter | Qt::AlignLeft);
+          case COL_VALUE:
+            return QVariant (Qt::AlignVCenter | Qt::AlignLeft);
+          case COL_TYPE:
+            return QVariant (Qt::AlignVCenter | Qt::AlignLeft);
+          case COL_DOCLINK:
+            return QVariant (Qt::AlignCenter);
+          default:
+            return QVariant{};
+          }
+      }
+
+    if (role == Qt::ToolTipRole && index.column () == COL_DOCLINK)
+      return go_property_doc_url (QString::fromStdString (m_go.type ()),
+                                  QString::fromStdString (prop->get_name ()));
+
+    if (role != Qt::DisplayRole && role != Qt::EditRole)
+      return QVariant{};
+
+    switch (index.column ())
+      {
+      // case COL_NAME:
+      //   return QString::fromStdString (prop->get_name ());
+      case COL_VALUE:
+        return property_display_data (prop, m_oct_qobj);
+      case COL_TYPE:
+        return property_type_string (prop);
+      case COL_DOCLINK:
+        {
+          QVariant val;
+          val.setValue (hyperlink (
+              go_property_doc_url (QString::fromStdString (m_go.type ()),
+                                   QString::fromStdString (prop->get_name ())),
+              "?"));
+          return val;
+        }
+      default:
+        return QVariant{};
+      }
+  }
+
+  bool go_properties_table_model::setData (const QModelIndex &index,
+                                           const QVariant &value, int role)
+  {
+    if (index.column () != COL_VALUE)
+      return false;
+    if (role != Qt::EditRole)
+      return false;
+    if (index.row () < 0 || index.row () >= rowCount ())
+      return false;
+
+    auto *prop = static_cast<base_property *> (index.internalPointer ());
+    if (!prop)
+      return false;
+
+    bool res = false;
+
+    const auto typ = static_cast<QMetaType::Type> (value.type ());
+
+    if (typ == QMetaType::Double)
+      res = prop->set (octave_value (value.toDouble ()));
+
+    else if (typ == QMetaType::QString)
+      res = prop->set (octave_value (value.toString ().toStdString ()));
+
+    else if (typ == QMetaType::QByteArray)
+      {
+        auto ba = value.toByteArray ();
+        QDataStream ds (ba);
+        qint64 rows = 0, cols = 0;
+        ds >> rows >> cols;
+        if (ds.status () != QDataStream::Ok)
+          return false;
+
+        Matrix matrix (rows, cols);
+
+        for (octave_idx_type r = 0; r < rows; ++r)
+          {
+            for (octave_idx_type c = 0; c < cols; ++c)
+              {
+                double val = 0;
+                ds >> val;
+                if (ds.status () != QDataStream::Ok)
+                  return false;
+                matrix (r, c) = val;
+              }
+          }
+
+        res = prop->set (octave_value (matrix));
+      }
+
+    if (res)
+      emit dataChanged (index, index);
+
+    return res;
+  }
+
+  void go_properties_table_model::handle_go_update (const graphics_object &go,
+                                                    int prop_id)
+  {
+    if (go.get_handle () != m_go.get_handle ())
+      return;
+
+    auto prop = std::find_if (
+        m_props.begin (), m_props.end (),
+        [=] (const prop_index &pi) { return pi.id == prop_id; });
+
+    if (prop == m_props.end ())
+      return;
+
+    const auto row = static_cast<int> (prop - m_props.begin ());
+
+    const auto topLeft = index (row, COL_VALUE, QModelIndex{});
+    const auto bottomRight = index (row, COL_TYPE, QModelIndex{});
+
+    emit dataChanged (topLeft, bottomRight);
+  }
+
+  go_properties_item_delegate::go_properties_item_delegate (
+      QObject *p, base_qobject &oct_qobj, QTableView *view,
+      item_hyperlink_delegate *hl_dg, const gui_settings *settings)
+      : QStyledItemDelegate (p),
+        m_oct_qobj (oct_qobj), m_view{ view }, m_hl_dg{ hl_dg }
+  {
+    m_default_row_height = settings->value (pi_table_row_height).toInt ();
+    QObject::connect (this, &go_properties_item_delegate::layoutWillChange,
+                      m_view, &QTableView::resizeRowToContents,
+                      Qt::QueuedConnection);
+  }
+
+  void
+  go_properties_item_delegate::notice_settings (const gui_settings *settings)
+  {
+    m_default_row_height = settings->value (pi_table_row_height).toInt ();
+  }
+
+  inline int color_int_comp (const double comp)
+  {
+    return static_cast<int> (
+        std::round (255 * std::min (1.0, std::max (0.0, comp))));
+  }
+
+  inline QColor color_from_doubles (const double r, const double g,
+                                    const double b)
+  {
+    return QColor{ color_int_comp (r), color_int_comp (g),
+                   color_int_comp (b) };
+  }
+
+  constexpr int color_cell_vpadding = 3;
+  constexpr int color_cell_hpadding = 6;
+  constexpr int color_cell_spacing = 6;
+  constexpr int color_cell_icon_width = 30;
+  constexpr int color_cell_icon_height = 15;
+
+  static void paint_color (QPainter *painter,
+                           const QStyleOptionViewItem &option,
+                           const color_property *prop)
+  {
+    if (prop->is_rgb ())
+      {
+        auto m = prop->rgb ();
+        if (m.cols () != 3)
+          return;
+
+        painter->save ();
+
+        int dy = (option.rect.height () - color_cell_icon_height) / 2;
+
+        QRect iconRect
+            = option.rect.adjusted (color_cell_hpadding, dy, 0, -dy);
+        iconRect.setWidth (color_cell_icon_width);
+
+        const auto color = color_from_doubles (m (0, 0), m (0, 1), m (0, 2));
+
+        painter->setBrush (QBrush (color));
+        painter->fillRect (iconRect, Qt::SolidPattern);
+        painter->setPen (QPen (color.darker ()));
+        painter->drawRect (iconRect);
+
+        painter->restore ();
+
+        const auto text = array_display_string (m);
+        QRect textBoundingRect = option.fontMetrics.boundingRect (text);
+
+        dy = (option.rect.height () - textBoundingRect.height ()) / 2;
+
+        const auto textRect = option.rect.adjusted (
+            color_cell_hpadding + color_cell_spacing + color_cell_icon_width,
+            dy, -color_cell_hpadding, -dy);
+
+        const auto role = option.state.testFlag (QStyle::State_Selected)
+                              ? QPalette::HighlightedText
+                              : QPalette::NoRole;
+
+        QApplication::style ()->drawItemText (
+            painter, textRect, Qt::AlignLeft | Qt::AlignVCenter,
+            option.palette, true, text, role);
+      }
+  }
+
+  constexpr int hpadding = 3;
+
+  void go_properties_item_delegate::paint (QPainter *painter,
+                                           const QStyleOptionViewItem &option,
+                                           const QModelIndex &index) const
+  {
+    if (index.column () == COL_VALUE)
+      {
+        const auto *prop
+            = static_cast<base_property *> (index.internalPointer ());
+
+        if (prop->prop_type () == PROP_COLOR)
+          {
+            const auto *cp = static_cast<const color_property *> (prop);
+            if (cp->is_rgb ())
+              {
+                paint_color (painter, option, cp);
+                return;
+              }
+          }
+        else if (specific_matrix_item (prop))
+          {
+            QLocale loc;
+
+            const auto matrix = prop->get ().matrix_value ();
+            const auto orig = option.rect.topLeft ();
+            const int h = option.rect.height () / matrix.rows ();
+            const int w = option.rect.width () / matrix.cols ();
+            const auto alignModel = index.data (Qt::TextAlignmentRole);
+            const auto align = alignModel.isValid () ? alignModel.toInt ()
+                                                     : Qt::AlignCenter;
+
+            for (int r = 0; r < matrix.rows (); r++)
+              {
+                for (int c = 0; c < matrix.cols (); c++)
+                  {
+                    const auto tl = orig + QPoint (w * c + hpadding, h * r);
+                    const auto rect = QRect (tl, QSize (w - 2 * hpadding, h));
+                    auto role = QPalette::NoRole;
+                    if (option.state.testFlag (QStyle::State_Selected))
+                      {
+                        role = QPalette::HighlightedText;
+                      }
+                    QApplication::style ()->drawItemText (
+                        painter, rect, align, option.palette, true,
+                        loc.toString (matrix (r, c)), role);
+                  }
+              }
+            painter->save ();
+            QPen pen (QColor (Qt::gray));
+            painter->setPen (pen);
+            for (int r = 1; r < matrix.rows (); r++)
+              {
+                painter->drawLine (
+                    QPoint (option.rect.left (), orig.y () + r * h),
+                    QPoint (option.rect.right (), orig.y () + r * h));
+              }
+            for (int c = 1; c < matrix.cols (); c++)
+              {
+                painter->drawLine (
+                    QPoint (orig.x () + c * w, option.rect.top ()),
+                    QPoint (orig.x () + c * w, option.rect.bottom ()));
+              }
+            painter->restore ();
+            return;
+          }
+      }
+    else if (index.column () == COL_DOCLINK)
+      {
+        return m_hl_dg->paint (painter, option, index);
+      }
+    return QStyledItemDelegate::paint (painter, option, index);
+  }
+
+  QSize
+  go_properties_item_delegate::sizeHint (const QStyleOptionViewItem &option,
+                                         const QModelIndex &index) const
+  {
+    const auto minRowH = m_default_row_height;
+
+    if (index.column () == COL_VALUE)
+      {
+        const auto *prop
+            = static_cast<base_property *> (index.internalPointer ());
+
+        if (prop->prop_type () == PROP_COLOR)
+          {
+            const auto *cp = static_cast<const color_property *> (prop);
+            if (cp->is_rgb ())
+              {
+                auto m = cp->rgb ();
+                auto text = array_display_string (m);
+                auto textRect = option.fontMetrics.boundingRect (text);
+                return QSize (2 * color_cell_hpadding + color_cell_spacing
+                                  + color_cell_icon_width + textRect.width (),
+                              2 * color_cell_vpadding + textRect.height ());
+              }
+          }
+        else if (specific_matrix_item (prop))
+          {
+            QLocale loc;
+            const auto matrix = prop->get ().matrix_value ();
+
+            // we draw all cells of the same size
+            int maxW = 0, maxH = 0;
+            for (int r = 0; r < matrix.rows (); ++r)
+              {
+                for (int c = 0; c < matrix.cols (); ++c)
+                  {
+                    const auto txt = loc.toString (matrix (r, c));
+                    const auto rct = option.fontMetrics.boundingRect (txt);
+                    maxW = std::max (maxW, rct.width ());
+                    maxH = std::max (maxH, rct.height ());
+                  }
+              }
+
+            maxW += 2 * hpadding;
+
+            return QSize (
+                maxW * matrix.cols (),
+                std::max (static_cast<int> (maxH * matrix.rows ()), minRowH));
+          }
+      }
+    else if (index.column () == COL_DOCLINK)
+      {
+        return m_hl_dg->size_hint (option, index);
+      }
+
+    auto sz = QStyledItemDelegate::sizeHint (option, index);
+    sz.setHeight (std::max (sz.height (), minRowH));
+    return sz;
+  }
+
+  // editing
+  QWidget *go_properties_item_delegate::createEditor (
+      QWidget *parent, const QStyleOptionViewItem &option,
+      const QModelIndex &index) const
+  {
+    if (index.column () == COL_VALUE)
+      {
+        const auto *prop
+            = static_cast<base_property *> (index.internalPointer ());
+
+        if (prop->prop_type () == PROP_COLOR)
+          {
+            // Color editor is quite big. it need to fit in at least three rows
+
+            auto *editor = new color_editor_widget (
+                parent, static_cast<const color_property *> (prop));
+
+            resize_editor_cell (editor, index);
+
+            return editor;
+          }
+
+        if (specific_matrix_item (prop))
+          {
+            return new matrix_widget (parent, prop->get ().matrix_value ());
+          }
+
+        if (prop->is_radio ())
+          {
+            QComboBox *w = new QComboBox (parent);
+            const auto cell = prop->values_as_cell ();
+            const auto strvec = cell.string_vector_value ();
+            for (int i = 0; i < strvec.numel (); ++i)
+              {
+                const auto s = strvec.xelem (i);
+                w->addItem (QString::fromStdString (s));
+              }
+            w->setCurrentText (
+                QString::fromStdString (prop->get ().string_value ()));
+            return w;
+          }
+      }
+    return QStyledItemDelegate::createEditor (parent, option, index);
+  }
+
+  void go_properties_item_delegate::resize_editor_cell (
+      QWidget *editor, const QModelIndex &index) const
+  {
+    const auto sz = editor->sizeHint ();
+    m_former_size
+        = QSize (m_view->horizontalHeader ()->sectionSize (index.column ()),
+                 m_view->verticalHeader ()->sectionSize (index.row ()));
+
+    if (sz.width () > m_former_size.width ())
+      {
+        m_view->horizontalHeader ()->resizeSection (index.column (),
+                                                    sz.width ());
+      }
+    if (sz.height () > m_former_size.height ())
+      {
+        m_view->verticalHeader ()->resizeSection (index.row (), sz.height ());
+      }
+  }
+
+  void
+  go_properties_item_delegate::destroyEditor (QWidget *editor,
+                                              const QModelIndex &index) const
+  {
+    if (m_former_size.isValid ())
+      {
+        m_view->horizontalHeader ()->resizeSection (index.column (),
+                                                    m_former_size.width ());
+        m_view->verticalHeader ()->resizeSection (index.row (),
+                                                  m_former_size.height ());
+        m_former_size = QSize{};
+      }
+
+    QStyledItemDelegate::destroyEditor (editor, index);
+  }
+
+  void
+  go_properties_item_delegate::setEditorData (QWidget *editor,
+                                              const QModelIndex &index) const
+  {
+    if (index.column () == COL_VALUE)
+      {
+        const auto *prop
+            = static_cast<base_property *> (index.internalPointer ());
+
+        auto *combo = dynamic_cast<QComboBox *> (editor);
+        if (combo)
+          {
+            combo->setCurrentText (
+                QString::fromStdString (prop->get ().string_value ()));
+            return;
+          }
+
+        auto *cw = dynamic_cast<color_editor_widget *> (editor);
+        if (cw)
+          {
+            const auto ov = prop->get ();
+            if (ov.isnumeric ())
+              {
+                cw->set (color_editor_val (ov.matrix_value ()));
+              }
+            else
+              {
+                cw->set (color_editor_val (ov.string_value ()));
+              }
+            return;
+          }
+        auto *mw = dynamic_cast<matrix_widget *> (editor);
+        if (mw)
+          {
+            mw->set (prop->get ().matrix_value ());
+            return;
+          }
+      }
+    QStyledItemDelegate::setEditorData (editor, index);
+  }
+
+  static QByteArray matrix_byte_array (const Matrix &mat)
+  {
+    QByteArray ba;
+    {
+      QDataStream ds (&ba, QIODevice::WriteOnly);
+      qint64 rows = mat.rows ();
+      qint64 cols = mat.cols ();
+      ds << rows << cols;
+      for (octave_idx_type r = 0; r < mat.rows (); ++r)
+        {
+          for (octave_idx_type c = 0; c < mat.cols (); ++c)
+            {
+              double val = mat (r, c);
+              ds << val;
+            }
+        }
+    }
+    return ba;
+  }
+
+  void
+  go_properties_item_delegate::setModelData (QWidget *editor,
+                                             QAbstractItemModel *model,
+                                             const QModelIndex &index) const
+  {
+    if (index.column () == COL_VALUE)
+      {
+        // const auto *prop
+        //     = static_cast<base_property *> (index.internalPointer ());
+
+        QComboBox *w = dynamic_cast<QComboBox *> (editor);
+        if (w)
+          {
+            model->setData (index, w->currentText (), Qt::EditRole);
+            return;
+          }
+
+        auto *cw = dynamic_cast<color_editor_widget *> (editor);
+        if (cw)
+          {
+            const auto val = cw->get ();
+            switch (val.type ())
+              {
+              case color_editor_val::STR:
+                model->setData (index,
+                                QString::fromStdString (val.string_value ()),
+                                Qt::EditRole);
+                break;
+              case color_editor_val::RGB:
+                model->setData (index, matrix_byte_array (val.rgb_value ()),
+                                Qt::EditRole);
+              }
+            return;
+          }
+
+        auto *mw = dynamic_cast<matrix_widget *> (editor);
+        if (mw)
+          {
+            bool ok = false;
+            auto mat = mw->get (&ok);
+            if (!ok)
+              return;
+            model->setData (index, matrix_byte_array (mat), Qt::EditRole);
+          }
+      }
+    QStyledItemDelegate::setModelData (editor, model, index);
+  }
+
+  void go_properties_item_delegate::updateEditorGeometry (
+      QWidget *editor, const QStyleOptionViewItem &option,
+      const QModelIndex &index) const
+  {
+    QStyledItemDelegate::updateEditorGeometry (editor, option, index);
+  }
+
+  class numbers_only_key_filter : public QObject
+  {
+  public:
+    numbers_only_key_filter (QObject *p) : QObject (p) {}
+    virtual ~numbers_only_key_filter () {}
+
+  protected:
+    bool eventFilter (QObject *obj, QEvent *ev) override
+    {
+      if (ev->type () == QEvent::KeyPress || ev->type () == QEvent::KeyRelease)
+        {
+          auto *kev = static_cast<QKeyEvent *> (ev);
+          switch (kev->key ())
+            {
+            case Qt::Key_0:
+            case Qt::Key_1:
+            case Qt::Key_2:
+            case Qt::Key_3:
+            case Qt::Key_4:
+            case Qt::Key_5:
+            case Qt::Key_6:
+            case Qt::Key_7:
+            case Qt::Key_8:
+            case Qt::Key_9:
+            case Qt::Key_Period:
+            case Qt::Key_Comma:
+              break;
+            default:
+              return true;
+            }
+        }
+      return QObject::eventFilter (obj, ev);
+    }
+  };
+
+  matrix_widget::matrix_widget (QWidget *parent, const Matrix &matrix)
+      : QWidget (parent)
+  {
+    setAutoFillBackground (true);
+    setFocusPolicy (Qt::StrongFocus);
+
+    m_layout = new QGridLayout (this);
+    m_layout->setSpacing (0);
+    m_layout->setMargin (0);
+
+    auto *numbers_only = new numbers_only_key_filter (this);
+
+    QLocale loc;
+
+    for (int r = 0; r < static_cast<int> (matrix.rows ()); ++r)
+      {
+        for (int c = 0; c < static_cast<int> (matrix.cols ()); ++c)
+          {
+            const double val = matrix (r, c);
+            auto *edit = new QLineEdit (loc.toString (val), this);
+            edit->installEventFilter (numbers_only);
+            m_layout->addWidget (edit, r, c);
+          }
+      }
+  }
+
+  static double parseDouble (const QString &txt)
+  {
+    // we can't just use QLocale::toDouble as it will almost always fail.
+    // In fact it even fails to parse result of QLocale::toString.
+    // so we analyse the string a bit and convert to C locale before parsing.
+    // NaN is returned if parsing doesn't succeed.
+
+    QString C;
+
+    // step 1: remove all whitespace
+    std::copy_if (txt.begin (), txt.end (), std::back_inserter (C),
+                  [] (QChar c) { return !c.isSpace (); });
+
+    // step 2: remove separators (can be dot or comma) and normalize decimal
+    // separator (can also be dot or comma but would be the last one)
+    int dots = C.count ('.');
+    int lastDot = C.lastIndexOf ('.');
+    int commas = C.count (',');
+    int lastComma = C.lastIndexOf (',');
+
+    if (lastDot > lastComma)
+      {
+        // dot is decimal sep, comma (if any) are thousands sep
+        if (dots > 1)
+          {
+            return std::numeric_limits<double>::quiet_NaN ();
+          }
+        C.replace (",", "");
+      }
+    if (lastComma > lastDot)
+      {
+        // comma is decimal sep, dot (if any) are thousands sep
+        if (commas > 1)
+          {
+            return std::numeric_limits<double>::quiet_NaN ();
+          }
+        C.replace (".", "");
+        C.replace (",", ".");
+      }
+
+    bool ok = false;
+    const auto val = C.toDouble (&ok);
+    return ok ? val : std::numeric_limits<double>::quiet_NaN ();
+  }
+
+  Matrix matrix_widget::get (bool *ok) const
+  {
+    Matrix matrix (m_layout->rowCount (), m_layout->columnCount ());
+
+    for (int r = 0; r < static_cast<int> (matrix.rows ()); ++r)
+      {
+        for (int c = 0; c < static_cast<int> (matrix.cols ()); ++c)
+          {
+            auto *item = m_layout->itemAtPosition (r, c);
+            auto *edit
+                = item ? dynamic_cast<QLineEdit *> (item->widget ()) : nullptr;
+            if (!edit)
+              {
+                if (ok)
+                  *ok = false;
+                return matrix;
+              }
+            const double val = parseDouble (edit->text ());
+            if (val != val)
+              {
+                // NaN
+                if (ok)
+                  *ok = false;
+                return matrix;
+              }
+            matrix (r, c) = val;
+          }
+      }
+    if (ok)
+      *ok = true;
+    return matrix;
+  }
+
+  void matrix_widget::set (const Matrix &matrix)
+  {
+    if (matrix.rows () != m_layout->rowCount ()
+        || matrix.cols () != m_layout->columnCount ())
+      return;
+
+    QLocale loc;
+
+    for (int r = 0; r < static_cast<int> (matrix.rows ()); ++r)
+      {
+        for (int c = 0; c < static_cast<int> (matrix.cols ()); ++c)
+          {
+            auto *edit
+                = dynamic_cast<QLineEdit *> (m_layout->itemAtPosition (r, c));
+            if (!edit)
+              continue;
+            edit->setText (loc.toString (matrix (r, c)));
+          }
+      }
+  }
+
+  color_editor_val::color_editor_val (const std::string &str)
+      : m_type{ STR }, m_str{ str }
+  {
+  }
+
+  color_editor_val::color_editor_val (const Matrix &rgb)
+      : m_type{ RGB }, m_rgb{ rgb }
+  {
+  }
+
+  std::string color_editor_val::string_value () const
+  {
+    if (m_type != STR)
+      {
+        throw std::logic_error{ "color_editor_val: not a string" };
+      }
+    return m_str;
+  }
+
+  Matrix color_editor_val::rgb_value () const
+  {
+    if (m_type != RGB)
+      {
+        throw std::logic_error{ "color_editor_val: not RGB" };
+      }
+    return m_rgb;
+  }
+
+  // return #RRGGBB
+  static QString color_web_code (const QColor &color)
+  {
+    char buf[64];
+    if (std::snprintf (buf, 64, "#%02X%02X%02X", color.red (), color.green (),
+                       color.blue ())
+        == 7)
+      {
+        buf[7] = '\0';
+        return QString (buf);
+      }
+
+    return QString{};
+  }
+
+  constexpr auto default_icon_sz = QSize{ 35, 23 };
+
+  static QPixmap color_pixmap (QColor color, QSize size = default_icon_sz)
+  {
+    QPixmap pm{ size };
+    {
+      QPainter painter{ &pm };
+      QRect r{ QPoint{ 0, 0 }, size };
+      painter.setBrush (QBrush{ color });
+      painter.fillRect (r, Qt::SolidPattern);
+      painter.setPen (QPen{ color.darker () });
+      painter.drawRect (r);
+    }
+    return pm;
+  }
+
+  static QIcon color_icon (QColor color, QSize size = default_icon_sz)
+  {
+    auto pm = color_pixmap (color, size);
+    return QIcon{ pm };
+  }
+
+  struct color_entry
+  {
+    QString name;     // e.g. "aliceblue" or "red"
+    QString web_code; // #RRGGBB
+    QColor color;
+    QIcon icon; // for the drop down list
+  };
+
+  static std::vector<QString> svg_color_names ();
+
+  static const std::vector<color_entry> &svg_colors ()
+  {
+    static std::vector<color_entry> result;
+    if (result.size () == 0)
+      {
+        const auto names = svg_color_names ();
+        result.reserve (names.size ());
+        for (const auto &name : names)
+          {
+            const auto color = QColor (name);
+            const auto web_code = color_web_code (color);
+            const auto icon = color_icon (color);
+            result.push_back (color_entry{ name, web_code, color, icon });
+          }
+      }
+    return result;
+  }
+
+  static const std::vector<color_entry> &octave_colors ()
+  {
+    static std::vector<color_entry> result;
+    if (result.size () == 0)
+      {
+        const auto src = std::vector<std::pair<QString, QString>>{
+          { "blue", "#0000FF" },    { "black", "#000000" },
+          { "cyan", "#00FFFF" },    { "green", "#00FF00" },
+          { "magenta", "#FF00FF" }, { "red", "#FF0000" },
+          { "white", "#FFFFFF" },   { "yellow", "#FFFF00" },
+        };
+
+        result.reserve (src.size ());
+        for (const auto &pair : src)
+          {
+            const auto color = QColor (pair.second);
+            const auto icon = color_icon (color);
+            result.push_back (
+                color_entry{ pair.first, pair.second, color, icon });
+          }
+      }
+    return result;
+  }
+
+  // functor for std::find_if
+  struct by_web_code
+  {
+    QString web_code;
+
+    by_web_code (const QString &wc) : web_code{ wc.toUpper () } {}
+
+    bool operator() (const color_entry &entry) const
+    {
+      return entry.web_code == web_code;
+    }
+  };
+
+  color_editor_widget::color_editor_widget (QWidget *parent,
+                                            const color_property *prop)
+      : QWidget{ parent }, ui{ new Ui_color_editor_widget }
+  {
+    ui->setupUi (this);
+    // need to setup a button group because parent widget is not the same
+    auto *grp = new QButtonGroup (this);
+    grp->addButton (ui->radioRad);
+    grp->addButton (ui->rgbValRad);
+    grp->setExclusive (true);
+
+    setAutoFillBackground (true);
+    setFocusPolicy (Qt::StrongFocus);
+
+    auto cell = prop->values_as_cell ();
+    const auto strvec = cell.string_vector_value ();
+    for (int i = 0; i < strvec.numel (); ++i)
+      {
+        const auto s = strvec.xelem (i);
+        ui->radioBox->addItem (QString::fromStdString (s));
+      }
+
+    // if property do not have any radio value, then it only accepts RGB values
+    if (strvec.numel () == 0 && prop->is_rgb ())
+      {
+        ui->radioWgt->setVisible (false);
+        ui->line->setVisible (false);
+        ui->rgbValRad->setVisible (false);
+      }
+
+    ui->octaveBox->addItem ("");
+    for (const auto &col : octave_colors ())
+      {
+        ui->octaveBox->addItem (col.icon, col.name, col.web_code);
+      }
+
+    for (const auto &col : svg_colors ())
+      {
+        ui->svgBox->addItem (col.icon, col.name, col.web_code);
+      }
+
+    if (prop->is_rgb ())
+      {
+        const auto m = prop->rgb ();
+        if (m.rows () != 1 || m.cols () != 3)
+          {
+            throw std::logic_error{ "unconsistent color value" };
+          }
+        const auto col = color_from_doubles (m (0, 0), m (0, 1), m (0, 2));
+        set_rgb (col);
+      }
+    else
+      {
+        ui->radioBox->setCurrentText (
+            QString::fromStdString (prop->current_value ()));
+        set_rgb (QColor ("black"));
+      }
+
+    ui->radioRad->setChecked (prop->is_radio ());
+    ui->radioBox->setEnabled (prop->is_radio ());
+    ui->rgbValRad->setChecked (prop->is_rgb ());
+    ui->rgbWgt1->setEnabled (prop->is_rgb ());
+    ui->rgbWgt2->setEnabled (prop->is_rgb ());
+
+    QObject::connect (ui->radioRad, &QRadioButton::toggled, ui->radioBox,
+                      &QWidget::setEnabled);
+    QObject::connect (ui->rgbValRad, &QRadioButton::toggled, ui->rgbWgt1,
+                      &QWidget::setEnabled);
+    QObject::connect (ui->rgbValRad, &QRadioButton::toggled, ui->rgbWgt2,
+                      &QWidget::setEnabled);
+
+    QObject::connect (ui->rSpn,
+                      QOverload<double>::of (&QDoubleSpinBox::valueChanged),
+                      this, &color_editor_widget::handle_rgb_mat_change);
+    QObject::connect (ui->gSpn,
+                      QOverload<double>::of (&QDoubleSpinBox::valueChanged),
+                      this, &color_editor_widget::handle_rgb_mat_change);
+    QObject::connect (ui->bSpn,
+                      QOverload<double>::of (&QDoubleSpinBox::valueChanged),
+                      this, &color_editor_widget::handle_rgb_mat_change);
+
+    QObject::connect (
+        ui->octaveBox, QOverload<int>::of (&QComboBox::currentIndexChanged),
+        this, &color_editor_widget::handle_rgb_octave_color_change);
+
+    QObject::connect (ui->svgBox, &QComboBox::currentTextChanged, this,
+                      &color_editor_widget::handle_rgb_svg_change);
+
+    QObject::connect (ui->pickBtn, &QPushButton::clicked, this,
+                      &color_editor_widget::handle_rgb_pick);
+  }
+
+  color_editor_widget::~color_editor_widget () { delete ui; }
+
+  void color_editor_widget::set (const color_editor_val &val)
+  {
+    switch (val.type ())
+      {
+      case color_editor_val::STR:
+        ui->radioRad->setChecked (true);
+        ui->radioBox->setCurrentText (
+            QString::fromStdString (val.string_value ()));
+        break;
+      case color_editor_val::RGB:
+        ui->rgbValRad->setChecked (true);
+        const auto m = val.rgb_value ();
+        if (m.cols () != 3)
+          {
+            throw std::logic_error{ "inconsistent color matrix" };
+          }
+        set_rgb (color_from_doubles (m (0, 0), m (0, 1), m (0, 2)));
+        break;
+      }
+  }
+
+  color_editor_val color_editor_widget::get () const
+  {
+    if (ui->radioRad->isChecked ())
+      {
+        return color_editor_val (ui->radioBox->currentText ().toStdString ());
+      }
+    else if (ui->rgbValRad->isChecked ())
+      {
+        auto m = Matrix (1, 3);
+        m (0, 0) = ui->rSpn->value ();
+        m (0, 1) = ui->gSpn->value ();
+        m (0, 2) = ui->bSpn->value ();
+        return color_editor_val (m);
+      }
+    return color_editor_val ("");
+  }
+
+  void color_editor_widget::set_rgb (const QColor &color, rgb_source source)
+  {
+    if (source != rgb_source::spinbox)
+      {
+        ui->rSpn->setValue (color.redF ());
+        ui->gSpn->setValue (color.greenF ());
+        ui->bSpn->setValue (color.blueF ());
+      }
+
+    const auto web_code = color_web_code (color);
+
+    if (source != rgb_source::octave)
+      {
+        const auto &oct_cols = octave_colors ();
+        auto oct_it = std::find_if (oct_cols.begin (), oct_cols.end (),
+                                    by_web_code{ web_code });
+        if (oct_it != oct_cols.end ())
+          {
+            ui->octaveBox->setCurrentIndex (
+                static_cast<int> (oct_it - oct_cols.begin ()) + 1);
+          }
+        else
+          {
+            ui->octaveBox->setCurrentIndex (0);
+          }
+      }
+
+    if (source != rgb_source::svg)
+      {
+        const auto &svg_cols = svg_colors ();
+        auto svg_it = std::find_if (svg_cols.begin (), svg_cols.end (),
+                                    by_web_code{ web_code });
+        if (svg_it != svg_cols.end ())
+          {
+            ui->svgBox->setCurrentIndex (
+                static_cast<int> (svg_it - svg_cols.begin ()));
+          }
+        else
+          {
+            ui->svgBox->setCurrentText (color_web_code (color));
+          }
+      }
+
+    const auto sz = ui->rgbIcon->size () - QSize (8, 8);
+    auto pixmap = color_pixmap (color, sz);
+    ui->rgbIcon->setPixmap (pixmap);
+  }
+
+  void color_editor_widget::handle_rgb_mat_change ()
+  {
+    const auto color = color_from_doubles (
+        ui->rSpn->value (), ui->gSpn->value (), ui->bSpn->value ());
+    set_rgb (color, rgb_source::spinbox);
+  }
+
+  void color_editor_widget::handle_rgb_octave_color_change ()
+  {
+    if (ui->octaveBox->currentIndex () <= 0)
+      return;
+
+    const auto &oct_cols = octave_colors ();
+
+    set_rgb (oct_cols[ui->octaveBox->currentIndex () - 1].color,
+             rgb_source::octave);
+  }
+
+  void color_editor_widget::handle_rgb_svg_change ()
+  {
+    // this is an editable combo box, the user may enter web code value, or
+    // svg name directly. If string is not a valid color, indicate by
+    // displaying red text
+
+    const auto value = ui->svgBox->currentText ();
+
+    const auto col = QColor (value);
+    auto *edit = ui->svgBox->lineEdit ();
+    auto palette = edit->palette ();
+    // discriminate #RGB web code (only accept #RRGGBB)
+    if (!col.isValid () || (value.startsWith ('#') && value.size () != 7))
+      {
+        palette.setColor (QPalette::Text, QColor ("red"));
+      }
+    else
+      {
+        set_rgb (col, rgb_source::svg);
+        palette.setColor (QPalette::Text, QColor ("black"));
+      }
+    edit->setPalette (palette);
+  }
+
+  void color_editor_widget::handle_rgb_pick ()
+  {
+    auto val = get ();
+    QColor c;
+    if (ui->rgbValRad->isChecked ())
+      {
+        c = color_from_doubles (ui->rSpn->value (), ui->gSpn->value (),
+                                ui->bSpn->value ());
+      }
+    QColorDialog dlg (c, this);
+    dlg.setOption (QColorDialog::ShowAlphaChannel, false);
+    if (dlg.exec () == QColorDialog::Accepted)
+      {
+        set_rgb (dlg.currentColor ());
+      }
+  }
+
+  static std::vector<QString> svg_color_names ()
+  {
+    return std::vector<QString>{ "aliceblue",
+                                 "antiquewhite",
+                                 "aqua",
+                                 "aquamarine",
+                                 "azure",
+                                 "beige",
+                                 "bisque",
+                                 "black",
+                                 "blanchedalmond",
+                                 "blue",
+                                 "blueviolet",
+                                 "brown",
+                                 "burlywood",
+                                 "cadetblue",
+                                 "chartreuse",
+                                 "chocolate",
+                                 "coral",
+                                 "cornflowerblue",
+                                 "cornsilk",
+                                 "crimson",
+                                 "cyan",
+                                 "darkblue",
+                                 "darkcyan",
+                                 "darkgoldenrod",
+                                 "darkgray",
+                                 "darkgreen",
+                                 "darkgrey",
+                                 "darkkhaki",
+                                 "darkmagenta",
+                                 "darkolivegreen",
+                                 "darkorange",
+                                 "darkorchid",
+                                 "darkred",
+                                 "darksalmon",
+                                 "darkseagreen",
+                                 "darkslateblue",
+                                 "darkslategray",
+                                 "darkslategrey",
+                                 "darkturquoise",
+                                 "darkviolet",
+                                 "deeppink",
+                                 "deepskyblue",
+                                 "dimgray",
+                                 "dimgrey",
+                                 "dodgerblue",
+                                 "firebrick",
+                                 "floralwhite",
+                                 "forestgreen",
+                                 "fuchsia",
+                                 "gainsboro",
+                                 "ghostwhite",
+                                 "gold",
+                                 "goldenrod",
+                                 "gray",
+                                 "grey",
+                                 "green",
+                                 "greenyellow",
+                                 "honeydew",
+                                 "hotpink",
+                                 "indianred",
+                                 "indigo",
+                                 "ivory",
+                                 "khaki",
+                                 "lavender",
+                                 "lavenderblush",
+                                 "lawngreen",
+                                 "lemonchiffon",
+                                 "lightblue",
+                                 "lightcoral",
+                                 "lightcyan",
+                                 "lightgoldenrodyellow",
+                                 "lightgray",
+                                 "lightgreen",
+                                 "lightgrey",
+                                 "lightpink",
+                                 "lightsalmon",
+                                 "lightseagreen",
+                                 "lightskyblue",
+                                 "lightslategray",
+                                 "lightslategrey",
+                                 "lightsteelblue",
+                                 "lightyellow",
+                                 "lime",
+                                 "limegreen",
+                                 "linen",
+                                 "magenta",
+                                 "maroon",
+                                 "mediumaquamarine",
+                                 "mediumblue",
+                                 "mediumorchid",
+                                 "mediumpurple",
+                                 "mediumseagreen",
+                                 "mediumslateblue",
+                                 "mediumspringgreen",
+                                 "mediumturquoise",
+                                 "mediumvioletred",
+                                 "midnightblue",
+                                 "mintcream",
+                                 "mistyrose",
+                                 "moccasin",
+                                 "navajowhite",
+                                 "navy",
+                                 "oldlace",
+                                 "olive",
+                                 "olivedrab",
+                                 "orange",
+                                 "orangered",
+                                 "orchid",
+                                 "palegoldenrod",
+                                 "palegreen",
+                                 "paleturquoise",
+                                 "palevioletred",
+                                 "papayawhip",
+                                 "peachpuff",
+                                 "peru",
+                                 "pink",
+                                 "plum",
+                                 "powderblue",
+                                 "purple",
+                                 "red",
+                                 "rosybrown",
+                                 "royalblue",
+                                 "saddlebrown",
+                                 "salmon",
+                                 "sandybrown",
+                                 "seagreen",
+                                 "seashell",
+                                 "sienna",
+                                 "silver",
+                                 "skyblue",
+                                 "slateblue",
+                                 "slategray",
+                                 "slategrey",
+                                 "snow",
+                                 "springgreen",
+                                 "steelblue",
+                                 "tan",
+                                 "teal",
+                                 "thistle",
+                                 "tomato",
+                                 "turquoise",
+                                 "violet",
+                                 "wheat",
+                                 "white",
+                                 "whitesmoke",
+                                 "yellow",
+                                 "yellowgreen" };
+  }
+}
diff --git a/libgui/src/property-inspector-table.h b/libgui/src/property-inspector-table.h
new file mode 100644
--- /dev/null
+++ b/libgui/src/property-inspector-table.h
@@ -0,0 +1,239 @@
+////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2013-2020 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_property_inspector_table_h)
+#define octave_property_inspector_table_h 1
+
+#include <memory>
+
+#include <QAbstractTableModel>
+#include <QStyledItemDelegate>
+#include <QTableView>
+#include <QWidget>
+
+#include "Matrix.h"
+
+#include "graphics-handle.h"
+#include "graphics.h"
+
+class QGridLayout;
+class QTableView;
+
+class color_property;
+
+class Ui_color_editor_widget;
+
+namespace octave
+{
+  class base_qobject;
+  class gui_settings;
+  class item_hyperlink_delegate;
+
+  class go_properties_table_view : public QTableView
+  {
+    Q_OBJECT
+  public:
+    go_properties_table_view (QWidget *parent);
+    virtual ~go_properties_table_view ();
+
+    void set_hl_delegate (item_hyperlink_delegate *hl_dg) { m_hl_dg = hl_dg; }
+
+  protected:
+    void mouseMoveEvent (QMouseEvent *ev) override;
+    void mousePressEvent (QMouseEvent *ev) override;
+    void mouseReleaseEvent (QMouseEvent *ev) override;
+
+  private:
+    item_hyperlink_delegate *m_hl_dg = nullptr;
+  };
+
+  class go_properties_table_model : public QAbstractTableModel
+  {
+    Q_OBJECT
+
+  public:
+    go_properties_table_model (QObject *p, base_qobject &oct_qobj,
+                               const graphics_object &go);
+    ~go_properties_table_model ();
+
+    int columnCount (const QModelIndex &parent = QModelIndex{}) const override;
+
+    int rowCount (const QModelIndex &parent = QModelIndex{}) const override;
+
+    QModelIndex index (int row, int column,
+                       const QModelIndex &parent) const override;
+
+    Qt::ItemFlags flags (const QModelIndex &index) const override;
+
+    QVariant headerData (int section, Qt::Orientation orientation,
+                         int role) const override;
+
+    QVariant data (const QModelIndex &index, int role) const override;
+
+    bool setData (const QModelIndex &indes, const QVariant &value,
+                  int role) override;
+
+
+  private slots:
+
+    void handle_go_update (const graphics_object& go, int prop_id);
+
+  private:
+
+    struct prop_index {
+      std::string name;
+      int id;
+    };
+
+    base_qobject &m_oct_qobj;
+    graphics_object m_go;
+
+    std::vector<prop_index> m_props;
+  };
+
+  class go_properties_item_delegate : public QStyledItemDelegate
+  {
+    Q_OBJECT
+  public:
+    go_properties_item_delegate (QObject *parent, base_qobject &oct_qobj,
+                                 QTableView *view,
+                                 item_hyperlink_delegate *hl_dg,
+                                 const gui_settings *settings);
+    ~go_properties_item_delegate () = default;
+
+    void notice_settings (const gui_settings *settings);
+
+    // painting
+    void paint (QPainter *painter, const QStyleOptionViewItem &option,
+                const QModelIndex &index) const override;
+    QSize sizeHint (const QStyleOptionViewItem &option,
+                    const QModelIndex &index) const override;
+
+    // editing
+    QWidget *createEditor (QWidget *parent, const QStyleOptionViewItem &option,
+                           const QModelIndex &index) const override;
+
+    void setEditorData (QWidget *editor,
+                        const QModelIndex &index) const override;
+
+    void setModelData (QWidget *editor, QAbstractItemModel *model,
+                       const QModelIndex &index) const override;
+
+    void updateEditorGeometry (QWidget *editor,
+                               const QStyleOptionViewItem &option,
+                               const QModelIndex &index) const override;
+
+    void destroyEditor (QWidget *editor,
+                        const QModelIndex &index) const override;
+
+  signals:
+
+    void layoutWillChange (int row) const;
+
+  private:
+    void resize_editor_cell (QWidget *editor, const QModelIndex &index) const;
+
+    base_qobject &m_oct_qobj;
+    QTableView *m_view;
+    item_hyperlink_delegate *m_hl_dg;
+    int m_default_row_height;
+
+    mutable QSize m_former_size;
+  };
+
+  // widget to edit (small) matrix and arrays
+  class matrix_widget : public QWidget
+  {
+    Q_OBJECT
+  public:
+    matrix_widget (QWidget *parent, const Matrix &matrix);
+
+    virtual ~matrix_widget () = default;
+
+    void set (const Matrix &matrix);
+    Matrix get (bool *ok = nullptr) const;
+
+  private:
+    QGridLayout *m_layout;
+  };
+
+  class color_editor_val
+  {
+  public:
+    enum value_type
+    {
+      STR,
+      RGB,
+    };
+
+    color_editor_val (const std::string &str);
+    color_editor_val (const Matrix &rgb);
+
+    value_type type () const { return m_type; }
+    std::string string_value () const;
+    Matrix rgb_value () const;
+
+  private:
+    value_type m_type;
+    std::string m_str;
+    Matrix m_rgb;
+  };
+
+  class color_editor_widget : public QWidget
+  {
+    Q_OBJECT
+  public:
+    color_editor_widget (QWidget *parent, const color_property *prop);
+    virtual ~color_editor_widget ();
+
+    void set (const color_editor_val &val);
+    color_editor_val get () const;
+
+  signals:
+    void rgb_changed (const QColor &rgb);
+
+  private:
+    enum class rgb_source
+    {
+      spinbox,
+      octave,
+      svg,
+      other,
+    };
+
+  private slots:
+    void set_rgb (const QColor &color, rgb_source source = rgb_source::other);
+    void handle_rgb_mat_change ();
+    void handle_rgb_octave_color_change ();
+    void handle_rgb_svg_change ();
+    void handle_rgb_pick ();
+
+  private:
+    Ui_color_editor_widget *ui;
+  };
+
+}
+
+#endif
diff --git a/libgui/src/property-inspector-tree.cc b/libgui/src/property-inspector-tree.cc
new file mode 100644
--- /dev/null
+++ b/libgui/src/property-inspector-tree.cc
@@ -0,0 +1,430 @@
+////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2013-2020 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/>.
+//
+////////////////////////////////////////////////////////////////////////
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <algorithm>
+#include <vector>
+
+#include <QThread>
+
+#include "caseless-str.h"
+
+#include "graphics.h"
+#include "interpreter.h"
+
+#include "octave-qobject.h"
+#include "qt-application.h"
+
+#include "property-inspector-tree.h"
+
+namespace octave
+{
+
+  enum Column {
+    COL_TYPE,
+    COL_DESC,
+    COL_VALUE,
+    COL_COUNT,
+  };
+
+  static QString go_description (graphics_object& go, gh_manager& gh_mgr)
+  {
+    const auto type = go.type ();
+    if (type == "uimenu")
+      {
+        return QString::fromStdString (go.get_properties ()
+                                           .get_property ("label")
+                                           .get ()
+                                           .string_value ());
+      }
+    if (type == "uitoggletool" || type == "uipushtool")
+      {
+        return QString::fromStdString (go.get_properties ()
+                                           .get_property ("tooltipstring")
+                                           .get ()
+                                           .string_value ());
+      }
+    if (type == "text")
+      {
+        return QString::fromStdString (go.get_properties ()
+                                           .get_property ("string")
+                                           .get ()
+                                           .string_value ());
+      }
+    if (type == "axes")
+      {
+        auto titlehandle = go.get_properties ()
+                               .get_property ("title")
+                               .get ()
+                               .scalar_value ();
+        auto title = gh_mgr.get_object (titlehandle);
+        return go_description (title, gh_mgr);
+      }
+    return "";
+  }
+
+  using node_ref = std::shared_ptr<pi_tree_node>;
+
+  // a node corresponding to a single graphics object
+  class pi_tree_node : public std::enable_shared_from_this<pi_tree_node>
+  {
+  public:
+    pi_tree_node (const graphics_object& gobj, int row,
+                  const node_ref& parent = nullptr)
+        : m_gh{ gobj.get_handle () }, m_row{ row }, m_parent{ parent }
+    {
+    }
+
+    // not in ctor because we need shared_from_this
+    void prepare (gh_manager& gh_mgr)
+    {
+      auto obj = gh_mgr.get_object (m_gh);
+
+      m_type = QString::fromStdString (obj.type ());
+      m_description = go_description (obj, gh_mgr);
+
+      Matrix kids = obj.get_properties ().get_children ();
+      for (octave_idx_type i = 0; i < kids.numel (); ++i)
+        {
+          const auto kid = gh_mgr.get_object (kids (i));
+          if (!kid)
+            continue;
+
+          const auto r = static_cast<int> (m_children.size ());
+          const auto c
+              = std::make_shared<pi_tree_node> (kid, r, shared_from_this ());
+          c->prepare (gh_mgr);
+          m_children.push_back (c);
+        }
+    }
+
+    node_ref parent () const { return m_parent.lock (); }
+    graphics_handle handle () const { return m_gh; }
+    int row () const { return m_row; }
+    QString type () const { return m_type; }
+
+    QString description () const { return m_description; }
+
+    void update_description (gh_manager& gh_mgr)
+    {
+      auto obj = gh_mgr.get_object (m_gh);
+      m_description = go_description (obj, gh_mgr);
+    }
+
+    const std::vector<node_ref>& children () const { return m_children; }
+
+    node_ref lookup (const graphics_object& go)
+    {
+      if (go.get_handle () == m_gh)
+        return shared_from_this ();
+      for (const auto& c : m_children)
+        {
+          auto n = c->lookup (go);
+          if (n)
+            return n;
+        }
+      return nullptr;
+    }
+
+    node_ref add_child (const node_ref& node)
+    {
+      m_children.push_back (node);
+      return node;
+    }
+
+    void rem_child (const node_ref& node)
+    {
+      if (node->row () >= static_cast<int> (m_children.size ()))
+        {
+          // error
+        }
+      if (m_children[node->row ()] != node)
+        {
+          // error
+        }
+      auto it = std::begin (m_children) + node->row ();
+      m_children.erase (it);
+      // reindex lower children
+      for (int r = node->row (); r < static_cast<int> (m_children.size ());
+           ++r)
+        {
+          m_children[r]->m_row = r;
+        }
+    }
+
+  private:
+    graphics_handle m_gh;
+    int m_row;
+    QString m_type;
+    QString m_description;
+    std::weak_ptr<pi_tree_node> m_parent;
+    std::vector<node_ref> m_children;
+  };
+
+  go_tree_model::go_tree_model (QObject* p, base_qobject& oct_qobj)
+      : QAbstractItemModel (p), m_oct_qobj{ oct_qobj }
+  {
+    auto gh_mgr = oct_qobj.interpreter_qobj ()->get_gh_manager ();
+
+    graphics_object root = gh_mgr->get_object (0);
+
+    initialize (*gh_mgr, root);
+  }
+
+  go_tree_model::go_tree_model (QObject* p, base_qobject& oct_qobj,
+                                const graphics_object& root)
+      : QAbstractItemModel (p), m_oct_qobj{ oct_qobj }
+  {
+    auto gh_mgr = oct_qobj.interpreter_qobj ()->get_gh_manager ();
+
+    initialize (*gh_mgr, root);
+  }
+
+  void go_tree_model::initialize (gh_manager& gh_mgr,
+                                  const graphics_object& root)
+  {
+    if (!root)
+      {
+        throw new std::logic_error (
+            "go_tree_model::initialize: invalide root");
+      }
+
+    auto events = m_oct_qobj.get_qt_interpreter_events ();
+
+    QObject::connect (events.get (), &qt_interpreter_events::go_created, this,
+                      &go_tree_model::add_graphics_object);
+
+    QObject::connect (events.get (), &qt_interpreter_events::go_will_delete,
+                      this, &go_tree_model::rem_graphics_object);
+
+    QObject::connect (events.get (), &qt_interpreter_events::go_updated, this,
+                      &go_tree_model::handle_go_update);
+
+    m_root = std::make_shared<pi_tree_node> (root, 0);
+    m_root->prepare (gh_mgr);
+  }
+
+  go_tree_model::~go_tree_model () {}
+
+  QModelIndex go_tree_model::index (int row, int column,
+                                    const QModelIndex& parent) const
+  {
+    auto pnode = get_node (parent);
+    if (!pnode)
+      {
+        // root index
+        return node_index (m_root, column);
+      }
+    else
+      {
+        const auto& children = pnode->children ();
+        if (row >= 0 && row < static_cast<int> (children.size ()))
+          {
+            const auto child = children[row];
+            return node_index (child, column);
+          }
+      }
+    return QModelIndex{};
+  }
+
+  QModelIndex go_tree_model::parent (const QModelIndex& index) const
+  {
+    const auto node = get_node (index);
+    if (node)
+      return node_index (node->parent ());
+    return QModelIndex{};
+  }
+
+  int go_tree_model::columnCount (const QModelIndex&) const { return COL_COUNT; }
+
+  int go_tree_model::rowCount (const QModelIndex& parent) const
+  {
+    const auto pnode = get_node (parent);
+    if (!pnode)
+      return m_root ? 1 : 0;
+    else
+      {
+        return static_cast<int> (pnode->children ().size ());
+      }
+  }
+
+  QVariant go_tree_model::headerData (int section, Qt::Orientation orientation,
+                                      int role) const
+  {
+    if (orientation != Qt::Horizontal)
+      return QVariant{};
+    if (role != Qt::DisplayRole)
+      return QVariant{};
+    switch (section)
+      {
+      case COL_TYPE:
+        return "Handle type";
+      case COL_DESC:
+        return "Description";
+      case COL_VALUE:
+        return "Value";
+      default:
+        break;
+      }
+    return QVariant{};
+  }
+
+  QVariant go_tree_model::data (const QModelIndex& index, int role) const
+  {
+    const auto pnode = get_node (index);
+    if (!pnode)
+      return QVariant{};
+    if (role == Qt::DisplayRole)
+      {
+        switch (index.column ())
+          {
+          case COL_TYPE:
+            return pnode->type ();
+          case COL_DESC:
+            return pnode->description ();
+          case COL_VALUE:
+            return pnode->handle ().value ();
+          default:
+            break;
+          }
+      }
+    return QVariant{};
+  }
+
+  graphics_object go_tree_model::get_go_at (const QModelIndex& index) const
+  {
+
+    const auto node = get_node (index);
+    if (node)
+      {
+        auto gh_mgr = m_oct_qobj.interpreter_qobj ()->get_gh_manager ();
+        const auto gh = node->handle ();
+        return gh_mgr->get_object (gh);
+      }
+    return graphics_object{};
+  }
+
+  void go_tree_model::add_graphics_object (const graphics_object& go)
+  {
+    auto gh_mgr = m_oct_qobj.interpreter_qobj ()->get_gh_manager ();
+
+    const auto ph = go.get_parent ();
+    const auto pnode = lookup (gh_mgr->get_object (ph));
+    if (!pnode)
+      return;
+
+    const auto pindex = node_index (pnode);
+    const auto r = static_cast<int> (pnode->children ().size ());
+
+    beginInsertRows (pindex, r, r);
+
+    const auto node = std::make_shared<pi_tree_node> (go, r, pnode);
+    node->prepare (*gh_mgr);
+    pnode->add_child (node);
+
+    endInsertRows ();
+  }
+
+  void go_tree_model::rem_graphics_object (const graphics_object& go)
+  {
+    const auto node = lookup (go);
+    if (!node)
+      return;
+
+    const auto pnode = node->parent ();
+    const auto pindex = node_index (pnode);
+
+    if (!pnode)
+      throw std::logic_error ("go_tree_model::rem_graphics_object: "
+                              "parentless node");
+
+    beginRemoveRows (pindex, node->row (), node->row ());
+
+    pnode->rem_child (node);
+
+    endRemoveRows ();
+  }
+
+  void go_tree_model::handle_go_update (const graphics_object& go, int prop_id)
+  {
+    Q_UNUSED(prop_id)
+
+    const auto node = lookup (go);
+
+    if (!node)
+      return;
+
+    const auto olddesc = node->description ();
+
+    {
+      auto gh_mgr = m_oct_qobj.interpreter_qobj ()->get_gh_manager ();
+      node->update_description (*gh_mgr);
+    }
+
+    const auto newdesc = node->description ();
+
+    if (olddesc == newdesc)
+      return;
+
+    auto index = node_index(node, COL_DESC);
+    emit dataChanged(index, index);
+  }
+
+  QModelIndex
+  go_tree_model::node_index (const std::shared_ptr<pi_tree_node>& node,
+                             int column) const
+  {
+    if (!node)
+      return QModelIndex{};
+
+    return createIndex (node->row (), column,
+                        static_cast<void*> (node.get ()));
+  }
+
+  node_ref go_tree_model::get_node (const QModelIndex& index) const
+  {
+    if (!index.isValid ())
+      return nullptr;
+    if (!index.internalPointer ())
+      return nullptr;
+
+    return static_cast<pi_tree_node*> (index.internalPointer ())
+        ->shared_from_this ();
+  }
+
+  node_ref go_tree_model::lookup (const graphics_object& obj) const
+  {
+    if (!obj)
+      return nullptr;
+    if (!m_root)
+      return nullptr;
+
+    return m_root->lookup (obj);
+  }
+
+}
diff --git a/libgui/src/property-inspector-tree.h b/libgui/src/property-inspector-tree.h
new file mode 100644
--- /dev/null
+++ b/libgui/src/property-inspector-tree.h
@@ -0,0 +1,90 @@
+////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2013-2020 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_property_inspector_tree_h)
+#define octave_property_inspector_tree_h 1
+
+#include <memory>
+
+#include <QAbstractItemModel>
+
+#include "graphics-handle.h"
+
+class gh_manager;
+class graphics_object;
+
+namespace octave
+{
+  class base_qobject;
+
+  class pi_tree_node;
+
+  class go_tree_model : public QAbstractItemModel
+  {
+    Q_OBJECT
+
+  public:
+    go_tree_model (QObject* p, base_qobject& oct_qobj);
+    go_tree_model (QObject* p, base_qobject& oct_qobj,
+                   const graphics_object& root);
+
+  private:
+    void initialize (gh_manager& gh_mgr, const graphics_object& root);
+
+  public:
+    virtual ~go_tree_model ();
+
+    QModelIndex index (int row, int column,
+                       const QModelIndex& parent) const override;
+    QModelIndex parent (const QModelIndex& index) const override;
+
+    int columnCount (const QModelIndex& parent) const override;
+
+    int rowCount (const QModelIndex& parent) const override;
+
+    QVariant headerData (int section, Qt::Orientation orientation,
+                         int role) const override;
+
+    QVariant data (const QModelIndex& index, int role) const override;
+
+    graphics_object get_go_at (const QModelIndex& index) const;
+
+  private slots:
+    void add_graphics_object (const graphics_object& go);
+    void rem_graphics_object (const graphics_object& go);
+    void handle_go_update (const graphics_object& go, int prop_id);
+
+  private:
+    base_qobject& m_oct_qobj;
+    std::shared_ptr<pi_tree_node> m_root;
+
+    QModelIndex node_index (const std::shared_ptr<pi_tree_node>& node,
+                            int column = 0) const;
+    std::shared_ptr<pi_tree_node> get_node (const QModelIndex& index) const;
+    std::shared_ptr<pi_tree_node> lookup (const graphics_object& obj) const;
+  };
+}
+
+#endif
diff --git a/libgui/src/property-inspector.cc b/libgui/src/property-inspector.cc
new file mode 100644
--- /dev/null
+++ b/libgui/src/property-inspector.cc
@@ -0,0 +1,194 @@
+////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2013-2020 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/>.
+//
+////////////////////////////////////////////////////////////////////////
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <QHeaderView>
+#include <QSplitter>
+#include <QTableView>
+#include <QTreeView>
+
+#include "gui-preferences-pi.h"
+#include "gui-settings.h"
+#include "item-hyperlink-delegate.h"
+#include "octave-qobject.h"
+#include "property-inspector-table.h"
+#include "property-inspector-tree.h"
+#include "property-inspector.h"
+#include "qt-application.h"
+#include "resource-manager.h"
+
+namespace octave
+{
+
+  property_inspector::property_inspector (QWidget *p, base_qobject &oct_qobj)
+      : octave_dock_widget ("PropertyInspector", p, oct_qobj),
+        m_splitter (new QSplitter (Qt::Horizontal, this)),
+        m_tree (new QTreeView (this)),
+        m_table (new go_properties_table_view (this)), m_tree_model (nullptr),
+        m_table_model (nullptr)
+  {
+    set_title (tr ("Property Inspector"));
+    setStatusTip (tr ("Inspect properties."));
+    setWindowIcon (QIcon (":/actions/icons/logo.png"));
+    setAttribute (Qt::WA_AlwaysShowToolTips);
+
+    // setting widget layout
+    m_tree->setUniformRowHeights (true);
+
+    m_splitter->addWidget (m_tree);
+    m_splitter->addWidget (m_table);
+    m_splitter->setChildrenCollapsible (true);
+
+    // show handle of QSplitter
+    m_splitter->setStyleSheet (
+        "QSplitter::handle:horizontal {"
+        "  image: url(:/actions/icons/hsplitter-handle.png)"
+        "}");
+    m_splitter->setHandleWidth(7);
+
+    m_splitter->setSizes (QIntList{ 300, 600 });
+
+    setWidget (m_splitter);
+
+    // read preferences
+    resource_manager &rmgr = m_octave_qobj.get_resource_manager ();
+    gui_settings *settings = rmgr.get_settings ();
+
+    const auto table_row_height = settings->value (pi_table_row_height);
+    if (table_row_height.isValid ())
+      {
+        // m_table->verticalHeader ()->setDefaultSectionSize (
+        //     table_row_height_pref.toInt ());
+      }
+    else
+      {
+        settings->setValue (
+            pi_table_row_height.key,
+            QVariant (m_table->verticalHeader ()->defaultSectionSize ()));
+      }
+
+    m_tree->setAlternatingRowColors (
+        settings->value (pi_tree_alternate_rows).toBool ());
+    m_table->setAlternatingRowColors (
+        settings->value (pi_table_alternate_rows).toBool ());
+
+    auto *table_hl_dg = new item_hyperlink_delegate (this, m_table);
+
+    m_table_delegate = new go_properties_item_delegate (
+        this, m_octave_qobj, m_table, table_hl_dg, settings);
+
+    m_table->set_hl_delegate (table_hl_dg);
+    m_table->setItemDelegate (m_table_delegate);
+
+    if (oct_qobj.app_context ().interpreter_initialized ())
+      {
+        initialize_models ();
+      }
+    else
+      {
+        QObject::connect (oct_qobj.interpreter_qobj (),
+                          &interpreter_qobject::ready, this,
+                          &property_inspector::initialize_models);
+      }
+  }
+
+  property_inspector::~property_inspector () = default;
+
+  void property_inspector::notice_settings (const gui_settings *settings)
+  {
+    // m_table->verticalHeader ()->setDefaultSectionSize (
+    //     settings->value (pi_table_row_height).toInt ());
+    m_tree->setAlternatingRowColors (
+        settings->value (pi_tree_alternate_rows).toBool ());
+    m_table->setAlternatingRowColors (
+        settings->value (pi_table_alternate_rows).toBool ());
+
+    m_table_delegate->notice_settings (settings);
+
+    octave_dock_widget::notice_settings (settings);
+
+    m_table->resizeRowsToContents ();
+  }
+
+  void property_inspector::save_settings ()
+  {
+    // resource_manager &rmgr = m_octave_qobj.get_resource_manager ();
+    // gui_settings *settings = rmgr.get_settings ();
+
+    // settings->setValue (pi_table_row_height.key,
+    //                     m_table->verticalHeader ()->defaultSectionSize ());
+    // settings->setValue (pi_table_alternate_rows.key,
+    //                     m_table->alternatingRowColors ());
+    // settings->setValue (pi_tree_alternate_rows.key,
+    //                     m_tree->alternatingRowColors ());
+
+    octave_dock_widget::save_settings ();
+  }
+
+  void property_inspector::initialize_models ()
+  {
+    m_tree_model = new go_tree_model (this, m_octave_qobj);
+    auto *old_model = m_tree->model ();
+    m_tree->setModel (m_tree_model);
+    if (old_model)
+      delete old_model;
+
+    // give the first column some space
+    m_tree->header ()->resizeSection (0, 150);
+
+    QObject::connect (m_tree->selectionModel (),
+                      &QItemSelectionModel::currentChanged, this,
+                      &property_inspector::tree_current_changed);
+  }
+
+  void property_inspector::tree_current_changed (const QModelIndex &current,
+                                                 const QModelIndex &)
+  {
+    auto go = m_tree_model->get_go_at (current);
+    if (!go)
+      return;
+
+    auto *prev_model = m_table->model ();
+
+    auto new_model = std::make_unique<go_properties_table_model> (
+        nullptr, m_octave_qobj, go);
+
+    m_table->setModel (new_model.get ());
+    m_table->resizeColumnsToContents ();
+    m_table->resizeRowsToContents ();
+
+    if (prev_model && prev_model != m_table_model.get ())
+      {
+        // initial empty model placed by Qt - we don't need it anymore
+        delete prev_model;
+      }
+
+    m_table_model = std::move (new_model);
+  }
+
+}
diff --git a/libgui/src/property-inspector.h b/libgui/src/property-inspector.h
new file mode 100644
--- /dev/null
+++ b/libgui/src/property-inspector.h
@@ -0,0 +1,86 @@
+////////////////////////////////////////////////////////////////////////
+//
+// Copyright (C) 2013-2020 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_property_inspector_h)
+#define octave_property_inspector_h 1
+
+#include <memory>
+
+#include "octave-dock-widget.h"
+
+class QSplitter;
+class QTreeView;
+
+namespace octave
+{
+  class base_qobject;
+
+  class go_tree_model;
+  class go_properties_table_model;
+  class go_properties_table_view;
+  class go_properties_item_delegate;
+
+  class property_inspector : public octave_dock_widget
+  {
+    Q_OBJECT
+
+  public:
+    property_inspector (QWidget *parent, base_qobject &oct_qobj);
+
+    ~property_inspector ();
+
+    // No copying!
+
+    property_inspector (const property_inspector &) = delete;
+
+    property_inspector &operator= (const property_inspector &) = delete;
+
+  public slots:
+
+    virtual void notice_settings (const gui_settings *) override;
+
+    void save_settings ();
+
+  private slots:
+
+    // we need the interpreter ready, so we defer this to a slot.
+    void initialize_models ();
+
+    void tree_current_changed (const QModelIndex &current,
+                               const QModelIndex &previous);
+
+  private:
+    QSplitter *m_splitter;
+    QTreeView *m_tree;
+    go_properties_table_view *m_table;
+    go_properties_item_delegate *m_table_delegate;
+
+    go_tree_model *m_tree_model;
+    // the table is often switched, so it is easier to put it in unique_ptr
+    std::unique_ptr<go_properties_table_model> m_table_model;
+  };
+}
+
+#endif
diff --git a/libgui/src/qt-interpreter-events.cc b/libgui/src/qt-interpreter-events.cc
--- a/libgui/src/qt-interpreter-events.cc
+++ b/libgui/src/qt-interpreter-events.cc
@@ -575,6 +575,22 @@ namespace octave
     emit delete_debugger_pointer_signal (QString::fromStdString (file), line);
   }
 
+  void qt_interpreter_events::notify_go_created (const graphics_object& go)
+  {
+    emit go_created (go);
+  }
+
+  void qt_interpreter_events::notify_go_updated (const graphics_object& go,
+                                                 int prop_id)
+  {
+    emit go_updated (go, prop_id);
+  }
+
+  void qt_interpreter_events::notify_go_will_delete (const graphics_object& go)
+  {
+    emit go_will_delete (go);
+  }
+
   void
   qt_interpreter_events::confirm_shutdown_octave (void)
   {
diff --git a/libgui/src/qt-interpreter-events.h b/libgui/src/qt-interpreter-events.h
--- a/libgui/src/qt-interpreter-events.h
+++ b/libgui/src/qt-interpreter-events.h
@@ -35,15 +35,22 @@
 #include <QString>
 #include <QWaitCondition>
 
+#include "event-manager.h"
+#include "graphics-handle.h"
+
 #include "dialog.h"
 #include "gui-settings.h"
 
-#include "event-manager.h"
-
 // Defined for purposes of sending QList<int> as part of signal.
 typedef QList<int> QIntList;
 
 class octave_value;
+class graphics_object;
+
+namespace QtHandles
+{
+  class qt_graphics_toolkit;
+}
 
 namespace octave
 {
@@ -178,6 +185,12 @@ namespace octave
     void update_breakpoint (bool insert, const std::string& file, int line,
                             const std::string& cond);
 
+    void notify_go_created(const graphics_object& go) override;
+
+    void notify_go_updated(const graphics_object& go, int prop_id) override;
+
+    void notify_go_will_delete(const graphics_object& go) override;
+
     void lock (void) { m_mutex.lock (); }
 
     void wait (void) { m_waitcondition.wait (&m_mutex); }
@@ -260,6 +273,12 @@ namespace octave
 
     void apply_new_settings (void);
 
+    void go_created (const graphics_object& go);
+
+    void go_updated (const graphics_object& go, int prop_id);
+
+    void go_will_delete (const graphics_object& go);
+
   private:
 
     QString gui_preference_adjust (const QString& key, const QString& value);
diff --git a/libgui/src/resource.qrc b/libgui/src/resource.qrc
--- a/libgui/src/resource.qrc
+++ b/libgui/src/resource.qrc
@@ -59,6 +59,7 @@
         <file>icons/graphic_logo_VariableEditor.png</file>
         <file>icons/fleur.png</file>
         <file>icons/hand2.png</file>
+        <file>icons/hsplitter-handle.png</file>
         <file>icons/left_side.png</file>
         <file>icons/letter_logo_FilesDockWidget.png</file>
         <file>icons/letter_logo_FileEditor.png</file>
diff --git a/libgui/src/settings-dialog.cc b/libgui/src/settings-dialog.cc
--- a/libgui/src/settings-dialog.cc
+++ b/libgui/src/settings-dialog.cc
@@ -451,6 +451,11 @@ namespace octave
     // variable editor colors
     read_varedit_colors (settings);
 
+    // property inspector
+    pi_treeAlternateRows->setChecked(settings->value(pi_tree_alternate_rows).toBool());
+    pi_tableRowHeight->setValue(settings->value(pi_table_row_height).toInt());
+    pi_tableAlternateRows->setChecked(settings->value(pi_table_alternate_rows).toBool());
+
     // shortcuts
 
     shortcut_manager& scmgr = m_octave_qobj.get_shortcut_manager ();
@@ -1069,6 +1074,11 @@ namespace octave
     settings->setValue (ve_font_size.key, varedit_fontSize->value ());
     write_varedit_colors (settings);
 
+    // Property inspector
+    settings->setValue(pi_tree_alternate_rows.key, pi_treeAlternateRows->isChecked());
+    settings->setValue(pi_table_alternate_rows.key, pi_tableAlternateRows->isChecked());
+    settings->setValue(pi_table_row_height.key, pi_tableRowHeight->value());
+
     // shortcuts
 
     settings->setValue (sc_prevent_rl_conflicts.key, cb_prevent_readline_conflicts->isChecked ());
diff --git a/libgui/src/settings-dialog.ui b/libgui/src/settings-dialog.ui
--- a/libgui/src/settings-dialog.ui
+++ b/libgui/src/settings-dialog.ui
@@ -22,8 +22,8 @@
   <property name="windowTitle">
    <string>Preferences</string>
   </property>
-  <layout class="QVBoxLayout" name="verticalLayout_2">
-   <item>
+  <layout class="QGridLayout" name="gridLayout_25">
+   <item row="0" column="0">
     <widget class="QTabWidget" name="tabWidget">
      <property name="minimumSize">
       <size>
@@ -32,7 +32,7 @@
       </size>
      </property>
      <property name="currentIndex">
-      <number>7</number>
+      <number>0</number>
      </property>
      <widget class="QWidget" name="tab_general">
       <property name="enabled">
@@ -843,7 +843,7 @@
             <x>0</x>
             <y>0</y>
             <width>1021</width>
-            <height>1529</height>
+            <height>1528</height>
            </rect>
           </property>
           <layout class="QVBoxLayout" name="verticalLayout_16">
@@ -2609,6 +2609,91 @@
        </item>
       </layout>
      </widget>
+     <widget class="QWidget" name="tab_pi">
+      <attribute name="title">
+       <string>Property Inspector</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout_26">
+       <item row="0" column="0">
+        <widget class="QGroupBox" name="groupBox_14">
+         <property name="title">
+          <string>Graphics objects tree</string>
+         </property>
+         <layout class="QGridLayout" name="gridLayout_6">
+          <item row="0" column="0" colspan="2">
+           <widget class="QCheckBox" name="pi_treeAlternateRows">
+            <property name="text">
+             <string>Alternate row colors</string>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item row="0" column="1">
+        <widget class="QGroupBox" name="groupBox_15">
+         <property name="title">
+          <string>Graphics properties table</string>
+         </property>
+         <layout class="QGridLayout" name="gridLayout_24">
+          <item row="0" column="1">
+           <widget class="QSpinBox" name="pi_tableRowHeight">
+            <property name="minimumSize">
+             <size>
+              <width>80</width>
+              <height>0</height>
+             </size>
+            </property>
+            <property name="value">
+             <number>32</number>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="0" colspan="3">
+           <widget class="QCheckBox" name="pi_tableAlternateRows">
+            <property name="text">
+             <string>Alternate row colors</string>
+            </property>
+           </widget>
+          </item>
+          <item row="0" column="0">
+           <widget class="QLabel" name="label_4">
+            <property name="text">
+             <string>Default row height</string>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item row="1" column="0" colspan="2">
+        <spacer name="verticalSpacer_8">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>40</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item row="0" column="2">
+        <spacer name="horizontalSpacer_33">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>40</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </widget>
      <widget class="QWidget" name="tab_shortcuts">
       <attribute name="title">
        <string>Shortcuts</string>
@@ -2764,15 +2849,15 @@
                  <attribute name="headerCascadingSectionResizes">
                   <bool>false</bool>
                  </attribute>
+                 <attribute name="headerMinimumSectionSize">
+                  <number>64</number>
+                 </attribute>
                  <attribute name="headerDefaultSectionSize">
                   <number>120</number>
                  </attribute>
                  <attribute name="headerHighlightSections">
                   <bool>false</bool>
                  </attribute>
-                 <attribute name="headerMinimumSectionSize">
-                  <number>64</number>
-                 </attribute>
                  <attribute name="headerStretchLastSection">
                   <bool>true</bool>
                  </attribute>
@@ -2996,7 +3081,7 @@
      </widget>
     </widget>
    </item>
-   <item>
+   <item row="1" column="0">
     <widget class="QDialogButtonBox" name="button_box">
      <property name="standardButtons">
       <set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
diff --git a/libgui/src/shortcut-manager.cc b/libgui/src/shortcut-manager.cc
--- a/libgui/src/shortcut-manager.cc
+++ b/libgui/src/shortcut-manager.cc
@@ -177,6 +177,7 @@ namespace octave
     init (tr ("Show Editor"), sc_main_window_show_editor);
     init (tr ("Show Documentation"), sc_main_window_show_doc);
     init (tr ("Show Variable Editor"), sc_main_window_show_variable_editor);
+    init (tr ("Show Property Inspector"), sc_main_window_show_property_inspector);
     init (tr ("Command Window"), sc_main_window_command);
     init (tr ("Command History"), sc_main_window_history);
     init (tr ("File Browser"), sc_main_window_file_browser);
@@ -184,6 +185,7 @@ namespace octave
     init (tr ("Editor"), sc_main_window_editor);
     init (tr ("Documentation"), sc_main_window_doc);
     init (tr ("Variable Editor"), sc_main_window_variable_editor);
+    init (tr ("Property Inspector"), sc_main_window_property_inspector);
     init (tr ("Previous Widget"), sc_main_window_previous_dock);
     init (tr ("Reset Default Window Layout"), sc_main_window_reset);
 
diff --git a/libinterp/corefcn/event-manager.h b/libinterp/corefcn/event-manager.h
--- a/libinterp/corefcn/event-manager.h
+++ b/libinterp/corefcn/event-manager.h
@@ -40,6 +40,7 @@
 
 class octave_value;
 class string_vector;
+class graphics_object;
 
 namespace octave
 {
@@ -244,6 +245,18 @@ namespace octave
     update_breakpoint (bool /*insert*/, const std::string& /*file*/,
                        int /*line*/, const std::string& /*cond*/)
     { }
+
+    virtual void
+    notify_go_created(const graphics_object& /*go*/)
+    { }
+
+    virtual void
+    notify_go_updated(const graphics_object& /*go*/, int /*prop_id*/)
+    { }
+
+    virtual void
+    notify_go_will_delete(const graphics_object& /*go*/)
+    { }
   };
 
   //! Provides threadsafe access to octave.
@@ -623,6 +636,24 @@ namespace octave
         instance->update_breakpoint (insert, file, line, cond);
     }
 
+    void notify_go_created (const graphics_object& go)
+    {
+      if (enabled ())
+        instance->notify_go_created(go);
+    }
+
+    void notify_go_updated (const graphics_object& go, int prop_id)
+    {
+      if (enabled ())
+        instance->notify_go_updated(go, prop_id);
+    }
+
+    void notify_go_will_delete (const graphics_object& go)
+    {
+      if (enabled ())
+        instance->notify_go_will_delete(go);
+    }
+
   private:
 
     interpreter& m_interpreter;
diff --git a/libinterp/corefcn/genprops.awk b/libinterp/corefcn/genprops.awk
--- a/libinterp/corefcn/genprops.awk
+++ b/libinterp/corefcn/genprops.awk
@@ -300,7 +300,7 @@ function emit_declarations ()
   if (class_name && ! base)
     emit_common_declarations();
 
-  printf ("public:\n\n\n  static std::set<std::string> core_property_names (void);\n\n  static std::set<std::string> readonly_property_names (void);\n\n  static bool has_core_property (const caseless_str& pname);\n\n  static bool has_readonly_property (const caseless_str& pname);\n\n  std::set<std::string> all_property_names (void) const;\n\n");
+  printf ("public:\n\n\n  static std::set<std::string> core_property_names (void);\n\n  static std::set<std::string> readonly_property_names (void);\n\n  static bool has_core_property (const caseless_str& pname);\n\n  static bool has_readonly_property (const caseless_str& pname);\n\n  virtual std::set<std::string> all_property_names (void) const;\n\n");
 
   if (! base)
     printf ("  bool has_property (const caseless_str& pname) const;\n\n");
diff --git a/libinterp/corefcn/graphics.cc b/libinterp/corefcn/graphics.cc
--- a/libinterp/corefcn/graphics.cc
+++ b/libinterp/corefcn/graphics.cc
@@ -51,6 +51,7 @@
 #include "defun.h"
 #include "display.h"
 #include "error.h"
+#include "event-manager.h"
 #include "graphics.h"
 #include "input.h"
 #include "interpreter-private.h"
@@ -1261,13 +1262,17 @@ base_property::set (const octave_value& 
 {
   if (do_set (v))
     {
+      gh_manager& gh_mgr
+        = octave::__get_gh_manager__ ("base_property::set");
+
+      octave::event_manager& ev_mgr
+        = octave::__get_event_manager__ ("base_property::set");
+
+      graphics_object go = gh_mgr.get_object (parent);
+
       // Notify graphics toolkit.
       if (id >= 0 && do_notify_toolkit)
         {
-          gh_manager& gh_mgr
-            = octave::__get_gh_manager__ ("base_property::set");
-
-          graphics_object go = gh_mgr.get_object (parent);
           if (go)
             go.update (id);
         }
@@ -1276,6 +1281,9 @@ base_property::set (const octave_value& 
       if (do_run)
         run_listeners (GCB_POSTSET);
 
+      // notify gui of the property change
+      ev_mgr.notify_go_updated(go, id);
+
       return true;
     }
 
@@ -2858,6 +2866,9 @@ gh_manager::free (const graphics_handle&
       if (p == m_handle_map.end ())
         error ("graphics_handle::free: invalid object %g", h.value ());
 
+      // Notify the event-manager
+      m_interpreter.get_event_manager ().notify_go_will_delete (p->second);
+
       base_properties& bp = p->second.get_properties ();
 
       if (! p->second.valid_object () || bp.is_beingdeleted ())
@@ -2912,6 +2923,8 @@ void
 gh_manager::renumber_figure (const graphics_handle& old_gh,
                              const graphics_handle& new_gh)
 {
+  // FIXME: do we have to notify the event manager?
+
   auto p = m_handle_map.find (old_gh);
 
   if (p == m_handle_map.end ())
@@ -2919,10 +2932,14 @@ gh_manager::renumber_figure (const graph
 
   graphics_object go = p->second;
 
+  // m_interpreter.get_event_manager().notify_go_will_delete(go);
+
   m_handle_map.erase (p);
 
   m_handle_map[new_gh] = go;
 
+  // m_interpreter.get_event_manager().notify_go_created(go);
+
   if (old_gh.value () < 0)
     m_handle_free_list.insert (std::ceil (old_gh.value ())
                                - make_handle_fraction ());
@@ -11892,6 +11909,9 @@ gh_manager::make_graphics_handle (const 
   if (notify_toolkit)
     go.initialize ();
 
+  // Notify the event manager
+  m_interpreter.get_event_manager().notify_go_created(go);
+
   return h;
 }
 
@@ -11909,6 +11929,9 @@ gh_manager::make_figure_handle (double v
   if (notify_toolkit)
     go.initialize ();
 
+  // Notify the event manager
+  m_interpreter.get_event_manager().notify_go_created(go);
+
   go.override_defaults ();
 
   return h;
diff --git a/libinterp/corefcn/graphics.in.h b/libinterp/corefcn/graphics.in.h
--- a/libinterp/corefcn/graphics.in.h
+++ b/libinterp/corefcn/graphics.in.h
@@ -60,6 +60,25 @@
 
 // ---------------------------------------------------------------------
 
+enum property_type
+{
+  PROP_INVALID,
+  PROP_ARRAY,
+  PROP_BOOL,
+  PROP_CALLBACK,
+  PROP_CHILDREN,
+  PROP_COLOR,
+  PROP_DOUBLE,
+  PROP_DOUBLE_RADIO,
+  PROP_HANDLE,
+  PROP_RADIO,
+  PROP_ROW_VECTOR,
+  PROP_STRING,
+  PROP_STRING_ARRAY,
+  PROP_TEXT_LABEL,
+  PROP_ANY,
+};
+
 class base_scaler
 {
 public:
@@ -314,6 +333,8 @@ public:
 
   virtual bool is_radio (void) const { return false; }
 
+  virtual property_type prop_type () const { return PROP_INVALID; }
+
   int get_id (void) const { return id; }
 
   void set_id (int d) { id = d; }
@@ -440,6 +461,11 @@ public:
   string_property (const string_property& p)
     : base_property (p), str (p.str) { }
 
+  virtual property_type prop_type () const override
+  {
+    return PROP_STRING;
+  }
+
   octave_value get (void) const
   { return octave_value (str); }
 
@@ -525,6 +551,11 @@ public:
     : base_property (p), desired_type (p.desired_type),
       separator (p.separator), str (p.str) { }
 
+  virtual property_type prop_type () const override
+  {
+    return PROP_STRING_ARRAY;
+  }
+
   octave_value get (void) const
   {
     if (desired_type == string_t)
@@ -737,6 +768,11 @@ public:
     return tmp.isempty ();
   }
 
+  virtual property_type prop_type () const override
+  {
+    return PROP_TEXT_LABEL;
+  }
+
   octave_value get (void) const
   {
     if (stored_type == char_t)
@@ -941,6 +977,11 @@ public:
   radio_property (const radio_property& p)
     : base_property (p), vals (p.vals), current_val (p.current_val) { }
 
+  virtual property_type prop_type () const override
+  {
+    return PROP_RADIO;
+  }
+
   octave_value get (void) const { return octave_value (current_val); }
 
   const std::string& current_value (void) const { return current_val; }
@@ -1107,6 +1148,11 @@ public:
       color_val (p.color_val), radio_val (p.radio_val),
       current_val (p.current_val) { }
 
+  virtual property_type prop_type () const override
+  {
+    return PROP_COLOR;
+  }
+
   octave_value get (void) const
   {
     if (current_type == color_t)
@@ -1188,6 +1234,11 @@ public:
       minval (std::pair<double, bool> (octave_NaN, true)),
       maxval (std::pair<double, bool> (octave_NaN, true)) { }
 
+  virtual property_type prop_type () const override
+  {
+    return PROP_DOUBLE;
+  }
+
   octave_value get (void) const { return octave_value (current_val); }
 
   double double_value (void) const { return current_val; }
@@ -1312,6 +1363,11 @@ public:
       dval (p.dval), radio_val (p.radio_val),
       current_val (p.current_val) { }
 
+  virtual property_type prop_type () const override
+  {
+    return PROP_DOUBLE_RADIO;
+  }
+
   octave_value get (void) const
   {
     if (current_type == double_t)
@@ -1401,6 +1457,11 @@ public:
       maxval (std::pair<double, bool> (octave_NaN, true))
   { }
 
+  virtual property_type prop_type () const override
+  {
+    return PROP_ARRAY;
+  }
+
   octave_value get (void) const { return data; }
 
   void add_constraint (const std::string& type)
@@ -1517,6 +1578,11 @@ public:
     add_constraint (dim_vector (0, 0));
   }
 
+  virtual property_type prop_type () const override
+  {
+    return PROP_ROW_VECTOR;
+  }
+
   void add_constraint (const std::string& type)
   {
     array_property::add_constraint (type);
@@ -1608,6 +1674,11 @@ public:
   bool_property (const bool_property& p)
     : radio_property (p) { }
 
+  virtual property_type prop_type () const override
+  {
+    return PROP_BOOL;
+  }
+
   bool is_on (void) const { return is ("on"); }
 
   bool_property& operator = (const octave_value& val)
@@ -1641,6 +1712,11 @@ public:
   handle_property (const handle_property& p)
     : base_property (p), current_val (p.current_val) { }
 
+  virtual property_type prop_type () const override
+  {
+    return PROP_HANDLE;
+  }
+
   octave_value get (void) const { return current_val.as_octave_value (); }
 
   graphics_handle handle_value (void) const { return current_val; }
@@ -1693,6 +1769,8 @@ public:
     return *this;
   }
 
+  virtual property_type prop_type () const override { return PROP_ANY; }
+
   base_property * clone (void) const { return new any_property (*this); }
 
 protected:
@@ -1730,6 +1808,11 @@ public:
     do_init_children (p.children_list);
   }
 
+  virtual property_type prop_type () const override
+  {
+    return PROP_CHILDREN;
+  }
+
   children_property& operator = (const octave_value& val)
   {
     set (val);
@@ -1909,6 +1992,11 @@ public:
   callback_property (const callback_property& p)
     : base_property (p), callback (p.callback) { }
 
+  virtual property_type prop_type () const override
+  {
+    return PROP_CALLBACK;
+  }
+
   octave_value get (void) const { return callback; }
 
   OCTINTERP_API void execute (const octave_value& data = octave_value ()) const;
@@ -1947,6 +2035,24 @@ private:
 
 // ---------------------------------------------------------------------
 
+#if 0
+
+namespace detail {
+
+template<class prop_type>
+inline prop_type& prop_cast(base_property *rep, const char *who)
+{
+  auto *p = dynamic_cast<prop_type*>(rep);
+  if (!p) {
+    throw std::logic_error(std::string(who) + ": invalid property cast");
+  }
+  return *p;
+}
+
+}
+
+#endif // 0
+
 class property
 {
 public:
@@ -2037,6 +2143,11 @@ public:
   void run_listeners (listener_mode mode = GCB_POSTSET)
   { rep->run_listeners (mode); }
 
+  property_type prop_type() const
+  {
+    return rep->prop_type();
+  }
+
   OCTINTERP_API static
   property create (const std::string& name, const graphics_handle& parent,
                    const caseless_str& type,
@@ -2045,25 +2156,180 @@ public:
   property clone (void) const
   { return property (rep->clone ()); }
 
+  const base_property& as_base_property (void) const { return *rep; }
+
+  base_property& as_base_property (void) { return *rep; }
+
 #if 0
-  const string_property& as_string_property (void) const
-  { return *(dynamic_cast<string_property *> (rep)); }
-
-  const radio_property& as_radio_property (void) const
-  { return *(dynamic_cast<radio_property *> (rep)); }
+
+  const array_property& as_array_property (void) const
+  {
+    return detail::prop_cast<const array_property> (
+        rep, "property::as_array_property");
+  }
+
+  array_property& as_array_property (void)
+  {
+    return detail::prop_cast<array_property> (rep,
+                                              "property::as_array_property");
+  }
+
+  const bool_property& as_bool_property (void) const
+  {
+    return detail::prop_cast<const bool_property> (
+        rep, "property::as_bool_property");
+  }
+
+  bool_property& as_bool_property (void)
+  {
+    return detail::prop_cast<bool_property> (rep,
+                                             "property::as_bool_property");
+  }
+
+  const callback_property& as_callback_property (void) const
+  {
+    return detail::prop_cast<const callback_property> (
+        rep, "property::as_callback_property");
+  }
+
+  callback_property& as_callback_property (void)
+  {
+    return detail::prop_cast<callback_property> (
+        rep, "property::as_callback_property");
+  }
+
+  const children_property& as_children_property (void) const
+  {
+    return detail::prop_cast<const children_property> (
+        rep, "property::as_children_property");
+  }
+
+  children_property& as_children_property (void)
+  {
+    return detail::prop_cast<children_property> (
+        rep, "property::as_children_property");
+  }
 
   const color_property& as_color_property (void) const
-  { return *(dynamic_cast<color_property *> (rep)); }
+  {
+    return detail::prop_cast<const color_property> (
+        rep, "property::as_color_property");
+  }
+
+  color_property& as_color_property (void)
+  {
+    return detail::prop_cast<color_property> (rep,
+                                              "property::as_color_property");
+  }
 
   const double_property& as_double_property (void) const
-  { return *(dynamic_cast<double_property *> (rep)); }
-
-  const bool_property& as_bool_property (void) const
-  { return *(dynamic_cast<bool_property *> (rep)); }
+  {
+    return detail::prop_cast<const double_property> (
+        rep, "property::as_double_property");
+  }
+
+  double_property& as_double_property (void)
+  {
+    return detail::prop_cast<double_property> (rep,
+                                               "property::as_double_property");
+  }
+
+  const double_radio_property& as_double_radio_property (void) const
+  {
+    return detail::prop_cast<const double_radio_property> (
+        rep, "property::as_double_radio_property");
+  }
+
+  double_radio_property& as_double_radio_property (void)
+  {
+    return detail::prop_cast<double_radio_property> (
+        rep, "property::as_double_radio_property");
+  }
 
   const handle_property& as_handle_property (void) const
-  { return *(dynamic_cast<handle_property *> (rep)); }
-#endif
+  {
+    return detail::prop_cast<const handle_property> (
+        rep, "property::as_handle_property");
+  }
+
+  handle_property& as_handle_property (void)
+  {
+    return detail::prop_cast<handle_property> (rep,
+                                               "property::as_handle_property");
+  }
+
+  const radio_property& as_radio_property (void) const
+  {
+    return detail::prop_cast<const radio_property> (
+        rep, "property::as_radio_property");
+  }
+
+  radio_property& as_radio_property (void)
+  {
+    return detail::prop_cast<radio_property> (rep,
+                                              "property::as_radio_property");
+  }
+
+  const row_vector_property& as_row_vector_property (void) const
+  {
+    return detail::prop_cast<const row_vector_property> (
+        rep, "property::as_row_vector_property");
+  }
+
+  row_vector_property& as_row_vector_property (void)
+  {
+    return detail::prop_cast<row_vector_property> (
+        rep, "property::as_row_vector_property");
+  }
+
+  const string_property& as_string_property (void) const
+  {
+    return detail::prop_cast<const string_property> (
+        rep, "property::as_string_property");
+  }
+
+  string_property& as_string_property (void)
+  {
+    return detail::prop_cast<string_property> (rep,
+                                               "property::as_string_property");
+  }
+
+  const string_array_property& as_string_array_property (void) const
+  {
+    return detail::prop_cast<const string_array_property> (
+        rep, "property::as_string_array_property");
+  }
+
+  string_array_property& as_string_array_property (void)
+  {
+    return detail::prop_cast<string_array_property> (
+        rep, "property::as_string_array_property");
+  }
+
+  const text_label_property& as_text_label_property (void) const
+  {
+    return detail::prop_cast<const text_label_property> (
+        rep, "property::as_text_label_property");
+  }
+
+  text_label_property& as_text_label_property (void)
+  {
+    return detail::prop_cast<text_label_property> (
+        rep, "property::as_text_label_property");
+  }
+
+  const any_property& as_any_property (void) const
+  {
+    return detail::prop_cast<const any_property> (rep,
+                                                  "property::as_any_property");
+  }
+
+  any_property& as_any_property (void)
+  {
+    return detail::prop_cast<any_property> (rep, "property::as_any_property");
+  }
+
+#endif // 0
 
 private:
   base_property *rep;
diff --git a/liboctave/util/oct-mutex.h b/liboctave/util/oct-mutex.h
--- a/liboctave/util/oct-mutex.h
+++ b/liboctave/util/oct-mutex.h
@@ -26,6 +26,8 @@
 #if ! defined (octave_oct_mutex_h)
 #define octave_oct_mutex_h 1
 
+#include <stdexcept>
+
 #include "octave-config.h"
 
 #include "oct-refcount.h"
@@ -145,6 +147,48 @@ namespace octave
     bool m_lock_result;
   };
 
+  // a moveable, non-copiable lock that acts like a smart pointer
+  // the type must implement lock and unlock. (it also work with plain mutex)
+  template <class T>
+  class unique_lock
+  {
+  public:
+
+    // may be holding a ref is better
+    unique_lock (T& r) : rep (r), owned (true) { rep.lock (); }
+
+    ~unique_lock ()
+    {
+      if (owned)
+        rep.unlock ();
+    }
+
+    unique_lock (const unique_lock&) = delete;
+    unique_lock& operator= (const unique_lock&) = delete;
+
+    unique_lock (unique_lock&& other) : rep (other.rep), owned (other.owned)
+    {
+      other.owned = false;
+    }
+
+    T* operator->()
+    {
+      if (!owned)
+        throw std::logic_error { "access of non-owned locked resource" };
+      return &rep;
+    }
+
+    T& operator* ()
+    {
+      if (!owned)
+        throw std::logic_error { "access of non-owned locked resource" };
+      return rep;
+    }
+
+  private:
+    T& rep;
+    bool owned;
+  };
 
   class
   OCTAVE_API
