You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							618 lines
						
					
					
						
							17 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							618 lines
						
					
					
						
							17 KiB
						
					
					
				| /* | |
|  * 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 | |
|  */ | |
| 
 | |
| #include <macros.h> | |
| #include <tool/tool_manager.h> | |
| #include <tools/ee_tool_base.h> | |
|  | |
| #include <lib_item.h> | |
| #include <lib_symbol.h> | |
|  | |
| #include <sch_screen.h> | |
| #include <schematic.h> | |
|  | |
| #include <view/view.h> | |
| #include <sch_commit.h> | |
| #include <connection_graph.h> | |
|  | |
| #include <functional> | |
|  | |
| 
 | |
| SCH_COMMIT::SCH_COMMIT( TOOL_MANAGER* aToolMgr ) : | |
|         m_toolMgr( aToolMgr ), | |
|         m_isLibEditor( false ) | |
| { | |
|     SCH_BASE_FRAME* frame = static_cast<SCH_BASE_FRAME*>( m_toolMgr->GetToolHolder() ); | |
|     m_isLibEditor = frame && frame->IsType( FRAME_SCH_SYMBOL_EDITOR ); | |
| } | |
| 
 | |
| 
 | |
| SCH_COMMIT::SCH_COMMIT( EE_TOOL_BASE<SCH_BASE_FRAME>* aTool ) | |
| { | |
|     m_toolMgr = aTool->GetManager(); | |
|     m_isLibEditor = aTool->IsSymbolEditor(); | |
| } | |
| 
 | |
| 
 | |
| SCH_COMMIT::SCH_COMMIT( EDA_DRAW_FRAME* aFrame ) | |
| { | |
|     m_toolMgr = aFrame->GetToolManager(); | |
|     m_isLibEditor = aFrame->IsType( FRAME_SCH_SYMBOL_EDITOR ); | |
| } | |
| 
 | |
| 
 | |
| SCH_COMMIT::~SCH_COMMIT() | |
| { | |
| } | |
| 
 | |
| 
 | |
| COMMIT& SCH_COMMIT::Stage( EDA_ITEM *aItem, CHANGE_TYPE aChangeType, BASE_SCREEN *aScreen ) | |
| { | |
|     wxCHECK( aItem, *this ); | |
| 
 | |
|     // If aItem belongs a symbol, sheet or label, the full parent will be saved because undo/redo | |
|     // does not handle "sub items" modifications. | |
|     if( aItem->Type() != SCH_SHEET_T | |
|             && aItem->GetParent() && aItem->GetParent()->IsType( { SCH_SYMBOL_T, LIB_SYMBOL_T, | |
|                                                                    SCH_SHEET_T, | |
|                                                                    SCH_LABEL_LOCATE_ANY_T } ) ) | |
|     { | |
|         aItem = aItem->GetParent(); | |
|         aChangeType = CHT_MODIFY; | |
|     } | |
| 
 | |
|     // IS_SELECTED flag should not be set on undo items which were added for | |
|     // a drag operation. | |
|     if( aItem->IsSelected() && aItem->HasFlag( SELECTED_BY_DRAG ) ) | |
|     { | |
|         aItem->ClearSelected(); | |
|         COMMIT::Stage( aItem, aChangeType, aScreen ); | |
|         aItem->SetSelected(); | |
|     } | |
|     else | |
|     { | |
|         COMMIT::Stage( aItem, aChangeType, aScreen ); | |
|     } | |
| 
 | |
|     return *this; | |
| } | |
| 
 | |
| 
 | |
| COMMIT& SCH_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& SCH_COMMIT::Stage( const PICKED_ITEMS_LIST &aItems, UNDO_REDO aModFlag, | |
|                            BASE_SCREEN *aScreen ) | |
| { | |
|     return COMMIT::Stage( aItems, aModFlag, aScreen ); | |
| } | |
| 
 | |
| 
 | |
