From 5d37a00759b3e1361799bd2fe2d88e2169eebb92 Mon Sep 17 00:00:00 2001
From: Jeff Young <jeff@rokeby.ie>
Date: Sun, 24 Mar 2024 16:25:03 +0000
Subject: [PATCH] Table plotting for PCBNew.

Also fixes some bugs with property exposure that (along with
other things) allowed you to put table cells on a different
layer than their parent table.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/17438
---
 common/eda_shape.cpp             |   8 ++
 pcbnew/pcb_tablecell.cpp         |  13 +++-
 pcbnew/pcbplot.h                 |   2 +
 pcbnew/plot_brditems_plotter.cpp | 123 +++++++++++++++++++++++++++++++
 4 files changed, 145 insertions(+), 1 deletion(-)

diff --git a/common/eda_shape.cpp b/common/eda_shape.cpp
index 4c95593f1b..8aae5bad4e 100644
--- a/common/eda_shape.cpp
+++ b/common/eda_shape.cpp
@@ -2050,6 +2050,14 @@ static struct EDA_SHAPE_DESC
         auto fillAvailable =
                 [=]( INSPECTABLE* aItem ) -> bool
                 {
+                    if( EDA_ITEM* edaItem = dynamic_cast<EDA_ITEM*>( aItem ) )
+                    {
+                        // For some reason masking "Filled" and "Fill Color" at the
+                        // PCB_TABLECELL level doesn't work.
+                        if( edaItem->Type() == PCB_TABLECELL_T )
+                            return false;
+                    }
+
                     if( EDA_SHAPE* edaShape = dynamic_cast<EDA_SHAPE*>( aItem ) )
                     {
                         switch( edaShape->GetShape() )
diff --git a/pcbnew/pcb_tablecell.cpp b/pcbnew/pcb_tablecell.cpp
index 5a134e2a3f..80678e0059 100644
--- a/pcbnew/pcb_tablecell.cpp
+++ b/pcbnew/pcb_tablecell.cpp
@@ -162,15 +162,25 @@ static struct PCB_TABLECELL_DESC
         PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
         REGISTER_TYPE( PCB_TABLECELL );
 
+        propMgr.AddTypeCast( new TYPE_CAST<PCB_TABLECELL, BOARD_ITEM> );
+        propMgr.AddTypeCast( new TYPE_CAST<PCB_TABLECELL, BOARD_CONNECTED_ITEM> );
         propMgr.AddTypeCast( new TYPE_CAST<PCB_TABLECELL, PCB_TEXTBOX> );
         propMgr.AddTypeCast( new TYPE_CAST<PCB_TABLECELL, PCB_SHAPE> );
         propMgr.AddTypeCast( new TYPE_CAST<PCB_TABLECELL, EDA_SHAPE> );
         propMgr.AddTypeCast( new TYPE_CAST<PCB_TABLECELL, EDA_TEXT> );
+        propMgr.InheritsAfter( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( BOARD_ITEM ) );
+        propMgr.InheritsAfter( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( BOARD_CONNECTED_ITEM ) );
         propMgr.InheritsAfter( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( PCB_TEXTBOX ) );
         propMgr.InheritsAfter( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( PCB_SHAPE ) );
         propMgr.InheritsAfter( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( EDA_SHAPE ) );
         propMgr.InheritsAfter( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( EDA_TEXT ) );
 
+        propMgr.Mask( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( BOARD_ITEM ), _HKI( "Position X" ) );
+        propMgr.Mask( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( BOARD_ITEM ), _HKI( "Position Y" ) );
+        propMgr.Mask( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( PCB_SHAPE ), _HKI( "Layer" ) );
+
+        propMgr.Mask( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( BOARD_CONNECTED_ITEM ), _HKI( "Net" ) );
+
         propMgr.Mask( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( PCB_TEXTBOX ), _HKI( "Border" ) );
         propMgr.Mask( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( PCB_TEXTBOX ), _HKI( "Border Style" ) );
         propMgr.Mask( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( PCB_TEXTBOX ), _HKI( "Border Width" ) );
@@ -179,10 +189,10 @@ static struct PCB_TABLECELL_DESC
         propMgr.Mask( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( EDA_SHAPE ), _HKI( "Start Y" ) );
         propMgr.Mask( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( EDA_SHAPE ), _HKI( "End X" ) );
         propMgr.Mask( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( EDA_SHAPE ), _HKI( "End Y" ) );
-
         propMgr.Mask( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( EDA_SHAPE ), _HKI( "Shape" ) );
         propMgr.Mask( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( EDA_SHAPE ), _HKI( "Line Width" ) );
         propMgr.Mask( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( EDA_SHAPE ), _HKI( "Line Style" ) );
+        propMgr.Mask( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( EDA_SHAPE ), _HKI( "Line Color" ) );
 
         propMgr.Mask( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( EDA_TEXT ), _HKI( "Visible" ) );
         propMgr.Mask( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( EDA_TEXT ), _HKI( "Width" ) );
@@ -190,5 +200,6 @@ static struct PCB_TABLECELL_DESC
         propMgr.Mask( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( EDA_TEXT ), _HKI( "Thickness" ) );
         propMgr.Mask( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( EDA_TEXT ), _HKI( "Orientation" ) );
         propMgr.Mask( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( EDA_TEXT ), _HKI( "Hyperlink" ) );
+        propMgr.Mask( TYPE_HASH( PCB_TABLECELL ), TYPE_HASH( EDA_TEXT ), _HKI( "Color" ) );
     }
 } _PCB_TABLECELL_DESC;
