556 lines
17 KiB
C++
556 lines
17 KiB
C++
/*
|
|
* 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 <tool/tool_base.h>
|
|
#include <tool/selection.h>
|
|
#include <tool/actions.h>
|
|
#include <wx/translation.h>
|
|
|
|
class BASE_SCREEN;
|
|
|
|
|
|
/**
|
|
* SCH_TABLE_EDIT_TOOL and PCB_TABLE_EDIT_TOOL share most of their algorithms, which are
|
|
* implemented here.
|
|
*/
|
|
|
|
|
|
template<typename T_TABLE, typename T_TABLECELL, typename T_COMMIT>
|
|
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<int>::max();
|
|
int colMax = 0;
|
|
int rowMin = std::numeric_limits<int>::max();
|
|
int rowMax = 0;
|
|
int selectedArea = 0;
|
|
|
|
for( EDA_ITEM* item : sel )
|
|
{
|
|
if( T_TABLECELL* cell = dynamic_cast<T_TABLECELL*>( 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<T_TABLECELL*>( 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 );
|
|
}
|
|
|
|
int doAddRowAbove( const TOOL_EVENT& aEvent )
|
|
{
|
|
const SELECTION& selection = getTableCellSelection();
|
|
T_TABLECELL* topmost = nullptr;
|
|
|
|
for( EDA_ITEM* item : selection )
|
|
{
|
|
T_TABLECELL* cell = static_cast<T_TABLECELL*>( item );
|
|
|
|
if( !topmost || cell->GetRow() < topmost->GetRow() )
|
|
topmost = cell;
|
|
}
|
|
|
|
if( !topmost )
|
|
return 0;
|
|
|
|
int row = topmost->GetRow();
|
|
T_TABLE* table = static_cast<T_TABLE*>( topmost->GetParent() );
|
|
T_COMMIT commit( getToolMgr() );
|
|
|
|
// Make a copy of the source row before things start moving around
|
|
std::vector<T_TABLECELL*> 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->Normalize();
|
|
|
|
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<T_TABLECELL*>( item );
|
|
|
|
if( !bottommost || cell->GetRow() > bottommost->GetRow() )
|
|
bottommost = cell;
|
|
}
|
|
|
|
if( !bottommost )
|
|
return 0;
|
|
|
|
int row = bottommost->GetRow();
|
|
T_TABLE* table = static_cast<T_TABLE*>( bottommost->GetParent() );
|
|
T_COMMIT commit( getToolMgr() );
|
|
|
|
// Make a copy of the source row before things start moving around
|
|
std::vector<T_TABLECELL*> 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->Normalize();
|
|
|
|
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<T_TABLECELL*>( item );
|
|
|
|
if( !leftmost || cell->GetColumn() < leftmost->GetColumn() )
|
|
leftmost = cell;
|
|
}
|
|
|
|
if( !leftmost )
|
|
return 0;
|
|
|
|
int col = leftmost->GetColumn();
|
|
T_TABLE* table = static_cast<T_TABLE*>( leftmost->GetParent() );
|
|
int rowCount = table->GetRowCount();
|
|
T_COMMIT commit( getToolMgr() );
|
|
|
|
// Make a copy of the source column before things start moving around
|
|
std::vector<T_TABLECELL*> 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->Normalize();
|
|
|
|
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<T_TABLECELL*>( item );
|
|
|
|
if( !rightmost || cell->GetColumn() > rightmost->GetColumn() )
|
|
rightmost = cell;
|
|
}
|
|
|
|
if( !rightmost )
|
|
return 0;
|
|
|
|
int col = rightmost->GetColumn();
|
|
T_TABLE* table = static_cast<T_TABLE*>( rightmost->GetParent() );
|
|
int rowCount = table->GetRowCount();
|
|
T_COMMIT commit( getToolMgr() );
|
|
|
|
// Make a copy of the source column before things start moving around
|
|
std::vector<T_TABLECELL*> 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->Normalize();
|
|
|
|
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<T_TABLE*>( selection[0]->GetParent() );
|
|
std::vector<int> 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() );
|
|
|
|
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->Normalize();
|
|
}
|
|
|
|
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<T_TABLE*>( selection[0]->GetParent() );
|
|
std::vector<int> 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() );
|
|
|
|
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->Normalize();
|
|
}
|
|
|
|
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<int>::max();
|
|
int colMax = 0;
|
|
int rowMin = std::numeric_limits<int>::max();
|
|
int rowMax = 0;
|
|
|
|
T_COMMIT commit( getToolMgr() );
|
|
T_TABLE* table = static_cast<T_TABLE*>( sel[0]->GetParent() );
|
|
|
|
for( EDA_ITEM* item : sel )
|
|
{
|
|
if( T_TABLECELL* cell = dynamic_cast<T_TABLECELL*>( 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" ) );
|
|
|
|
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<T_TABLE*>( sel[0]->GetParent() );
|
|
|
|
for( EDA_ITEM* item : sel )
|
|
{
|
|
if( T_TABLECELL* cell = dynamic_cast<T_TABLECELL*>( 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" ) );
|
|
|
|
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
|