From 3a0f8214fad8d3502753505ffe54b1ff9270ccc0 Mon Sep 17 00:00:00 2001
From: Jon Evans <jon@craftyjon.com>
Date: Tue, 20 Jun 2023 21:57:20 -0400
Subject: [PATCH] ADDED: Properties panel for schematic editor

Initial infrastructure work; follow-ons will add more
properties for schematic items.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/6351
Fixes https://gitlab.com/kicad/code/kicad/-/issues/14105
---
 common/CMakeLists.txt                         |   2 +
 common/eda_draw_frame.cpp                     |  15 ++
 common/properties/color4d_variant.cpp         |  78 +++++++
 common/properties/pg_properties.cpp           |  77 ++++++-
 common/properties/property_mgr.cpp            |  10 +-
 common/tool/actions.cpp                       |   5 +
 .../tools => common/tool}/properties_tool.cpp |   7 +-
 common/widgets/properties_panel.cpp           |   3 +-
 eeschema/CMakeLists.txt                       |   1 +
 eeschema/eeschema_settings.cpp                |   9 +
 eeschema/eeschema_settings.h                  |   3 +
 eeschema/menubar.cpp                          |   1 +
 eeschema/sch_bitmap.cpp                       |  11 +
 eeschema/sch_bus_entry.cpp                    |  15 ++
 eeschema/sch_edit_frame.cpp                   |  24 +++
 eeschema/sch_edit_frame.h                     |   2 +
 eeschema/sch_field.cpp                        |  27 +++
 eeschema/sch_item.cpp                         |  34 ++++
 eeschema/sch_item.h                           |   4 +
 eeschema/sch_junction.cpp                     |  10 +
 eeschema/sch_label.cpp                        |  35 ++++
 eeschema/sch_line.cpp                         |  10 +
 eeschema/sch_pin.cpp                          |  11 +
 eeschema/sch_shape.cpp                        |  28 +++
 eeschema/sch_sheet.cpp                        |  23 +++
 eeschema/sch_symbol.cpp                       |  11 +
 eeschema/sch_text.cpp                         |  17 ++
 eeschema/sch_textbox.cpp                      |  22 ++
 eeschema/toolbars_sch_editor.cpp              |  27 +++
 eeschema/tools/sch_editor_control.cpp         |   8 +
 eeschema/tools/sch_editor_control.h           |   1 +
 eeschema/widgets/sch_properties_panel.cpp     | 190 ++++++++++++++++++
 eeschema/widgets/sch_properties_panel.h       |  64 ++++++
 include/eda_draw_frame.h                      |  10 +
 include/properties/color4d_variant.h          |  55 +++++
 include/properties/pg_properties.h            |  16 ++
 include/properties/property.h                 |   6 +
 include/properties/property_mgr.h             |  10 +-
 include/tool/actions.h                        |   1 +
 .../tools => include/tool}/properties_tool.h  |   8 +-
 pcbnew/CMakeLists.txt                         |   1 -
 pcbnew/footprint_edit_frame.cpp               |  11 +-
 pcbnew/menubar_pcb_editor.cpp                 |   2 +-
 pcbnew/pcb_base_edit_frame.cpp                |  25 +--
 pcbnew/pcb_base_edit_frame.h                  |   9 +-
 pcbnew/pcb_edit_frame.cpp                     |  14 +-
 pcbnew/tools/board_editor_control.cpp         |   2 +-
 pcbnew/tools/pcb_actions.cpp                  |   5 -
 pcbnew/tools/pcb_actions.h                    |   1 -
 49 files changed, 897 insertions(+), 64 deletions(-)
 create mode 100644 common/properties/color4d_variant.cpp
 rename {pcbnew/tools => common/tool}/properties_tool.cpp (90%)
 create mode 100644 eeschema/widgets/sch_properties_panel.cpp
 create mode 100644 eeschema/widgets/sch_properties_panel.h
 create mode 100644 include/properties/color4d_variant.h
 rename {pcbnew/tools => include/tool}/properties_tool.h (84%)

diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index 11e7c5d319..103f718c79 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -434,6 +434,7 @@ set( COMMON_SRCS
     tool/grid_helper.cpp
     tool/grid_menu.cpp
     tool/picker_tool.cpp
+    tool/properties_tool.cpp
     tool/selection.cpp
     tool/selection_tool.cpp
     tool/selection_conditions.cpp
@@ -466,6 +467,7 @@ set( COMMON_SRCS
     project/project_file.cpp
     project/project_local_settings.cpp
 
+    properties/color4d_variant.cpp
     properties/eda_angle_variant.cpp
     properties/pg_cell_renderer.cpp
     properties/pg_editors.cpp
diff --git a/common/eda_draw_frame.cpp b/common/eda_draw_frame.cpp
index 9ec620b98a..1db8d61cd3 100644
--- a/common/eda_draw_frame.cpp
+++ b/common/eda_draw_frame.cpp
@@ -58,6 +58,7 @@
 #include <view/view.h>
 #include <drawing_sheet/ds_draw_item.h>
 #include <widgets/msgpanel.h>
+#include <widgets/properties_panel.h>
 #include <wx/event.h>
 #include <wx/snglinst.h>
 #include <dialogs/dialog_grid_settings.h>
@@ -115,6 +116,7 @@ EDA_DRAW_FRAME::EDA_DRAW_FRAME( KIWAY* aKiway, wxWindow* aParent, FRAME_T aFrame
     m_polarCoords         = false;
     m_findReplaceData     = std::make_unique<EDA_SEARCH_DATA>();
     m_hotkeyPopup         = nullptr;
+    m_propertiesPanel     = nullptr;
 
     SetUserUnits( EDA_UNITS::MILLIMETRES );
 
@@ -278,6 +280,7 @@ void EDA_DRAW_FRAME::unitsChangeRefresh()
 
     UpdateStatusBar();
     UpdateMsgPanel();
+    UpdateProperties();
 }
 
 
@@ -1133,6 +1136,18 @@ void EDA_DRAW_FRAME::ShowChangedLanguage()
     {
         m_searchPane->OnLanguageChange();
     }
+
+    if( m_propertiesPanel )
+        m_propertiesPanel->OnLanguageChanged();
+}
+
+
+void EDA_DRAW_FRAME::UpdateProperties()
+{
+    if( !m_propertiesPanel || !m_propertiesPanel->IsShownOnScreen() )
+        return;
+
+    m_propertiesPanel->UpdateData();
 }
 
 
diff --git a/common/properties/color4d_variant.cpp b/common/properties/color4d_variant.cpp
new file mode 100644
index 0000000000..eafd2d8303
--- /dev/null
+++ b/common/properties/color4d_variant.cpp
@@ -0,0 +1,78 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <properties/color4d_variant.h>
+
+COLOR4D_VARIANT_DATA::COLOR4D_VARIANT_DATA() :
+        wxVariantData()
+{}
+
+
+COLOR4D_VARIANT_DATA::COLOR4D_VARIANT_DATA( const wxString& aColorStr ) :
+        wxVariantData(),
+        m_color( aColorStr )
+{}
+
+
+COLOR4D_VARIANT_DATA::COLOR4D_VARIANT_DATA( const KIGFX::COLOR4D& aColor ) :
+        wxVariantData(),
+        m_color( aColor )
+{}
+
+
+bool COLOR4D_VARIANT_DATA::Eq( wxVariantData& aOther ) const
+{
+    try
+    {
+        COLOR4D_VARIANT_DATA& evd = dynamic_cast<COLOR4D_VARIANT_DATA&>( aOther );
+
+        return evd.m_color == m_color;
+    }
+    catch( std::bad_cast& )
+    {
+        return false;
+    }
+}
+
+
+bool COLOR4D_VARIANT_DATA::Read( wxString& aString )
+{
+    m_color = KIGFX::COLOR4D( aString );
+    return true;
+}
+
+
+bool COLOR4D_VARIANT_DATA::Write( wxString& aString ) const
+{
+    aString = m_color.ToCSSString();
+    return true;
+}
+
+
+bool COLOR4D_VARIANT_DATA::GetAsAny( wxAny* aAny ) const
+{
+    *aAny = m_color;
+    return true;
+}
+
+
+wxVariantData* COLOR4D_VARIANT_DATA::VariantDataFactory( const wxAny& aAny )
+{
+    return new COLOR4D_VARIANT_DATA( aAny.As<KIGFX::COLOR4D>() );
+}
diff --git a/common/properties/pg_properties.cpp b/common/properties/pg_properties.cpp
index 34e7856bcd..77cc0cf2ec 100644
--- a/common/properties/pg_properties.cpp
+++ b/common/properties/pg_properties.cpp
@@ -26,6 +26,7 @@
 #include <validators.h>
 #include <eda_draw_frame.h>
 #include <eda_units.h>
+#include <properties/color4d_variant.h>
 #include <properties/eda_angle_variant.h>
 #include <properties/pg_properties.h>
 #include <properties/pg_editors.h>
@@ -77,6 +78,43 @@ wxAnyValueTypeScopedPtr wxAnyToEDA_ANGLE_VARIANTRegistrationImpl::s_instance( ne
 static wxAnyToEDA_ANGLE_VARIANTRegistrationImpl s_wxAnyToEDA_ANGLE_VARIANTRegistration( &EDA_ANGLE_VARIANT_DATA::VariantDataFactory );
 
 
+class wxAnyToCOLOR4D_VARIANTRegistrationImpl : public wxAnyToVariantRegistration
+{
+public:
+    wxAnyToCOLOR4D_VARIANTRegistrationImpl( wxVariantDataFactory factory )
+            : wxAnyToVariantRegistration( factory )
+    {
+    }
+
+public:
+    static bool IsSameClass(const wxAnyValueType* otherType)
+    {
+        return AreSameClasses( *s_instance.get(), *otherType );
+    }
+
+    static wxAnyValueType* GetInstance()
+    {
+        return s_instance.get();
+    }
+
+    virtual wxAnyValueType* GetAssociatedType() override
+    {
+        return wxAnyToCOLOR4D_VARIANTRegistrationImpl::GetInstance();
+    }
+private:
+    static bool AreSameClasses(const wxAnyValueType& a, const wxAnyValueType& b)
+    {
+        return wxTypeId(a) == wxTypeId(b);
+    }
+
+    static wxAnyValueTypeScopedPtr s_instance;
+};
+
+wxAnyValueTypeScopedPtr wxAnyToCOLOR4D_VARIANTRegistrationImpl::s_instance( new wxAnyValueTypeImpl<KIGFX::COLOR4D>() );
+
+static wxAnyToCOLOR4D_VARIANTRegistrationImpl s_wxAnyToCOLOR4D_VARIANTRegistration( &COLOR4D_VARIANT_DATA::VariantDataFactory );
+
+
 wxPGProperty* PGPropertyFactory( const PROPERTY_BASE* aProperty, EDA_DRAW_FRAME* aFrame )
 {
     wxPGProperty* ret = nullptr;
@@ -144,6 +182,10 @@ wxPGProperty* PGPropertyFactory( const PROPERTY_BASE* aProperty, EDA_DRAW_FRAME*
         {
             ret = new PGPROPERTY_STRING();
         }
+        else if( typeId == TYPE_HASH( COLOR4D ) )
+        {
+            ret = new PGPROPERTY_COLOR4D();
+        }
         else
         {
             wxFAIL_MSG( wxString::Format( wxS( "Property %s not supported by PGPropertyFactory" ),
@@ -195,6 +237,7 @@ wxString PGPROPERTY_DISTANCE::DistanceToString( wxVariant& aVariant, int aArgFla
     long distanceIU = aVariant.GetLong();
 
     ORIGIN_TRANSFORMS* transforms = PROPERTY_MANAGER::Instance().GetTransforms();
+    const EDA_IU_SCALE* iuScale   = PROPERTY_MANAGER::Instance().GetIuScale();
 
     if( transforms )
         distanceIU = transforms->ToDisplay( static_cast<long long int>( distanceIU ), m_coordType );
@@ -202,13 +245,13 @@ wxString PGPROPERTY_DISTANCE::DistanceToString( wxVariant& aVariant, int aArgFla
     switch( PROPERTY_MANAGER::Instance().GetUnits() )
     {
         case EDA_UNITS::INCHES:
-            return wxString::Format( wxS( "%g in" ), pcbIUScale.IUToMils( distanceIU ) / 1000.0 );
+            return wxString::Format( wxS( "%g in" ), iuScale->IUToMils( distanceIU ) / 1000.0 );
 
         case EDA_UNITS::MILS:
-            return wxString::Format( wxS( "%d mils" ), pcbIUScale.IUToMils( distanceIU ) );
+            return wxString::Format( wxS( "%d mils" ), iuScale->IUToMils( distanceIU ) );
 
         case EDA_UNITS::MILLIMETRES:
-            return wxString::Format( wxS( "%g mm" ), pcbIUScale.IUTomm( distanceIU ) );
+            return wxString::Format( wxS( "%g mm" ), iuScale->IUTomm( distanceIU ) );
 
         case EDA_UNITS::UNSCALED:
             return wxString::Format( wxS( "%li" ), distanceIU );
@@ -360,3 +403,31 @@ const wxPGEditor* PGPROPERTY_BOOL::DoGetEditorClass() const
                  wxT( "Make sure to set custom editor for PGPROPERTY_BOOL!" ) );
     return m_customEditor;
 }
+
+
+PGPROPERTY_COLOR4D::PGPROPERTY_COLOR4D( const wxString& aLabel, const wxString& aName,
+                                        COLOR4D aValue ) :
+        wxColourProperty( aLabel, aName, aValue.ToColour() )
+{
+}
+
+
+bool PGPROPERTY_COLOR4D::StringToValue( wxVariant& aVariant, const wxString& aString,
+                                        int aFlags ) const
+{
+    aVariant.SetData( new COLOR4D_VARIANT_DATA( aString ) );
+    return true;
+}
+
+
+wxString PGPROPERTY_COLOR4D::ValueToString( wxVariant& aValue, int aFlags ) const
+{
+    wxString ret;
+
+    if( aValue.IsType( wxS( "COLOR4D" ) ) )
+        static_cast<COLOR4D_VARIANT_DATA*>( aValue.GetData() )->Write( ret );
+    else
+        return wxColourProperty::ValueToString( aValue, aFlags );
+
+    return ret;
+}
diff --git a/common/properties/property_mgr.cpp b/common/properties/property_mgr.cpp
index 37aca025d2..11be249452 100644
--- a/common/properties/property_mgr.cpp
+++ b/common/properties/property_mgr.cpp
@@ -265,9 +265,10 @@ PROPERTY_MANAGER::CLASS_DESC& PROPERTY_MANAGER::getClass( TYPE_ID aTypeId )
 
 void PROPERTY_MANAGER::CLASS_DESC::rebuild()
 {
-    PROPERTY_SET replaced( m_replaced );
+    PROPERTY_SET replaced;
+    PROPERTY_SET masked;
     m_allProperties.clear();
-    collectPropsRecur( m_allProperties, replaced, m_displayOrder, m_maskedBaseProperties );
+    collectPropsRecur( m_allProperties, replaced, m_displayOrder, masked );
     // We need to keep properties sorted to be able to use std::set_* functions
     sort( m_allProperties.begin(), m_allProperties.end() );
 
@@ -307,11 +308,14 @@ void PROPERTY_MANAGER::CLASS_DESC::rebuild()
 void PROPERTY_MANAGER::CLASS_DESC::collectPropsRecur( PROPERTY_LIST& aResult,
                                                       PROPERTY_SET& aReplaced,
                                                       PROPERTY_DISPLAY_ORDER& aDisplayOrder,
-                                                      const PROPERTY_SET& aMasked ) const
+                                                      PROPERTY_SET& aMasked ) const
 {
     for( const std::pair<size_t, wxString>& replacedEntry : m_replaced )
         aReplaced.emplace( replacedEntry );
 
+    for( const std::pair<size_t, wxString>& maskedEntry : m_maskedBaseProperties )
+        aMasked.emplace( maskedEntry );
+
     /*
      * We want to insert our own properties in forward order, but earlier than anything already in
      * the list (which will have been added by a subclass of us)
diff --git a/common/tool/actions.cpp b/common/tool/actions.cpp
index 0694478abd..71da3948c6 100644
--- a/common/tool/actions.cpp
+++ b/common/tool/actions.cpp
@@ -704,6 +704,11 @@ TOOL_ACTION ACTIONS::showFootprintEditor( TOOL_ACTION_ARGS()
         .Flags( AF_NONE )
         .Parameter( FRAME_FOOTPRINT_EDITOR ) );
 
+TOOL_ACTION ACTIONS::showProperties( "common.Control.showProperties",
+        AS_GLOBAL, 0, "",
+        _( "Show Properties Manager" ), _( "Show/hide the properties manager" ),
+        BITMAPS::tools );
+
 TOOL_ACTION ACTIONS::updatePcbFromSchematic( "common.Control.updatePcbFromSchematic",
         AS_GLOBAL,
         WXK_F8, LEGACY_HK_NAME( "Update PCB from Schematic" ),
diff --git a/pcbnew/tools/properties_tool.cpp b/common/tool/properties_tool.cpp
similarity index 90%
rename from pcbnew/tools/properties_tool.cpp
rename to common/tool/properties_tool.cpp
index 5d1c6131d9..d6de347494 100644
--- a/pcbnew/tools/properties_tool.cpp
+++ b/common/tool/properties_tool.cpp
@@ -18,14 +18,15 @@
  * with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include "properties_tool.h"
-#include <widgets/pcb_properties_panel.h>
+#include <eda_draw_frame.h>
 #include <tool/actions.h>
+#include <tool/properties_tool.h>
+#include <widgets/properties_panel.h>
 
 
 int PROPERTIES_TOOL::UpdateProperties( const TOOL_EVENT& aEvent )
 {
-    PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
+    EDA_DRAW_FRAME* editFrame = getEditFrame<EDA_DRAW_FRAME>();
 
     if( editFrame )
         editFrame->UpdateProperties();
diff --git a/common/widgets/properties_panel.cpp b/common/widgets/properties_panel.cpp
index da6f3d09cf..260645787b 100644
--- a/common/widgets/properties_panel.cpp
+++ b/common/widgets/properties_panel.cpp
@@ -169,6 +169,7 @@ void PROPERTIES_PANEL::rebuildProperties( const SELECTION& aSelection )
 
     PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
     propMgr.SetUnits( m_frame->GetUserUnits() );
+    propMgr.SetIuScale( &m_frame->GetIuScale() );
     propMgr.SetTransforms( &m_frame->GetOriginTransforms() );
 
     std::set<PROPERTY_BASE*> commonProps;
@@ -249,7 +250,7 @@ void PROPERTIES_PANEL::rebuildProperties( const SELECTION& aSelection )
         existingProps.insert( property );
     }
 
-    if( existingProps == availableProps )
+    if( !existingProps.empty() && existingProps == availableProps )
         return;
 
     // Some difference exists:  start from scratch
diff --git a/eeschema/CMakeLists.txt b/eeschema/CMakeLists.txt
index 007c0a5b90..103a08cb7b 100644
--- a/eeschema/CMakeLists.txt
+++ b/eeschema/CMakeLists.txt
@@ -246,6 +246,7 @@ set( EESCHEMA_WIDGETS
     widgets/pin_shape_combobox.cpp
     widgets/pin_type_combobox.cpp
     widgets/symbol_diff_widget.cpp
+    widgets/sch_properties_panel.cpp
     widgets/sch_search_pane.cpp
     widgets/search_handlers.cpp
     widgets/symbol_preview_widget.cpp
diff --git a/eeschema/eeschema_settings.cpp b/eeschema/eeschema_settings.cpp
index 01e04499d1..748a8fff3e 100644
--- a/eeschema/eeschema_settings.cpp
+++ b/eeschema/eeschema_settings.cpp
@@ -226,6 +226,15 @@ EESCHEMA_SETTINGS::EESCHEMA_SETTINGS() :
     m_params.emplace_back( new PARAM<wxSize>( "aui.net_nav_panel_float_size",
             &m_AuiPanels.net_nav_panel_float_size, wxSize( 200, 200 ) ) );
 
+    m_params.emplace_back( new PARAM<bool>( "aui.show_properties",
+            &m_AuiPanels.show_properties, true ) );
+
+    m_params.emplace_back( new PARAM<int>( "aui.properties_panel_width",
+            &m_AuiPanels.properties_panel_width, -1 ) );
+
+    m_params.emplace_back( new PARAM<float>( "aui.properties_splitter_proportion",
+            &m_AuiPanels.properties_splitter_proportion, 0.5f ) );
+
     m_params.emplace_back( new PARAM<bool>( "autoplace_fields.enable",
             &m_AutoplaceFields.enable, true ) );
 
diff --git a/eeschema/eeschema_settings.h b/eeschema/eeschema_settings.h
index 0d1fe7db4d..aeae7349f8 100644
--- a/eeschema/eeschema_settings.h
+++ b/eeschema/eeschema_settings.h
@@ -94,6 +94,9 @@ public:
         wxSize net_nav_panel_float_size;
         bool float_net_nav_panel;
         bool show_net_nav_panel;
+        int  properties_panel_width;
+        float properties_splitter_proportion;
+        bool show_properties;
     };
 
     struct AUTOPLACE_FIELDS
diff --git a/eeschema/menubar.cpp b/eeschema/menubar.cpp
index 91888f6ba9..e1cfafe22b 100644
--- a/eeschema/menubar.cpp
+++ b/eeschema/menubar.cpp
@@ -176,6 +176,7 @@ void SCH_EDIT_FRAME::doReCreateMenuBar()
     viewMenu->Add( ACTIONS::showSymbolBrowser );
     viewMenu->Add( ACTIONS::showSearch, ACTION_MENU::CHECK );
     viewMenu->Add( EE_ACTIONS::showHierarchy, ACTION_MENU::CHECK );
+    viewMenu->Add( ACTIONS::showProperties, ACTION_MENU::CHECK );
     viewMenu->Add( EE_ACTIONS::navigateBack );
     viewMenu->Add( EE_ACTIONS::navigateUp );
     viewMenu->Add( EE_ACTIONS::navigateForward );
diff --git a/eeschema/sch_bitmap.cpp b/eeschema/sch_bitmap.cpp
index 588e9ba509..a09ff65295 100644
--- a/eeschema/sch_bitmap.cpp
+++ b/eeschema/sch_bitmap.cpp
@@ -231,3 +231,14 @@ void SCH_BITMAP::ViewGetLayers( int aLayers[], int& aCount ) const
     aLayers[0] = LAYER_DRAW_BITMAPS;
     aLayers[1] = LAYER_SELECTION_SHADOWS;
 }
+
+
+static struct SCH_BITMAP_DESC
+{
+    SCH_BITMAP_DESC()
+    {
+        PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
+        REGISTER_TYPE( SCH_BITMAP );
+        propMgr.InheritsAfter( TYPE_HASH( SCH_BITMAP ), TYPE_HASH( SCH_ITEM ) );
+    }
+} _SCH_BITMAP_DESC;
diff --git a/eeschema/sch_bus_entry.cpp b/eeschema/sch_bus_entry.cpp
index 37a4221f02..4619d35884 100644
--- a/eeschema/sch_bus_entry.cpp
+++ b/eeschema/sch_bus_entry.cpp
@@ -568,3 +568,18 @@ bool SCH_BUS_WIRE_ENTRY::ConnectionPropagatesTo( const EDA_ITEM* aItem ) const
 
     return true;
 }
+
+
+static struct SCH_BUS_ENTRY_DESC
+{
+    SCH_BUS_ENTRY_DESC()
+    {
+        PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
+        REGISTER_TYPE( SCH_BUS_WIRE_ENTRY );
+        REGISTER_TYPE( SCH_BUS_BUS_ENTRY );
+        REGISTER_TYPE( SCH_BUS_ENTRY_BASE );
+        propMgr.InheritsAfter( TYPE_HASH( SCH_BUS_ENTRY_BASE ), TYPE_HASH( SCH_ITEM ) );
+        propMgr.InheritsAfter( TYPE_HASH( SCH_BUS_WIRE_ENTRY ), TYPE_HASH( SCH_BUS_ENTRY_BASE ) );
+        propMgr.InheritsAfter( TYPE_HASH( SCH_BUS_BUS_ENTRY ), TYPE_HASH( SCH_BUS_ENTRY_BASE ) );
+    }
+} _SCH_BUS_ENTRY_DESC;
diff --git a/eeschema/sch_edit_frame.cpp b/eeschema/sch_edit_frame.cpp
index 852922f4c1..9404a9c565 100644
--- a/eeschema/sch_edit_frame.cpp
+++ b/eeschema/sch_edit_frame.cpp
@@ -62,6 +62,7 @@
 #include <tool/common_control.h>
 #include <tool/common_tools.h>
 #include <tool/picker_tool.h>
+#include <tool/properties_tool.h>
 #include <tool/selection.h>
 #include <tool/tool_dispatcher.h>
 #include <tool/tool_manager.h>
@@ -81,6 +82,7 @@
 #include <view/view_controls.h>
 #include <widgets/wx_infobar.h>
 #include <widgets/hierarchy_pane.h>
+#include <widgets/sch_properties_panel.h>
 #include <widgets/sch_search_pane.h>
 #include <wildcards_and_files_ext.h>
 #include <wx/cmdline.h>
@@ -177,6 +179,9 @@ SCH_EDIT_FRAME::SCH_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) :
     m_pageSetupData.GetPrintData().SetNoCopies( 1 );
 
     m_searchPane = new SCH_SEARCH_PANE( this );
+    m_propertiesPanel = new SCH_PROPERTIES_PANEL( this, this );
+
+    m_propertiesPanel->SetSplitterProportion( eeconfig()->m_AuiPanels.properties_splitter_proportion );
 
     m_auimgr.SetManagedWindow( this );
 
@@ -202,6 +207,10 @@ SCH_EDIT_FRAME::SCH_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) :
                       .FloatingPosition( 50, 50 )
                       .Show( false ) );
 
+    m_auimgr.AddPane( m_propertiesPanel, EDA_PANE().Name( PropertiesPaneName() )
+                      .Left().Layer( 3 ).Caption( _( "Properties" ) )
+                      .PaneBorder( false ).MinSize( 240, -1 ).BestSize( 300, -1 ) );
+
     m_auimgr.AddPane( createHighlightedNetNavigator(), defaultNetNavigatorPaneInfo() );
 
     m_auimgr.AddPane( m_optionsToolBar, EDA_PANE().VToolbar().Name( wxS( "OptToolbar" ) )
@@ -239,10 +248,12 @@ SCH_EDIT_FRAME::SCH_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) :
 
     wxAuiPaneInfo&     hierarchy_pane = m_auimgr.GetPane( SchematicHierarchyPaneName() );
     wxAuiPaneInfo&     netNavigatorPane = m_auimgr.GetPane( NetNavigatorPaneName() );
+    wxAuiPaneInfo&     propertiesPane = m_auimgr.GetPane( PropertiesPaneName() );
     EESCHEMA_SETTINGS* cfg = eeconfig();
 
     hierarchy_pane.Show( cfg->m_AuiPanels.show_schematic_hierarchy );
     netNavigatorPane.Show( cfg->m_AuiPanels.show_net_nav_panel );
+    propertiesPane.Show( cfg->m_AuiPanels.show_properties );
 
     if( cfg->m_AuiPanels.hierarchy_panel_float_width > 0
             && cfg->m_AuiPanels.hierarchy_panel_float_height > 0 )
@@ -258,6 +269,9 @@ SCH_EDIT_FRAME::SCH_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) :
         netNavigatorPane.FloatingSize( cfg->m_AuiPanels.net_nav_panel_float_size );
     }
 
+    if( cfg->m_AuiPanels.properties_panel_width > 0 )
+        SetAuiPaneSize( m_auimgr, propertiesPane, cfg->m_AuiPanels.properties_panel_width, -1 );
+
     if( cfg->m_AuiPanels.schematic_hierarchy_float )
         hierarchy_pane.Float();
 
@@ -463,6 +477,7 @@ void SCH_EDIT_FRAME::setupTools()
     m_toolManager->RegisterTool( new SCH_FIND_REPLACE_TOOL );
     m_toolManager->RegisterTool( new EE_POINT_EDITOR );
     m_toolManager->RegisterTool( new SCH_NAVIGATE_TOOL );
+    m_toolManager->RegisterTool( new PROPERTIES_TOOL );
     m_toolManager->InitTools();
 
     // Run the selection tool, it is supposed to be always active
@@ -494,6 +509,12 @@ void SCH_EDIT_FRAME::setupUIConditions()
                 return m_auimgr.GetPane( SearchPaneName() ).IsShown();
             };
 
+    auto propertiesCond =
+            [this] ( const SELECTION& )
+            {
+                return m_auimgr.GetPane( PropertiesPaneName() ).IsShown();
+            };
+
     auto hierarchyNavigatorCond =
             [ this ] ( const SELECTION& aSel )
             {
@@ -525,6 +546,7 @@ void SCH_EDIT_FRAME::setupUIConditions()
     mgr->SetConditions( EE_ACTIONS::showSearch,       CHECK( searchPaneCond ) );
     mgr->SetConditions( EE_ACTIONS::showHierarchy,    CHECK( hierarchyNavigatorCond ) );
     mgr->SetConditions( EE_ACTIONS::showNetNavigator, CHECK( netNavigatorCond ) );
+    mgr->SetConditions( ACTIONS::showProperties,      CHECK( propertiesCond ) );
     mgr->SetConditions( ACTIONS::toggleGrid,          CHECK( cond.GridVisible() ) );
     mgr->SetConditions( ACTIONS::toggleCursorStyle,   CHECK( cond.FullscreenCursor() ) );
     mgr->SetConditions( ACTIONS::millimetersUnits,
@@ -2314,4 +2336,6 @@ void SCH_EDIT_FRAME::unitsChangeRefresh()
         m_netNavigator->DeleteAllItems();
         RefreshNetNavigator( refreshSelection ? &itemData : nullptr );
     }
+
+    UpdateProperties();
 }
diff --git a/eeschema/sch_edit_frame.h b/eeschema/sch_edit_frame.h
index 9038c05868..bbb94570d5 100644
--- a/eeschema/sch_edit_frame.h
+++ b/eeschema/sch_edit_frame.h
@@ -828,6 +828,8 @@ public:
      */
     void ToggleSearch();
 
+    void ToggleProperties() override;
+
     DIALOG_BOOK_REPORTER* GetSymbolDiffDialog();
 
     DIALOG_ERC* GetErcDialog();
diff --git a/eeschema/sch_field.cpp b/eeschema/sch_field.cpp
index f4ae21aeea..32991feb1f 100644
--- a/eeschema/sch_field.cpp
+++ b/eeschema/sch_field.cpp
@@ -1136,3 +1136,30 @@ bool SCH_FIELD::operator <( const SCH_ITEM& aItem ) const
 
     return GetName() < field->GetName();
 }
+
+
+static struct SCH_FIELD_DESC
+{
+    SCH_FIELD_DESC()
+    {
+        PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
+        REGISTER_TYPE( SCH_FIELD );
+        propMgr.AddTypeCast( new TYPE_CAST<SCH_FIELD, SCH_ITEM> );
+        propMgr.AddTypeCast( new TYPE_CAST<SCH_FIELD, EDA_TEXT> );
+        propMgr.InheritsAfter( TYPE_HASH( SCH_FIELD ), TYPE_HASH( SCH_ITEM ) );
+        propMgr.InheritsAfter( TYPE_HASH( SCH_FIELD ), TYPE_HASH( EDA_TEXT ) );
+
+        propMgr.AddProperty( new PROPERTY<SCH_FIELD, bool>( _HKI( "Show Field Name" ),
+                &SCH_FIELD::SetNameShown, &SCH_FIELD::IsNameShown ) );
+
+        propMgr.AddProperty( new PROPERTY<SCH_FIELD, bool>( _HKI( "Allow Autoplacement" ),
+                &SCH_FIELD::SetCanAutoplace, &SCH_FIELD::CanAutoplace ) );
+
+        propMgr.Mask( TYPE_HASH( SCH_FIELD ), TYPE_HASH( EDA_TEXT ), _HKI( "Hyperlink" ) );
+        propMgr.Mask( TYPE_HASH( SCH_FIELD ), TYPE_HASH( EDA_TEXT ), _HKI( "Thickness" ) );
+        propMgr.Mask( TYPE_HASH( SCH_FIELD ), TYPE_HASH( EDA_TEXT ), _HKI( "Mirrored" ) );
+        propMgr.Mask( TYPE_HASH( SCH_FIELD ), TYPE_HASH( EDA_TEXT ), _HKI( "Visible" ) );
+        propMgr.Mask( TYPE_HASH( SCH_FIELD ), TYPE_HASH( EDA_TEXT ), _HKI( "Width" ) );
+        propMgr.Mask( TYPE_HASH( SCH_FIELD ), TYPE_HASH( EDA_TEXT ), _HKI( "Height" ) );
+    }
+} _SCH_FIELD_DESC;
diff --git a/eeschema/sch_item.cpp b/eeschema/sch_item.cpp
index 21ea837622..0afdf62cec 100644
--- a/eeschema/sch_item.cpp
+++ b/eeschema/sch_item.cpp
@@ -318,3 +318,37 @@ void SCH_ITEM::Plot( PLOTTER* aPlotter, bool aBackground ) const
 {
     wxFAIL_MSG( wxT( "Plot() method not implemented for class " ) + GetClass() );
 }
+
+
+static struct SCH_ITEM_DESC
+{
+    SCH_ITEM_DESC()
+    {
+#ifdef NOTYET
+        ENUM_MAP<SCH_LAYER_ID>& layerEnum = ENUM_MAP<SCH_LAYER_ID>::Instance();
+
+        if( layerEnum.Choices().GetCount() == 0 )
+        {
+            layerEnum.Undefined( SCH_LAYER_ID_END );
+
+            for( SCH_LAYER_ID value : magic_enum::enum_values<SCH_LAYER_ID>() )
+                layerEnum.Map( value, LayerName( value ) );
+        }
+#endif
+
+        PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
+        REGISTER_TYPE( SCH_ITEM );
+        propMgr.InheritsAfter( TYPE_HASH( SCH_ITEM ), TYPE_HASH( EDA_ITEM ) );
+
+        // Not sure if this will ever be needed
+//        propMgr.AddProperty( new PROPERTY_ENUM<SCH_ITEM, SCH_LAYER_ID>( _HKI( "Layer" ),
+//                &SCH_ITEM::SetLayer, &SCH_ITEM::GetLayer ) )
+//                .SetIsHiddenFromPropertiesManager();
+
+        // Not yet functional in UI
+//        propMgr.AddProperty( new PROPERTY<SCH_ITEM, bool>( _HKI( "Locked" ),
+//                &SCH_ITEM::SetLocked, &SCH_ITEM::IsLocked ) );
+    }
+} _SCH_ITEM_DESC;
+
+IMPLEMENT_ENUM_TO_WXANY( SCH_LAYER_ID )
\ No newline at end of file
diff --git a/eeschema/sch_item.h b/eeschema/sch_item.h
index 4211190b8d..e9962b72b3 100644
--- a/eeschema/sch_item.h
+++ b/eeschema/sch_item.h
@@ -509,4 +509,8 @@ protected:
     bool              m_connectivity_dirty;
 };
 
+#ifndef SWIG
+DECLARE_ENUM_TO_WXANY( SCH_LAYER_ID );
+#endif
+
 #endif /* SCH_ITEM_H */
diff --git a/eeschema/sch_junction.cpp b/eeschema/sch_junction.cpp
index ef2f186177..50dd1afb30 100644
--- a/eeschema/sch_junction.cpp
+++ b/eeschema/sch_junction.cpp
@@ -304,3 +304,13 @@ void SCH_JUNCTION::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANE
     }
 }
 
+
+static struct SCH_JUNCTION_DESC
+{
+    SCH_JUNCTION_DESC()
+    {
+        PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
+        REGISTER_TYPE( SCH_JUNCTION );
+        propMgr.InheritsAfter( TYPE_HASH( SCH_JUNCTION ), TYPE_HASH( SCH_ITEM ) );
+    }
+} _SCH_JUNCTION_DESC;
diff --git a/eeschema/sch_label.cpp b/eeschema/sch_label.cpp
index 4e07095f8f..3c4d1d8726 100644
--- a/eeschema/sch_label.cpp
+++ b/eeschema/sch_label.cpp
@@ -1820,3 +1820,38 @@ HTML_MESSAGE_BOX* SCH_TEXT::ShowSyntaxHelp( wxWindow* aParentWindow )
 
     return dlg;
 }
+
+
+static struct SCH_LABEL_DESC
+{
+    SCH_LABEL_DESC()
+    {
+        PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
+        REGISTER_TYPE( SCH_LABEL_BASE );
+        REGISTER_TYPE( SCH_LABEL );
+        REGISTER_TYPE( SCH_HIERLABEL );
+        REGISTER_TYPE( SCH_GLOBALLABEL );
+        REGISTER_TYPE( SCH_DIRECTIVE_LABEL );
+
+        propMgr.AddTypeCast( new TYPE_CAST<SCH_LABEL, SCH_LABEL_BASE> );
+        propMgr.AddTypeCast( new TYPE_CAST<SCH_HIERLABEL, SCH_LABEL_BASE> );
+        propMgr.AddTypeCast( new TYPE_CAST<SCH_GLOBALLABEL, SCH_LABEL_BASE> );
+        propMgr.AddTypeCast( new TYPE_CAST<SCH_DIRECTIVE_LABEL, SCH_LABEL_BASE> );
+
+        propMgr.AddTypeCast( new TYPE_CAST<SCH_LABEL, SCH_TEXT> );
+        propMgr.AddTypeCast( new TYPE_CAST<SCH_HIERLABEL, SCH_TEXT> );
+        propMgr.AddTypeCast( new TYPE_CAST<SCH_GLOBALLABEL, SCH_TEXT> );
+        propMgr.AddTypeCast( new TYPE_CAST<SCH_DIRECTIVE_LABEL, SCH_TEXT> );
+
+        propMgr.AddTypeCast( new TYPE_CAST<SCH_LABEL, EDA_TEXT> );
+        propMgr.AddTypeCast( new TYPE_CAST<SCH_HIERLABEL, EDA_TEXT> );
+        propMgr.AddTypeCast( new TYPE_CAST<SCH_GLOBALLABEL, EDA_TEXT> );
+        propMgr.AddTypeCast( new TYPE_CAST<SCH_DIRECTIVE_LABEL, EDA_TEXT> );
+
+        propMgr.InheritsAfter( TYPE_HASH( SCH_LABEL_BASE ), TYPE_HASH( SCH_TEXT ) );
+        propMgr.InheritsAfter( TYPE_HASH( SCH_LABEL ), TYPE_HASH( SCH_LABEL_BASE ) );
+        propMgr.InheritsAfter( TYPE_HASH( SCH_HIERLABEL ), TYPE_HASH( SCH_LABEL_BASE ) );
+        propMgr.InheritsAfter( TYPE_HASH( SCH_GLOBALLABEL ), TYPE_HASH( SCH_LABEL_BASE ) );
+        propMgr.InheritsAfter( TYPE_HASH( SCH_DIRECTIVE_LABEL ), TYPE_HASH( SCH_LABEL_BASE ) );
+    }
+} _SCH_LABEL_DESC;
diff --git a/eeschema/sch_line.cpp b/eeschema/sch_line.cpp
index cd63db6899..43c74ffc6b 100644
--- a/eeschema/sch_line.cpp
+++ b/eeschema/sch_line.cpp
@@ -976,3 +976,13 @@ bool SCH_LINE::IsBus() const
     return ( GetLayer() == LAYER_BUS );
 }
 
+
+static struct SCH_LINE_DESC
+{
+    SCH_LINE_DESC()
+    {
+        PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
+        REGISTER_TYPE( SCH_LINE );
+        propMgr.InheritsAfter( TYPE_HASH( SCH_LINE ), TYPE_HASH( SCH_ITEM ) );
+    }
+} _SCH_LINE_DESC;
diff --git a/eeschema/sch_pin.cpp b/eeschema/sch_pin.cpp
index 488af3ece6..02bafbfb38 100644
--- a/eeschema/sch_pin.cpp
+++ b/eeschema/sch_pin.cpp
@@ -415,3 +415,14 @@ bool SCH_PIN::ConnectionPropagatesTo( const EDA_ITEM* aItem ) const
     // Reciprocal checking is done in CONNECTION_GRAPH anyway
     return !( m_libPin->GetType() == ELECTRICAL_PINTYPE::PT_NC );
 }
+
+
+static struct SCH_PIN_DESC
+{
+    SCH_PIN_DESC()
+    {
+        PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
+        REGISTER_TYPE( SCH_PIN );
+        propMgr.InheritsAfter( TYPE_HASH( SCH_PIN ), TYPE_HASH( SCH_ITEM ) );
+    }
+} _SCH_PIN_DESC;
diff --git a/eeschema/sch_shape.cpp b/eeschema/sch_shape.cpp
index e239af3571..fa24cb91db 100644
--- a/eeschema/sch_shape.cpp
+++ b/eeschema/sch_shape.cpp
@@ -517,3 +517,31 @@ void SCH_SHAPE::AddPoint( const VECTOR2I& aPosition )
 }
 
 
+static struct SCH_SHAPE_DESC
+{
+    SCH_SHAPE_DESC()
+    {
+        PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
+        REGISTER_TYPE( SCH_SHAPE );
+        propMgr.AddTypeCast( new TYPE_CAST<SCH_SHAPE, SCH_ITEM> );
+        propMgr.AddTypeCast( new TYPE_CAST<SCH_SHAPE, EDA_SHAPE> );
+        propMgr.InheritsAfter( TYPE_HASH( SCH_SHAPE ), TYPE_HASH( SCH_ITEM ) );
+        propMgr.InheritsAfter( TYPE_HASH( SCH_SHAPE ), TYPE_HASH( EDA_SHAPE ) );
+
+        // Only polygons have meaningful Position properties.
+        // On other shapes, these are duplicates of the Start properties.
+        auto isPolygon =
+                []( INSPECTABLE* aItem ) -> bool
+        {
+            if( SCH_SHAPE* shape = dynamic_cast<SCH_SHAPE*>( aItem ) )
+                return shape->GetShape() == SHAPE_T::POLY;
+
+            return false;
+        };
+
+        propMgr.OverrideAvailability( TYPE_HASH( SCH_SHAPE ), TYPE_HASH( SCH_ITEM ),
+                                      _HKI( "Position X" ), isPolygon );
+        propMgr.OverrideAvailability( TYPE_HASH( SCH_SHAPE ), TYPE_HASH( SCH_ITEM ),
+                                      _HKI( "Position Y" ), isPolygon );
+    }
+} _SCH_SHAPE_DESC;
diff --git a/eeschema/sch_sheet.cpp b/eeschema/sch_sheet.cpp
index 5bbe7268ce..6cc02612e8 100644
--- a/eeschema/sch_sheet.cpp
+++ b/eeschema/sch_sheet.cpp
@@ -1392,3 +1392,26 @@ void SCH_SHEET::Show( int nestLevel, std::ostream& os ) const
 }
 
 #endif
