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.

504 lines
17 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. SCH_SHEET_LIST 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. paths.SortByPageNumbers( false );
  231. if( isReversed )
  232. std::reverse( paths.begin(), paths.end() );
  233. for( SCH_SHEET_PATH& sheet : paths )
  234. {
  235. if( afterSheet )
  236. {
  237. if( afterSheet->GetCurrentHash() == sheet.GetCurrentHash() )
  238. afterSheet = nullptr;
  239. continue;
  240. }
  241. item = nextMatch( sheet.LastScreen(), &sheet, nullptr, data, isReversed );
  242. if( item )
  243. {
  244. if( m_frame->Schematic().CurrentSheet() != sheet )
  245. {
  246. m_frame->Schematic().SetCurrentSheet( sheet );
  247. m_frame->DisplayCurrentSheet();
  248. }
  249. break;
  250. }
  251. }
  252. }
  253. if( item )
  254. {
  255. m_afterItem = item;
  256. if( !item->IsBrightened() )
  257. {
  258. // Clear any previous brightening
  259. UpdateFind( aEvent );
  260. // Brighten (and show) found object
  261. item->SetForceVisible( true );
  262. m_selectionTool->BrightenItem( item );
  263. m_foundItemHighlighted = true;
  264. }
  265. if( !selectedOnly )
  266. {
  267. m_selectionTool->ClearSelection();
  268. m_selectionTool->AddItemToSel( item );
  269. }
  270. m_frame->FocusOnLocation( item->GetBoundingBox().GetCenter() );
  271. m_frame->GetCanvas()->Refresh();
  272. }
  273. else
  274. {
  275. wxString msg = searchAllSheets ? _( "Reached end of schematic." )
  276. : _( "Reached end of sheet." );
  277. // Show the popup during the time period the user can wrap the search
  278. m_frame->ShowFindReplaceStatus( msg + wxS( " " ) +
  279. _( "Find again to wrap around to the start." ), 4000 );
  280. m_wrapAroundTimer.StartOnce( 4000 );
  281. }
  282. return 0;
  283. }
  284. EDA_ITEM* SCH_FIND_REPLACE_TOOL::getCurrentMatch()
  285. {
  286. EDA_SEARCH_DATA& data = m_frame->GetFindReplaceData();
  287. SCH_SEARCH_DATA* schSearchData = dynamic_cast<SCH_SEARCH_DATA*>( &data );
  288. bool selectedOnly = schSearchData ? schSearchData->searchSelectedOnly : false;
  289. return selectedOnly ? m_afterItem : m_selectionTool->GetSelection().Front();
  290. }
  291. bool SCH_FIND_REPLACE_TOOL::HasMatch()
  292. {
  293. EDA_SEARCH_DATA& data = m_frame->GetFindReplaceData();
  294. EDA_ITEM* match = getCurrentMatch();
  295. return match && match->Matches( data, &m_frame->GetCurrentSheet() );
  296. }
  297. int SCH_FIND_REPLACE_TOOL::ReplaceAndFindNext( const TOOL_EVENT& aEvent )
  298. {
  299. EDA_SEARCH_DATA& data = m_frame->GetFindReplaceData();
  300. EDA_ITEM* item = getCurrentMatch();
  301. SCH_SHEET_PATH* sheet = &m_frame->GetCurrentSheet();
  302. if( data.findString.IsEmpty() )
  303. return FindAndReplace( ACTIONS::find.MakeEvent() );
  304. // TODO: move to SCH_COMMIT....
  305. if( item && HasMatch() )
  306. {
  307. SCH_ITEM* sch_item = static_cast<SCH_ITEM*>( item );
  308. m_frame->SaveCopyInUndoList( sheet->LastScreen(), sch_item, UNDO_REDO::CHANGED, false );
  309. if( item->Replace( data, sheet ) )
  310. {
  311. m_frame->UpdateItem( item, false, true );
  312. m_frame->GetCurrentSheet().UpdateAllScreenReferences();
  313. m_frame->OnModify();
  314. }
  315. FindNext( ACTIONS::findNext.MakeEvent() );
  316. }
  317. return 0;
  318. }
  319. int SCH_FIND_REPLACE_TOOL::ReplaceAll( const TOOL_EVENT& aEvent )
  320. {
  321. EDA_SEARCH_DATA& data = m_frame->GetFindReplaceData();
  322. bool currentSheetOnly = false;
  323. bool selectedOnly = false;
  324. try
  325. {
  326. const SCH_SEARCH_DATA& schSearchData = dynamic_cast<const SCH_SEARCH_DATA&>( data );
  327. currentSheetOnly = schSearchData.searchCurrentSheetOnly;
  328. selectedOnly = schSearchData.searchSelectedOnly;
  329. }
  330. catch( const std::bad_cast& )
  331. {
  332. }
  333. bool modified = false; // TODO: move to SCH_COMMIT....
  334. if( data.findString.IsEmpty() )
  335. return FindAndReplace( ACTIONS::find.MakeEvent() );
  336. auto doReplace =
  337. [&]( SCH_ITEM* aItem, SCH_SHEET_PATH* aSheet, EDA_SEARCH_DATA& aData )
  338. {
  339. m_frame->SaveCopyInUndoList( aSheet->LastScreen(), aItem, UNDO_REDO::CHANGED,
  340. modified );
  341. if( aItem->Replace( aData, aSheet ) )
  342. {
  343. m_frame->UpdateItem( aItem, false, true );
  344. modified = true;
  345. }
  346. };
  347. if( currentSheetOnly || selectedOnly )
  348. {
  349. SCH_SHEET_PATH* currentSheet = &m_frame->GetCurrentSheet();
  350. SCH_ITEM* item = nextMatch( m_frame->GetScreen(), currentSheet, nullptr, data, false );
  351. while( item )
  352. {
  353. if( !selectedOnly || item->IsSelected() )
  354. doReplace( item, currentSheet, data );
  355. item = nextMatch( m_frame->GetScreen(), currentSheet, item, data, false );
  356. }
  357. }
  358. else
  359. {
  360. SCH_SHEET_LIST allSheets = m_frame->Schematic().BuildUnorderedSheetList();
  361. SCH_SCREENS screens( m_frame->Schematic().Root() );
  362. for( SCH_SCREEN* screen = screens.GetFirst(); screen; screen = screens.GetNext() )
  363. {
  364. SCH_SHEET_LIST sheets = allSheets.FindAllSheetsForScreen( screen );
  365. for( unsigned ii = 0; ii < sheets.size(); ++ii )
  366. {
  367. SCH_ITEM* item = nextMatch( screen, &sheets[ii], nullptr, data, false );
  368. while( item )
  369. {
  370. if( ii == 0 )
  371. {
  372. doReplace( item, &sheets[0], data );
  373. }
  374. else if( item->Type() == SCH_FIELD_T )
  375. {
  376. SCH_FIELD* field = static_cast<SCH_FIELD*>( item );
  377. if( field->GetParent() && field->GetParent()->Type() == SCH_SYMBOL_T )
  378. {
  379. switch( field->GetId() )
  380. {
  381. case REFERENCE_FIELD:
  382. case VALUE_FIELD:
  383. case FOOTPRINT_FIELD:
  384. // must be handled for each distinct sheet
  385. doReplace( field, &sheets[ii], data );
  386. break;
  387. default:
  388. // handled in first iteration
  389. break;
  390. }
  391. }
  392. }
  393. item = nextMatch( screen, &sheets[ii], item, data, false );
  394. }
  395. }
  396. }
  397. }
  398. if( modified )
  399. {
  400. m_frame->GetCurrentSheet().UpdateAllScreenReferences();
  401. m_frame->OnModify();
  402. }
  403. return 0;
  404. }
  405. void SCH_FIND_REPLACE_TOOL::setTransitions()
  406. {
  407. Go( &SCH_FIND_REPLACE_TOOL::FindAndReplace, ACTIONS::find.MakeEvent() );
  408. Go( &SCH_FIND_REPLACE_TOOL::FindAndReplace, ACTIONS::findAndReplace.MakeEvent() );
  409. Go( &SCH_FIND_REPLACE_TOOL::FindNext, ACTIONS::findNext.MakeEvent() );
  410. Go( &SCH_FIND_REPLACE_TOOL::FindNext, ACTIONS::findPrevious.MakeEvent() );
  411. Go( &SCH_FIND_REPLACE_TOOL::FindNext, ACTIONS::findNextMarker.MakeEvent() );
  412. Go( &SCH_FIND_REPLACE_TOOL::ReplaceAndFindNext, ACTIONS::replaceAndFindNext.MakeEvent() );
  413. Go( &SCH_FIND_REPLACE_TOOL::ReplaceAll, ACTIONS::replaceAll.MakeEvent() );
  414. Go( &SCH_FIND_REPLACE_TOOL::UpdateFind, ACTIONS::updateFind.MakeEvent() );
  415. Go( &SCH_FIND_REPLACE_TOOL::UpdateFind, EVENTS::SelectedItemsModified );
  416. Go( &SCH_FIND_REPLACE_TOOL::UpdateFind, EVENTS::PointSelectedEvent );
  417. Go( &SCH_FIND_REPLACE_TOOL::UpdateFind, EVENTS::SelectedEvent );
  418. Go( &SCH_FIND_REPLACE_TOOL::UpdateFind, EVENTS::UnselectedEvent );
  419. Go( &SCH_FIND_REPLACE_TOOL::UpdateFind, EVENTS::ClearedEvent );
  420. }