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.

515 lines
18 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2019 CERN
  5. * Copyright (C) 1992-2023 KiCad Developers, see AUTHORS.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. #include <sch_sheet_pin.h>
  25. #include <schematic.h>
  26. #include <tools/ee_actions.h>
  27. #include <tools/sch_find_replace_tool.h>
  28. #include <sch_sheet_path.h>
  29. int SCH_FIND_REPLACE_TOOL::FindAndReplace( const TOOL_EVENT& aEvent )
  30. {
  31. m_frame->ShowFindReplaceDialog( aEvent.IsAction( &ACTIONS::findAndReplace ) );
  32. return UpdateFind( aEvent );
  33. }
  34. int SCH_FIND_REPLACE_TOOL::UpdateFind( const TOOL_EVENT& aEvent )
  35. {
  36. EDA_SEARCH_DATA& data = m_frame->GetFindReplaceData();
  37. SCH_SEARCH_DATA* schSearchData = dynamic_cast<SCH_SEARCH_DATA*>( &data );
  38. bool selectedOnly = schSearchData ? schSearchData->searchSelectedOnly : false;
  39. auto visit =
  40. [&]( EDA_ITEM* aItem, SCH_SHEET_PATH* aSheet )
  41. {
  42. // We may get triggered when the dialog is not opened due to binding
  43. // SelectedItemsModified we also get triggered when the find dialog is
  44. // closed....so we need to double check the dialog is open.
  45. if( m_frame->m_findReplaceDialog != nullptr
  46. && !data.findString.IsEmpty()
  47. && aItem->Matches( data, aSheet )
  48. && ( !selectedOnly || aItem->IsSelected() ) )
  49. {
  50. aItem->SetForceVisible( true );
  51. m_selectionTool->BrightenItem( aItem );
  52. m_foundItemHighlighted = true;
  53. }
  54. else if( aItem->IsBrightened() )
  55. {
  56. aItem->SetForceVisible( false );
  57. m_selectionTool->UnbrightenItem( aItem );
  58. }
  59. };
  60. auto visitAll =
  61. [&]()
  62. {
  63. for( SCH_ITEM* item : m_frame->GetScreen()->Items() )
  64. {
  65. visit( item, &m_frame->GetCurrentSheet() );
  66. item->RunOnChildren(
  67. [&]( SCH_ITEM* aChild )
  68. {
  69. visit( aChild, &m_frame->GetCurrentSheet() );
  70. } );
  71. }
  72. };
  73. if( aEvent.IsAction( &ACTIONS::find ) || aEvent.IsAction( &ACTIONS::findAndReplace )
  74. || aEvent.IsAction( &ACTIONS::updateFind ) )
  75. {
  76. m_foundItemHighlighted = false;
  77. visitAll();
  78. }
  79. else if( aEvent.Matches( EVENTS::SelectedItemsModified ) )
  80. {
  81. for( EDA_ITEM* item : m_selectionTool->GetSelection() )
  82. visit( item, &m_frame->GetCurrentSheet() );
  83. }
  84. else if( aEvent.Matches( EVENTS::PointSelectedEvent )
  85. || aEvent.Matches( EVENTS::SelectedEvent )
  86. || aEvent.Matches( EVENTS::UnselectedEvent )
  87. || aEvent.Matches( EVENTS::ClearedEvent ) )
  88. {
  89. if( !m_frame->m_findReplaceDialog )
  90. {
  91. if( m_foundItemHighlighted )
  92. {
  93. m_foundItemHighlighted = false;
  94. visitAll();
  95. }
  96. }
  97. else if( selectedOnly )
  98. {
  99. // Normal find modifies the selection, but selection-based find does not, so we want
  100. // to start over in the items we are searching through when the selection changes
  101. m_afterItem = nullptr;
  102. visitAll();
  103. }
  104. }
  105. else if( m_foundItemHighlighted )
  106. {
  107. m_foundItemHighlighted = false;
  108. visitAll();
  109. }
  110. getView()->UpdateItems();
  111. m_frame->GetCanvas()->Refresh();
  112. m_frame->updateTitle();
  113. return 0;
  114. }
  115. SCH_ITEM* SCH_FIND_REPLACE_TOOL::nextMatch( SCH_SCREEN* aScreen, SCH_SHEET_PATH* aSheet,
  116. SCH_ITEM* aAfter, EDA_SEARCH_DATA& aData,
  117. bool reversed )
  118. {
  119. SCH_SEARCH_DATA* schSearchData = dynamic_cast<SCH_SEARCH_DATA*>( &aData );
  120. bool selectedOnly = schSearchData ? schSearchData->searchSelectedOnly : false;
  121. bool past_item = !aAfter;
  122. std::vector<SCH_ITEM*> sorted_items;
  123. auto addItem =
  124. [&](SCH_ITEM* item)
  125. {
  126. sorted_items.push_back( item );
  127. if( item->Type() == SCH_SYMBOL_T )
  128. {
  129. SCH_SYMBOL* cmp = static_cast<SCH_SYMBOL*>( item );
  130. for( SCH_FIELD& field : cmp->GetFields() )
  131. sorted_items.push_back( &field );
  132. for( SCH_PIN* pin : cmp->GetPins() )
  133. sorted_items.push_back( pin );
  134. }
  135. else if( item->Type() == SCH_SHEET_T )
  136. {
  137. SCH_SHEET* sheet = static_cast<SCH_SHEET*>( item );
  138. for( SCH_FIELD& field : sheet->GetFields() )
  139. sorted_items.push_back( &field );
  140. for( SCH_SHEET_PIN* pin : sheet->GetPins() )
  141. sorted_items.push_back( pin );
  142. }
  143. else if( item->IsType( { SCH_LABEL_LOCATE_ANY_T } ) )
  144. {
  145. SCH_LABEL_BASE* label = static_cast<SCH_LABEL_BASE*>( item );
  146. for( SCH_FIELD& field : label->GetFields() )
  147. sorted_items.push_back( &field );
  148. }
  149. };
  150. if( selectedOnly )
  151. for( EDA_ITEM* item : m_selectionTool->GetSelection() )
  152. addItem( static_cast<SCH_ITEM*>( item ) );
  153. else
  154. for( SCH_ITEM* item : aScreen->Items() )
  155. addItem( item );
  156. std::sort( sorted_items.begin(), sorted_items.end(),
  157. [&]( SCH_ITEM* a, SCH_ITEM* b )
  158. {
  159. if( a->GetPosition().x == b->GetPosition().x )
  160. {
  161. // Ensure deterministic sort
  162. if( a->GetPosition().y == b->GetPosition().y )
  163. return a->m_Uuid < b->m_Uuid;
  164. return a->GetPosition().y < b->GetPosition().y;
  165. }
  166. else
  167. return a->GetPosition().x < b->GetPosition().x;
  168. } );
  169. if( reversed )
  170. std::reverse( sorted_items.begin(), sorted_items.end() );
  171. for( SCH_ITEM* item : sorted_items )
  172. {
  173. if( item == aAfter )
  174. {
  175. past_item = true;
  176. }
  177. else if( past_item )
  178. {
  179. if( aData.markersOnly && item->Type() == SCH_MARKER_T )
  180. return item;
  181. if( item->Matches( aData, aSheet ) )
  182. return item;
  183. }
  184. }
  185. return nullptr;
  186. }
  187. int SCH_FIND_REPLACE_TOOL::FindNext( const TOOL_EVENT& aEvent )
  188. {
  189. EDA_SEARCH_DATA& data = m_frame->GetFindReplaceData();
  190. bool searchAllSheets = false;
  191. bool selectedOnly = false;
  192. bool isReversed = aEvent.IsAction( &ACTIONS::findPrevious );
  193. SCH_ITEM* item = nullptr;
  194. SCH_SHEET_PATH* afterSheet = &m_frame->GetCurrentSheet();
  195. try
  196. {
  197. const SCH_SEARCH_DATA& schSearchData = dynamic_cast<const SCH_SEARCH_DATA&>( data );
  198. searchAllSheets = !( schSearchData.searchCurrentSheetOnly );
  199. selectedOnly = schSearchData.searchSelectedOnly;
  200. }
  201. catch( const std::bad_cast& )
  202. {
  203. }
  204. if( aEvent.IsAction( &ACTIONS::findNextMarker ) )
  205. data.markersOnly = true;
  206. else if( data.findString.IsEmpty() )
  207. return FindAndReplace( ACTIONS::find.MakeEvent() );
  208. if( m_wrapAroundTimer.IsRunning() )
  209. {
  210. afterSheet = nullptr;
  211. m_afterItem = nullptr;
  212. m_wrapAroundTimer.Stop();
  213. m_frame->ClearFindReplaceStatus();
  214. }
  215. if( afterSheet || !searchAllSheets )
  216. {
  217. item = nextMatch( m_frame->GetScreen(), &m_frame->GetCurrentSheet(), m_afterItem, data,
  218. isReversed );
  219. }
  220. if( !item && searchAllSheets )
  221. {
  222. SCH_SCREENS screens( m_frame->Schematic().Root() );
  223. std::vector<SCH_SHEET_PATH*> paths;
  224. screens.BuildClientSheetPathList();
  225. for( SCH_SCREEN* screen = screens.GetFirst(); screen; screen = screens.GetNext() )
  226. {
  227. for( SCH_SHEET_PATH& sheet : screen->GetClientSheetPaths() )
  228. paths.push_back( &sheet );
  229. }
  230. std::sort( paths.begin(), paths.end(), [] ( const SCH_SHEET_PATH* lhs,
  231. const SCH_SHEET_PATH* rhs ) -> bool
  232. {
  233. int retval = lhs->ComparePageNum( *rhs );
  234. if( retval < 0 )
  235. return true;
  236. else if( retval > 0 )
  237. return false;
  238. else /// Enforce strict ordering. If the page numbers are the same, use UUIDs
  239. return lhs->GetCurrentHash() < rhs->GetCurrentHash();
  240. } );
  241. if( isReversed )
  242. std::reverse( paths.begin(), paths.end() );
  243. for( SCH_SHEET_PATH* sheet : paths )
  244. {
  245. if( afterSheet )
  246. {
  247. if( afterSheet->GetCurrentHash() == sheet->GetCurrentHash() )
  248. afterSheet = nullptr;
  249. continue;
  250. }
  251. item = nextMatch( sheet->LastScreen(), sheet, nullptr, data, isReversed );
  252. if( item )
  253. {
  254. if( m_frame->Schematic().CurrentSheet() != *sheet )
  255. {
  256. m_frame->Schematic().SetCurrentSheet( *sheet );
  257. m_frame->DisplayCurrentSheet();
  258. }
  259. break;
  260. }
  261. }
  262. }
  263. if( item )
  264. {
  265. m_afterItem = item;
  266. if( !item->IsBrightened() )
  267. {
  268. // Clear any previous brightening
  269. UpdateFind( aEvent );
  270. // Brighten (and show) found object
  271. item->SetForceVisible( true );
  272. m_selectionTool->BrightenItem( item );
  273. m_foundItemHighlighted = true;
  274. }
  275. if( !selectedOnly )
  276. {
  277. m_selectionTool->ClearSelection();
  278. m_selectionTool->AddItemToSel( item );
  279. }
  280. m_frame->FocusOnLocation( item->GetBoundingBox().GetCenter() );
  281. m_frame->GetCanvas()->Refresh();
  282. }
  283. else
  284. {
  285. wxString msg = searchAllSheets ? _( "Reached end of schematic." )
  286. : _( "Reached end of sheet." );
  287. // Show the popup during the time period the user can wrap the search
  288. m_frame->ShowFindReplaceStatus( msg + wxS( " " ) +
  289. _( "Find again to wrap around to the start." ), 4000 );
  290. m_wrapAroundTimer.StartOnce( 4000 );
  291. }
  292. return 0;
  293. }
  294. EDA_ITEM* SCH_FIND_REPLACE_TOOL::getCurrentMatch()
  295. {
  296. EDA_SEARCH_DATA& data = m_frame->GetFindReplaceData();
  297. SCH_SEARCH_DATA* schSearchData = dynamic_cast<SCH_SEARCH_DATA*>( &data );
  298. bool selectedOnly = schSearchData ? schSearchData->searchSelectedOnly : false;
  299. return selectedOnly ? m_afterItem : m_selectionTool->GetSelection().Front();
  300. }
  301. bool SCH_FIND_REPLACE_TOOL::HasMatch()
  302. {
  303. EDA_SEARCH_DATA& data = m_frame->GetFindReplaceData();
  304. EDA_ITEM* match = getCurrentMatch();
  305. return match && match->Matches( data, &m_frame->GetCurrentSheet() );
  306. }
  307. int SCH_FIND_REPLACE_TOOL::ReplaceAndFindNext( const TOOL_EVENT& aEvent )
  308. {
  309. EDA_SEARCH_DATA& data = m_frame->GetFindReplaceData();
  310. EDA_ITEM* item = getCurrentMatch();
  311. SCH_SHEET_PATH* sheet = &m_frame->GetCurrentSheet();
  312. if( data.findString.IsEmpty() )
  313. return FindAndReplace( ACTIONS::find.MakeEvent() );
  314. // TODO: move to SCH_COMMIT....
  315. if( item && HasMatch() )
  316. {
  317. SCH_ITEM* sch_item = static_cast<SCH_ITEM*>( item );
  318. m_frame->SaveCopyInUndoList( sheet->LastScreen(), sch_item, UNDO_REDO::CHANGED, false );
  319. if( item->Replace( data, sheet ) )
  320. {
  321. m_frame->UpdateItem( item, false, true );
  322. m_frame->GetCurrentSheet().UpdateAllScreenReferences();
  323. m_frame->OnModify();
  324. }
  325. FindNext( ACTIONS::findNext.MakeEvent() );
  326. }
  327. return 0;
  328. }
  329. int SCH_FIND_REPLACE_TOOL::ReplaceAll( const TOOL_EVENT& aEvent )
  330. {
  331. EDA_SEARCH_DATA& data = m_frame->GetFindReplaceData();
  332. bool currentSheetOnly = false;
  333. bool selectedOnly = false;
  334. try
  335. {
  336. const SCH_SEARCH_DATA& schSearchData = dynamic_cast<const SCH_SEARCH_DATA&>( data );
  337. currentSheetOnly = schSearchData.searchCurrentSheetOnly;
  338. selectedOnly = schSearchData.searchSelectedOnly;
  339. }
  340. catch( const std::bad_cast& )
  341. {
  342. }
  343. bool modified = false; // TODO: move to SCH_COMMIT....
  344. if( data.findString.IsEmpty() )
  345. return FindAndReplace( ACTIONS::find.MakeEvent() );
  346. auto doReplace =
  347. [&]( SCH_ITEM* aItem, SCH_SHEET_PATH* aSheet, EDA_SEARCH_DATA& aData )
  348. {
  349. m_frame->SaveCopyInUndoList( aSheet->LastScreen(), aItem, UNDO_REDO::CHANGED,
  350. modified );
  351. if( aItem->Replace( aData, aSheet ) )
  352. {
  353. m_frame->UpdateItem( aItem, false, true );
  354. modified = true;
  355. }
  356. };
  357. if( currentSheetOnly || selectedOnly )
  358. {
  359. SCH_SHEET_PATH* currentSheet = &m_frame->GetCurrentSheet();
  360. SCH_ITEM* item = nextMatch( m_frame->GetScreen(), currentSheet, nullptr, data, false );
  361. while( item )
  362. {
  363. if( !selectedOnly || item->IsSelected() )
  364. doReplace( item, currentSheet, data );
  365. item = nextMatch( m_frame->GetScreen(), currentSheet, item, data, false );
  366. }
  367. }
  368. else
  369. {
  370. SCH_SHEET_LIST allSheets = m_frame->Schematic().GetSheets();
  371. SCH_SCREENS screens( m_frame->Schematic().Root() );
  372. for( SCH_SCREEN* screen = screens.GetFirst(); screen; screen = screens.GetNext() )
  373. {
  374. SCH_SHEET_LIST sheets = allSheets.FindAllSheetsForScreen( screen );
  375. for( unsigned ii = 0; ii < sheets.size(); ++ii )
  376. {
  377. SCH_ITEM* item = nextMatch( screen, &sheets[ii], nullptr, data, false );
  378. while( item )
  379. {
  380. if( ii == 0 )
  381. {
  382. doReplace( item, &sheets[0], data );
  383. }
  384. else if( item->Type() == SCH_FIELD_T )
  385. {
  386. SCH_FIELD* field = static_cast<SCH_FIELD*>( item );
  387. if( field->GetParent() && field->GetParent()->Type() == SCH_SYMBOL_T )
  388. {
  389. switch( field->GetId() )
  390. {
  391. case REFERENCE_FIELD:
  392. case VALUE_FIELD:
  393. case FOOTPRINT_FIELD:
  394. // must be handled for each distinct sheet
  395. doReplace( field, &sheets[ii], data );
  396. break;
  397. default:
  398. // handled in first iteration
  399. break;
  400. }
  401. }
  402. }
  403. item = nextMatch( screen, &sheets[ii], item, data, false );
  404. }
  405. }
  406. }
  407. }
  408. if( modified )
  409. {
  410. m_frame->GetCurrentSheet().UpdateAllScreenReferences();
  411. m_frame->OnModify();
  412. }
  413. return 0;
  414. }
  415. void SCH_FIND_REPLACE_TOOL::setTransitions()
  416. {
  417. Go( &SCH_FIND_REPLACE_TOOL::FindAndReplace, ACTIONS::find.MakeEvent() );
  418. Go( &SCH_FIND_REPLACE_TOOL::FindAndReplace, ACTIONS::findAndReplace.MakeEvent() );
  419. Go( &SCH_FIND_REPLACE_TOOL::FindNext, ACTIONS::findNext.MakeEvent() );
  420. Go( &SCH_FIND_REPLACE_TOOL::FindNext, ACTIONS::findPrevious.MakeEvent() );
  421. Go( &SCH_FIND_REPLACE_TOOL::FindNext, ACTIONS::findNextMarker.MakeEvent() );
  422. Go( &SCH_FIND_REPLACE_TOOL::ReplaceAndFindNext, ACTIONS::replaceAndFindNext.MakeEvent() );
  423. Go( &SCH_FIND_REPLACE_TOOL::ReplaceAll, ACTIONS::replaceAll.MakeEvent() );
  424. Go( &SCH_FIND_REPLACE_TOOL::UpdateFind, ACTIONS::updateFind.MakeEvent() );
  425. Go( &SCH_FIND_REPLACE_TOOL::UpdateFind, EVENTS::SelectedItemsModified );
  426. Go( &SCH_FIND_REPLACE_TOOL::UpdateFind, EVENTS::PointSelectedEvent );
  427. Go( &SCH_FIND_REPLACE_TOOL::UpdateFind, EVENTS::SelectedEvent );
  428. Go( &SCH_FIND_REPLACE_TOOL::UpdateFind, EVENTS::UnselectedEvent );
  429. Go( &SCH_FIND_REPLACE_TOOL::UpdateFind, EVENTS::ClearedEvent );
  430. }