+
+static struct SCH_SHEET_DESC
+{
+    SCH_SHEET_DESC()
+    {
+        PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
+        REGISTER_TYPE( SCH_SHEET );
+        propMgr.InheritsAfter( TYPE_HASH( SCH_SHEET ), TYPE_HASH( SCH_ITEM ) );
+
+        propMgr.AddProperty( new PROPERTY<SCH_SHEET, wxString>( _HKI( "Sheet Name" ),
+                             &SCH_SHEET::SetName, &SCH_SHEET::GetName ) );
+
+        propMgr.AddProperty( new PROPERTY<SCH_SHEET, int>( _HKI( "Border Width" ),
+                             &SCH_SHEET::SetBorderWidth, &SCH_SHEET::GetBorderWidth,
+                             PROPERTY_DISPLAY::PT_SIZE ) );
+
+        propMgr.AddProperty( new PROPERTY<SCH_SHEET, COLOR4D>( _HKI( "Border Color" ),
+                             &SCH_SHEET::SetBorderColor, &SCH_SHEET::GetBorderColor ) );
+
+        propMgr.AddProperty( new PROPERTY<SCH_SHEET, COLOR4D>( _HKI( "Background Color" ),
+                             &SCH_SHEET::SetBackgroundColor, &SCH_SHEET::GetBackgroundColor ) );
+    }
+} _SCH_SHEET_DESC;
diff --git a/eeschema/sch_symbol.cpp b/eeschema/sch_symbol.cpp
index 7d3fae5af0..0046570c63 100644
--- a/eeschema/sch_symbol.cpp
+++ b/eeschema/sch_symbol.cpp
@@ -2414,3 +2414,14 @@ bool SCH_SYMBOL::IsPower() const
 
     return m_part->IsPower();
 }
