/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2023-2024 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 2 * 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, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #ifndef EDIT_TABLE_TOOL_BASE_H #define EDIT_TABLE_TOOL_BASE_H #include #include #include #include class BASE_SCREEN; /** * SCH_TABLE_EDIT_TOOL and PCB_TABLE_EDIT_TOOL share most of their algorithms, which are * implemented here. */ template class EDIT_TABLE_TOOL_BASE { protected: void addMenus( CONDITIONAL_MENU& selToolMenu ) { auto cellSelection = SELECTION_CONDITIONS::MoreThan( 0 ) && SELECTION_CONDITIONS::OnlyTypes( { SCH_TABLECELL_T, PCB_TABLECELL_T } ); auto cellBlockSelection = [&]( const SELECTION& sel ) { if( sel.Size() < 2 ) return false; int colMin = std::numeric_limits::max(); int colMax = 0; int rowMin = std::numeric_limits::max(); int rowMax = 0; int selectedArea = 0; for( EDA_ITEM* item : sel ) { if( T_TABLECELL* cell = dynamic_cast( item ) ) { colMin = std::min( colMin, cell->GetColumn() ); colMax = std::max( colMax, cell->GetColumn() + cell->GetColSpan() ); rowMin = std::min( rowMin, cell->GetRow() ); rowMax = std::max( rowMax, cell->GetRow() + cell->GetRowSpan() ); selectedArea += cell->GetColSpan() * cell->GetRowSpan(); } } return selectedArea == ( colMax - colMin ) * ( rowMax - rowMin ); }; auto mergedCellsSelection = [&]( const SELECTION& sel ) { for( EDA_ITEM* item : sel ) { if( T_TABLECELL* cell = dynamic_cast( item ) ) { if( cell->GetColSpan() > 1 || cell->GetRowSpan() > 1 ) return true; } } return false; }; // // Add editing actions to the selection tool menu // selToolMenu.AddSeparator( 100 ); selToolMenu.AddItem( ACTIONS::addRowAbove, cellSelection && SELECTION_CONDITIONS::Idle, 100 ); selToolMenu.AddItem( ACTIONS::addRowBelow, cellSelection && SELECTION_CONDITIONS::Idle, 100 ); selToolMenu.AddItem( ACTIONS::addColBefore, cellSelection && SELECTION_CONDITIONS::Idle, 100 ); selToolMenu.AddItem( ACTIONS::addColAfter, cellSelection && SELECTION_CONDITIONS::Idle, 100 ); selToolMenu.AddSeparator( 100 ); selToolMenu.AddItem( ACTIONS::deleteRows, cellSelection && SELECTION_CONDITIONS::Idle, 100 ); selToolMenu.AddItem( ACTIONS::deleteColumns, cellSelection && SELECTION_CONDITIONS::Idle, 100 ); selToolMenu.AddSeparator( 100 ); selToolMenu.AddItem( ACTIONS::mergeCells, cellSelection && cellBlockSelection, 100 ); selToolMenu.AddItem( ACTIONS::unmergeCells, cellSelection && mergedCellsSelection, 100 ); selToolMenu.AddSeparator( 100 ); selToolMenu.AddItem( ACTIONS::editTable, cellSelection && SELECTION_CONDITIONS::Idle, 100 ); selToolMenu.AddSeparator( 100 ); } int doAddRowAbove( const TOOL_EVENT& aEvent ) { const SELECTION& selection = getTableCellSelection(); T_TABLECELL* topmost = nullptr; for( EDA_ITEM* item : selection ) { T_TABLECELL* cell = static_cast( item ); if( !topmost || cell->GetRow() < topmost->GetRow() ) topmost = cell; } if( !topmost ) return 0; int row = topmost->GetRow(); T_TABLE* table = static_cast( topmost->GetParent() ); T_COMMIT commit( getToolMgr() ); VECTOR2I pos = table->GetPosition(); // Make a copy of the source row before things start moving around std::vector sources; sources.reserve( table->GetColCount() ); for( int col = 0; col < table->GetColCount(); ++col ) sources.push_back( table->GetCell( row, col ) ); commit.Modify( table, getScreen() ); for( int col = 0; col < table->GetColCount(); ++col ) { T_TABLECELL* cell = copyCell( sources[col] ); table->InsertCell( row * table->GetColCount(), cell ); } for( int afterRow = table->GetRowCount() - 1; afterRow > row; afterRow-- ) table->SetRowHeight( afterRow, table->GetRowHeight( afterRow - 1 ) ); table->SetPosition( pos ); table->Normalize(); getToolMgr()->PostEvent( EVENTS::SelectedEvent ); commit.Push( _( "Add Row Above" ) ); return 0; } int doAddRowBelow( const TOOL_EVENT& aEvent ) { const SELECTION& selection = getTableCellSelection(); T_TABLECELL* bottommost = nullptr; if( selection.Empty() ) return 0; for( EDA_ITEM* item : selection ) { T_TABLECELL* cell = static_cast( item ); if( !bottommost || cell->GetRow() > bottommost->GetRow() ) bottommost = cell; } if( !bottommost ) return 0; int row = bottommost->GetRow(); T_TABLE* table = static_cast( bottommost->GetParent() ); T_COMMIT commit( getToolMgr() ); VECTOR2I pos = table->GetPosition(); // Make a copy of the source row before things start moving around std::vector sources; sources.reserve( table->GetColCount() ); for( int col = 0; col < table->GetColCount(); ++col ) sources.push_back( table->GetCell( row, col ) ); commit.Modify( table, getScreen() ); for( int col = 0; col < table->GetColCount(); ++col ) { T_TABLECELL* cell = copyCell( sources[col] ); table->InsertCell( ( row + 1 ) * table->GetColCount(), cell ); } for( int afterRow = table->GetRowCount() - 1; afterRow > row; afterRow-- ) table->SetRowHeight( afterRow, table->GetRowHeight( afterRow - 1 ) ); table->SetPosition( pos ); table->Normalize(); getToolMgr()->PostEvent( EVENTS::SelectedEvent ); commit.Push( _( "Add Row Below" ) ); return 0; } int doAddColumnBefore( const TOOL_EVENT& aEvent ) { const SELECTION& selection = getTableCellSelection(); T_TABLECELL* leftmost = nullptr; for( EDA_ITEM* item : selection ) { T_TABLECELL* cell = static_cast( item ); if( !leftmost || cell->GetColumn() < leftmost->GetColumn() ) leftmost = cell; } if( !leftmost ) return 0; int col = leftmost->GetColumn(); T_TABLE* table = static_cast( leftmost->GetParent() ); int rowCount = table->GetRowCount(); T_COMMIT commit( getToolMgr() ); VECTOR2I pos = table->GetPosition(); // Make a copy of the source column before things start moving around std::vector sources; sources.reserve( rowCount ); for( int row = 0; row < rowCount; ++row ) sources.push_back( table->GetCell( row, col ) ); commit.Modify( table, getScreen() ); table->SetColCount( table->GetColCount() + 1 ); for( int row = 0; row < rowCount; ++row ) { T_TABLECELL* cell = copyCell( sources[row] ); table->InsertCell( row * table->GetColCount() + col, cell ); } for( int afterCol = table->GetColCount() - 1; afterCol > col; afterCol-- ) table->SetColWidth( afterCol, table->GetColWidth( afterCol - 1 ) ); table->SetPosition( pos ); table->Normalize(); getToolMgr()->PostEvent( EVENTS::SelectedEvent ); commit.Push( _( "Add Column Before" ) ); return 0; } int doAddColumnAfter( const TOOL_EVENT& aEvent ) { const SELECTION& selection = getTableCellSelection(); T_TABLECELL* rightmost = nullptr; for( EDA_ITEM* item : selection ) { T_TABLECELL* cell = static_cast( item ); if( !rightmost || cell->GetColumn() > rightmost->GetColumn() ) rightmost = cell; } if( !rightmost ) return 0; int col = rightmost->GetColumn(); T_TABLE* table = static_cast( rightmost->GetParent() ); int rowCount = table->GetRowCount(); T_COMMIT commit( getToolMgr() ); VECTOR2I pos = table->GetPosition(); // Make a copy of the source column before things start moving around std::vector sources; sources.reserve( rowCount ); for( int row = 0; row < rowCount; ++row ) sources.push_back( table->GetCell( row, col ) ); commit.Modify( table, getScreen() ); table->SetColCount( table->GetColCount() + 1 ); for( int row = 0; row < rowCount; ++row ) { T_TABLECELL* cell = copyCell( sources[row] ); table->InsertCell( row * table->GetColCount() + col + 1, cell ); } for( int afterCol = table->GetColCount() - 1; afterCol > col; afterCol-- ) table->SetColWidth( afterCol, table->GetColWidth( afterCol - 1 ) ); table->SetPosition( pos ); table->Normalize(); getToolMgr()->PostEvent( EVENTS::SelectedEvent ); commit.Push( _( "Add Column After" ) ); return 0; } int doDeleteRows( const TOOL_EVENT& aEvent ) { const SELECTION& selection = getTableCellSelection(); if( selection.Empty() ) return 0; T_TABLE* table = static_cast( selection[0]->GetParent() ); std::vector deleted; for( T_TABLECELL* cell : table->GetCells() ) cell->ClearFlags( STRUCT_DELETED ); for( int row = 0; row < table->GetRowCount(); ++row ) { bool deleteRow = false; for( int col = 0; col < table->GetColCount(); ++col ) { if( table->GetCell( row, col )->IsSelected() ) { deleteRow = true; break; } } if( deleteRow ) { for( int col = 0; col < table->GetColCount(); ++col ) table->GetCell( row, col )->SetFlags( STRUCT_DELETED ); deleted.push_back( row ); } } T_COMMIT commit( getToolMgr() ); if( deleted.size() == (unsigned) table->GetRowCount() ) { commit.Remove( table ); } else { commit.Modify( table, getScreen() ); VECTOR2I pos = table->GetPosition(); clearSelection(); table->DeleteMarkedCells(); for( int row = 0; row < table->GetRowCount(); ++row ) { int offset = 0; for( int deletedRow : deleted ) { if( deletedRow >= row ) offset++; } table->SetRowHeight( row, table->GetRowHeight( row + offset ) ); } table->SetPosition( pos ); table->Normalize(); getToolMgr()->PostEvent( EVENTS::SelectedEvent ); } if( deleted.size() > 1 ) commit.Push( _( "Delete Rows" ) ); else commit.Push( _( "Delete Row" ) ); return 0; } int doDeleteColumns( const TOOL_EVENT& aEvent ) { const SELECTION& selection = getTableCellSelection(); if( selection.Empty() ) return 0; T_TABLE* table = static_cast( selection[0]->GetParent() ); std::vector deleted; for( T_TABLECELL* cell : table->GetCells() ) cell->ClearFlags( STRUCT_DELETED ); for( int col = 0; col < table->GetColCount(); ++col ) { bool deleteColumn = false; for( int row = 0; row < table->GetRowCount(); ++row ) { if( table->GetCell( row, col )->IsSelected() ) { deleteColumn = true; break; } } if( deleteColumn ) { for( int row = 0; row < table->GetRowCount(); ++row ) table->GetCell( row, col )->SetFlags( STRUCT_DELETED ); deleted.push_back( col ); } } T_COMMIT commit( getToolMgr() ); if( deleted.size() == (unsigned) table->GetColCount() ) { commit.Remove( table ); } else { commit.Modify( table, getScreen() ); VECTOR2I pos = table->GetPosition(); clearSelection(); table->DeleteMarkedCells(); table->SetColCount( table->GetColCount() - deleted.size() ); for( int col = 0; col < table->GetColCount(); ++col ) { int offset = 0; for( int deletedCol : deleted ) { if( deletedCol >= col ) offset++; } table->SetColWidth( col, table->GetColWidth( col + offset ) ); } table->SetPosition( pos ); table->Normalize(); getToolMgr()->PostEvent( EVENTS::SelectedEvent ); } if( deleted.size() > 1 ) commit.Push( _( "Delete Columns" ) ); else commit.Push( _( "Delete Column" ) ); return 0; } int doMergeCells( const TOOL_EVENT& aEvent ) { const SELECTION& sel = getTableCellSelection(); if( sel.Empty() ) return 0; int colMin = std::numeric_limits::max(); int colMax = 0; int rowMin = std::numeric_limits::max(); int rowMax = 0; T_COMMIT commit( getToolMgr() ); T_TABLE* table = static_cast( sel[0]->GetParent() ); for( EDA_ITEM* item : sel ) { if( T_TABLECELL* cell = dynamic_cast( item ) ) { colMin = std::min( colMin, cell->GetColumn() ); colMax = std::max( colMax, cell->GetColumn() + cell->GetColSpan() ); rowMin = std::min( rowMin, cell->GetRow() ); rowMax = std::max( rowMax, cell->GetRow() + cell->GetRowSpan() ); } } wxString content; VECTOR2I extents; for( int row = rowMin; row < rowMax; ++row ) { extents.y += table->GetRowHeight( row ); extents.x = 0; for( int col = colMin; col < colMax; ++col ) { extents.x += table->GetColWidth( col ); T_TABLECELL* cell = table->GetCell( row, col ); if( !cell->GetText().IsEmpty() ) { if( !content.IsEmpty() ) content += "\n"; content += cell->GetText(); } commit.Modify( cell, getScreen() ); cell->SetColSpan( 0 ); cell->SetRowSpan( 0 ); cell->SetText( wxEmptyString ); } } T_TABLECELL* topLeft = table->GetCell( rowMin, colMin ); topLeft->SetColSpan( colMax - colMin ); topLeft->SetRowSpan( rowMax - rowMin ); topLeft->SetText( content ); topLeft->SetEnd( topLeft->GetStart() + extents ); table->Normalize(); commit.Push( _( "Merge Cells" ) ); getToolMgr()->PostEvent( EVENTS::SelectedEvent ); return 0; } int doUnmergeCells( const TOOL_EVENT& aEvent ) { const SELECTION& sel = getTableCellSelection(); if( sel.Empty() ) return 0; T_COMMIT commit( getToolMgr() ); T_TABLE* table = static_cast( sel[0]->GetParent() ); for( EDA_ITEM* item : sel ) { if( T_TABLECELL* cell = dynamic_cast( item ) ) { int rowSpan = cell->GetRowSpan(); int colSpan = cell->GetColSpan(); for( int row = cell->GetRow(); row < cell->GetRow() + rowSpan; ++row ) { for( int col = cell->GetColumn(); col < cell->GetColumn() + colSpan; ++col ) { T_TABLECELL* target = table->GetCell( row, col ); commit.Modify( target, getScreen() ); target->SetColSpan( 1 ); target->SetRowSpan( 1 ); VECTOR2I extents( table->GetColWidth( col ), table->GetRowHeight( row ) ); target->SetEnd( target->GetStart() + extents ); } } } } table->Normalize(); commit.Push( _( "Unmerge Cells" ) ); getToolMgr()->PostEvent( EVENTS::SelectedEvent ); return 0; } virtual TOOL_MANAGER* getToolMgr() = 0; virtual BASE_SCREEN* getScreen() = 0; virtual const SELECTION& getTableCellSelection() = 0; virtual void clearSelection() = 0; virtual T_TABLECELL* copyCell( T_TABLECELL* aSource ) = 0; }; #endif //EDIT_TABLE_TOOL_BASE_H