7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2024-11-21 21:45:01 +00:00
kicad/include/tool/edit_table_tool_base.h
Jeff Young 7bdf1c7e0f Clear flags before depending on them.
(While at first it may look like any item with the flag set
is going to get deleted anyway, if an undo happens then we
end up with table cells with the STRUCT_DELETED flag already
set.)

Fixes https://gitlab.com/kicad/code/kicad/-/issues/17487
2024-03-20 17:55:58 +00:00

595 lines
19 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 );
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<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() );
VECTOR2I pos = table->GetPosition();
// 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->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<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() );
VECTOR2I pos = table->GetPosition();
// 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->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<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() );
VECTOR2I pos = table->GetPosition();
// 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->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<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() );
VECTOR2I pos = table->GetPosition();
// 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->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<T_TABLE*>( selection[0]->GetParent() );
std::vector<int> 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<T_TABLE*>( selection[0]->GetParent() );
std::vector<int> 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<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" ) );
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<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" ) );
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