+
+
+static struct SCH_SYMBOL_DESC
+{
+    SCH_SYMBOL_DESC()
+    {
+        PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
+        REGISTER_TYPE( SCH_SYMBOL );
+        propMgr.InheritsAfter( TYPE_HASH( SCH_SYMBOL ), TYPE_HASH( SCH_ITEM ) );
+    }
+} _SCH_SYMBOL_DESC;
diff --git a/eeschema/sch_text.cpp b/eeschema/sch_text.cpp
index cd1eaf4ebe..c9e36902ec 100644
--- a/eeschema/sch_text.cpp
+++ b/eeschema/sch_text.cpp
@@ -542,3 +542,20 @@ void SCH_TEXT::Show( int nestLevel, std::ostream& os ) const
 #endif
 
 
+static struct SCH_TEXT_DESC
+{
+    SCH_TEXT_DESC()
+    {
+        PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
+        REGISTER_TYPE( SCH_TEXT );
+        propMgr.AddTypeCast( new TYPE_CAST<SCH_TEXT, SCH_ITEM> );
+        propMgr.AddTypeCast( new TYPE_CAST<SCH_TEXT, EDA_TEXT> );
+        propMgr.InheritsAfter( TYPE_HASH( SCH_TEXT ), TYPE_HASH( SCH_ITEM ) );
+        propMgr.InheritsAfter( TYPE_HASH( SCH_TEXT ), TYPE_HASH( EDA_TEXT ) );
+
+        propMgr.Mask( TYPE_HASH( SCH_TEXT ), TYPE_HASH( EDA_TEXT ), _HKI( "Mirrored" ) );
+        propMgr.Mask( TYPE_HASH( SCH_TEXT ), TYPE_HASH( EDA_TEXT ), _HKI( "Visible" ) );
+        propMgr.Mask( TYPE_HASH( SCH_TEXT ), TYPE_HASH( EDA_TEXT ), _HKI( "Width" ) );
+        propMgr.Mask( TYPE_HASH( SCH_TEXT ), TYPE_HASH( EDA_TEXT ), _HKI( "Height" ) );
+    }
+} _SCH_TEXT_DESC;
diff --git a/eeschema/sch_textbox.cpp b/eeschema/sch_textbox.cpp
index cbc87a79bc..118bc389ad 100644
--- a/eeschema/sch_textbox.cpp
+++ b/eeschema/sch_textbox.cpp
@@ -463,3 +463,25 @@ void SCH_TEXTBOX::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL
 
     m_stroke.GetMsgPanelInfo( aFrame, aList );
 }