| void SCH_COMMIT::pushLibEdit( const wxString& aMessage, int aCommitFlags ) | |
| { | |
|     KIGFX::VIEW*       view = m_toolMgr->GetView(); | |
|     SYMBOL_EDIT_FRAME* frame = static_cast<SYMBOL_EDIT_FRAME*>( m_toolMgr->GetToolHolder() ); | |
| 
 | |
|     if( Empty() ) | |
|         return; | |
| 
 | |
|     // Symbol editor just saves copies of the whole symbol, so grab the first and discard the rest | |
|     LIB_SYMBOL* symbol = dynamic_cast<LIB_SYMBOL*>( m_changes.front().m_item ); | |
|     LIB_SYMBOL* copy = dynamic_cast<LIB_SYMBOL*>( m_changes.front().m_copy ); | |
| 
 | |
|     if( symbol ) | |
|     { | |
|         if( view ) | |
|         { | |
|             view->Update( symbol ); | |
| 
 | |
|             symbol->RunOnChildren( | |
|                     [&]( LIB_ITEM* aChild ) | |
|                     { | |
|                         view->Update( aChild ); | |
|                     }); | |
|         } | |
| 
 | |
|         if( !( aCommitFlags & SKIP_UNDO ) ) | |
|         { | |
|             if( frame && copy ) | |
|             { | |
|                 frame->PushSymbolToUndoList( aMessage, copy ); | |
|                 copy = nullptr;   // we've transferred ownership to the undo stack | |
|             } | |
|         } | |
| 
 | |
|         if( copy ) | |
|         { | |
|             // if no undo entry was needed, the copy would create a memory leak | |
|             delete copy; | |
|             copy = nullptr; | |
|         } | |
|     } | |
| 
 | |
|     m_toolMgr->PostEvent( { TC_MESSAGE, TA_MODEL_CHANGE, AS_GLOBAL } ); | |
|     m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified ); | |
| 
 | |
|     if( !( aCommitFlags & SKIP_SET_DIRTY ) ) | |
|     { | |
|         if( frame ) | |
|             frame->OnModify(); | |
|     } | |
| 
 | |
|     for( size_t ii = 1; ii < m_changes.size(); ++ii ) | |
|         delete m_changes[ii].m_copy; | |
| 
 | |
|     clear(); | |
| } | |
| 
 | |
| 
 | |
| void SCH_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; | |
|     bool                dirtyConnectivity = false; | |
|     SCH_CLEANUP_FLAGS   connectivityCleanUp = NO_CLEANUP; | |
| 
 | |
|     if( Empty() ) | |
|         return; | |
| 
 | |
|     undoList.SetDescription( aMessage ); | |
| 
 | |
|     SCHEMATIC*             schematic = nullptr; | |
|     std::vector<SCH_ITEM*> bulkAddedItems; | |
|     std::vector<SCH_ITEM*> bulkRemovedItems; | |
|     std::vector<SCH_ITEM*> itemsChanged; | |
| 
 | |
