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.

587 lines
19 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2012 Jean-Pierre Charras, jean-pierre.charras@ujf-grenoble.fr
  5. * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
  6. * Copyright (C) 2016 CERN
  7. * Copyright (C) 2012-2021 KiCad Developers, see AUTHORS.txt for contributors.
  8. * @author Maciej Suminski <maciej.suminski@cern.ch>
  9. *
  10. * This program is free software; you can redistribute it and/or
  11. * modify it under the terms of the GNU General Public License
  12. * as published by the Free Software Foundation; either version 2
  13. * of the License, or (at your option) any later version.
  14. *
  15. * This program is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU General Public License
  21. * along with this program; if not, you may find one here:
  22. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  23. * or you may search the http://www.gnu.org website for the version 2 license,
  24. * or you may write to the Free Software Foundation, Inc.,
  25. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  26. */
  27. #include <functional>
  28. using namespace std::placeholders;
  29. #include <macros.h>
  30. #include <pcb_edit_frame.h>
  31. #include <board.h>
  32. #include <pcb_track.h>
  33. #include <pcb_group.h>
  34. #include <pcb_target.h>
  35. #include <footprint.h>
  36. #include <pad.h>
  37. #include <pcb_dimension.h>
  38. #include <origin_viewitem.h>
  39. #include <connectivity/connectivity_data.h>
  40. #include <pcbnew_settings.h>
  41. #include <tool/tool_manager.h>
  42. #include <tool/actions.h>
  43. #include <tools/pcb_selection_tool.h>
  44. #include <tools/pcb_control.h>
  45. #include <tools/board_editor_control.h>
  46. #include <drawing_sheet/ds_proxy_undo_item.h>
  47. #include <wx/msgdlg.h>
  48. /* Functions to undo and redo edit commands.
  49. * commands to undo are stored in CurrentScreen->m_UndoList
  50. * commands to redo are stored in CurrentScreen->m_RedoList
  51. *
  52. * m_UndoList and m_RedoList handle a std::vector of PICKED_ITEMS_LIST
  53. * Each PICKED_ITEMS_LIST handle a std::vector of pickers (class ITEM_PICKER),
  54. * that store the list of schematic items that are concerned by the command to undo or redo
  55. * and is created for each command to undo (handle also a command to redo).
  56. * each picker has a pointer pointing to an item to undo or redo (in fact: deleted, added or
  57. * modified),
  58. * and has a pointer to a copy of this item, when this item has been modified
  59. * (the old values of parameters are therefore saved)
  60. *
  61. * there are 3 cases:
  62. * - delete item(s) command
  63. * - change item(s) command
  64. * - add item(s) command
  65. *
  66. * Undo command
  67. * - delete item(s) command:
  68. * => deleted items are moved in undo list
  69. *
  70. * - change item(s) command
  71. * => A copy of item(s) is made (a DrawPickedStruct list of wrappers)
  72. * the .m_Link member of each wrapper points the modified item.
  73. * the .m_Item member of each wrapper points the old copy of this item.
  74. *
  75. * - add item(s) command
  76. * =>A list of item(s) is made. The .m_Item member of each wrapper points the new item.
  77. *
  78. * Redo command
  79. * - delete item(s) old command:
  80. * => deleted items are moved in EEDrawList list, and in
  81. *
  82. * - change item(s) command
  83. * => the copy of item(s) is moved in Undo list
  84. *
  85. * - add item(s) command
  86. * => The list of item(s) is used to create a deleted list in undo list(same as a delete
  87. * command)
  88. *
  89. * Some block operations that change items can be undone without memorize items, just the
  90. * coordinates of the transform:
  91. * move list of items (undo/redo is made by moving with the opposite move vector)
  92. * mirror (Y) and flip list of items (undo/redo is made by mirror or flip items)
  93. * so they are handled specifically.
  94. *
  95. */
  96. /**
  97. * Test if aItem exists somewhere in undo/redo lists of items. Used by PutDataInPreviousState
  98. * to be sure an item was not deleted since an undo or redo.
  99. *
  100. * This could be possible:
  101. * - if a call to SaveCopyInUndoList was forgotten in Pcbnew
  102. * - in zones outlines, when a change in one zone merges this zone with an other
  103. * Before using this function to test existence of items, it must be called with aItem = NULL to
  104. * prepare the list.
  105. *
  106. * @param aPcb is the board to test.
  107. * @param aItem is the item to find or NULL to build the list of existing items.
  108. */
  109. static bool TestForExistingItem( BOARD* aPcb, BOARD_ITEM* aItem )
  110. {
  111. for( PCB_TRACK* item : aPcb->Tracks() )
  112. {
  113. if( aItem == static_cast<BOARD_ITEM*>( item ) )
  114. return true;
  115. }
  116. for( FOOTPRINT* item : aPcb->Footprints() )
  117. {
  118. if( aItem == static_cast<BOARD_ITEM*>( item ) )
  119. return true;
  120. }
  121. for( BOARD_ITEM* item : aPcb->Drawings() )
  122. {
  123. if( aItem == static_cast<BOARD_ITEM*>( item ) )
  124. return true;
  125. }
  126. for( ZONE* item : aPcb->Zones() )
  127. {
  128. if( aItem == static_cast<BOARD_ITEM*>( item ) )
  129. return true;
  130. }
  131. NETINFO_LIST& netInfo = aPcb->GetNetInfo();
  132. for( NETINFO_LIST::iterator i = netInfo.begin(); i != netInfo.end(); ++i )
  133. {
  134. if( aItem == static_cast<BOARD_ITEM*>( *i ) )
  135. return true;
  136. }
  137. for( PCB_GROUP* item : aPcb->Groups() )
  138. {
  139. if( aItem == static_cast<BOARD_ITEM*>( item ) )
  140. return true;
  141. }
  142. return false;
  143. }
  144. static void SwapItemData( BOARD_ITEM* aItem, BOARD_ITEM* aImage )
  145. {
  146. if( aImage == nullptr )
  147. return;
  148. wxASSERT( aItem->Type() == aImage->Type() );
  149. // Remark: to create images of edited items to undo, we are using Clone method
  150. // which does not do a deep copy.
  151. // So we have to use the current values of these parameters.
  152. wxASSERT( aItem->m_Uuid == aImage->m_Uuid );
  153. EDA_ITEM* parent = aItem->GetParent();
  154. aItem->SwapData( aImage );
  155. // Restore pointers to be sure they are not broken
  156. aItem->SetParent( parent );
  157. }
  158. void PCB_BASE_EDIT_FRAME::SaveCopyInUndoList( EDA_ITEM* aItem, UNDO_REDO aCommandType )
  159. {
  160. PICKED_ITEMS_LIST commandToUndo;
  161. commandToUndo.PushItem( ITEM_PICKER( nullptr, aItem, aCommandType ) );
  162. SaveCopyInUndoList( commandToUndo, aCommandType );
  163. }
  164. void PCB_BASE_EDIT_FRAME::SaveCopyInUndoList( const PICKED_ITEMS_LIST& aItemsList,
  165. UNDO_REDO aCommandType )
  166. {
  167. PICKED_ITEMS_LIST* commandToUndo = new PICKED_ITEMS_LIST();
  168. // First, filter unnecessary stuff from the list (i.e. for multiple pads / labels modified),
  169. // take the first occurrence of the footprint (we save copies of footprints when one of its
  170. // subitems is changed).
  171. for( unsigned ii = 0; ii < aItemsList.GetCount(); ii++ )
  172. {
  173. ITEM_PICKER curr_picker = aItemsList.GetItemWrapper(ii);
  174. BOARD_ITEM* item = dynamic_cast<BOARD_ITEM*>( aItemsList.GetPickedItem( ii ) );
  175. // For items belonging to footprints, we need to save state of the parent footprint
  176. if( item && item->GetParent() && item->GetParent()->Type() == PCB_FOOTPRINT_T )
  177. {
  178. item = item->GetParent();
  179. // Check if the parent footprint has already been saved in another entry
  180. bool found = false;
  181. for( unsigned j = 0; j < commandToUndo->GetCount(); j++ )
  182. {
  183. if( commandToUndo->GetPickedItem( j ) == item
  184. && commandToUndo->GetPickedItemStatus( j ) == UNDO_REDO::CHANGED )
  185. {
  186. found = true;
  187. break;
  188. }
  189. }
  190. if( !found )
  191. {
  192. // Create a clean copy of the parent footprint
  193. FOOTPRINT* orig = static_cast<FOOTPRINT*>( item );
  194. FOOTPRINT* clone = new FOOTPRINT( *orig );
  195. clone->SetParent( GetBoard() );
  196. // Clear current flags (which can be temporary set by a current edit command)
  197. for( BOARD_ITEM* child : clone->GraphicalItems() )
  198. child->ClearEditFlags();
  199. for( PAD* pad : clone->Pads() )
  200. pad->ClearEditFlags();
  201. clone->Reference().ClearEditFlags();
  202. clone->Value().ClearEditFlags();
  203. ITEM_PICKER picker( nullptr, item, UNDO_REDO::CHANGED );
  204. picker.SetLink( clone );
  205. commandToUndo->PushItem( picker );
  206. orig->SetLastEditTime();
  207. }
  208. else
  209. {
  210. continue;
  211. }
  212. }
  213. else
  214. {
  215. // Normal case: all other BOARD_ITEMs, are simply copied to the new list
  216. commandToUndo->PushItem( curr_picker );
  217. }
  218. }
  219. for( unsigned ii = 0; ii < commandToUndo->GetCount(); ii++ )
  220. {
  221. EDA_ITEM* item = aItemsList.GetPickedItem( ii );
  222. UNDO_REDO command = commandToUndo->GetPickedItemStatus( ii );
  223. if( command == UNDO_REDO::UNSPECIFIED )
  224. {
  225. command = aCommandType;
  226. commandToUndo->SetPickedItemStatus( command, ii );
  227. }
  228. wxASSERT( item );
  229. switch( command )
  230. {
  231. case UNDO_REDO::CHANGED:
  232. case UNDO_REDO::DRILLORIGIN:
  233. case UNDO_REDO::GRIDORIGIN:
  234. /* If needed, create a copy of item, and put in undo list
  235. * in the picker, as link
  236. * If this link is not null, the copy is already done
  237. */
  238. if( commandToUndo->GetPickedItemLink( ii ) == nullptr )
  239. {
  240. EDA_ITEM* cloned = item->Clone();
  241. commandToUndo->SetPickedItemLink( cloned, ii );
  242. }
  243. break;
  244. case UNDO_REDO::NEWITEM:
  245. case UNDO_REDO::DELETED:
  246. case UNDO_REDO::PAGESETTINGS:
  247. case UNDO_REDO::REGROUP:
  248. case UNDO_REDO::UNGROUP:
  249. break;
  250. default:
  251. wxFAIL_MSG( wxString::Format( "SaveCopyInUndoList() error (unknown code %X)",
  252. command ) );
  253. break;
  254. }
  255. }
  256. if( commandToUndo->GetCount() )
  257. {
  258. /* Save the copy in undo list */
  259. PushCommandToUndoList( commandToUndo );
  260. /* Clear redo list, because after a new command one cannot redo a command */
  261. ClearUndoORRedoList( REDO_LIST );
  262. }
  263. else
  264. {
  265. // Should not occur
  266. wxASSERT( false );
  267. delete commandToUndo;
  268. }
  269. }
  270. void PCB_BASE_EDIT_FRAME::RestoreCopyFromUndoList( wxCommandEvent& aEvent )
  271. {
  272. if( UndoRedoBlocked() )
  273. return;
  274. if( GetUndoCommandCount() <= 0 )
  275. return;
  276. // Inform tools that undo command was issued
  277. m_toolManager->ProcessEvent( { TC_MESSAGE, TA_UNDO_REDO_PRE, AS_GLOBAL } );
  278. // Get the old list
  279. PICKED_ITEMS_LIST* list = PopCommandFromUndoList();
  280. // Undo the command
  281. PutDataInPreviousState( list );
  282. // Put the old list in RedoList
  283. list->ReversePickersListOrder();
  284. PushCommandToRedoList( list );
  285. OnModify();
  286. m_toolManager->ProcessEvent( { TC_MESSAGE, TA_UNDO_REDO_POST, AS_GLOBAL } );
  287. m_toolManager->PostEvent( EVENTS::SelectedItemsModified );
  288. GetCanvas()->Refresh();
  289. }
  290. void PCB_BASE_EDIT_FRAME::RestoreCopyFromRedoList( wxCommandEvent& aEvent )
  291. {
  292. if( UndoRedoBlocked() )
  293. return;
  294. if( GetRedoCommandCount() == 0 )
  295. return;
  296. // Inform tools that redo command was issued
  297. m_toolManager->ProcessEvent( { TC_MESSAGE, TA_UNDO_REDO_PRE, AS_GLOBAL } );
  298. // Get the old list
  299. PICKED_ITEMS_LIST* list = PopCommandFromRedoList();
  300. // Redo the command
  301. PutDataInPreviousState( list );
  302. // Put the old list in UndoList
  303. list->ReversePickersListOrder();
  304. PushCommandToUndoList( list );
  305. OnModify();
  306. m_toolManager->ProcessEvent( { TC_MESSAGE, TA_UNDO_REDO_POST, AS_GLOBAL } );
  307. m_toolManager->PostEvent( EVENTS::SelectedItemsModified );
  308. GetCanvas()->Refresh();
  309. }
  310. void PCB_BASE_EDIT_FRAME::PutDataInPreviousState( PICKED_ITEMS_LIST* aList )
  311. {
  312. bool not_found = false;
  313. bool reBuild_ratsnest = false;
  314. bool deep_reBuild_ratsnest = false; // true later if pointers must be rebuilt
  315. auto view = GetCanvas()->GetView();
  316. auto connectivity = GetBoard()->GetConnectivity();
  317. PCB_GROUP* group = nullptr;
  318. // Undo in the reverse order of list creation: (this can allow stacked changes
  319. // like the same item can be changes and deleted in the same complex command
  320. // Restore changes in reverse order
  321. for( int ii = aList->GetCount() - 1; ii >= 0 ; ii-- )
  322. {
  323. EDA_ITEM* eda_item = aList->GetPickedItem( (unsigned) ii );
  324. /* Test for existence of item on board.
  325. * It could be deleted, and no more on board:
  326. * - if a call to SaveCopyInUndoList was forgotten in Pcbnew
  327. * - in zones outlines, when a change in one zone merges this zone with an other
  328. * This test avoids a Pcbnew crash
  329. * Obviously, this test is not made for deleted items
  330. */
  331. UNDO_REDO status = aList->GetPickedItemStatus( ii );
  332. if( status != UNDO_REDO::DELETED
  333. && status != UNDO_REDO::DRILLORIGIN // origin markers never on board
  334. && status != UNDO_REDO::GRIDORIGIN // origin markers never on board
  335. && status != UNDO_REDO::PAGESETTINGS ) // nor are page settings proxy items
  336. {
  337. if( !TestForExistingItem( GetBoard(), (BOARD_ITEM*) eda_item ) )
  338. {
  339. // Checking if it ever happens
  340. wxASSERT_MSG( false, "Item in the undo buffer does not exist" );
  341. // Remove this non existent item
  342. aList->RemovePicker( ii );
  343. not_found = true;
  344. if( aList->GetCount() == 0 )
  345. break;
  346. continue;
  347. }
  348. }
  349. // see if we must rebuild ratsnets and pointers lists
  350. switch( eda_item->Type() )
  351. {
  352. case PCB_FOOTPRINT_T:
  353. deep_reBuild_ratsnest = true; // Pointers on pads can be invalid
  354. KI_FALLTHROUGH;
  355. case PCB_ZONE_T:
  356. case PCB_TRACE_T:
  357. case PCB_ARC_T:
  358. case PCB_VIA_T:
  359. case PCB_PAD_T:
  360. reBuild_ratsnest = true;
  361. break;
  362. case PCB_NETINFO_T:
  363. reBuild_ratsnest = true;
  364. deep_reBuild_ratsnest = true;
  365. break;
  366. default:
  367. break;
  368. }
  369. switch( aList->GetPickedItemStatus( ii ) )
  370. {
  371. case UNDO_REDO::CHANGED: /* Exchange old and new data for each item */
  372. {
  373. BOARD_ITEM* item = (BOARD_ITEM*) eda_item;
  374. BOARD_ITEM* image = (BOARD_ITEM*) aList->GetPickedItemLink( ii );
  375. // Remove all pads/drawings/texts, as they become invalid
  376. // for the VIEW after SwapData() called for footprints
  377. view->Remove( item );
  378. connectivity->Remove( item );
  379. SwapItemData( item, image );
  380. view->Add( item );
  381. view->Hide( item, false );
  382. connectivity->Add( item );
  383. item->GetBoard()->OnItemChanged( item );
  384. break;
  385. }
  386. case UNDO_REDO::NEWITEM: /* new items are deleted */
  387. aList->SetPickedItemStatus( UNDO_REDO::DELETED, ii );
  388. GetModel()->Remove( (BOARD_ITEM*) eda_item );
  389. if( eda_item->Type() != PCB_NETINFO_T )
  390. view->Remove( eda_item );
  391. break;
  392. case UNDO_REDO::DELETED: /* deleted items are put in List, as new items */
  393. aList->SetPickedItemStatus( UNDO_REDO::NEWITEM, ii );
  394. GetModel()->Add( (BOARD_ITEM*) eda_item );
  395. if( eda_item->Type() != PCB_NETINFO_T )
  396. view->Add( eda_item );
  397. if( eda_item->Type() == PCB_GROUP_T )
  398. group = static_cast<PCB_GROUP*>( eda_item );
  399. break;
  400. case UNDO_REDO::REGROUP:
  401. aList->SetPickedItemStatus( UNDO_REDO::UNGROUP, ii );
  402. static_cast<BOARD_ITEM*>( eda_item )->SetParentGroup( nullptr );
  403. break;
  404. case UNDO_REDO::UNGROUP:
  405. aList->SetPickedItemStatus( UNDO_REDO::REGROUP, ii );
  406. if( group )
  407. group->AddItem( static_cast<BOARD_ITEM*>( eda_item ) );
  408. break;
  409. case UNDO_REDO::DRILLORIGIN:
  410. case UNDO_REDO::GRIDORIGIN:
  411. {
  412. BOARD_ITEM* item = (BOARD_ITEM*) eda_item;
  413. BOARD_ITEM* image = (BOARD_ITEM*) aList->GetPickedItemLink( ii );
  414. VECTOR2D origin = image->GetPosition();
  415. image->SetPosition( eda_item->GetPosition() );
  416. if( aList->GetPickedItemStatus( ii ) == UNDO_REDO::DRILLORIGIN )
  417. BOARD_EDITOR_CONTROL::DoSetDrillOrigin( view, this, item, origin );
  418. else
  419. PCB_CONTROL::DoSetGridOrigin( view, this, item, origin );
  420. break;
  421. }
  422. case UNDO_REDO::PAGESETTINGS:
  423. {
  424. // swap current settings with stored settings
  425. DS_PROXY_UNDO_ITEM alt_item( this );
  426. DS_PROXY_UNDO_ITEM* item = static_cast<DS_PROXY_UNDO_ITEM*>( eda_item );
  427. item->Restore( this );
  428. *item = alt_item;
  429. break;
  430. }
  431. default:
  432. wxFAIL_MSG( wxString::Format( "PutDataInPreviousState() error (unknown code %X)",
  433. aList->GetPickedItemStatus( ii ) ) );
  434. break;
  435. }
  436. }
  437. if( not_found )
  438. wxMessageBox( _( "Incomplete undo/redo operation: some items not found" ) );
  439. // Rebuild pointers and connectivity that can be changed.
  440. // connectivity can be rebuilt only in the board editor frame
  441. if( IsType( FRAME_PCB_EDITOR ) && ( reBuild_ratsnest || deep_reBuild_ratsnest ) )
  442. {
  443. Compile_Ratsnest( false );
  444. }
  445. PCB_SELECTION_TOOL* selTool = m_toolManager->GetTool<PCB_SELECTION_TOOL>();
  446. selTool->RebuildSelection();
  447. GetBoard()->SanitizeNetcodes();
  448. }
  449. void PCB_BASE_EDIT_FRAME::ClearUndoORRedoList( UNDO_REDO_LIST whichList, int aItemCount )
  450. {
  451. if( aItemCount == 0 )
  452. return;
  453. UNDO_REDO_CONTAINER& list = whichList == UNDO_LIST ? m_undoList : m_redoList;
  454. unsigned icnt = list.m_CommandsList.size();
  455. if( aItemCount > 0 )
  456. icnt = aItemCount;
  457. for( unsigned ii = 0; ii < icnt; ii++ )
  458. {
  459. if( list.m_CommandsList.size() == 0 )
  460. break;
  461. PICKED_ITEMS_LIST* curr_cmd = list.m_CommandsList[0];
  462. list.m_CommandsList.erase( list.m_CommandsList.begin() );
  463. curr_cmd->ClearListAndDeleteItems();
  464. delete curr_cmd; // Delete command
  465. }
  466. }
  467. void PCB_BASE_EDIT_FRAME::RollbackFromUndo()
  468. {
  469. PICKED_ITEMS_LIST* undo = PopCommandFromUndoList();
  470. PutDataInPreviousState( undo );
  471. undo->ClearListAndDeleteItems();
  472. delete undo;
  473. GetCanvas()->Refresh();
  474. }