diff --git a/pcbnew/pcbplot.h b/pcbnew/pcbplot.h
index 9846638e38..8943c0cb2c 100644
--- a/pcbnew/pcbplot.h
+++ b/pcbnew/pcbplot.h
@@ -37,6 +37,7 @@ class PLOTTER;
 class PCB_TEXT;
 class PAD;
 class PCB_SHAPE;
+class PCB_TABLE;
 class PCB_DIMENSION_BASE;
 class FOOTPRINT;
 class PCB_TARGET;
@@ -91,6 +92,7 @@ public:
     void PlotText( const EDA_TEXT* aText, PCB_LAYER_ID aLayer, bool aIsKnockout,
                    const KIFONT::METRICS& aFontMetrics );
     void PlotShape( const PCB_SHAPE* aShape );
+    void PlotTableBorders( const PCB_TABLE* aTable );
 
     /**
      * Plot a pad.
diff --git a/pcbnew/plot_brditems_plotter.cpp b/pcbnew/plot_brditems_plotter.cpp
index fffcd84616..479e946444 100644
--- a/pcbnew/plot_brditems_plotter.cpp
+++ b/pcbnew/plot_brditems_plotter.cpp
@@ -53,6 +53,8 @@
 #include <pcb_target.h>
 #include <pcb_text.h>
 #include <pcb_textbox.h>
+#include <pcb_tablecell.h>
+#include <pcb_table.h>
 #include <zone.h>
 
 #include <wx/debug.h>                         // for wxASSERT_MSG
@@ -369,6 +371,21 @@ void BRDITEMS_PLOTTER::PlotBoardGraphicItem( const BOARD_ITEM* item )
         break;
     }
 
+    case PCB_TABLE_T:
+    {
+        const PCB_TABLE* table = static_cast<const PCB_TABLE*>( item );
+
+        m_plotter->SetTextMode( PLOT_TEXT_MODE::STROKE );
+
+        for( const PCB_TABLECELL* cell : table->GetCells() )
+            PlotText( cell, cell->GetLayer(), cell->IsKnockout(), cell->GetFontMetrics() );
+
+        PlotTableBorders( table );
+
+        m_plotter->SetTextMode( GetTextMode() );
+        break;
+    }
+
     case PCB_DIM_ALIGNED_T:
     case PCB_DIM_CENTER_T:
     case PCB_DIM_RADIAL_T:
@@ -890,6 +907,112 @@ void BRDITEMS_PLOTTER::PlotShape( const PCB_SHAPE* aShape )
 }
 
 
+void BRDITEMS_PLOTTER::PlotTableBorders( const PCB_TABLE* aTable )
+{
+    VECTOR2I     pos = aTable->GetPosition();
+    VECTOR2I     end = aTable->GetEnd();
+    int          lineWidth;
+    LINE_STYLE   lineStyle;
+    GBR_METADATA gbr_metadata;
+
+    if( const FOOTPRINT* parentFP = aTable->GetParentFootprint() )
+    {
+        gbr_metadata.SetCmpReference( parentFP->GetReference() );
+        gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_CMP );
+    }
+
+    auto setupStroke =
+            [&]( const STROKE_PARAMS& stroke )
+            {
+                lineWidth = stroke.GetWidth();
+                lineStyle = stroke.GetLineStyle();
+            };
+
+    auto strokeShape =
+            [&]( const SHAPE& shape )
+            {
+                STROKE_PARAMS::Stroke( &shape, lineStyle, lineWidth, m_plotter->RenderSettings(),
+                        [&]( const VECTOR2I& a, const VECTOR2I& b )
+                        {
+                            m_plotter->ThickSegment( a, b, lineWidth, GetPlotMode(),
+                                                     &gbr_metadata );
+                        } );
+            };
+
+    auto strokeLine =
+            [&]( const VECTOR2I& ptA, const VECTOR2I& ptB )
+            {
+                if( lineStyle <= LINE_STYLE::FIRST_TYPE )
+                {
+                    m_plotter->ThickSegment( ptA, ptB, lineWidth, GetPlotMode(), &gbr_metadata );
+                }
+                else
+                {
+                    SHAPE_SEGMENT seg( ptA, ptB );
+                    strokeShape( seg );
+                }
+            };
+
+    auto strokeRect =
+            [&]( const VECTOR2I& ptA, const VECTOR2I& ptB )
+            {
+                strokeLine( VECTOR2I( ptA.x, ptA.y ), VECTOR2I( ptB.x, ptA.y ) );
+                strokeLine( VECTOR2I( ptB.x, ptA.y ), VECTOR2I( ptB.x, ptB.y ) );
+                strokeLine( VECTOR2I( ptB.x, ptB.y ), VECTOR2I( ptA.x, ptB.y ) );
+                strokeLine( VECTOR2I( ptA.x, ptB.y ), VECTOR2I( ptA.x, ptA.y ) );
+            };
+
+    if( aTable->GetSeparatorsStroke().GetWidth() >= 0 )
+    {
+        setupStroke( aTable->GetSeparatorsStroke() );
+
+        if( aTable->StrokeColumns() )
+        {
+            for( int col = 0; col < aTable->GetColCount() - 1; ++col )
+            {
+                for( int row = 0; row < aTable->GetRowCount(); ++row )
+                {
+                    PCB_TABLECELL* cell = aTable->GetCell( row, col );
+                    VECTOR2I       topRight( cell->GetEndX(), cell->GetStartY() );
+
+                    if( cell->GetColSpan() > 0 && cell->GetRowSpan() > 0 )
+                        strokeLine( topRight, cell->GetEnd() );
+                }
+            }
+        }
+
+        if( aTable->StrokeRows() )
+        {
+            for( int row = 0; row < aTable->GetRowCount() - 1; ++row )
+            {
+                for( int col = 0; col < aTable->GetColCount(); ++col )
+                {
+                    PCB_TABLECELL* cell = aTable->GetCell( row, col );
+                    VECTOR2I       botLeft( cell->GetStartX(), cell->GetEndY() );
+
+                    if( cell->GetColSpan() > 0 && cell->GetRowSpan() > 0 )
+                        strokeLine( botLeft, cell->GetEnd() );
+                }
+            }
+        }
+    }
+
+    if( aTable->GetBorderStroke().GetWidth() >= 0 )
+    {
+        setupStroke( aTable->GetBorderStroke() );
+
+        if( aTable->StrokeHeader() )
+        {
+            PCB_TABLECELL* cell = aTable->GetCell( 0, 0 );
+            strokeLine( VECTOR2I( pos.x, cell->GetEndY() ), VECTOR2I( end.x, cell->GetEndY() ) );
+        }
+
+        if( aTable->StrokeExternal() )
+            strokeRect( pos, end );
+    }
+}
+
+
 void BRDITEMS_PLOTTER::plotOneDrillMark( PAD_DRILL_SHAPE_T aDrillShape, const VECTOR2I& aDrillPos,
                                          const VECTOR2I& aDrillSize, const VECTOR2I& aPadSize,
                                          const EDA_ANGLE& aOrientation, int aSmallDrill )