|     for( COMMIT_LINE& ent : m_changes ) | |
|     { | |
|         int         changeType = ent.m_type & CHT_TYPE; | |
|         int         changeFlags = ent.m_type & CHT_FLAGS; | |
|         SCH_ITEM*   schItem = dynamic_cast<SCH_ITEM*>( ent.m_item ); | |
|         SCH_SCREEN* screen = dynamic_cast<SCH_SCREEN*>( ent.m_screen ); | |
| 
 | |
|         wxCHECK2( schItem && screen, continue ); | |
| 
 | |
|         if( !schematic ) | |
|             schematic = schItem->Schematic(); | |
| 
 | |
|         if( schItem->IsSelected() ) | |
|             selectedModified = true; | |
| 
 | |
|         auto updateConnectivityFlag = | |
|                 [&]() | |
|                 { | |
|                     if( schItem->IsConnectable() ) | |
|                     { | |
|                         dirtyConnectivity = true; | |
| 
 | |
|                         // Do a local clean up if there are any connectable objects in the commit. | |
|                         if( connectivityCleanUp == NO_CLEANUP ) | |
|                             connectivityCleanUp = LOCAL_CLEANUP; | |
| 
 | |
|                         // Do a full rebauild of the connectivity if there is a sheet in the commit. | |
|                         if( schItem->Type() == SCH_SHEET_T ) | |
|                             connectivityCleanUp = GLOBAL_CLEANUP; | |
|                     } | |
|                 }; | |
| 
 | |
|         switch( changeType ) | |
|         { | |
|         case CHT_ADD: | |
|         { | |
|             updateConnectivityFlag(); | |
| 
 | |
|             if( !( aCommitFlags & SKIP_UNDO ) ) | |
|                 undoList.PushItem( ITEM_PICKER( screen, schItem, UNDO_REDO::NEWITEM ) ); | |
| 
 | |
|             if( !( changeFlags & CHT_DONE ) ) | |
|             { | |
|                 if( !screen->CheckIfOnDrawList( schItem ) )  // don't want a loop! | |
|                     screen->Append( schItem ); | |
| 
 | |
|                 if( view ) | |
|                     view->Add( schItem ); | |
|             } | |
| 
 | |
|             if( frame ) | |
|                 frame->UpdateItem( schItem, true, true ); | |
| 
 | |
|             bulkAddedItems.push_back( schItem ); | |
| 
 | |
|             break; | |
|         } | |
| 
 | |
|         case CHT_REMOVE: | |
|         { | |
|             updateConnectivityFlag(); | |
| 
 | |
|             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( !( changeFlags & CHT_DONE ) ) | |
|             { | |
|                 screen->Remove( schItem ); | |
| 
 | |
|                 if( view ) | |
|                     view->Remove( schItem ); | |
|             } | |
| 
 | |
|             if( frame ) | |
|                 frame->UpdateItem( schItem, true, true ); | |
| 
 | |
|             bulkRemovedItems.push_back( 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 ); | |
| 
 | |
|                 const SCH_ITEM* itemCopy = static_cast<const SCH_ITEM*>( ent.m_copy ); | |
| 
 | |
|                 wxCHECK2( itemCopy, continue ); | |
| 
 | |
|                 SCH_SHEET_PATH currentSheet; | |
| 
 | |
|                 if( frame ) | |
|                     currentSheet = frame->GetCurrentSheet(); | |
| 
 | |
|                 if( itemCopy->HasConnectivityChanges( schItem, ¤tSheet ) ) | |
|                     updateConnectivityFlag(); | |
| 
 | |
|                 undoList.PushItem( itemWrapper ); | |
|                 ent.m_copy = nullptr;   // We've transferred ownership to the undo list | |
|             } | |
| 
 | |
|             if( frame ) | |
|                 frame->UpdateItem( schItem, false, true ); | |
| 
 | |
|             itemsChanged.push_back( schItem ); | |
| 
 | |
|             if( ent.m_copy ) | |
|             { | |
|                 // if no undo entry is needed, the copy would create a memory leak | |
|                 delete ent.m_copy; | |
|                 ent.m_copy = nullptr; | |
|             } | |
| 
 | |
|             break; | |
|         } | |
| 
 | |
|         default: | |
|             wxASSERT( false ); | |
|             break; | |
|         } | |
| 
 | |
|         // Clear all flags but SELECTED and others used to move and rotate commands, | |
|         // after edition (selected items must keep their selection flag). | |
|         const int selected_mask = ( SELECTED | STARTPOINT | ENDPOINT ); | |
|         schItem->ClearFlags( EDA_ITEM_ALL_FLAGS - selected_mask ); | |
|     } | |
| 
 | |
|     if( schematic ) | |
|     { | |
|         if( bulkAddedItems.size() > 0 ) | |
|             schematic->OnItemsAdded( bulkAddedItems ); | |
| 
 | |
|         if( bulkRemovedItems.size() > 0 ) | |
|             schematic->OnItemsRemoved( bulkRemovedItems ); | |
| 
 | |
|         if( itemsChanged.size() > 0 ) | |
|             schematic->OnItemsChanged( itemsChanged ); | |
|     } | |
| 
 | |
|     if( !( aCommitFlags & SKIP_UNDO ) ) | |
|     { | |
|         if( frame ) | |
|         { | |
|             frame->SaveCopyInUndoList( undoList, UNDO_REDO::UNSPECIFIED, false, dirtyConnectivity ); | |
| 
 | |
|             if( dirtyConnectivity ) | |
|             { | |
|                 wxLogTrace( wxS( "CONN_PROFILE" ), | |
|                             wxS( "SCH_COMMIT::pushSchEdit() %s clean up connectivity rebuild." ), | |
|                             ( connectivityCleanUp == LOCAL_CLEANUP ) ? wxS( "local" ) : wxS( "global" ) ); | |
|                 frame->RecalculateConnections( this, connectivityCleanUp ); | |
|             } | |
|         } | |
|     } | |
| 
 | |
|     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 && frame->GetCanvas() ) | |
|         frame->GetCanvas()->Refresh(); | |
| 
 | |
|     if( !( aCommitFlags & SKIP_SET_DIRTY ) ) | |
|     { | |
|         if( frame ) | |
|             frame->OnModify(); | |
|     } | |
| 
 | |
|     clear(); | |
| } | |
| 
 | |
| 
 | |