+
+
+static struct SCH_TEXTBOX_DESC
+{
+    SCH_TEXTBOX_DESC()
+    {
+        PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
+        REGISTER_TYPE( SCH_TEXTBOX );
+
+        propMgr.AddTypeCast( new TYPE_CAST<SCH_TEXTBOX, SCH_SHAPE> );
+        propMgr.AddTypeCast( new TYPE_CAST<SCH_TEXTBOX, EDA_TEXT> );
+        propMgr.InheritsAfter( TYPE_HASH( SCH_TEXTBOX ), TYPE_HASH( SCH_SHAPE ) );
+        propMgr.InheritsAfter( TYPE_HASH( SCH_TEXTBOX ), TYPE_HASH( EDA_TEXT ) );
+
+        propMgr.Mask( TYPE_HASH( SCH_TEXTBOX ), TYPE_HASH( EDA_SHAPE ), _HKI( "Shape" ) );
+        propMgr.Mask( TYPE_HASH( SCH_TEXTBOX ), TYPE_HASH( EDA_SHAPE ), _HKI( "Start X" ) );
+        propMgr.Mask( TYPE_HASH( SCH_TEXTBOX ), TYPE_HASH( EDA_SHAPE ), _HKI( "Start Y" ) );
+        propMgr.Mask( TYPE_HASH( SCH_TEXTBOX ), TYPE_HASH( EDA_SHAPE ), _HKI( "End X" ) );
+        propMgr.Mask( TYPE_HASH( SCH_TEXTBOX ), TYPE_HASH( EDA_SHAPE ), _HKI( "End Y" ) );
+        propMgr.Mask( TYPE_HASH( SCH_TEXTBOX ), TYPE_HASH( EDA_SHAPE ), _HKI( "Line Width" ) );
+    }
+} _SCH_TEXTBOX_DESC;
diff --git a/eeschema/toolbars_sch_editor.cpp b/eeschema/toolbars_sch_editor.cpp
index 480c1460b2..263d588a7c 100644
--- a/eeschema/toolbars_sch_editor.cpp
+++ b/eeschema/toolbars_sch_editor.cpp
@@ -37,6 +37,7 @@
 #include <tools/ee_selection_tool.h>
 #include <widgets/hierarchy_pane.h>
 #include <widgets/wx_aui_utils.h>
