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.

708 lines
21 KiB

4 years ago
4 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2020-2022 KiCad Developers, see AUTHORS.txt for contributors.
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU General Public License
  8. * as published by the Free Software Foundation; either version 2
  9. * of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; if not, you may find one here:
  18. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  19. * or you may search the http://www.gnu.org website for the version 2 license,
  20. * or you may write to the Free Software Foundation, Inc.,
  21. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  22. */
  23. #include <wx/wupdlock.h>
  24. #include <wx/dataview.h>
  25. #include <wx/settings.h>
  26. #include <widgets/ui_common.h>
  27. #include <marker_base.h>
  28. #include <eda_draw_frame.h>
  29. #include <rc_item.h>
  30. #include <rc_json_schema.h>
  31. #include <eda_item.h>
  32. #include <base_units.h>
  33. #define WX_DATAVIEW_WINDOW_PADDING 6
  34. wxString RC_ITEM::GetErrorMessage() const
  35. {
  36. if( m_errorMessage.IsEmpty() )
  37. return GetErrorText();
  38. else
  39. return m_errorMessage;
  40. }
  41. static wxString showCoord( UNITS_PROVIDER* aUnitsProvider, const VECTOR2I& aPos )
  42. {
  43. return wxString::Format( wxT( "@(%s, %s)" ),
  44. aUnitsProvider->MessageTextFromValue( aPos.x ),
  45. aUnitsProvider->MessageTextFromValue( aPos.y ) );
  46. }
  47. void RC_ITEM::AddItem( EDA_ITEM* aItem )
  48. {
  49. m_ids.push_back( aItem->m_Uuid );
  50. }
  51. void RC_ITEM::SetItems( const EDA_ITEM* aItem, const EDA_ITEM* bItem,
  52. const EDA_ITEM* cItem, const EDA_ITEM* dItem )
  53. {
  54. m_ids.clear();
  55. if( aItem )
  56. m_ids.push_back( aItem->m_Uuid );
  57. if( bItem )
  58. m_ids.push_back( bItem->m_Uuid );
  59. if( cItem )
  60. m_ids.push_back( cItem->m_Uuid );
  61. if( dItem )
  62. m_ids.push_back( dItem->m_Uuid );
  63. }
  64. wxString RC_ITEM::getSeverityString( SEVERITY aSeverity )
  65. {
  66. wxString severity;
  67. switch( aSeverity )
  68. {
  69. case RPT_SEVERITY_ERROR: severity = wxS( "error" ); break;
  70. case RPT_SEVERITY_WARNING: severity = wxS( "warning" ); break;
  71. case RPT_SEVERITY_ACTION: severity = wxS( "action" ); break;
  72. case RPT_SEVERITY_INFO: severity = wxS( "info" ); break;
  73. case RPT_SEVERITY_EXCLUSION: severity = wxS( "exclusion" ); break;
  74. case RPT_SEVERITY_DEBUG: severity = wxS( "debug" ); break;
  75. default:;
  76. };
  77. return severity;
  78. }
  79. wxString RC_ITEM::ShowReport( UNITS_PROVIDER* aUnitsProvider, SEVERITY aSeverity,
  80. const std::map<KIID, EDA_ITEM*>& aItemMap ) const
  81. {
  82. wxString severity = getSeverityString( aSeverity );
  83. if( m_parent && m_parent->IsExcluded() )
  84. severity += wxT( " (excluded)" );
  85. EDA_ITEM* mainItem = nullptr;
  86. EDA_ITEM* auxItem = nullptr;
  87. auto ii = aItemMap.find( GetMainItemID() );
  88. if( ii != aItemMap.end() )
  89. mainItem = ii->second;
  90. ii = aItemMap.find( GetAuxItemID() );
  91. if( ii != aItemMap.end() )
  92. auxItem = ii->second;
  93. // Note: some customers machine-process these. So:
  94. // 1) don't translate
  95. // 2) try not to re-order or change syntax
  96. // 3) report settings key (which should be more stable) in addition to message
  97. if( mainItem && auxItem )
  98. {
  99. return wxString::Format( wxT( "[%s]: %s\n %s; %s\n %s: %s\n %s: %s\n" ),
  100. GetSettingsKey(),
  101. GetErrorMessage(),
  102. GetViolatingRuleDesc(),
  103. severity,
  104. showCoord( aUnitsProvider, mainItem->GetPosition()),
  105. mainItem->GetItemDescription( aUnitsProvider ),
  106. showCoord( aUnitsProvider, auxItem->GetPosition()),
  107. auxItem->GetItemDescription( aUnitsProvider ) );
  108. }
  109. else if( mainItem )
  110. {
  111. return wxString::Format( wxT( "[%s]: %s\n %s; %s\n %s: %s\n" ),
  112. GetSettingsKey(),
  113. GetErrorMessage(),
  114. GetViolatingRuleDesc(),
  115. severity,
  116. showCoord( aUnitsProvider, mainItem->GetPosition()),
  117. mainItem->GetItemDescription( aUnitsProvider ) );
  118. }
  119. else
  120. {
  121. return wxString::Format( wxT( "[%s]: %s\n %s; %s\n" ),
  122. GetSettingsKey(),
  123. GetErrorMessage(),
  124. GetViolatingRuleDesc(),
  125. severity );
  126. }
  127. }
  128. void RC_ITEM::GetJsonViolation( RC_JSON::VIOLATION& aViolation, UNITS_PROVIDER* aUnitsProvider,
  129. SEVERITY aSeverity,
  130. const std::map<KIID, EDA_ITEM*>& aItemMap ) const
  131. {
  132. wxString severity = getSeverityString( aSeverity );
  133. aViolation.severity = severity;
  134. aViolation.description = GetErrorMessage();
  135. aViolation.type = GetSettingsKey();
  136. EDA_ITEM* mainItem = nullptr;
  137. EDA_ITEM* auxItem = nullptr;
  138. auto ii = aItemMap.find( GetMainItemID() );
  139. if( ii != aItemMap.end() )
  140. mainItem = ii->second;
  141. ii = aItemMap.find( GetAuxItemID() );
  142. if( ii != aItemMap.end() )
  143. auxItem = ii->second;
  144. if( mainItem )
  145. {
  146. RC_JSON::AFFECTED_ITEM item;
  147. item.description = mainItem->GetItemDescription( aUnitsProvider );
  148. item.uuid = mainItem->m_Uuid.AsString();
  149. item.pos.x = EDA_UNIT_UTILS::UI::ToUserUnit( aUnitsProvider->GetIuScale(),
  150. aUnitsProvider->GetUserUnits(),
  151. mainItem->GetPosition().x );
  152. item.pos.y = EDA_UNIT_UTILS::UI::ToUserUnit( aUnitsProvider->GetIuScale(),
  153. aUnitsProvider->GetUserUnits(),
  154. mainItem->GetPosition().y );
  155. aViolation.items.emplace_back( item );
  156. }
  157. if( auxItem )
  158. {
  159. RC_JSON::AFFECTED_ITEM item;
  160. item.description = auxItem->GetItemDescription( aUnitsProvider );
  161. item.uuid = auxItem->m_Uuid.AsString();
  162. item.pos.x = EDA_UNIT_UTILS::UI::ToUserUnit( aUnitsProvider->GetIuScale(),
  163. aUnitsProvider->GetUserUnits(),
  164. auxItem->GetPosition().x );
  165. item.pos.y = EDA_UNIT_UTILS::UI::ToUserUnit( aUnitsProvider->GetIuScale(),
  166. aUnitsProvider->GetUserUnits(),
  167. auxItem->GetPosition().y );
  168. aViolation.items.emplace_back( item );
  169. }
  170. }
  171. KIID RC_TREE_MODEL::ToUUID( wxDataViewItem aItem )
  172. {
  173. const RC_TREE_NODE* node = RC_TREE_MODEL::ToNode( aItem );
  174. if( node && node->m_RcItem )
  175. {
  176. const std::shared_ptr<RC_ITEM> rc_item = node->m_RcItem;
  177. switch( node->m_Type )
  178. {
  179. case RC_TREE_NODE::MARKER:
  180. // rc_item->GetParent() can be null, if the parent is not existing
  181. // when a RC item has no corresponding ERC/DRC marker
  182. if( rc_item->GetParent() )
  183. return rc_item->GetParent()->GetUUID();
  184. break;
  185. case RC_TREE_NODE::MAIN_ITEM: return rc_item->GetMainItemID();
  186. case RC_TREE_NODE::AUX_ITEM: return rc_item->GetAuxItemID();
  187. case RC_TREE_NODE::AUX_ITEM2: return rc_item->GetAuxItem2ID();
  188. case RC_TREE_NODE::AUX_ITEM3: return rc_item->GetAuxItem3ID();
  189. }
  190. }
  191. return niluuid;
  192. }
  193. RC_TREE_MODEL::RC_TREE_MODEL( EDA_DRAW_FRAME* aParentFrame, wxDataViewCtrl* aView ) :
  194. m_editFrame( aParentFrame ),
  195. m_view( aView ),
  196. m_severities( 0 ),
  197. m_rcItemsProvider( nullptr )
  198. {
  199. }
  200. RC_TREE_MODEL::~RC_TREE_MODEL()
  201. {
  202. for( RC_TREE_NODE* topLevelNode : m_tree )
  203. delete topLevelNode;
  204. }
  205. void RC_TREE_MODEL::rebuildModel( std::shared_ptr<RC_ITEMS_PROVIDER> aProvider, int aSeverities )
  206. {
  207. wxWindowUpdateLocker updateLock( m_view );
  208. std::shared_ptr<RC_ITEM> selectedRcItem = nullptr;
  209. if( m_view )
  210. {
  211. RC_TREE_NODE* selectedNode = ToNode( m_view->GetSelection() );
  212. selectedRcItem = selectedNode ? selectedNode->m_RcItem : nullptr;
  213. // Even with the updateLock, wxWidgets sometimes ties its knickers in a knot trying
  214. // to run a wxdataview_selection_changed_callback() on a row that has been deleted.
  215. m_view->UnselectAll();
  216. }
  217. BeforeReset();
  218. m_rcItemsProvider = std::move( aProvider );
  219. if( aSeverities != m_severities )
  220. m_severities = aSeverities;
  221. if( m_rcItemsProvider )
  222. m_rcItemsProvider->SetSeverities( m_severities );
  223. for( RC_TREE_NODE* topLevelNode : m_tree )
  224. delete topLevelNode;
  225. m_tree.clear();
  226. // wxDataView::ExpandAll() pukes with large lists
  227. int count = 0;
  228. if( m_rcItemsProvider )
  229. count = std::min( 1000, m_rcItemsProvider->GetCount() );
  230. for( int i = 0; i < count; ++i )
  231. {
  232. std::shared_ptr<RC_ITEM> rcItem = m_rcItemsProvider->GetItem( i );
  233. m_tree.push_back( new RC_TREE_NODE( nullptr, rcItem, RC_TREE_NODE::MARKER ) );
  234. RC_TREE_NODE* n = m_tree.back();
  235. if( rcItem->GetMainItemID() != niluuid )
  236. n->m_Children.push_back( new RC_TREE_NODE( n, rcItem, RC_TREE_NODE::MAIN_ITEM ) );
  237. if( rcItem->GetAuxItemID() != niluuid )
  238. n->m_Children.push_back( new RC_TREE_NODE( n, rcItem, RC_TREE_NODE::AUX_ITEM ) );
  239. if( rcItem->GetAuxItem2ID() != niluuid )
  240. n->m_Children.push_back( new RC_TREE_NODE( n, rcItem, RC_TREE_NODE::AUX_ITEM2 ) );
  241. if( rcItem->GetAuxItem3ID() != niluuid )
  242. n->m_Children.push_back( new RC_TREE_NODE( n, rcItem, RC_TREE_NODE::AUX_ITEM3 ) );
  243. }
  244. // Must be called after a significant change of items to force the
  245. // wxDataViewModel to reread all of them, repopulating itself entirely.
  246. AfterReset();
  247. #ifdef __WXGTK__
  248. // The fastest method to update wxDataViewCtrl is to rebuild from
  249. // scratch by calling Cleared(). Linux requires to reassociate model to
  250. // display data, but Windows will create multiple associations.
  251. // On MacOS, this crashes KiCad. See https://gitlab.com/kicad/code/kicad/-/issues/3666
  252. // and https://gitlab.com/kicad/code/kicad/-/issues/3653
  253. m_view->AssociateModel( this );
  254. #endif
  255. m_view->ClearColumns();
  256. m_view->AppendTextColumn( wxEmptyString, 0, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE );
  257. ExpandAll();
  258. // Most annoyingly wxWidgets won't tell us the scroll position (and no, all the usual
  259. // routines don't work), so we can only restore the scroll position based on a selection.
  260. if( selectedRcItem )
  261. {
  262. for( RC_TREE_NODE* candidate : m_tree )
  263. {
  264. if( candidate->m_RcItem == selectedRcItem )
  265. {
  266. m_view->Select( ToItem( candidate ) );
  267. m_view->EnsureVisible( ToItem( candidate ) );
  268. break;
  269. }
  270. }
  271. }
  272. }
  273. void RC_TREE_MODEL::Update( std::shared_ptr<RC_ITEMS_PROVIDER> aProvider, int aSeverities )
  274. {
  275. rebuildModel( aProvider, aSeverities );
  276. }
  277. void RC_TREE_MODEL::ExpandAll()
  278. {
  279. for( RC_TREE_NODE* topLevelNode : m_tree )
  280. m_view->Expand( ToItem( topLevelNode ) );
  281. }
  282. bool RC_TREE_MODEL::IsContainer( wxDataViewItem const& aItem ) const
  283. {
  284. if( ToNode( aItem ) == nullptr ) // must be tree root...
  285. return true;
  286. else
  287. return ToNode( aItem )->m_Type == RC_TREE_NODE::MARKER;
  288. }
  289. wxDataViewItem RC_TREE_MODEL::GetParent( wxDataViewItem const& aItem ) const
  290. {
  291. return ToItem( ToNode( aItem)->m_Parent );
  292. }
  293. unsigned int RC_TREE_MODEL::GetChildren( wxDataViewItem const& aItem,
  294. wxDataViewItemArray& aChildren ) const
  295. {
  296. const RC_TREE_NODE* node = ToNode( aItem );
  297. const std::vector<RC_TREE_NODE*>& children = node ? node->m_Children : m_tree;
  298. for( const RC_TREE_NODE* child: children )
  299. aChildren.push_back( ToItem( child ) );
  300. return children.size();
  301. }
  302. void RC_TREE_MODEL::GetValue( wxVariant& aVariant,
  303. wxDataViewItem const& aItem,
  304. unsigned int aCol ) const
  305. {
  306. const RC_TREE_NODE* node = ToNode( aItem );
  307. const std::shared_ptr<RC_ITEM> rcItem = node->m_RcItem;
  308. MARKER_BASE* marker = rcItem->GetParent();
  309. EDA_ITEM* item = nullptr;
  310. wxString msg;
  311. switch( node->m_Type )
  312. {
  313. case RC_TREE_NODE::MARKER:
  314. if( marker )
  315. {
  316. SEVERITY severity = marker->GetSeverity();
  317. if( severity == RPT_SEVERITY_EXCLUSION )
  318. {
  319. if( m_editFrame->GetSeverity( rcItem->GetErrorCode() ) == RPT_SEVERITY_WARNING )
  320. msg = _( "Excluded warning: " );
  321. else
  322. msg = _( "Excluded error: " );
  323. }
  324. else if( severity == RPT_SEVERITY_WARNING )
  325. {
  326. msg = _( "Warning: " );
  327. }
  328. else
  329. {
  330. msg = _( "Error: " );
  331. }
  332. }
  333. msg += rcItem->GetErrorMessage();
  334. break;
  335. case RC_TREE_NODE::MAIN_ITEM:
  336. if( marker && marker->GetMarkerType() == MARKER_BASE::MARKER_DRAWING_SHEET )
  337. msg = _( "Drawing Sheet" );
  338. else
  339. item = m_editFrame->GetItem( rcItem->GetMainItemID() );
  340. break;
  341. case RC_TREE_NODE::AUX_ITEM:
  342. item = m_editFrame->GetItem( rcItem->GetAuxItemID() );
  343. break;
  344. case RC_TREE_NODE::AUX_ITEM2:
  345. item = m_editFrame->GetItem( rcItem->GetAuxItem2ID() );
  346. break;
  347. case RC_TREE_NODE::AUX_ITEM3:
  348. item = m_editFrame->GetItem( rcItem->GetAuxItem3ID() );
  349. break;
  350. }
  351. if( item )
  352. msg += item->GetItemDescription( m_editFrame );
  353. msg.Replace( wxS( "\n" ), wxS( " " ) );
  354. aVariant = msg;
  355. }
  356. bool RC_TREE_MODEL::GetAttr( wxDataViewItem const& aItem,
  357. unsigned int aCol,
  358. wxDataViewItemAttr& aAttr ) const
  359. {
  360. const RC_TREE_NODE* node = ToNode( aItem );
  361. wxASSERT( node );
  362. bool ret = false;
  363. bool heading = node->m_Type == RC_TREE_NODE::MARKER;
  364. if( heading )
  365. {
  366. aAttr.SetBold( true );
  367. ret = true;
  368. }
  369. if( node->m_RcItem->GetParent()
  370. && node->m_RcItem->GetParent()->GetSeverity() == RPT_SEVERITY_EXCLUSION )
  371. {
  372. wxColour textColour = wxSystemSettings::GetColour( wxSYS_COLOUR_LISTBOXTEXT );
  373. double brightness = KIGFX::COLOR4D( textColour ).GetBrightness();
  374. if( brightness > 0.5 )
  375. {
  376. int lightness = static_cast<int>( brightness * ( heading ? 50 : 60 ) );
  377. aAttr.SetColour( textColour.ChangeLightness( lightness ) );
  378. }
  379. else
  380. {
  381. aAttr.SetColour( textColour.ChangeLightness( heading ? 170 : 165 ) );
  382. }
  383. aAttr.SetItalic( true ); // Strikethrough would be better, if wxWidgets supported it
  384. ret = true;
  385. }
  386. return ret;
  387. }
  388. void RC_TREE_MODEL::ValueChanged( const RC_TREE_NODE* aNode )
  389. {
  390. if( aNode->m_Type == RC_TREE_NODE::MAIN_ITEM || aNode->m_Type == RC_TREE_NODE::AUX_ITEM )
  391. {
  392. ValueChanged( aNode->m_Parent );
  393. }
  394. if( aNode->m_Type == RC_TREE_NODE::MARKER )
  395. {
  396. wxDataViewModel::ValueChanged( ToItem( aNode ), 0 );
  397. for( const RC_TREE_NODE* child : aNode->m_Children )
  398. wxDataViewModel::ValueChanged( ToItem( child ), 0 );
  399. }
  400. }
  401. void RC_TREE_MODEL::DeleteCurrentItem( bool aDeep )
  402. {
  403. DeleteItems( true, true, aDeep );
  404. }
  405. void RC_TREE_MODEL::DeleteItems( bool aCurrentOnly, bool aIncludeExclusions, bool aDeep )
  406. {
  407. RC_TREE_NODE* current_node = m_view ? ToNode( m_view->GetCurrentItem() ) : nullptr;
  408. const std::shared_ptr<RC_ITEM> current_item = current_node ? current_node->m_RcItem : nullptr;
  409. /// Keep a vector of elements to free after wxWidgets is definitely done accessing them
  410. std::vector<RC_TREE_NODE*> to_delete;
  411. std::vector<RC_TREE_NODE*> expanded;
  412. if( aCurrentOnly && !current_item )
  413. {
  414. wxBell();
  415. return;
  416. }
  417. // wxWidgets 3.1.x on MacOS (at least) loses the expanded state of the tree when deleting
  418. // items.
  419. if( m_view && aCurrentOnly )
  420. {
  421. for( RC_TREE_NODE* node : m_tree )
  422. {
  423. if( m_view->IsExpanded( ToItem( node ) ) )
  424. expanded.push_back( node );
  425. }
  426. }
  427. int lastGood = -1;
  428. bool itemDeleted = false;
  429. if( m_view )
  430. {
  431. m_view->UnselectAll();
  432. wxSafeYield();
  433. m_view->Freeze();
  434. }
  435. if( !m_rcItemsProvider )
  436. return;
  437. for( int i = m_rcItemsProvider->GetCount() - 1; i >= 0; --i )
  438. {
  439. std::shared_ptr<RC_ITEM> rcItem = m_rcItemsProvider->GetItem( i );
  440. MARKER_BASE* marker = rcItem->GetParent();
  441. bool excluded = false;
  442. if( marker && marker->GetSeverity() == RPT_SEVERITY_EXCLUSION )
  443. excluded = true;
  444. if( aCurrentOnly && itemDeleted && lastGood >= 0 )
  445. break;
  446. if( aCurrentOnly && rcItem != current_item )
  447. {
  448. lastGood = i;
  449. continue;
  450. }
  451. if( excluded && !aIncludeExclusions )
  452. continue;
  453. if( i < (int) m_tree.size() ) // Careful; tree is truncated for large datasets
  454. {
  455. wxDataViewItem markerItem = ToItem( m_tree[i] );
  456. wxDataViewItemArray childItems;
  457. wxDataViewItem parentItem = ToItem( m_tree[i]->m_Parent );
  458. for( RC_TREE_NODE* child : m_tree[i]->m_Children )
  459. {
  460. childItems.push_back( ToItem( child ) );
  461. to_delete.push_back( child );
  462. }
  463. m_tree[i]->m_Children.clear();
  464. ItemsDeleted( markerItem, childItems );
  465. to_delete.push_back( m_tree[i] );
  466. m_tree.erase( m_tree.begin() + i );
  467. ItemDeleted( parentItem, markerItem );
  468. }
  469. // Only deep delete the current item here; others will be done by the caller, which
  470. // can more efficiently delete all markers on the board.
  471. m_rcItemsProvider->DeleteItem( i, aDeep && aCurrentOnly );
  472. if( lastGood > i )
  473. lastGood--;
  474. itemDeleted = true;
  475. }
  476. if( m_view && aCurrentOnly && lastGood >= 0 )
  477. {
  478. for( RC_TREE_NODE* node : expanded )
  479. {
  480. wxDataViewItem item = ToItem( node );
  481. if( item.IsOk() )
  482. m_view->Expand( item );
  483. }
  484. wxDataViewItem selItem = ToItem( m_tree[ lastGood ] );
  485. m_view->Select( selItem );
  486. // wxEVT_COMMAND_DATAVIEW_SELECTION_CHANGED doesn't get propogated from the Select()
  487. // call on (at least) MSW.
  488. wxDataViewEvent selectEvent( wxEVT_COMMAND_DATAVIEW_SELECTION_CHANGED, m_view, selItem );
  489. m_view->GetEventHandler()->ProcessEvent( selectEvent );
  490. }
  491. for( RC_TREE_NODE* item : to_delete )
  492. delete( item );
  493. if( m_view )
  494. m_view->Thaw();
  495. }
  496. void RC_TREE_MODEL::PrevMarker()
  497. {
  498. RC_TREE_NODE* currentNode = ToNode( m_view->GetCurrentItem() );
  499. RC_TREE_NODE* prevMarker = nullptr;
  500. while( currentNode && currentNode->m_Type != RC_TREE_NODE::MARKER )
  501. currentNode = currentNode->m_Parent;
  502. for( RC_TREE_NODE* candidate : m_tree )
  503. {
  504. if( candidate == currentNode )
  505. break;
  506. else
  507. prevMarker = candidate;
  508. }
  509. if( prevMarker )
  510. m_view->Select( ToItem( prevMarker ) );
  511. }
  512. void RC_TREE_MODEL::NextMarker()
  513. {
  514. RC_TREE_NODE* currentNode = ToNode( m_view->GetCurrentItem() );
  515. while( currentNode && currentNode->m_Type != RC_TREE_NODE::MARKER )
  516. currentNode = currentNode->m_Parent;
  517. RC_TREE_NODE* nextMarker = nullptr;
  518. bool trigger = currentNode == nullptr;
  519. for( RC_TREE_NODE* candidate : m_tree )
  520. {
  521. if( candidate == currentNode )
  522. {
  523. trigger = true;
  524. }
  525. else if( trigger )
  526. {
  527. nextMarker = candidate;
  528. break;
  529. }
  530. }
  531. if( nextMarker )
  532. m_view->Select( ToItem( nextMarker ) );
  533. }
  534. void RC_TREE_MODEL::SelectMarker( const MARKER_BASE* aMarker )
  535. {
  536. for( RC_TREE_NODE* candidate : m_tree )
  537. {
  538. if( candidate->m_RcItem->GetParent() == aMarker )
  539. {
  540. m_view->Select( ToItem( candidate ) );
  541. return;
  542. }
  543. }
  544. }
  545. void RC_TREE_MODEL::CenterMarker( const MARKER_BASE* aMarker )
  546. {
  547. for( RC_TREE_NODE* candidate : m_tree )
  548. {
  549. if( candidate->m_RcItem->GetParent() == aMarker )
  550. {
  551. m_view->EnsureVisible( ToItem( candidate ) );
  552. return;
  553. }
  554. }
  555. }