| void SCH_COMMIT::Push( const wxString& aMessage, int aCommitFlags ) | |
| { | |
|     if( m_isLibEditor ) | |
|         pushLibEdit( aMessage, aCommitFlags ); | |
|     else | |
|         pushSchEdit( aMessage, aCommitFlags ); | |
| } | |
| 
 | |
| 
 | |
| EDA_ITEM* SCH_COMMIT::parentObject( EDA_ITEM* aItem ) const | |
| { | |
|     EDA_ITEM* parent = aItem->GetParent(); | |
| 
 | |
|     if( parent && parent->Type() == SCH_SYMBOL_T ) | |
|         return parent; | |
| 
 | |
|     if( parent && parent->Type() == LIB_SYMBOL_T ) | |
|         return parent; | |
| 
 | |
|     if( m_isLibEditor ) | |
|         return static_cast<SYMBOL_EDIT_FRAME*>( m_toolMgr->GetToolHolder() )->GetCurSymbol(); | |
| 
 | |
|     return aItem; | |
| } | |
| 
 | |
| 
 | |
| EDA_ITEM* SCH_COMMIT::makeImage( EDA_ITEM* aItem ) const | |
| { | |
|     if( m_isLibEditor ) | |
|     { | |
|         SYMBOL_EDIT_FRAME* frame = static_cast<SYMBOL_EDIT_FRAME*>( m_toolMgr->GetToolHolder() ); | |
|         LIB_SYMBOL*        symbol = frame->GetCurSymbol(); | |
|         std::vector<KIID>  selected; | |
| 
 | |
|         for( const LIB_ITEM& item : symbol->GetDrawItems() ) | |
|         { | |
|             if( item.IsSelected() ) | |
|                 selected.push_back( item.m_Uuid ); | |
|         } | |
| 
 | |
|         symbol = new LIB_SYMBOL( *symbol ); | |
| 
 | |
|         for( LIB_ITEM& item : symbol->GetDrawItems() ) | |
|         { | |
|             if( alg::contains( selected, item.m_Uuid ) ) | |
|                 item.SetSelected(); | |
|         } | |
| 
 | |
|         return symbol; | |
|     } | |
| 
 | |
|     return aItem->Clone(); | |
| } | |
| 
 | |
| 
 | |
| void SCH_COMMIT::revertLibEdit() | |
| { | |
|     if( Empty() ) | |
|         return; | |
| 
 | |
|     // Symbol editor just saves copies of the whole symbol, so grab the first and discard the rest | |
|     SYMBOL_EDIT_FRAME* frame = dynamic_cast<SYMBOL_EDIT_FRAME*>( m_toolMgr->GetToolHolder() ); | |
|     LIB_SYMBOL*        copy = dynamic_cast<LIB_SYMBOL*>( m_changes.front().m_copy ); | |
|     EE_SELECTION_TOOL* selTool = m_toolMgr->GetTool<EE_SELECTION_TOOL>(); | |
| 
 | |
|     if( frame && copy ) | |
|     { | |
|         frame->SetCurSymbol( copy, false ); | |
|         m_toolMgr->ResetTools( TOOL_BASE::MODEL_RELOAD ); | |
|     } | |
| 
 | |
|     for( size_t ii = 1; ii < m_changes.size(); ++ii ) | |
|         delete m_changes[ii].m_copy; | |
| 
 | |
|     if( selTool ) | |
|         selTool->RebuildSelection(); | |
| 
 | |
|     clear(); | |
| } | |
| 
 | |
| 
 | |
