kicad-source/eeschema/schematic_commit.cpp
Seth Hillbrand f4b43617e7 Add COMMIT structure to Schematic and Symbol editors
Provides a single-point access for modifying the schematic and symbol
elements that allows chaining updates and reverting partial changes.
Standardizes the undo hierarchy between pcb and schematic editors

As this is another layer on the existing undo/redo structure, the
initial commit does not replace all undo/redo calls currently existing.
These will be handled in a series of follow-on commits
2023-04-28 17:05:47 -07:00

485 lines
13 KiB
C++

/*
* 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 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
*/
#include <macros.h>
#include <tool/tool_manager.h>
#include <tools/ee_tool_base.h>
#include <lib_item.h>
#include <lib_pin.h>
#include <lib_shape.h>
#include <lib_symbol.h>
#include <lib_text.h>
#include <sch_screen.h>
#include <sch_sheet_path.h>
#include <schematic.h>
#include <view/view.h>
#include <schematic_commit.h>
#include <functional>
SCHEMATIC_COMMIT::SCHEMATIC_COMMIT( TOOL_MANAGER* aToolMgr ) :
m_toolMgr( aToolMgr ),
m_isLibEditor( false )
{
}
SCHEMATIC_COMMIT::SCHEMATIC_COMMIT( EE_TOOL_BASE<SCH_BASE_FRAME>* aTool )
{
m_toolMgr = aTool->GetManager();
m_isLibEditor = aTool->IsSymbolEditor();
}
SCHEMATIC_COMMIT::SCHEMATIC_COMMIT( EDA_DRAW_FRAME* aFrame )
{
m_toolMgr = aFrame->GetToolManager();
m_isLibEditor = aFrame->IsType( FRAME_SCH_SYMBOL_EDITOR );
}
SCHEMATIC_COMMIT::~SCHEMATIC_COMMIT()
{
}
COMMIT& SCHEMATIC_COMMIT::Stage( EDA_ITEM *aItem, CHANGE_TYPE aChangeType, BASE_SCREEN *aScreen )
{
wxCHECK( aItem, *this );
aItem->ClearFlags( IS_MODIFIED_CHILD );
// If aItem belongs a symbol, the full symbol will be saved because undo/redo does
// not handle "sub items" modifications.
if( aItem->GetParent() && aItem->GetParent()->IsType( { SCH_SYMBOL_T, LIB_SYMBOL_T } ) )
{
aItem->SetFlags( IS_MODIFIED_CHILD );
aItem = aItem->GetParent();
}
return COMMIT::Stage( aItem, aChangeType, aScreen );
}
COMMIT& SCHEMATIC_COMMIT::Stage( std::vector<EDA_ITEM*> &container, CHANGE_TYPE aChangeType,
BASE_SCREEN *aScreen )
{
for( EDA_ITEM* item : container )
Stage( item, aChangeType, aScreen );
return *this;
}
COMMIT& SCHEMATIC_COMMIT::Stage( const PICKED_ITEMS_LIST &aItems, UNDO_REDO aModFlag,
BASE_SCREEN *aScreen )
{
return COMMIT::Stage( aItems, aModFlag, aScreen );
}
void SCHEMATIC_COMMIT::pushLibEdit( const wxString& aMessage, int aCommitFlags )
{
// Objects potentially interested in changes:
PICKED_ITEMS_LIST undoList;
KIGFX::VIEW* view = m_toolMgr->GetView();
SYMBOL_EDIT_FRAME* sym_frame = static_cast<SYMBOL_EDIT_FRAME*>( m_toolMgr->GetToolHolder() );
LIB_SYMBOL* symbol = sym_frame->GetCurSymbol();
std::set<EDA_ITEM*> savedModules;
EE_SELECTION_TOOL* selTool = m_toolMgr->GetTool<EE_SELECTION_TOOL>();
bool itemsDeselected = false;
bool selectedModified = false;
if( Empty() )
return;
for( COMMIT_LINE& ent : m_changes )
{
int changeType = ent.m_type & CHT_TYPE;
int changeFlags = ent.m_type & CHT_FLAGS;
LIB_ITEM* libItem = static_cast<LIB_ITEM*>( ent.m_item );
wxASSERT( ent.m_item );
// Module items need to be saved in the undo buffer before modification
if( ent.m_item->Type() != LIB_SYMBOL_T )
{
ent.m_item = ent.m_item->GetParent();
wxASSERT( ent.m_item );
}
// We have not saved the symbol yet, so let's create an entry
if( savedModules.count( ent.m_item ) == 0 )
{
if( !( aCommitFlags & SKIP_UNDO ) && sym_frame )
sym_frame->SaveCopyInUndoList( ent.m_item, UNDO_REDO::CHANGED, aCommitFlags & APPEND_UNDO );
savedModules.insert( ent.m_item );
}
if( ent.m_item->IsSelected() )
selectedModified = true;
symbol->RunOnChildren(
[&selectedModified]( LIB_ITEM* aItem )
{
if( aItem->HasFlag( IS_MODIFIED_CHILD ) )
selectedModified = true;
} );
switch( changeType )
{
case CHT_ADD:
{
wxASSERT( libItem->Type() != LIB_SYMBOL_T );
libItem->SetParent( symbol );
if( !( changeFlags & CHT_DONE ) )
symbol->AddDrawItem( libItem );
if( view )
view->Add( libItem );
break;
}
case CHT_REMOVE:
{
if( libItem->IsSelected() )
{
if( selTool )
selTool->RemoveItemFromSel( libItem, true /* quiet mode */ );
itemsDeselected = true;
}
// Avoid removing mandatory fields
if( libItem->Type() == LIB_FIELD_T && static_cast<LIB_FIELD*>( libItem )->IsMandatory() )
break;
if( view )
view->Remove( libItem );
if( !( changeFlags & CHT_DONE ) )
symbol->RemoveDrawItem( libItem );
break;
}
case CHT_MODIFY:
{
if( view )
{
view->Update( libItem );
symbol->RunOnChildren(
[&]( LIB_ITEM* aChild )
{
view->Update( aChild );
});
}
// if no undo entry is needed, the copy would create a memory leak
if( aCommitFlags & SKIP_UNDO )
delete ent.m_copy;
break;
}
default:
wxASSERT( false );
break;
}
}
m_toolMgr->PostEvent( { TC_MESSAGE, TA_MODEL_CHANGE, AS_GLOBAL } );
if( itemsDeselected )
m_toolMgr->PostEvent( EVENTS::UnselectedEvent );
if( selectedModified )
m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
if( sym_frame && !( aCommitFlags & SKIP_SET_DIRTY ) )
sym_frame->OnModify();
clear();
}
void SCHEMATIC_COMMIT::pushSchEdit( const wxString& aMessage, int aCommitFlags )
{
// Objects potentially interested in changes:
PICKED_ITEMS_LIST undoList;
KIGFX::VIEW* view = m_toolMgr->GetView();
SCH_EDIT_FRAME* frame = static_cast<SCH_EDIT_FRAME*>( m_toolMgr->GetToolHolder() );
EE_SELECTION_TOOL* selTool = m_toolMgr->GetTool<EE_SELECTION_TOOL>();
bool itemsDeselected = false;
bool selectedModified = false;
if( Empty() )
return;
for( COMMIT_LINE& ent : m_changes )
{
int changeType = ent.m_type & CHT_TYPE;
int changeFlags = ent.m_type & CHT_FLAGS;
SCH_ITEM* schItem = static_cast<SCH_ITEM*>( ent.m_item );
SCH_SCREEN* screen = static_cast<SCH_SCREEN*>( ent.m_screen );
wxASSERT( ent.m_item );
if( ent.m_item->IsSelected() )
selectedModified = true;
switch( changeType )
{
case CHT_ADD:
{
if( !schItem->GetParent() )
{
if( !( aCommitFlags & SKIP_UNDO ) )
undoList.PushItem( ITEM_PICKER( screen, schItem, UNDO_REDO::NEWITEM ) );
if( !( changeFlags & CHT_DONE ) )
frame->GetScreen()->Append( schItem );
}
if( view )
view->Add( schItem );
break;
}
case CHT_REMOVE:
{
if( !( aCommitFlags & SKIP_UNDO ) )
undoList.PushItem( ITEM_PICKER( screen, schItem, UNDO_REDO::DELETED ) );
if( schItem->IsSelected() )
{
if( selTool )
selTool->RemoveItemFromSel( schItem, true /* quiet mode */ );
itemsDeselected = true;
}
if( schItem->Type() == SCH_FIELD_T )
{
static_cast<SCH_FIELD*>( schItem )->SetVisible( false );
break;
}
if( view )
view->Remove( schItem );
if( !( changeFlags & CHT_DONE ) )
frame->GetScreen()->Remove( schItem );
break;
}
case CHT_MODIFY:
{
if( !( aCommitFlags & SKIP_UNDO ) )
{
ITEM_PICKER itemWrapper( screen, schItem, UNDO_REDO::CHANGED );
wxASSERT( ent.m_copy );
itemWrapper.SetLink( ent.m_copy );
undoList.PushItem( itemWrapper );
}
if( view )
view->Update( schItem );
// if no undo entry is needed, the copy would create a memory leak
if( aCommitFlags & SKIP_UNDO )
delete ent.m_copy;
break;
}
default:
wxASSERT( false );
break;
}
}
if( !( aCommitFlags & SKIP_UNDO ) && frame )
frame->SaveCopyInUndoList(undoList, UNDO_REDO::UNSPECIFIED, aCommitFlags & APPEND_UNDO );
m_toolMgr->PostEvent( { TC_MESSAGE, TA_MODEL_CHANGE, AS_GLOBAL } );
if( itemsDeselected )
m_toolMgr->PostEvent( EVENTS::UnselectedEvent );
if( selectedModified )
m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
if( frame && !( aCommitFlags & SKIP_SET_DIRTY ) )
frame->OnModify();
clear();
}
void SCHEMATIC_COMMIT::Push( const wxString& aMessage, int aCommitFlags )
{
if( m_isLibEditor )
pushLibEdit( aMessage, aCommitFlags );
else
pushSchEdit( aMessage, aCommitFlags );
}
EDA_ITEM* SCHEMATIC_COMMIT::parentObject( EDA_ITEM* aItem ) const
{
if( SCH_SYMBOL* parentSymbol = dyn_cast<SCH_SYMBOL*>( aItem->GetParent() ) )
return parentSymbol;
if( LIB_SYMBOL* parentSymbol = dyn_cast<LIB_SYMBOL*>( aItem->GetParent() ) )
return parentSymbol;
if( m_isLibEditor )
return static_cast<SYMBOL_EDIT_FRAME*>( m_toolMgr->GetToolHolder() )->GetCurSymbol();
return aItem;
}
EDA_ITEM* SCHEMATIC_COMMIT::makeImage( EDA_ITEM* aItem ) const
{
if( m_isLibEditor )
return new LIB_SYMBOL(
*static_cast<SYMBOL_EDIT_FRAME*>( m_toolMgr->GetToolHolder() )->GetCurSymbol() );
return aItem->Clone();
}
void SCHEMATIC_COMMIT::revertLibEdit()
{
// The first element in the commit is the original, and libedit
// just saves copies of the whole symbol, so grab the original and discard the rest
SYMBOL_EDIT_FRAME* sym_frame = static_cast<SYMBOL_EDIT_FRAME*>( m_toolMgr->GetToolHolder() );
LIB_SYMBOL* sym = static_cast<LIB_SYMBOL*>( m_changes.front().m_item );
sym_frame->SetCurSymbol( sym, false );
for( size_t ii = 1; ii < m_changes.size(); ++ii )
delete m_changes[ii].m_item;
clear();
}
void SCHEMATIC_COMMIT::Revert()
{
PICKED_ITEMS_LIST undoList;
KIGFX::VIEW* view = m_toolMgr->GetView();
if( m_changes.empty() )
return;
if( m_isLibEditor )
{
revertLibEdit();
return;
}
SCHEMATIC& schematic = static_cast<SCH_EDIT_FRAME*>( m_toolMgr->GetToolHolder() )->Schematic();
for( auto it = m_changes.rbegin(); it != m_changes.rend(); ++it )
{
COMMIT_LINE& ent = *it;
SCH_ITEM* item = static_cast<SCH_ITEM*>( ent.m_item );
SCH_ITEM* copy = static_cast<SCH_ITEM*>( ent.m_copy );
SCH_SCREEN* screen = static_cast<SCH_SCREEN*>( ent.m_screen );
int changeType = ent.m_type & CHT_TYPE;
int changeFlags = ent.m_type & CHT_FLAGS;
switch( changeType )
{
case CHT_ADD:
if( !( changeFlags & CHT_DONE ) )
break;
view->Remove( item );
screen->Remove( item );
break;
case CHT_REMOVE:
if( !( changeFlags & CHT_DONE ) )
break;
view->Add( item );
screen->Append( item );
break;
case CHT_MODIFY:
{
view->Remove( item );
item->SwapData( copy );
// Special cases for items which have instance data
if( item->GetParent() && item->GetParent()->Type() == SCH_SYMBOL_T
&& item->Type() == SCH_FIELD_T )
{
SCH_FIELD* field = static_cast<SCH_FIELD*>( item );
SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item->GetParent() );
if( field->GetId() == REFERENCE_FIELD )
{
symbol->SetRef( schematic.GetSheets().FindSheetForScreen( screen ),
field->GetText() );
}
}
view->Add( item );
delete copy;
break;
}
default:
wxASSERT( false );
break;
}
}
EE_SELECTION_TOOL* selTool = m_toolMgr->GetTool<EE_SELECTION_TOOL>();
selTool->RebuildSelection();
clear();
}