1 /*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2004 Jean-Pierre Charras, jaen-pierre.charras@gipsa-lab.inpg.com
5 * Copyright (C) 2004-2021 KiCad Developers, see change_log.txt for contributors.
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 <sch_edit_frame.h>
26 #include <tool/tool_manager.h>
27 #include <schematic.h>
28 #include <sch_bus_entry.h>
29 #include <sch_junction.h>
30 #include <sch_line.h>
31 #include <sch_bitmap.h>
32 #include <tools/ee_selection_tool.h>
33 #include <drawing_sheet/ds_proxy_undo_item.h>
34 #include <tool/actions.h>
35
36
37 /* Functions to undo and redo edit commands.
38 *
39 * m_UndoList and m_RedoList handle a std::vector of PICKED_ITEMS_LIST
40 * Each PICKED_ITEMS_LIST handle a std::vector of pickers (class ITEM_PICKER),
41 * that store the list of schematic items that are concerned by the command to
42 * undo or redo and is created for each command to undo (handle also a command
43 * to redo). each picker has a pointer pointing to an item to undo or redo (in
44 * fact: deleted, added or modified), and has a pointer to a copy of this item,
45 * when this item has been modified (the old values of parameters are
46 * therefore saved)
47 *
48 * there are 3 cases:
49 * - delete item(s) command
50 * - change item(s) command
51 * - add item(s) command
52 * and 2 cases for block:
53 * - move list of items
54 * - mirror (Y) list of items
55 *
56 * Undo command
57 * - delete item(s) command:
58 * => deleted items are moved in undo list
59 *
60 * - change item(s) command
61 * => A copy of item(s) is made (a DrawPickedStruct list of wrappers)
62 * the .m_Link member of each wrapper points the modified item.
63 * the .m_Item member of each wrapper points the old copy of this item.
64 *
65 * - add item(s) command
66 * =>A list of item(s) is made. The .m_Item member of each wrapper points
67 * the new item.
68 *
69 * Redo command
70 * - delete item(s) old command:
71 * => deleted items are moved into m_tree
72 *
73 * - change item(s) command
74 * => the copy of item(s) is moved in Undo list
75 *
76 * - add item(s) command
77 * => The list of item(s) is used to create a deleted list in undo
78 * list(same as a delete command)
79 *
80 * Some block operations that change items can be undone without memorized
81 * items, just the coordinates of the transform: move list of items (undo/
82 * redo is made by moving with the opposite move vector) mirror (Y) and flip
83 * list of items (undo/redo is made by mirror or flip items) so they are
84 * handled specifically.
85 *
86 * A problem is the hierarchical sheet handling.
87 * the data associated (sub-hierarchy, undo/redo list) is deleted only
88 * when the sheet is really deleted (i.e. when deleted from undo or redo list)
89 * This is handled by its destructor.
90 */
91
92
93 /* Used if undo / redo command:
94 * swap data between Item and its copy, pointed by its picked item link member
95 * swapped data is data modified by editing, so not all values are swapped
96 */
97
StartNewUndo()98 void SCH_EDIT_FRAME::StartNewUndo()
99 {
100 PICKED_ITEMS_LIST* blank = new PICKED_ITEMS_LIST();
101 PushCommandToUndoList( blank );
102 }
103
104
SaveCopyInUndoList(SCH_SCREEN * aScreen,SCH_ITEM * aItem,UNDO_REDO aCommandType,bool aAppend)105 void SCH_EDIT_FRAME::SaveCopyInUndoList( SCH_SCREEN* aScreen,
106 SCH_ITEM* aItem,
107 UNDO_REDO aCommandType,
108 bool aAppend )
109 {
110 PICKED_ITEMS_LIST* commandToUndo = nullptr;
111
112 wxCHECK( aItem, /* void */ );
113
114 // Connectivity may change
115 aItem->SetConnectivityDirty();
116
117 PICKED_ITEMS_LIST* lastUndo = PopCommandFromUndoList();
118
119 // If the last stack was empty, use that one instead of creating a new stack
120 if( lastUndo )
121 {
122 if( aAppend || !lastUndo->GetCount() )
123 commandToUndo = lastUndo;
124 else
125 PushCommandToUndoList( lastUndo );
126 }
127
128 if( !commandToUndo )
129 {
130 commandToUndo = new PICKED_ITEMS_LIST();
131 }
132
133 ITEM_PICKER itemWrapper( aScreen, aItem, aCommandType );
134 itemWrapper.SetFlags( aItem->GetFlags() );
135
136 switch( aCommandType )
137 {
138 case UNDO_REDO::CHANGED: /* Create a copy of item */
139 itemWrapper.SetLink( aItem->Duplicate( true ) );
140 commandToUndo->PushItem( itemWrapper );
141 break;
142
143 case UNDO_REDO::NEWITEM:
144 case UNDO_REDO::DELETED:
145 commandToUndo->PushItem( itemWrapper );
146 break;
147
148 default:
149 wxFAIL_MSG( wxString::Format( wxT( "SaveCopyInUndoList() error (unknown code %X)" ),
150 aCommandType ) );
151 break;
152 }
153
154 if( commandToUndo->GetCount() )
155 {
156 /* Save the copy in undo list */
157 PushCommandToUndoList( commandToUndo );
158
159 /* Clear redo list, because after new save there is no redo to do */
160 ClearUndoORRedoList( REDO_LIST );
161 }
162 else
163 {
164 delete commandToUndo;
165 }
166 }
167
168
SaveCopyInUndoList(const PICKED_ITEMS_LIST & aItemsList,UNDO_REDO aTypeCommand,bool aAppend)169 void SCH_EDIT_FRAME::SaveCopyInUndoList( const PICKED_ITEMS_LIST& aItemsList,
170 UNDO_REDO aTypeCommand,
171 bool aAppend )
172 {
173 PICKED_ITEMS_LIST* commandToUndo = nullptr;
174
175 if( !aItemsList.GetCount() )
176 return;
177
178 PICKED_ITEMS_LIST* lastUndo = PopCommandFromUndoList();
179
180 // If the last stack was empty, use that one instead of creating a new stack
181 if( lastUndo )
182 {
183 if( aAppend || !lastUndo->GetCount() )
184 commandToUndo = lastUndo;
185 else
186 PushCommandToUndoList( lastUndo );
187 }
188
189 if( !commandToUndo )
190 commandToUndo = new PICKED_ITEMS_LIST();
191
192 // Copy picker list:
193 if( !commandToUndo->GetCount() )
194 commandToUndo->CopyList( aItemsList );
195 else
196 {
197 // Unless we are appending, in which case, get the picker items
198 for( unsigned ii = 0; ii < aItemsList.GetCount(); ii++ )
199 commandToUndo->PushItem( aItemsList.GetItemWrapper( ii) );
200 }
201
202 // Verify list, and creates data if needed
203 for( unsigned ii = 0; ii < commandToUndo->GetCount(); ii++ )
204 {
205 SCH_ITEM* sch_item = dynamic_cast<SCH_ITEM*>( commandToUndo->GetPickedItem( ii ) );
206
207 // Common items implemented in EDA_DRAW_FRAME will not be SCH_ITEMs.
208 if( !sch_item )
209 continue;
210
211 // Connectivity may change
212 sch_item->SetConnectivityDirty();
213
214 UNDO_REDO command = commandToUndo->GetPickedItemStatus( ii );
215
216 if( command == UNDO_REDO::UNSPECIFIED )
217 {
218 command = aTypeCommand;
219 commandToUndo->SetPickedItemStatus( command, ii );
220 }
221
222 switch( command )
223 {
224 case UNDO_REDO::CHANGED:
225
226 /* If needed, create a copy of item, and put in undo list
227 * in the picker, as link
228 * If this link is not null, the copy is already done
229 */
230 if( commandToUndo->GetPickedItemLink( ii ) == nullptr )
231 commandToUndo->SetPickedItemLink( sch_item->Duplicate( true ), ii );
232
233 wxASSERT( commandToUndo->GetPickedItemLink( ii ) );
234 break;
235
236 case UNDO_REDO::NEWITEM:
237 case UNDO_REDO::DELETED:
238 case UNDO_REDO::EXCHANGE_T:
239 case UNDO_REDO::PAGESETTINGS:
240 break;
241
242 default:
243 wxFAIL_MSG( wxString::Format( wxT( "Unknown undo/redo command %d" ), command ) );
244 break;
245 }
246 }
247
248 if( commandToUndo->GetCount() )
249 {
250 /* Save the copy in undo list */
251 PushCommandToUndoList( commandToUndo );
252
253 /* Clear redo list, because after new save there is no redo to do */
254 ClearUndoORRedoList( REDO_LIST );
255 }
256 else // Should not occur
257 {
258 delete commandToUndo;
259 }
260 }
261
262
PutDataInPreviousState(PICKED_ITEMS_LIST * aList)263 void SCH_EDIT_FRAME::PutDataInPreviousState( PICKED_ITEMS_LIST* aList )
264 {
265 // Undo in the reverse order of list creation: (this can allow stacked changes like the
266 // same item can be changed and deleted in the same complex command).
267 for( int ii = aList->GetCount() - 1; ii >= 0; ii-- )
268 {
269 UNDO_REDO status = aList->GetPickedItemStatus((unsigned) ii );
270 EDA_ITEM* eda_item = aList->GetPickedItem( (unsigned) ii );
271 SCH_SCREEN* screen =
272 dynamic_cast< SCH_SCREEN* >( aList->GetScreenForItem( (unsigned) ii ) );
273
274 wxCHECK( screen, /* void */ );
275
276 eda_item->SetFlags( aList->GetPickerFlags( (unsigned) ii ) );
277 eda_item->ClearEditFlags();
278 eda_item->ClearTempFlags();
279
280 if( status == UNDO_REDO::NOP )
281 {
282 continue;
283 }
284 if( status == UNDO_REDO::NEWITEM )
285 {
286 // new items are deleted on undo
287 RemoveFromScreen( eda_item, screen );
288 aList->SetPickedItemStatus( UNDO_REDO::DELETED, (unsigned) ii );
289 }
290 else if( status == UNDO_REDO::DELETED )
291 {
292 // deleted items are re-inserted on undo
293 AddToScreen( eda_item, screen );
294 aList->SetPickedItemStatus( UNDO_REDO::NEWITEM, (unsigned) ii );
295 }
296 else if( status == UNDO_REDO::PAGESETTINGS )
297 {
298 // swap current settings with stored settings
299 DS_PROXY_UNDO_ITEM alt_item( this );
300 DS_PROXY_UNDO_ITEM* item = static_cast<DS_PROXY_UNDO_ITEM*>( eda_item );
301 item->Restore( this );
302 *item = alt_item;
303 }
304 else if( dynamic_cast<SCH_ITEM*>( eda_item ) )
305 {
306 // everything else is modified in place
307 SCH_ITEM* item = (SCH_ITEM*) eda_item;
308 SCH_ITEM* alt_item = (SCH_ITEM*) aList->GetPickedItemLink( (unsigned) ii );
309
310 // The root sheet is a pseudo object that owns the root screen object but is not on
311 // the root screen so do not attempt to remove it from the screen it owns.
312 if( item != &Schematic().Root() )
313 RemoveFromScreen( item, screen );
314
315 switch( status )
316 {
317 case UNDO_REDO::CHANGED:
318 item->SwapData( alt_item );
319 break;
320
321 case UNDO_REDO::EXCHANGE_T:
322 aList->SetPickedItem( alt_item, (unsigned) ii );
323 aList->SetPickedItemLink( item, (unsigned) ii );
324 item = alt_item;
325 break;
326
327 default:
328 wxFAIL_MSG( wxString::Format( wxT( "Unknown undo/redo command %d" ),
329 aList->GetPickedItemStatus( (unsigned) ii ) ) );
330 break;
331 }
332
333 if( item != &Schematic().Root() )
334 AddToScreen( item, screen );
335 }
336 }
337
338 // Bitmaps are cached in Opengl: clear the cache, because
339 // the cache data can be invalid
340 GetCanvas()->GetView()->RecacheAllItems();
341 GetCanvas()->GetView()->ClearHiddenFlags();
342 }
343
344
RollbackSchematicFromUndo()345 void SCH_EDIT_FRAME::RollbackSchematicFromUndo()
346 {
347 PICKED_ITEMS_LIST* undo = PopCommandFromUndoList();
348
349 // Skip empty frames
350 while( undo && ( !undo->GetCount()
351 || ( undo->GetCount() == 1 && undo->GetPickedItemStatus( 0 ) == UNDO_REDO::NOP ) ) )
352 {
353 delete undo;
354 undo = PopCommandFromUndoList();
355 }
356
357 if( undo )
358 {
359 PutDataInPreviousState( undo );
360 undo->ClearListAndDeleteItems();
361 delete undo;
362
363 SetSheetNumberAndCount();
364 UpdateHierarchyNavigator();
365
366 TestDanglingEnds();
367
368 m_toolManager->GetTool<EE_SELECTION_TOOL>()->RebuildSelection();
369 }
370
371 SyncView();
372 GetCanvas()->Refresh();
373 }
374
375
ClearUndoORRedoList(UNDO_REDO_LIST whichList,int aItemCount)376 void SCH_EDIT_FRAME::ClearUndoORRedoList( UNDO_REDO_LIST whichList, int aItemCount )
377 {
378 if( aItemCount == 0 )
379 return;
380
381 UNDO_REDO_CONTAINER& list = whichList == UNDO_LIST ? m_undoList : m_redoList;
382
383 for( PICKED_ITEMS_LIST* command : list.m_CommandsList )
384 {
385 command->ClearListAndDeleteItems();
386 delete command;
387 }
388
389 list.m_CommandsList.clear();
390 }
391
392
393