| void SCH_COMMIT::Revert() | |
| { | |
|     KIGFX::VIEW*       view = m_toolMgr->GetView(); | |
|     SCH_EDIT_FRAME*    frame = dynamic_cast<SCH_EDIT_FRAME*>( m_toolMgr->GetToolHolder() ); | |
|     EE_SELECTION_TOOL* selTool = m_toolMgr->GetTool<EE_SELECTION_TOOL>(); | |
| 
 | |
|     if( m_changes.empty() ) | |
|         return; | |
| 
 | |
|     if( m_isLibEditor ) | |
|     { | |
|         revertLibEdit(); | |
|         return; | |
|     } | |
| 
 | |
|     SCHEMATIC*             schematic = nullptr; | |
|     std::vector<SCH_ITEM*> bulkAddedItems; | |
|     std::vector<SCH_ITEM*> bulkRemovedItems; | |
|     std::vector<SCH_ITEM*> itemsChanged; | |
| 
 | |
|     for( COMMIT_LINE& ent : m_changes ) | |
|     { | |
|         int         changeType = ent.m_type & CHT_TYPE; | |
|         int         changeFlags = ent.m_type & CHT_FLAGS; | |
|         SCH_ITEM*   item = dynamic_cast<SCH_ITEM*>( ent.m_item ); | |
|         SCH_ITEM*   copy = dynamic_cast<SCH_ITEM*>( ent.m_copy ); | |
|         SCH_SCREEN* screen = dynamic_cast<SCH_SCREEN*>( ent.m_screen ); | |
| 
 | |
|         wxCHECK2( item && screen, continue ); | |
| 
 | |
|         if( !schematic ) | |
|             schematic = item->Schematic(); | |
| 
 | |
|         switch( changeType ) | |
|         { | |
|         case CHT_ADD: | |
|             if( !( changeFlags & CHT_DONE ) ) | |
|                 break; | |
| 
 | |
|             if( view ) | |
|                 view->Remove( item ); | |
| 
 | |
|             screen->Remove( item ); | |
|             bulkRemovedItems.push_back( item ); | |
|             break; | |
| 
 | |
|         case CHT_REMOVE: | |
|             item->SetConnectivityDirty(); | |
| 
 | |
|             if( !( changeFlags & CHT_DONE ) ) | |
|                 break; | |
| 
 | |
|             if( view ) | |
|                 view->Add( item ); | |
| 
 | |
|             screen->Append( item ); | |
|             bulkAddedItems.push_back( item ); | |
|             break; | |
| 
 | |
|         case CHT_MODIFY: | |
|         { | |
|             if( view ) | |
|                 view->Remove( item ); | |
| 
 | |
|             bool unselect = !item->IsSelected(); | |
| 
 | |
|             item->SwapData( copy ); | |
| 
 | |
|             if( unselect ) | |
|             { | |
|                 item->ClearSelected(); | |
|                 item->RunOnChildren( []( SCH_ITEM* aChild ) { aChild->ClearSelected(); } ); | |
|             } | |
| 
 | |
|             // 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 ) | |
|                 { | |
|                     SCH_SHEET_PATH sheet = schematic->GetSheets().FindSheetForScreen( screen ); | |
|                     symbol->SetRef( &sheet, field->GetText() ); | |
|                 } | |
|             } | |
| 
 | |
|             // This must be called before any calls that require stable object pointers. | |
|             screen->Update( item ); | |
| 
 | |
|             // This hack is to prevent incorrectly parented symbol pins from breaking the | |
|             // connectivity algorithm. | |
|             if( item->Type() == SCH_SYMBOL_T ) | |
|             { | |
|                 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item ); | |
|                 symbol->UpdatePins(); | |
| 
 | |
|                 CONNECTION_GRAPH* graph = schematic->ConnectionGraph(); | |
| 
 | |
|                 SCH_SYMBOL* symbolCopy = static_cast<SCH_SYMBOL*>( copy ); | |
|                 graph->RemoveItem( symbolCopy ); | |
| 
 | |
|                 for( SCH_PIN* pin : symbolCopy->GetPins() ) | |
|                     graph->RemoveItem( pin ); | |
|             } | |
| 
 | |
|             item->SetConnectivityDirty(); | |
| 
 | |
|             if( view ) | |
|                 view->Add( item ); | |
| 
 | |
|             delete copy; | |
|             break; | |
|         } | |
| 
 | |
|         default: | |
|             wxASSERT( false ); | |
|             break; | |
|         } | |
|     } | |
| 
 | |
|     if( schematic ) | |
|     { | |
|         if( bulkAddedItems.size() > 0 ) | |
|             schematic->OnItemsAdded( bulkAddedItems ); | |
| 
 | |
|         if( bulkRemovedItems.size() > 0 ) | |
|             schematic->OnItemsRemoved( bulkRemovedItems ); | |
| 
 | |
|         if( itemsChanged.size() > 0 ) | |
|             schematic->OnItemsChanged( itemsChanged ); | |
|     } | |
| 
 | |
|     if( selTool ) | |
|         selTool->RebuildSelection(); | |
| 
 | |
|     if( frame ) | |
|     { | |
|         frame->RecalculateConnections( nullptr, NO_CLEANUP ); | |
|     } | |
| 
 | |
|     clear(); | |
| } | |
| 
 |