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.

495 lines
16 KiB

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