+#include <widgets/sch_properties_panel.h>
 #include <widgets/sch_search_pane.h>
 
 /* Create  the main Horizontal Toolbar for the schematic editor
@@ -213,6 +214,7 @@ void SCH_EDIT_FRAME::ReCreateOptToolbar()
 
     m_optionsToolBar->AddScaledSeparator( this );
     m_optionsToolBar->Add( EE_ACTIONS::showHierarchy,           ACTION_TOOLBAR::TOGGLE );
+    m_optionsToolBar->Add( ACTIONS::showProperties,             ACTION_TOOLBAR::TOGGLE );
 
     if( ADVANCED_CFG::GetCfg().m_DrawBoundingBoxes )
         m_optionsToolBar->Add( ACTIONS::toggleBoundingBoxes,    ACTION_TOOLBAR::TOGGLE );
@@ -265,6 +267,31 @@ void SCH_EDIT_FRAME::ToggleSearch()
 }
 
 
+void SCH_EDIT_FRAME::ToggleProperties()
+{
+    if( !m_propertiesPanel )
+        return;
+
+    bool show = !m_propertiesPanel->IsShownOnScreen();
+
+    wxAuiPaneInfo& propertiesPaneInfo = m_auimgr.GetPane( PropertiesPaneName() );
+    propertiesPaneInfo.Show( show );
+
+    EESCHEMA_SETTINGS* settings = eeconfig();
+
+    if( show )
+    {
+        SetAuiPaneSize( m_auimgr, propertiesPaneInfo,
+                        settings->m_AuiPanels.properties_panel_width, -1 );
+    }
+    else
+    {
+        settings->m_AuiPanels.properties_panel_width = m_propertiesPanel->GetSize().x;
+        m_auimgr.Update();
+    }
+}
+
+
 void SCH_EDIT_FRAME::ToggleSchematicHierarchy()
 {
     EESCHEMA_SETTINGS* cfg = eeconfig();
diff --git a/eeschema/tools/sch_editor_control.cpp b/eeschema/tools/sch_editor_control.cpp
index 7f619369ab..78cad9986b 100644
--- a/eeschema/tools/sch_editor_control.cpp
+++ b/eeschema/tools/sch_editor_control.cpp
@@ -2125,6 +2125,13 @@ int SCH_EDITOR_CONTROL::ShowNetNavigator( const TOOL_EVENT& aEvent )
 }
 
 
+int SCH_EDITOR_CONTROL::ToggleProperties( const TOOL_EVENT& aEvent )
+{
+    getEditFrame<SCH_EDIT_FRAME>()->ToggleProperties();
+    return 0;
+}
+
+
 int SCH_EDITOR_CONTROL::ToggleHiddenPins( const TOOL_EVENT& aEvent )
 {
     EESCHEMA_SETTINGS* cfg = m_frame->eeconfig();
@@ -2438,6 +2445,7 @@ void SCH_EDITOR_CONTROL::setTransitions()
     Go( &SCH_EDITOR_CONTROL::ShowSearch,            EE_ACTIONS::showSearch.MakeEvent() );
     Go( &SCH_EDITOR_CONTROL::ShowHierarchy,         EE_ACTIONS::showHierarchy.MakeEvent() );
     Go( &SCH_EDITOR_CONTROL::ShowNetNavigator,      EE_ACTIONS::showNetNavigator.MakeEvent() );
+    Go( &SCH_EDITOR_CONTROL::ToggleProperties,      ACTIONS::showProperties.MakeEvent() );
 
     Go( &SCH_EDITOR_CONTROL::ToggleHiddenPins,      EE_ACTIONS::toggleHiddenPins.MakeEvent() );
     Go( &SCH_EDITOR_CONTROL::ToggleHiddenFields,    EE_ACTIONS::toggleHiddenFields.MakeEvent() );
diff --git a/eeschema/tools/sch_editor_control.h b/eeschema/tools/sch_editor_control.h
index ff2ceb9d6f..99b81704c6 100644
--- a/eeschema/tools/sch_editor_control.h
+++ b/eeschema/tools/sch_editor_control.h
@@ -126,6 +126,7 @@ public:
     int ShowSearch( const TOOL_EVENT& aEvent );
     int ShowHierarchy( const TOOL_EVENT& aEvent );
     int ShowNetNavigator( const TOOL_EVENT& aEvent );
+    int ToggleProperties( const TOOL_EVENT& aEvent );
 
     int ToggleHiddenPins( const TOOL_EVENT& aEvent );
     int ToggleHiddenFields( const TOOL_EVENT& aEvent );
diff --git a/eeschema/widgets/sch_properties_panel.cpp b/eeschema/widgets/sch_properties_panel.cpp
new file mode 100644
index 0000000000..dba021c3cb
--- /dev/null
+++ b/eeschema/widgets/sch_properties_panel.cpp
@@ -0,0 +1,190 @@
+/*
+ * This program source code file is part of KICAD, a free EDA CAD application.
+ *
+ * Copyright (C) 2020 CERN
+ * Copyright (C) 2021-2022 KiCad Developers, see AUTHORS.txt for contributors.
+ * @author Maciej Suminski <maciej.suminski@cern.ch>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "sch_properties_panel.h"
+
+#include <connection_graph.h>
+#include <properties/pg_editors.h>
+#include <properties/pg_properties.h>
+#include <properties/property_mgr.h>
+#include <sch_commit.h>
+#include <sch_edit_frame.h>
+#include <schematic.h>
+#include <settings/color_settings.h>
+#include <string_utils.h>
+#include <tool/tool_manager.h>
+#include <tools/ee_selection_tool.h>
+
+
+SCH_PROPERTIES_PANEL::SCH_PROPERTIES_PANEL( wxWindow* aParent, SCH_BASE_FRAME* aFrame ) :
+        PROPERTIES_PANEL( aParent, aFrame ),
+        m_frame( aFrame ),
+        m_propMgr( PROPERTY_MANAGER::Instance() )
+{
+    m_propMgr.Rebuild();
+    bool found = false;
+
+    wxASSERT( wxPGGlobalVars );
+
+    wxString editorKey = PG_UNIT_EDITOR::BuildEditorName( m_frame );
+
+    auto it = wxPGGlobalVars->m_mapEditorClasses.find( editorKey );
+
+    if( it != wxPGGlobalVars->m_mapEditorClasses.end() )
+    {
+        m_unitEditorInstance = static_cast<PG_UNIT_EDITOR*>( it->second );
+        m_unitEditorInstance->UpdateFrame( m_frame );
+        found = true;
+    }
+
+    if( !found )
+    {
+        PG_UNIT_EDITOR* new_editor = new PG_UNIT_EDITOR( m_frame );
+        m_unitEditorInstance = static_cast<PG_UNIT_EDITOR*>( wxPropertyGrid::RegisterEditorClass( new_editor ) );
+    }
+
+    it = wxPGGlobalVars->m_mapEditorClasses.find( PG_CHECKBOX_EDITOR::EDITOR_NAME );
+
+    if( it == wxPGGlobalVars->m_mapEditorClasses.end() )
+    {
+        PG_CHECKBOX_EDITOR* cbEditor = new PG_CHECKBOX_EDITOR();
+        m_checkboxEditorInstance = static_cast<PG_CHECKBOX_EDITOR*>( wxPropertyGrid::RegisterEditorClass( cbEditor ) );
+    }
+    else
+    {
+        m_checkboxEditorInstance = static_cast<PG_CHECKBOX_EDITOR*>( it->second );
+    }
+}
+
+
+
+SCH_PROPERTIES_PANEL::~SCH_PROPERTIES_PANEL()
+{
+    m_unitEditorInstance->UpdateFrame( nullptr );
+}
+
+
+void SCH_PROPERTIES_PANEL::UpdateData()
+{
+    EE_SELECTION_TOOL* selectionTool = m_frame->GetToolManager()->GetTool<EE_SELECTION_TOOL>();
+    const SELECTION& selection = selectionTool->GetSelection();
+
+    // TODO perhaps it could be called less often? use PROPERTIES_TOOL and catch MODEL_RELOAD?
+    if( SCH_EDIT_FRAME* schFrame = dynamic_cast<SCH_EDIT_FRAME*>( m_frame ) )
+        updateLists( schFrame->Schematic() );
+
+    // Will actually just be updatePropertyValues() if selection hasn't changed
+    rebuildProperties( selection );
+}
+
+
+void SCH_PROPERTIES_PANEL::AfterCommit()
+{
+    EE_SELECTION_TOOL* selectionTool = m_frame->GetToolManager()->GetTool<EE_SELECTION_TOOL>();
+    const SELECTION& selection = selectionTool->GetSelection();
+
+    rebuildProperties( selection );
+
+    CallAfter( [&]()
+               {
+                   m_frame->GetCanvas()->SetFocus();
+               } );
+}
+
+
+wxPGProperty* SCH_PROPERTIES_PANEL::createPGProperty( const PROPERTY_BASE* aProperty ) const
+{
+    return PGPropertyFactory( aProperty, m_frame );
+}
+
+
+PROPERTY_BASE* SCH_PROPERTIES_PANEL::getPropertyFromEvent( const wxPropertyGridEvent& aEvent ) const
+{
+    EE_SELECTION_TOOL* selectionTool = m_frame->GetToolManager()->GetTool<EE_SELECTION_TOOL>();
+    const SELECTION& selection = selectionTool->GetSelection();
+    SCH_ITEM* firstItem = static_cast<SCH_ITEM*>( selection.Front() );
+
+    wxCHECK_MSG( firstItem, nullptr,
+                 wxT( "getPropertyFromEvent for a property with nothing selected!") );
+
+    PROPERTY_BASE* property = m_propMgr.GetProperty( TYPE_HASH( *firstItem ),
+                                                     aEvent.GetPropertyName() );
+    wxCHECK_MSG( property, nullptr,
+                 wxT( "getPropertyFromEvent for a property not found on the selected item!" ) );
+
+    return property;
+}
+
+
+void SCH_PROPERTIES_PANEL::valueChanging( wxPropertyGridEvent& aEvent )
+{
+    EE_SELECTION_TOOL* selectionTool = m_frame->GetToolManager()->GetTool<EE_SELECTION_TOOL>();
+    const SELECTION& selection = selectionTool->GetSelection();
+    EDA_ITEM* item = selection.Front();
+
+    PROPERTY_BASE* property = getPropertyFromEvent( aEvent );
+    wxCHECK( property, /* void */ );
+    wxCHECK( item, /* void */ );
+
+    wxVariant newValue = aEvent.GetPropertyValue();
+
+    if( VALIDATOR_RESULT validationFailure = property->Validate( newValue.GetAny(), item ) )
+    {
+        wxString errorMsg = wxString::Format( wxS( "%s: %s" ), wxGetTranslation( property->Name() ),
+                                              validationFailure->get()->Format( m_frame ) );
+        m_frame->ShowInfoBarError( errorMsg );
+        aEvent.Veto();
+        return;
+    }
+}
+
+
+void SCH_PROPERTIES_PANEL::valueChanged( wxPropertyGridEvent& aEvent )
+{
+    EE_SELECTION_TOOL* selectionTool = m_frame->GetToolManager()->GetTool<EE_SELECTION_TOOL>();
+    const SELECTION& selection = selectionTool->GetSelection();
+
+    PROPERTY_BASE* property = getPropertyFromEvent( aEvent );
+    wxCHECK( property, /* void */ );
+
+    wxVariant newValue = aEvent.GetPropertyValue();
+    SCH_COMMIT changes( m_frame );
+    SCH_SCREEN* screen = m_frame->GetScreen();
+
+    for( EDA_ITEM* edaItem : selection )
+    {
+        SCH_ITEM* item = static_cast<SCH_ITEM*>( edaItem );
+        changes.Modify( item, screen );
+        item->Set( property, newValue );
+    }
+
+    changes.Push( _( "Change property" ) );
+    m_frame->Refresh();
+
+    // Perform grid updates as necessary based on value change
+    AfterCommit();
+}
+
+
+void SCH_PROPERTIES_PANEL::updateLists( const SCHEMATIC& aSchematic )
+{
+    // No lists yet
+}
diff --git a/eeschema/widgets/sch_properties_panel.h b/eeschema/widgets/sch_properties_panel.h
new file mode 100644
index 0000000000..451adae10a
--- /dev/null
+++ b/eeschema/widgets/sch_properties_panel.h
@@ -0,0 +1,64 @@
+/*
+ * This program source code file is part of KICAD, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * @author Maciej Suminski <maciej.suminski@cern.ch>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SCH_PROPERTIES_PANEL_H
+#define SCH_PROPERTIES_PANEL_H
+
+#include <widgets/properties_panel.h>
+
+class SELECTION;
+class SCHEMATIC;
+class SCH_BASE_FRAME;
+class PROPERTY_MANAGER;
+class PG_UNIT_EDITOR;
+class PG_CHECKBOX_EDITOR;
+
+class SCH_PROPERTIES_PANEL : public PROPERTIES_PANEL
+{
+public:
+    SCH_PROPERTIES_PANEL( wxWindow* aParent, SCH_BASE_FRAME* aFrame );
+
+    virtual ~SCH_PROPERTIES_PANEL();
+
+    void UpdateData() override;
+
+    void AfterCommit() override;
+
+protected:
+    wxPGProperty* createPGProperty( const PROPERTY_BASE* aProperty ) const override;
+
+    PROPERTY_BASE* getPropertyFromEvent( const wxPropertyGridEvent& aEvent ) const;
+
+    void valueChanging( wxPropertyGridEvent& aEvent ) override;
+    void valueChanged( wxPropertyGridEvent& aEvent ) override;
+
+    ///< Regenerates caches of list properties
+    void updateLists( const SCHEMATIC& aSchematic );
+
+    SCH_BASE_FRAME* m_frame;
+    PROPERTY_MANAGER& m_propMgr;
+    PG_UNIT_EDITOR* m_unitEditorInstance;
+    PG_CHECKBOX_EDITOR* m_checkboxEditorInstance;
+
+    wxPGChoices m_nets;
+};
+
+#endif /* PCB_PROPERTIES_PANEL_H */
diff --git a/include/eda_draw_frame.h b/include/eda_draw_frame.h
index 37f6dd4638..6dbda113e6 100644
--- a/include/eda_draw_frame.h
+++ b/include/eda_draw_frame.h
@@ -44,6 +44,7 @@ class APP_SETTINGS_BASE;
 class wxFindReplaceData;
 class SEARCH_PANE;
 class HOTKEY_CYCLE_POPUP;
+class PROPERTIES_PANEL;
 
 namespace KIGFX
 {
@@ -387,6 +388,14 @@ public:
      */
     virtual void UpdateMsgPanel();
 
+    PROPERTIES_PANEL* GetPropertiesPanel() { return m_propertiesPanel; }
+
+    void UpdateProperties();
+
+    virtual void ToggleProperties() {}
+
+    static const wxString PropertiesPaneName() { return wxS( "PropertiesManager" ); }
+
     /**
      * Fetch an item by KIID.  Frame-type-specific implementation.
      */
@@ -523,6 +532,7 @@ protected:
 
     COLOR_SETTINGS*    m_colorSettings;
     SEARCH_PANE*       m_searchPane;
+    PROPERTIES_PANEL*  m_propertiesPanel;
 
     HOTKEY_CYCLE_POPUP* m_hotkeyPopup;
 
diff --git a/include/properties/color4d_variant.h b/include/properties/color4d_variant.h
new file mode 100644
index 0000000000..cd7239a360
--- /dev/null
+++ b/include/properties/color4d_variant.h
@@ -0,0 +1,55 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef KICAD_COLOR4D_VARIANT_H
+#define KICAD_COLOR4D_VARIANT_H
+
+#include <gal/color4d.h>
+#include <wx/variant.h>
+
+class COLOR4D_VARIANT_DATA : public wxVariantData
+{
+public:
+    COLOR4D_VARIANT_DATA();
+
+    COLOR4D_VARIANT_DATA( const wxString& aColorStr );
+
+    COLOR4D_VARIANT_DATA( const KIGFX::COLOR4D& aColor );
+
+    bool Eq( wxVariantData& aOther ) const override;
+
+    wxString GetType() const override { return wxT( "COLOR4D" ); }
+
+    bool Read( wxString& aString ) override;
+
+    bool Write( wxString& aString ) const override;
+
+    bool GetAsAny( wxAny* aAny ) const override;
+
+    static wxVariantData* VariantDataFactory( const wxAny& aAny );
+
+    const KIGFX::COLOR4D& Color() { return m_color; }
+
+    void SetColor( const KIGFX::COLOR4D& aColor ) { m_color = aColor; }
+
+protected:
+    KIGFX::COLOR4D m_color;
+};
+
+#endif //KICAD_COLOR4D_VARIANT_H
diff --git a/include/properties/pg_properties.h b/include/properties/pg_properties.h
index bc6d8d011e..ee8894ad33 100644
--- a/include/properties/pg_properties.h
+++ b/include/properties/pg_properties.h
@@ -26,6 +26,7 @@
 #include <wx/propgrid/propgrid.h>
 #include <wx/propgrid/property.h>
 #include <wx/propgrid/props.h>
+#include <wx/propgrid/advprops.h>
 #include <common.h>
 #include <origin_transforms.h>
 
@@ -187,4 +188,19 @@ public:
     const wxPGEditor* DoGetEditorClass() const override;
 };
 
