1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2016 CERN
5  * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, you may find one here:
19  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20  * or you may search the http://www.gnu.org website for the version 2 license,
21  * or you may write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
23  */
24 
25 #include <board.h>
26 #include <footprint.h>
27 #include <pcb_group.h>
28 #include <tool/tool_manager.h>
29 #include <tools/pcb_selection_tool.h>
30 #include <view/view.h>
31 #include <board_commit.h>
32 #include <tools/pcb_tool_base.h>
33 #include <tools/pcb_actions.h>
34 #include <connectivity/connectivity_data.h>
35 
36 #include <functional>
37 using namespace std::placeholders;
38 
39 
BOARD_COMMIT(TOOL_MANAGER * aToolMgr)40 BOARD_COMMIT::BOARD_COMMIT( TOOL_MANAGER* aToolMgr ) :
41         m_toolMgr( aToolMgr ),
42         m_isFootprintEditor( false ),
43         m_resolveNetConflicts( false )
44 {
45 }
46 
47 
BOARD_COMMIT(PCB_TOOL_BASE * aTool)48 BOARD_COMMIT::BOARD_COMMIT( PCB_TOOL_BASE* aTool ) :
49         m_resolveNetConflicts( false )
50 {
51     m_toolMgr = aTool->GetManager();
52     m_isFootprintEditor = aTool->IsFootprintEditor();
53 }
54 
55 
BOARD_COMMIT(EDA_DRAW_FRAME * aFrame)56 BOARD_COMMIT::BOARD_COMMIT( EDA_DRAW_FRAME* aFrame ) :
57         m_resolveNetConflicts( false )
58 {
59     m_toolMgr = aFrame->GetToolManager();
60     m_isFootprintEditor = aFrame->IsType( FRAME_FOOTPRINT_EDITOR );
61 }
62 
63 
~BOARD_COMMIT()64 BOARD_COMMIT::~BOARD_COMMIT()
65 {
66 }
67 
68 
Stage(EDA_ITEM * aItem,CHANGE_TYPE aChangeType)69 COMMIT& BOARD_COMMIT::Stage( EDA_ITEM* aItem, CHANGE_TYPE aChangeType )
70 {
71     // if aItem belongs a footprint, the full footprint will be saved
72     // because undo/redo does not handle "sub items" modifications
73     if( aItem && aItem->Type() != PCB_FOOTPRINT_T && aChangeType == CHT_MODIFY )
74     {
75         EDA_ITEM* item = aItem->GetParent();
76 
77         if( item && item->Type() == PCB_FOOTPRINT_T )  // means aItem belongs a footprint
78             aItem = item;
79     }
80 
81     return COMMIT::Stage( aItem, aChangeType );
82 }
83 
84 
Stage(std::vector<EDA_ITEM * > & container,CHANGE_TYPE aChangeType)85 COMMIT& BOARD_COMMIT::Stage( std::vector<EDA_ITEM*>& container, CHANGE_TYPE aChangeType )
86 {
87     return COMMIT::Stage( container, aChangeType );
88 }
89 
90 
Stage(const PICKED_ITEMS_LIST & aItems,UNDO_REDO aModFlag)91 COMMIT& BOARD_COMMIT::Stage( const PICKED_ITEMS_LIST& aItems, UNDO_REDO aModFlag )
92 {
93     return COMMIT::Stage( aItems, aModFlag );
94 }
95 
96 
Push(const wxString & aMessage,bool aCreateUndoEntry,bool aSetDirtyBit)97 void BOARD_COMMIT::Push( const wxString& aMessage, bool aCreateUndoEntry, bool aSetDirtyBit )
98 {
99     // Objects potentially interested in changes:
100     PICKED_ITEMS_LIST   undoList;
101     KIGFX::VIEW*        view = m_toolMgr->GetView();
102     BOARD*              board = (BOARD*) m_toolMgr->GetModel();
103     PCB_BASE_FRAME*     frame = dynamic_cast<PCB_BASE_FRAME*>( m_toolMgr->GetToolHolder() );
104     auto                connectivity = board->GetConnectivity();
105     std::set<EDA_ITEM*> savedModules;
106     PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
107     bool                itemsDeselected = false;
108 
109     std::vector<BOARD_ITEM*> bulkAddedItems;
110     std::vector<BOARD_ITEM*> bulkRemovedItems;
111     std::vector<BOARD_ITEM*> itemsChanged;
112 
113     if( Empty() )
114         return;
115 
116     for( COMMIT_LINE& ent : m_changes )
117     {
118         int changeType = ent.m_type & CHT_TYPE;
119         int changeFlags = ent.m_type & CHT_FLAGS;
120         BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( ent.m_item );
121 
122         // Module items need to be saved in the undo buffer before modification
123         if( m_isFootprintEditor )
124         {
125             // Be sure that we are storing a footprint
126             if( ent.m_item->Type() != PCB_FOOTPRINT_T )
127                 ent.m_item = ent.m_item->GetParent();
128 
129             // We have not saved the footprint yet, so let's create an entry
130             if( savedModules.count( ent.m_item ) == 0 )
131             {
132                 if( !ent.m_copy )
133                 {
134                     wxASSERT( changeType != CHT_MODIFY );     // too late to make a copy..
135                     ent.m_copy = ent.m_item->Clone();
136                 }
137 
138                 wxASSERT( ent.m_item->Type() == PCB_FOOTPRINT_T );
139                 wxASSERT( ent.m_copy->Type() == PCB_FOOTPRINT_T );
140 
141                 if( aCreateUndoEntry && frame )
142                 {
143                     ITEM_PICKER itemWrapper( nullptr, ent.m_item, UNDO_REDO::CHANGED );
144                     itemWrapper.SetLink( ent.m_copy );
145                     undoList.PushItem( itemWrapper );
146                     frame->SaveCopyInUndoList( undoList, UNDO_REDO::CHANGED );
147                 }
148 
149                 savedModules.insert( ent.m_item );
150                 static_cast<FOOTPRINT*>( ent.m_item )->SetLastEditTime();
151             }
152         }
153 
154         switch( changeType )
155         {
156             case CHT_ADD:
157             {
158                 if( m_isFootprintEditor )
159                 {
160                     // footprints inside footprints are not supported yet
161                     wxASSERT( boardItem->Type() != PCB_FOOTPRINT_T );
162 
163                     boardItem->SetParent( board->Footprints().front() );
164 
165                     if( !( changeFlags & CHT_DONE ) )
166                         board->Footprints().front()->Add( boardItem );
167                 }
168                 else if( boardItem->Type() == PCB_PAD_T ||
169                          boardItem->Type() == PCB_FP_TEXT_T ||
170                          boardItem->Type() == PCB_FP_SHAPE_T ||
171                          boardItem->Type() == PCB_FP_ZONE_T )
172                 {
173                     wxASSERT( boardItem->GetParent() &&
174                               boardItem->GetParent()->Type() == PCB_FOOTPRINT_T );
175                 }
176                 else
177                 {
178                     if( aCreateUndoEntry )
179                         undoList.PushItem( ITEM_PICKER( nullptr, boardItem, UNDO_REDO::NEWITEM ) );
180 
181                     if( !( changeFlags & CHT_DONE ) )
182                     {
183                         board->Add( boardItem, ADD_MODE::BULK_INSERT ); // handles connectivity
184                         bulkAddedItems.push_back( boardItem );
185                     }
186                 }
187 
188                 if( view && boardItem->Type() != PCB_NETINFO_T )
189                     view->Add( boardItem );
190 
191                 break;
192             }
193 
194             case CHT_REMOVE:
195             {
196                 PCB_GROUP* parentGroup = boardItem->GetParentGroup();
197 
198                 if( !m_isFootprintEditor && aCreateUndoEntry )
199                     undoList.PushItem( ITEM_PICKER( nullptr, boardItem, UNDO_REDO::DELETED ) );
200 
201                 if( boardItem->IsSelected() )
202                 {
203                     selTool->RemoveItemFromSel( boardItem, true /* quiet mode */ );
204                     itemsDeselected = true;
205                 }
206 
207                 switch( boardItem->Type() )
208                 {
209                 // Footprint items
210                 case PCB_PAD_T:
211                 case PCB_FP_SHAPE_T:
212                 case PCB_FP_TEXT_T:
213                 case PCB_FP_ZONE_T:
214                     // This level can only handle footprint children in the footprint editor as
215                     // only in that case has the entire footprint (and all its children) already
216                     // been saved for undo.
217                     wxASSERT( m_isFootprintEditor );
218 
219                     if( boardItem->Type() == PCB_FP_TEXT_T )
220                     {
221                         FP_TEXT* text = static_cast<FP_TEXT*>( boardItem );
222 
223                         // don't allow deletion of Reference or Value
224                         if( text->GetType() != FP_TEXT::TEXT_is_DIVERS )
225                             break;
226                     }
227 
228                     if( parentGroup && !( parentGroup->GetFlags() & STRUCT_DELETED ) )
229                         parentGroup->RemoveItem( boardItem );
230 
231                     if( view )
232                         view->Remove( boardItem );
233 
234                     if( !( changeFlags & CHT_DONE ) )
235                     {
236                         FOOTPRINT* footprint = static_cast<FOOTPRINT*>( boardItem->GetParent() );
237                         wxASSERT( footprint && footprint->Type() == PCB_FOOTPRINT_T );
238                         footprint->Delete( boardItem );
239                     }
240 
241                     break;
242 
243                 // Board items
244                 case PCB_SHAPE_T:            // a shape (normally not on copper layers)
245                 case PCB_TEXT_T:             // a text on a layer
246                 case PCB_TRACE_T:            // a track segment (segment on a copper layer)
247                 case PCB_ARC_T:              // an arced track segment (segment on a copper layer)
248                 case PCB_VIA_T:              // a via (like track segment on a copper layer)
249                 case PCB_DIM_ALIGNED_T:      // a dimension (graphic item)
250                 case PCB_DIM_CENTER_T:
251                 case PCB_DIM_ORTHOGONAL_T:
252                 case PCB_DIM_LEADER_T:       // a leader dimension
253                 case PCB_TARGET_T:           // a target (graphic item)
254                 case PCB_MARKER_T:           // a marker used to show something
255                 case PCB_ZONE_T:
256                     if( view )
257                         view->Remove( boardItem );
258 
259                     if( !( changeFlags & CHT_DONE ) )
260                     {
261                         board->Remove( boardItem, REMOVE_MODE::BULK );
262                         bulkRemovedItems.push_back( boardItem );
263                     }
264 
265                     break;
266 
267                 case PCB_FOOTPRINT_T:
268                 {
269                     // No support for nested footprints (yet)
270                     wxASSERT( !m_isFootprintEditor );
271 
272                     FOOTPRINT* footprint = static_cast<FOOTPRINT*>( boardItem );
273 
274                     if( view )
275                         view->Remove( footprint );
276 
277                     footprint->ClearFlags();
278 
279                     if( !( changeFlags & CHT_DONE ) )
280                     {
281                         board->Remove( footprint, REMOVE_MODE::BULK ); // handles connectivity
282                         bulkRemovedItems.push_back( footprint );
283                     }
284                 }
285                 break;
286 
287                 case PCB_GROUP_T:
288                     if( view )
289                         view->Remove( boardItem );
290 
291                     if( !( changeFlags & CHT_DONE ) )
292                     {
293                         if( m_isFootprintEditor )
294                             board->GetFirstFootprint()->Remove( boardItem );
295                         else
296                         {
297                             board->Remove( boardItem, REMOVE_MODE::BULK );
298                             bulkRemovedItems.push_back( boardItem );
299                         }
300                     }
301                     break;
302 
303                 // Metadata items
304                 case PCB_NETINFO_T:
305                     board->Remove( boardItem, REMOVE_MODE::BULK );
306                     bulkRemovedItems.push_back( boardItem );
307                     break;
308 
309                 default:                        // other types do not need to (or should not) be handled
310                     wxASSERT( false );
311                     break;
312                 }
313 
314                 break;
315             }
316 
317             case CHT_MODIFY:
318             {
319                 if( !m_isFootprintEditor && aCreateUndoEntry )
320                 {
321                     ITEM_PICKER itemWrapper( nullptr, boardItem, UNDO_REDO::CHANGED );
322                     wxASSERT( ent.m_copy );
323                     itemWrapper.SetLink( ent.m_copy );
324                     undoList.PushItem( itemWrapper );
325                 }
326 
327                 if( ent.m_copy )
328                     connectivity->MarkItemNetAsDirty( static_cast<BOARD_ITEM*>( ent.m_copy ) );
329 
330                 connectivity->Update( boardItem );
331 
332                 if( view )
333                 {
334                     view->Update( boardItem );
335 
336                     if( m_isFootprintEditor )
337                     {
338                         static_cast<FOOTPRINT*>( boardItem )->RunOnChildren(
339                                 [&]( BOARD_ITEM* aChild )
340                                 {
341                                     view->Update( aChild );
342                                 });
343                     }
344                 }
345 
346                 itemsChanged.push_back( boardItem );
347 
348                 // if no undo entry is needed, the copy would create a memory leak
349                 if( !aCreateUndoEntry )
350                     delete ent.m_copy;
351 
352                 break;
353             }
354 
355             default:
356                 wxASSERT( false );
357                 break;
358         }
359     }
360 
361     if( bulkAddedItems.size() > 0 )
362         board->FinalizeBulkAdd( bulkAddedItems );
363 
364     if( bulkRemovedItems.size() > 0 )
365         board->FinalizeBulkRemove( bulkRemovedItems );
366 
367     if( itemsChanged.size() > 0 )
368         board->OnItemsChanged( itemsChanged );
369 
370     if( !m_isFootprintEditor )
371     {
372         size_t num_changes = m_changes.size();
373 
374         if( m_resolveNetConflicts )
375             connectivity->PropagateNets( this, PROPAGATE_MODE::RESOLVE_CONFLICTS );
376 
377         connectivity->RecalculateRatsnest( this );
378         connectivity->ClearDynamicRatsnest();
379 
380         if( frame )
381             frame->GetCanvas()->RedrawRatsnest();
382 
383         if( m_changes.size() > num_changes )
384         {
385             for( size_t i = num_changes; i < m_changes.size(); ++i )
386             {
387                 COMMIT_LINE& ent = m_changes[i];
388 
389                 // This should only be modifications from the connectivity algo
390                 wxASSERT( ( ent.m_type & CHT_TYPE ) == CHT_MODIFY );
391 
392                 BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( ent.m_item );
393 
394                 if( aCreateUndoEntry )
395                 {
396                     ITEM_PICKER itemWrapper( nullptr, boardItem, UNDO_REDO::CHANGED );
397                     wxASSERT( ent.m_copy );
398                     itemWrapper.SetLink( ent.m_copy );
399                     undoList.PushItem( itemWrapper );
400                 }
401                 else
402                 {
403                     delete ent.m_copy;
404                 }
405 
406                 if( view )
407                     view->Update( boardItem );
408             }
409         }
410     }
411 
412     if( !m_isFootprintEditor && aCreateUndoEntry && frame )
413         frame->SaveCopyInUndoList( undoList, UNDO_REDO::UNSPECIFIED );
414 
415     m_toolMgr->PostEvent( { TC_MESSAGE, TA_MODEL_CHANGE, AS_GLOBAL } );
416 
417     if( itemsDeselected )
418         m_toolMgr->PostEvent( EVENTS::UnselectedEvent );
419 
420     if( frame )
421     {
422         if( aSetDirtyBit )
423             frame->OnModify();
424         else
425             frame->Update3DView( true, frame->GetDisplayOptions().m_Live3DRefresh );
426     }
427 
428     clear();
429 }
430 
431 
parentObject(EDA_ITEM * aItem) const432 EDA_ITEM* BOARD_COMMIT::parentObject( EDA_ITEM* aItem ) const
433 {
434     switch( aItem->Type() )
435     {
436         case PCB_PAD_T:
437         case PCB_FP_SHAPE_T:
438         case PCB_FP_TEXT_T:
439         case PCB_FP_ZONE_T:
440             return aItem->GetParent();
441 
442         case PCB_ZONE_T:
443             wxASSERT( !dynamic_cast<FOOTPRINT*>( aItem->GetParent() ) );
444             return aItem;
445 
446         default:
447             break;
448     }
449 
450     return aItem;
451 }
452 
453 
Revert()454 void BOARD_COMMIT::Revert()
455 {
456     PICKED_ITEMS_LIST undoList;
457     KIGFX::VIEW* view = m_toolMgr->GetView();
458     BOARD* board = (BOARD*) m_toolMgr->GetModel();
459     auto connectivity = board->GetConnectivity();
460 
461     std::vector<BOARD_ITEM*> bulkAddedItems;
462     std::vector<BOARD_ITEM*> bulkRemovedItems;
463     std::vector<BOARD_ITEM*> itemsChanged;
464 
465     for( auto it = m_changes.rbegin(); it != m_changes.rend(); ++it )
466     {
467         COMMIT_LINE& ent = *it;
468         BOARD_ITEM* item = static_cast<BOARD_ITEM*>( ent.m_item );
469         BOARD_ITEM* copy = static_cast<BOARD_ITEM*>( ent.m_copy );
470         int changeType = ent.m_type & CHT_TYPE;
471         int changeFlags = ent.m_type & CHT_FLAGS;
472 
473         switch( changeType )
474         {
475         case CHT_ADD:
476             if( !( changeFlags & CHT_DONE ) )
477                 break;
478 
479             view->Remove( item );
480             connectivity->Remove( item );
481             board->Remove( item, REMOVE_MODE::BULK );
482             bulkRemovedItems.push_back( item );
483             break;
484 
485         case CHT_REMOVE:
486             if( !( changeFlags & CHT_DONE ) )
487                 break;
488 
489             view->Add( item );
490             connectivity->Add( item );
491             board->Add( item, ADD_MODE::INSERT );
492             bulkAddedItems.push_back( item );
493             break;
494 
495         case CHT_MODIFY:
496         {
497             view->Remove( item );
498             connectivity->Remove( item );
499 
500             item->SwapData( copy );
501 
502             view->Add( item );
503             connectivity->Add( item );
504             board->OnItemChanged( item );
505             itemsChanged.push_back( item );
506 
507             delete copy;
508             break;
509         }
510 
511         default:
512             wxASSERT( false );
513             break;
514         }
515     }
516 
517     if( bulkAddedItems.size() > 0 )
518         board->FinalizeBulkAdd( bulkAddedItems );
519 
520     if( bulkRemovedItems.size() > 0 )
521         board->FinalizeBulkRemove( bulkRemovedItems );
522 
523     if( itemsChanged.size() > 0 )
524         board->OnItemsChanged( itemsChanged );
525 
526     if ( !m_isFootprintEditor )
527         connectivity->RecalculateRatsnest();
528 
529     PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
530     selTool->RebuildSelection();
531 
532     clear();
533 }
534 
535