/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include SCHEMATIC_COMMIT::SCHEMATIC_COMMIT( TOOL_MANAGER* aToolMgr ) : m_toolMgr( aToolMgr ), m_isLibEditor( false ) { } SCHEMATIC_COMMIT::SCHEMATIC_COMMIT( EE_TOOL_BASE* 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 &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( m_toolMgr->GetToolHolder() ); LIB_SYMBOL* symbol = sym_frame->GetCurSymbol(); std::set savedModules; EE_SELECTION_TOOL* selTool = m_toolMgr->GetTool(); 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( 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( 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( m_toolMgr->GetToolHolder() ); EE_SELECTION_TOOL* selTool = m_toolMgr->GetTool(); 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( ent.m_item ); SCH_SCREEN* screen = static_cast( 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( 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( aItem->GetParent() ) ) return parentSymbol; if( LIB_SYMBOL* parentSymbol = dyn_cast( aItem->GetParent() ) ) return parentSymbol; if( m_isLibEditor ) return static_cast( m_toolMgr->GetToolHolder() )->GetCurSymbol(); return aItem; } EDA_ITEM* SCHEMATIC_COMMIT::makeImage( EDA_ITEM* aItem ) const { if( m_isLibEditor ) return new LIB_SYMBOL( *static_cast( 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( m_toolMgr->GetToolHolder() ); LIB_SYMBOL* sym = static_cast( 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( m_toolMgr->GetToolHolder() )->Schematic(); for( auto it = m_changes.rbegin(); it != m_changes.rend(); ++it ) { COMMIT_LINE& ent = *it; SCH_ITEM* item = static_cast( ent.m_item ); SCH_ITEM* copy = static_cast( ent.m_copy ); SCH_SCREEN* screen = static_cast( 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( item ); SCH_SYMBOL* symbol = static_cast( 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(); selTool->RebuildSelection(); clear(); }