+
+class PGPROPERTY_COLOR4D : public wxColourProperty
+{
+public:
+    PGPROPERTY_COLOR4D( const wxString& aLabel = wxPG_LABEL, const wxString& aName = wxPG_LABEL,
+                        COLOR4D aValue = COLOR4D::UNSPECIFIED );
+
+    virtual ~PGPROPERTY_COLOR4D() = default;
+
+    wxString ValueToString( wxVariant& aValue, int aFlags = 0 ) const override;
+
+     bool StringToValue( wxVariant &aVariant, const wxString &aText,
+                        int aFlags = 0 ) const override;
+};
+
 #endif /* PG_PROPERTIES_H */
diff --git a/include/properties/property.h b/include/properties/property.h
index 5a2f3ce64c..aa15216bf7 100644
--- a/include/properties/property.h
+++ b/include/properties/property.h
@@ -26,6 +26,7 @@
 
 #include <core/wx_stl_compat.h>
 #include <origin_transforms.h>
+#include <properties/color4d_variant.h>
 #include <properties/eda_angle_variant.h>
 #include <properties/property_validator.h>
 
@@ -343,6 +344,11 @@ protected:
                 EDA_ANGLE_VARIANT_DATA* ad = static_cast<EDA_ANGLE_VARIANT_DATA*>( var.GetData() );
                 a = ad->Angle();
             }
+            else if( pv.CheckType<KIGFX::COLOR4D>() )
+            {
+                COLOR4D_VARIANT_DATA* cd = static_cast<COLOR4D_VARIANT_DATA*>( var.GetData() );
+                a = cd->Color();
+            }
         }
 
         setter( aObject, a );
diff --git a/include/properties/property_mgr.h b/include/properties/property_mgr.h
index c0eeb6632c..10eaf4d99d 100644
--- a/include/properties/property_mgr.h
+++ b/include/properties/property_mgr.h
@@ -224,6 +224,9 @@ public:
     ORIGIN_TRANSFORMS* GetTransforms() const { return m_originTransforms; }
     void SetTransforms( ORIGIN_TRANSFORMS* aTransforms ) { m_originTransforms = aTransforms; }
 
+    const EDA_IU_SCALE* GetIuScale() const { return m_iuScale; }
+    void SetIuScale( const EDA_IU_SCALE* aScale ) { m_iuScale = aScale; }
+
     /**
      * Rebuild the list of all registered properties. Needs to be called
      * once before GetProperty()/GetProperties() are used.
@@ -247,7 +250,8 @@ private:
     PROPERTY_MANAGER() :
             m_dirty( false ),
             m_units( EDA_UNITS::MILLIMETRES ),
-            m_originTransforms( nullptr )
+            m_originTransforms( nullptr ),
+            m_iuScale( &pcbIUScale )
     {
     }
 
@@ -304,7 +308,7 @@ private:
         ///< all properties available to a type
         void collectPropsRecur( PROPERTY_LIST& aResult, PROPERTY_SET& aReplaced,
                                 PROPERTY_DISPLAY_ORDER& aDisplayOrder,
-                                const PROPERTY_SET& aMasked ) const;
+                                PROPERTY_SET& aMasked ) const;
     };
 
     ///< Returns metadata for a specific type
@@ -321,6 +325,8 @@ private:
     EDA_UNITS m_units;
 
     ORIGIN_TRANSFORMS* m_originTransforms;
+
+    const EDA_IU_SCALE* m_iuScale;
 };
 
 
diff --git a/include/tool/actions.h b/include/tool/actions.h
index a4b7c027c4..80741f619e 100644
--- a/include/tool/actions.h
+++ b/include/tool/actions.h
@@ -167,6 +167,7 @@ public:
     static TOOL_ACTION showFootprintEditor;
     static TOOL_ACTION updatePcbFromSchematic;
     static TOOL_ACTION updateSchematicFromPcb;
+    static TOOL_ACTION showProperties;
 
     // Internal
     static TOOL_ACTION updateMenu;
diff --git a/pcbnew/tools/properties_tool.h b/include/tool/properties_tool.h
similarity index 84%
rename from pcbnew/tools/properties_tool.h
rename to include/tool/properties_tool.h
index 84d26b9a0e..43855eb2be 100644
--- a/pcbnew/tools/properties_tool.h
+++ b/include/tool/properties_tool.h
@@ -21,19 +21,21 @@
 #ifndef PROPERTIES_TOOL_H
 #define PROPERTIES_TOOL_H
 
-#include <tools/pcb_tool_base.h>
+#include <tool/tool_interactive.h>
 
 /**
  * Action handler for the Properties panel
  */
-class PROPERTIES_TOOL : public PCB_TOOL_BASE
+class PROPERTIES_TOOL : public TOOL_INTERACTIVE
 {
 public:
     PROPERTIES_TOOL()
-        : PCB_TOOL_BASE( "pcbnew.Properties" )
+        : TOOL_INTERACTIVE( "common.Properties" )
     {
     }
 
+    virtual void Reset( RESET_REASON aReason ) override {}
+
     int UpdateProperties( const TOOL_EVENT& aEvent );
 
     void setTransitions() override;
diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt
index 2dafb76af0..3bf67179f9 100644
--- a/pcbnew/CMakeLists.txt
+++ b/pcbnew/CMakeLists.txt
@@ -349,7 +349,6 @@ set( PCBNEW_CLASS_SRCS
     tools/placement_tool.cpp
     tools/pcb_point_editor.cpp
     tools/position_relative_tool.cpp
-    tools/properties_tool.cpp
     tools/tool_event_utils.cpp
     tools/zone_create_helper.cpp
     tools/zone_filler_tool.cpp
diff --git a/pcbnew/footprint_edit_frame.cpp b/pcbnew/footprint_edit_frame.cpp
index 98151d78c3..d0f370ba72 100644
--- a/pcbnew/footprint_edit_frame.cpp
+++ b/pcbnew/footprint_edit_frame.cpp
@@ -31,7 +31,6 @@
 #include "tools/placement_tool.h"
 #include "tools/pcb_point_editor.h"
 #include "tools/pcb_selection_tool.h"
-#include "tools/properties_tool.h"
 #include <python/scripting/pcb_scripting_tool.h>
 #include <3d_viewer/eda_3d_viewer_frame.h>
 #include <bitmaps.h>
@@ -56,6 +55,7 @@
 #include <tool/action_toolbar.h>
 #include <tool/common_control.h>
 #include <tool/common_tools.h>
+#include <tool/properties_tool.h>
 #include <tool/selection.h>
 #include <tool/tool_dispatcher.h>
 #include <tool/tool_manager.h>
@@ -222,7 +222,7 @@ FOOTPRINT_EDIT_FRAME::FOOTPRINT_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) :
                       .Left().Layer( 4 )
                       .Caption( _( "Libraries" ) )
                       .MinSize( 250, -1 ).BestSize( 250, -1 ) );
-    m_auimgr.AddPane( m_propertiesPanel, EDA_PANE().Name( wxS( "PropertiesManager" ) )
+    m_auimgr.AddPane( m_propertiesPanel, EDA_PANE().Name( PropertiesPaneName() )
                       .Left().Layer( 3 ).Caption( _( "Properties" ) )
                       .PaneBorder( false ).MinSize( 240, -1 ).BestSize( 300, -1 ) );
     m_auimgr.AddPane( m_optionsToolBar, EDA_PANE().VToolbar().Name( "OptToolbar" )
@@ -245,7 +245,7 @@ FOOTPRINT_EDIT_FRAME::FOOTPRINT_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) :
 
     m_auimgr.GetPane( "LayersManager" ).Show( m_show_layer_manager_tools );
     m_auimgr.GetPane( "SelectionFilter" ).Show( m_show_layer_manager_tools );
-    m_auimgr.GetPane( "PropertiesManager" ).Show( m_show_properties );
+    m_auimgr.GetPane( PropertiesPaneName() ).Show( GetSettings()->m_AuiPanels.show_properties );
 
     // The selection filter doesn't need to grow in the vertical direction when docked
     m_auimgr.GetPane( "SelectionFilter" ).dock_proportion = 0;
@@ -639,7 +639,6 @@ void FOOTPRINT_EDIT_FRAME::LoadSettings( APP_SETTINGS_BASE* aCfg )
 
         m_displayOptions = cfg->m_Display;
         m_show_layer_manager_tools = cfg->m_AuiPanels.show_layer_manager;
-        m_show_properties          = cfg->m_AuiPanels.show_properties;
 
         GetToolManager()->GetTool<PCB_SELECTION_TOOL>()->GetFilter() = cfg->m_SelectionFilter;
         m_selectionFilterPanel->SetCheckboxesFromFilter( cfg->m_SelectionFilter );
@@ -672,7 +671,7 @@ void FOOTPRINT_EDIT_FRAME::SaveSettings( APP_SETTINGS_BASE* aCfg )
         cfg->m_AuiPanels.right_panel_width    = m_appearancePanel->GetSize().x;
         cfg->m_AuiPanels.appearance_panel_tab = m_appearancePanel->GetTabIndex();
 
-        cfg->m_AuiPanels.show_properties        = m_show_properties;
+        cfg->m_AuiPanels.show_properties        = m_propertiesPanel->IsShownOnScreen();
         cfg->m_AuiPanels.properties_panel_width = m_propertiesPanel->GetSize().x;
 
         cfg->m_AuiPanels.properties_splitter_proportion =
@@ -1239,7 +1238,7 @@ void FOOTPRINT_EDIT_FRAME::setupUIConditions()
     auto propertiesCond =
             [this] ( const SELECTION& )
             {
-                return m_auimgr.GetPane( "PropertiesManager" ).IsShown();
+                return m_auimgr.GetPane( PropertiesPaneName() ).IsShown();
             };
 
     mgr->SetConditions( PCB_ACTIONS::toggleHV45Mode,        CHECK( constrainedDrawingModeCond ) );
diff --git a/pcbnew/menubar_pcb_editor.cpp b/pcbnew/menubar_pcb_editor.cpp
index e454617d26..f834ce4d55 100644
--- a/pcbnew/menubar_pcb_editor.cpp
+++ b/pcbnew/menubar_pcb_editor.cpp
@@ -226,7 +226,7 @@ void PCB_EDIT_FRAME::doReCreateMenuBar()
 
     viewMenu->Add( ACTIONS::showFootprintBrowser );
 
-    viewMenu->Add( PCB_ACTIONS::showProperties, ACTION_MENU::CHECK );
+    viewMenu->Add( ACTIONS::showProperties, ACTION_MENU::CHECK );
 
     viewMenu->Add( PCB_ACTIONS::showSearch, ACTION_MENU::CHECK );
     viewMenu->Add( ACTIONS::show3DViewer );
diff --git a/pcbnew/pcb_base_edit_frame.cpp b/pcbnew/pcb_base_edit_frame.cpp
index d0ac06a542..c2b3337aeb 100644
--- a/pcbnew/pcb_base_edit_frame.cpp
+++ b/pcbnew/pcb_base_edit_frame.cpp
@@ -53,9 +53,7 @@ PCB_BASE_EDIT_FRAME::PCB_BASE_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent,
         m_undoRedoBlocked( false ),
         m_selectionFilterPanel( nullptr ),
         m_appearancePanel( nullptr ),
-        m_propertiesPanel( nullptr ),
-        m_tabbedPanel( nullptr ),
-        m_show_properties( false )
+        m_tabbedPanel( nullptr )
 {
     m_darkMode = KIPLATFORM::UI::IsDarkTheme();
 
@@ -290,28 +288,19 @@ void PCB_BASE_EDIT_FRAME::onDarkModeToggle()
 }
 
 
-void PCB_BASE_EDIT_FRAME::UpdateProperties()
-{
-    if( !m_propertiesPanel || !m_propertiesPanel->IsShownOnScreen() )
-        return;
-
-    m_propertiesPanel->UpdateData();
-}
-
-
 void PCB_BASE_EDIT_FRAME::ToggleProperties()
 {
     if( !m_propertiesPanel )
         return;
 
+    bool show = !m_propertiesPanel->IsShownOnScreen();
+
+    wxAuiPaneInfo& propertiesPaneInfo = m_auimgr.GetPane( PropertiesPaneName() );
+    propertiesPaneInfo.Show( show );
+
     PCBNEW_SETTINGS* settings = GetPcbNewSettings();
 
-    m_show_properties = !m_show_properties;
-
-    wxAuiPaneInfo& propertiesPaneInfo = m_auimgr.GetPane( "PropertiesManager" );
-    propertiesPaneInfo.Show( m_show_properties );
-
-    if( m_show_properties )
+    if( show )
     {
         SetAuiPaneSize( m_auimgr, propertiesPaneInfo,
                         settings->m_AuiPanels.properties_panel_width, -1 );
diff --git a/pcbnew/pcb_base_edit_frame.h b/pcbnew/pcb_base_edit_frame.h
index f9a8e59d04..f1c8f0f870 100644
--- a/pcbnew/pcb_base_edit_frame.h
+++ b/pcbnew/pcb_base_edit_frame.h
@@ -32,7 +32,6 @@
 class APPEARANCE_CONTROLS;
 class BOARD_ITEM_CONTAINER;
 class PANEL_SELECTION_FILTER;
-class PROPERTIES_PANEL;
 class PCB_TEXTBOX;
 class PCB_TEXT;
 class PCB_SHAPE;
@@ -220,11 +219,7 @@ public:
 
     APPEARANCE_CONTROLS* GetAppearancePanel() { return m_appearancePanel; }
 
-    PROPERTIES_PANEL* GetPropertiesPanel() { return m_propertiesPanel; }
-
-    void UpdateProperties();
-
-    void ToggleProperties();
+    void ToggleProperties() override;
 
     void GetContextualTextVars( BOARD_ITEM* aSourceItem, const wxString& aCrossRef,
                                 wxArrayString* aTokens );
@@ -258,12 +253,10 @@ protected:
 
     PANEL_SELECTION_FILTER* m_selectionFilterPanel;
     APPEARANCE_CONTROLS*    m_appearancePanel;
-    PROPERTIES_PANEL*       m_propertiesPanel;
 
     wxAuiNotebook*          m_tabbedPanel;        /// Panel with Layers and Object Inspector tabs
 
     bool                    m_darkMode;
-    bool                    m_show_properties;
 };
 
 #endif
diff --git a/pcbnew/pcb_edit_frame.cpp b/pcbnew/pcb_edit_frame.cpp
index a64f2f3974..f93e859efe 100644
--- a/pcbnew/pcb_edit_frame.cpp
+++ b/pcbnew/pcb_edit_frame.cpp
@@ -61,6 +61,7 @@
 #include <tool/action_toolbar.h>
 #include <tool/common_control.h>
 #include <tool/common_tools.h>
+#include <tool/properties_tool.h>
 #include <tool/selection.h>
 #include <tool/zoom_tool.h>
 #include <tools/pcb_selection_tool.h>
@@ -82,7 +83,6 @@
 #include <tools/pad_tool.h>
 #include <microwave/microwave_tool.h>
 #include <tools/position_relative_tool.h>
-#include <tools/properties_tool.h>
 #include <tools/zone_filler_tool.h>
 #include <tools/pcb_actions.h>
 #include <router/router_tool.h>
@@ -203,7 +203,6 @@ PCB_EDIT_FRAME::PCB_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) :
     m_show_layer_manager_tools = true;
     m_supportsAutoSave = true;
     m_probingSchToPcb = false;
-    m_show_properties = true;
     m_show_search = false;
 
     // We don't know what state board was in when it was last saved, so we have to
@@ -304,7 +303,7 @@ PCB_EDIT_FRAME::PCB_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) :
                       .Caption( _( "Selection Filter" ) ).PaneBorder( false )
                       .MinSize( 180, -1 ).BestSize( 180, -1 ) );
 
-    m_auimgr.AddPane( m_propertiesPanel, EDA_PANE().Name( wxS( "PropertiesManager" ) )
+    m_auimgr.AddPane( m_propertiesPanel, EDA_PANE().Name( PropertiesPaneName() )
                       .Left().Layer( 5 ).Caption( _( "Properties" ) )
                       .PaneBorder( false ).MinSize( 240, -1 ).BestSize( 300, -1 ) );
 
@@ -327,7 +326,7 @@ PCB_EDIT_FRAME::PCB_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) :
 
     m_auimgr.GetPane( "LayersManager" ).Show( m_show_layer_manager_tools );
     m_auimgr.GetPane( "SelectionFilter" ).Show( m_show_layer_manager_tools );
-    m_auimgr.GetPane( "PropertiesManager" ).Show( m_show_properties );
+    m_auimgr.GetPane( PropertiesPaneName() ).Show( GetPcbNewSettings()->m_AuiPanels.show_properties );
 
     m_auimgr.GetPane( SearchPaneName() ).Show( m_show_search );
 
@@ -346,7 +345,7 @@ PCB_EDIT_FRAME::PCB_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) :
 
         if( settings->m_AuiPanels.properties_panel_width > 0 && m_propertiesPanel )
         {
-            wxAuiPaneInfo& propertiesPanel = m_auimgr.GetPane( wxS( "PropertiesManager" ) );
+            wxAuiPaneInfo& propertiesPanel = m_auimgr.GetPane( PropertiesPaneName() );
             SetAuiPaneSize( m_auimgr, propertiesPanel,
                             settings->m_AuiPanels.properties_panel_width, -1 );
         }
@@ -1301,7 +1300,6 @@ void PCB_EDIT_FRAME::LoadSettings( APP_SETTINGS_BASE* aCfg )
     if( cfg )
     {
         m_show_layer_manager_tools = cfg->m_AuiPanels.show_layer_manager;
-        m_show_properties          = cfg->m_AuiPanels.show_properties;
         m_show_search              = cfg->m_AuiPanels.show_search;
     }
 }
@@ -1322,7 +1320,7 @@ void PCB_EDIT_FRAME::SaveSettings( APP_SETTINGS_BASE* aCfg )
 
         if( m_propertiesPanel )
         {
-            cfg->m_AuiPanels.show_properties        = m_show_properties;
+            cfg->m_AuiPanels.show_properties        = m_propertiesPanel->IsShownOnScreen();
             cfg->m_AuiPanels.properties_panel_width = m_propertiesPanel->GetSize().x;
 
             cfg->m_AuiPanels.properties_splitter_proportion =
@@ -2441,7 +2439,7 @@ bool PCB_EDIT_FRAME::LayerManagerShown()
 
 bool PCB_EDIT_FRAME::PropertiesShown()
 {
-    return m_auimgr.GetPane( wxS( "PropertiesManager" ) ).IsShown();
+    return m_auimgr.GetPane( PropertiesPaneName() ).IsShown();
 }
 
 
diff --git a/pcbnew/tools/board_editor_control.cpp b/pcbnew/tools/board_editor_control.cpp
index c5aa4e248f..ff62634367 100644
--- a/pcbnew/tools/board_editor_control.cpp
+++ b/pcbnew/tools/board_editor_control.cpp
@@ -1670,7 +1670,7 @@ void BOARD_EDITOR_CONTROL::setTransitions()
         ACTIONS::updateSchematicFromPcb.MakeEvent() );
     Go( &BOARD_EDITOR_CONTROL::ShowEeschema,           PCB_ACTIONS::showEeschema.MakeEvent() );
     Go( &BOARD_EDITOR_CONTROL::ToggleLayersManager,    PCB_ACTIONS::showLayersManager.MakeEvent() );
-    Go( &BOARD_EDITOR_CONTROL::ToggleProperties,       PCB_ACTIONS::showProperties.MakeEvent() );
+    Go( &BOARD_EDITOR_CONTROL::ToggleProperties,       ACTIONS::showProperties.MakeEvent() );
     Go( &BOARD_EDITOR_CONTROL::ToggleSearch,           PCB_ACTIONS::showSearch.MakeEvent() );
     Go( &BOARD_EDITOR_CONTROL::TogglePythonConsole,    PCB_ACTIONS::showPythonConsole.MakeEvent() );
     Go( &BOARD_EDITOR_CONTROL::RepairBoard,            PCB_ACTIONS::repairBoard.MakeEvent() );
diff --git a/pcbnew/tools/pcb_actions.cpp b/pcbnew/tools/pcb_actions.cpp
index 717a26143a..44fc355f44 100644
--- a/pcbnew/tools/pcb_actions.cpp
+++ b/pcbnew/tools/pcb_actions.cpp
@@ -923,11 +923,6 @@ TOOL_ACTION PCB_ACTIONS::showLayersManager( "pcbnew.Control.showLayersManager",
         _( "Show Appearance Manager" ), _( "Show/hide the appearance manager" ),
         BITMAPS::layers_manager );
 
-TOOL_ACTION PCB_ACTIONS::showProperties( "pcbnew.Control.showProperties",
-        AS_GLOBAL, 0, "",
-        _( "Show Properties Manager" ), _( "Show/hide the properties manager" ),
-        BITMAPS::tools );
-
 TOOL_ACTION PCB_ACTIONS::flipBoard( "pcbnew.Control.flipBoard",
         AS_GLOBAL, 0, "",
         _( "Flip Board View" ), _( "View board from the opposite side" ),
diff --git a/pcbnew/tools/pcb_actions.h b/pcbnew/tools/pcb_actions.h
index df68bf5e18..1e97b701a6 100644
--- a/pcbnew/tools/pcb_actions.h
+++ b/pcbnew/tools/pcb_actions.h
@@ -388,7 +388,6 @@ public:
     static TOOL_ACTION editLibFpInFpEditor;
 
     static TOOL_ACTION showLayersManager;
-    static TOOL_ACTION showProperties;
     static TOOL_ACTION showPythonConsole;
 
     // Module editor tools