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.

642 lines
19 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 <eda_item.h>
  31. #include <base_units.h>
  32. #define WX_DATAVIEW_WINDOW_PADDING 6
  33. wxString RC_ITEM::GetErrorMessage() const
  34. {
  35. if( m_errorMessage.IsEmpty() )
  36. return GetErrorText();
  37. else
  38. return m_errorMessage;
  39. }
  40. static wxString showCoord( UNITS_PROVIDER* aUnitsProvider, const VECTOR2I& aPos )
  41. {
  42. return wxString::Format( wxT( "@(%s, %s)" ),
  43. aUnitsProvider->MessageTextFromValue( aPos.x ),
  44. aUnitsProvider->MessageTextFromValue( aPos.y ) );
  45. }
  46. void RC_ITEM::AddItem( EDA_ITEM* aItem )
  47. {
  48. m_ids.push_back( aItem->m_Uuid );
  49. }
  50. void RC_ITEM::SetItems( const EDA_ITEM* aItem, const EDA_ITEM* bItem,
  51. const EDA_ITEM* cItem, const EDA_ITEM* dItem )
  52. {
  53. m_ids.clear();
  54. m_ids.push_back( aItem->m_Uuid );
  55. if( bItem )
  56. m_ids.push_back( bItem->m_Uuid );
  57. if( cItem )
  58. m_ids.push_back( cItem->m_Uuid );
  59. if( dItem )
  60. m_ids.push_back( dItem->m_Uuid );
  61. }
  62. wxString RC_ITEM::ShowReport( UNITS_PROVIDER* aUnitsProvider, SEVERITY aSeverity,
  63. const std::map<KIID, EDA_ITEM*>& aItemMap ) const
  64. {
  65. wxString severity;
  66. switch( aSeverity )
  67. {
  68. case RPT_SEVERITY_ERROR: severity = wxT( "Severity: error" ); break;
  69. case RPT_SEVERITY_WARNING: severity = wxT( "Severity: warning" ); break;
  70. case RPT_SEVERITY_ACTION: severity = wxT( "Severity: action" ); break;
  71. case RPT_SEVERITY_INFO: severity = wxT( "Severity: info" ); break;
  72. case RPT_SEVERITY_EXCLUSION: severity = wxT( "Severity: exclusion" ); break;
  73. case RPT_SEVERITY_DEBUG: severity = wxT( "Severity: debug" ); break;
  74. default: ;
  75. };
  76. if( m_parent && m_parent->IsExcluded() )
  77. severity += wxT( " (excluded)" );
  78. EDA_ITEM* mainItem = nullptr;
  79. EDA_ITEM* auxItem = nullptr;
  80. auto ii = aItemMap.find( GetMainItemID() );
  81. if( ii != aItemMap.end() )
  82. mainItem = ii->second;
  83. ii = aItemMap.find( GetAuxItemID() );
  84. if( ii != aItemMap.end() )
  85. auxItem = ii->second;
  86. // Note: some customers machine-process these. So:
  87. // 1) don't translate
  88. // 2) try not to re-order or change syntax
  89. // 3) report settings key (which should be more stable) in addition to message
  90. if( mainItem && auxItem )
  91. {
  92. return wxString::Format( wxT( "[%s]: %s\n %s; %s\n %s: %s\n %s: %s\n" ),
  93. GetSettingsKey(),
  94. GetErrorMessage(),
  95. GetViolatingRuleDesc(),
  96. severity,
  97. showCoord( aUnitsProvider, mainItem->GetPosition()),
  98. mainItem->GetSelectMenuText( aUnitsProvider ),
  99. showCoord( aUnitsProvider, auxItem->GetPosition()),
  100. auxItem->GetSelectMenuText( aUnitsProvider ) );
  101. }
  102. else if( mainItem )
  103. {
  104. return wxString::Format( wxT( "[%s]: %s\n %s; %s\n %s: %s\n" ),
  105. GetSettingsKey(),
  106. GetErrorMessage(),
  107. GetViolatingRuleDesc(),
  108. severity,
  109. showCoord( aUnitsProvider, mainItem->GetPosition()),
  110. mainItem->GetSelectMenuText( aUnitsProvider ) );
  111. }
  112. else
  113. {
  114. return wxString::Format( wxT( "[%s]: %s\n %s; %s\n" ),
  115. GetSettingsKey(),
  116. GetErrorMessage(),
  117. GetViolatingRuleDesc(),
  118. severity );
  119. }
  120. }
  121. KIID RC_TREE_MODEL::ToUUID( wxDataViewItem aItem )
  122. {
  123. const RC_TREE_NODE* node = RC_TREE_MODEL::ToNode( aItem );
  124. if( node && node->m_RcItem )
  125. {
  126. const std::shared_ptr<RC_ITEM> rc_item = node->m_RcItem;
  127. switch( node->m_Type )
  128. {
  129. case RC_TREE_NODE::MARKER:
  130. // rc_item->GetParent() can be null, if the parent is not existing
  131. // when a RC item has no corresponding ERC/DRC marker
  132. if( rc_item->GetParent() )
  133. return rc_item->GetParent()->GetUUID();
  134. break;
  135. case RC_TREE_NODE::MAIN_ITEM: return rc_item->GetMainItemID();
  136. case RC_TREE_NODE::AUX_ITEM: return rc_item->GetAuxItemID();
  137. case RC_TREE_NODE::AUX_ITEM2: return rc_item->GetAuxItem2ID();
  138. case RC_TREE_NODE::AUX_ITEM3: return rc_item->GetAuxItem3ID();
  139. }
  140. }
  141. return niluuid;
  142. }
  143. RC_TREE_MODEL::RC_TREE_MODEL( EDA_DRAW_FRAME* aParentFrame, wxDataViewCtrl* aView ) :
  144. m_editFrame( aParentFrame ),
  145. m_view( aView ),
  146. m_severities( 0 ),
  147. m_rcItemsProvider( nullptr )
  148. {
  149. m_view->GetMainWindow()->Connect( wxEVT_SIZE, wxSizeEventHandler( RC_TREE_MODEL::onSizeView ),
  150. nullptr, this );
  151. }
  152. RC_TREE_MODEL::~RC_TREE_MODEL()
  153. {
  154. for( RC_TREE_NODE* topLevelNode : m_tree )
  155. delete topLevelNode;
  156. }
  157. void RC_TREE_MODEL::rebuildModel( std::shared_ptr<RC_ITEMS_PROVIDER> aProvider, int aSeverities )
  158. {
  159. wxWindowUpdateLocker updateLock( m_view );
  160. std::shared_ptr<RC_ITEM> selectedRcItem = nullptr;
  161. if( m_view )
  162. {
  163. RC_TREE_NODE* selectedNode = ToNode( m_view->GetSelection() );
  164. selectedRcItem = selectedNode ? selectedNode->m_RcItem : nullptr;
  165. // Even with the updateLock, wxWidgets sometimes ties its knickers in a knot trying
  166. // to run a wxdataview_selection_changed_callback() on a row that has been deleted.
  167. m_view->UnselectAll();
  168. }
  169. BeforeReset();
  170. m_rcItemsProvider = aProvider;
  171. if( aSeverities != m_severities )
  172. m_severities = aSeverities;
  173. if( m_rcItemsProvider )
  174. m_rcItemsProvider->SetSeverities( m_severities );
  175. for( RC_TREE_NODE* topLevelNode : m_tree )
  176. delete topLevelNode;
  177. m_tree.clear();
  178. // wxDataView::ExpandAll() pukes with large lists
  179. int count = 0;
  180. if( m_rcItemsProvider )
  181. count = std::min( 1000, m_rcItemsProvider->GetCount() );
  182. for( int i = 0; i < count; ++i )
  183. {
  184. std::shared_ptr<RC_ITEM> rcItem = m_rcItemsProvider->GetItem( i );
  185. m_tree.push_back( new RC_TREE_NODE( nullptr, rcItem, RC_TREE_NODE::MARKER ) );
  186. RC_TREE_NODE* n = m_tree.back();
  187. if( rcItem->GetMainItemID() != niluuid )
  188. n->m_Children.push_back( new RC_TREE_NODE( n, rcItem, RC_TREE_NODE::MAIN_ITEM ) );
  189. if( rcItem->GetAuxItemID() != niluuid )
  190. n->m_Children.push_back( new RC_TREE_NODE( n, rcItem, RC_TREE_NODE::AUX_ITEM ) );
  191. if( rcItem->GetAuxItem2ID() != niluuid )
  192. n->m_Children.push_back( new RC_TREE_NODE( n, rcItem, RC_TREE_NODE::AUX_ITEM2 ) );
  193. if( rcItem->GetAuxItem3ID() != niluuid )
  194. n->m_Children.push_back( new RC_TREE_NODE( n, rcItem, RC_TREE_NODE::AUX_ITEM3 ) );
  195. }
  196. // Must be called after a significant change of items to force the
  197. // wxDataViewModel to reread all of them, repopulating itself entirely.
  198. AfterReset();
  199. #ifdef __WXGTK__
  200. // The fastest method to update wxDataViewCtrl is to rebuild from
  201. // scratch by calling Cleared(). Linux requires to reassociate model to
  202. // display data, but Windows will create multiple associations.
  203. // On MacOS, this crashes KiCad. See https://gitlab.com/kicad/code/kicad/issues/3666
  204. // and https://gitlab.com/kicad/code/kicad/issues/3653
  205. m_view->AssociateModel( this );
  206. #endif
  207. m_view->ClearColumns();
  208. int width = m_view->GetMainWindow()->GetRect().GetWidth() - WX_DATAVIEW_WINDOW_PADDING;
  209. m_view->AppendTextColumn( wxEmptyString, 0, wxDATAVIEW_CELL_INERT, width );
  210. ExpandAll();
  211. // Most annoyingly wxWidgets won't tell us the scroll position (and no, all the usual
  212. // routines don't work), so we can only restore the scroll position based on a selection.
  213. if( selectedRcItem )
  214. {
  215. for( RC_TREE_NODE* candidate : m_tree )
  216. {
  217. if( candidate->m_RcItem == selectedRcItem )
  218. {
  219. m_view->Select( ToItem( candidate ) );
  220. m_view->EnsureVisible( ToItem( candidate ) );
  221. break;
  222. }
  223. }
  224. }
  225. }
  226. void RC_TREE_MODEL::Update( std::shared_ptr<RC_ITEMS_PROVIDER> aProvider, int aSeverities )
  227. {
  228. rebuildModel( aProvider, aSeverities );
  229. }
  230. void RC_TREE_MODEL::ExpandAll()
  231. {
  232. for( RC_TREE_NODE* topLevelNode : m_tree )
  233. m_view->Expand( ToItem( topLevelNode ) );
  234. }
  235. bool RC_TREE_MODEL::IsContainer( wxDataViewItem const& aItem ) const
  236. {
  237. if( ToNode( aItem ) == nullptr ) // must be tree root...
  238. return true;
  239. else
  240. return ToNode( aItem )->m_Type == RC_TREE_NODE::MARKER;
  241. }
  242. wxDataViewItem RC_TREE_MODEL::GetParent( wxDataViewItem const& aItem ) const
  243. {
  244. return ToItem( ToNode( aItem)->m_Parent );
  245. }
  246. unsigned int RC_TREE_MODEL::GetChildren( wxDataViewItem const& aItem,
  247. wxDataViewItemArray& aChildren ) const
  248. {
  249. const RC_TREE_NODE* node = ToNode( aItem );
  250. const std::vector<RC_TREE_NODE*>& children = node ? node->m_Children : m_tree;
  251. for( const RC_TREE_NODE* child: children )
  252. aChildren.push_back( ToItem( child ) );
  253. return children.size();
  254. }
  255. void RC_TREE_MODEL::GetValue( wxVariant& aVariant,
  256. wxDataViewItem const& aItem,
  257. unsigned int aCol ) const
  258. {
  259. const RC_TREE_NODE* node = ToNode( aItem );
  260. const std::shared_ptr<RC_ITEM> rcItem = node->m_RcItem;
  261. switch( node->m_Type )
  262. {
  263. case RC_TREE_NODE::MARKER:
  264. {
  265. wxString prefix;
  266. if( rcItem->GetParent() )
  267. {
  268. SEVERITY severity = rcItem->GetParent()->GetSeverity();
  269. if( severity == RPT_SEVERITY_EXCLUSION )
  270. {
  271. if( m_editFrame->GetSeverity( rcItem->GetErrorCode() ) == RPT_SEVERITY_WARNING )
  272. prefix = _( "Excluded warning: " );
  273. else
  274. prefix = _( "Excluded error: " );
  275. }
  276. else if( severity == RPT_SEVERITY_WARNING )
  277. {
  278. prefix = _( "Warning: " );
  279. }
  280. else
  281. {
  282. prefix = _( "Error: " );
  283. }
  284. }
  285. aVariant = prefix + rcItem->GetErrorMessage();
  286. }
  287. break;
  288. case RC_TREE_NODE::MAIN_ITEM:
  289. if( rcItem->GetParent() && rcItem->GetParent()->GetMarkerType() == MARKER_BASE::MARKER_DRAWING_SHEET )
  290. {
  291. aVariant = _( "Drawing Sheet" );
  292. break;
  293. }
  294. else
  295. {
  296. EDA_ITEM* item = m_editFrame->GetItem( rcItem->GetMainItemID() );
  297. aVariant = item->GetSelectMenuText( m_editFrame );
  298. }
  299. break;
  300. case RC_TREE_NODE::AUX_ITEM:
  301. {
  302. EDA_ITEM* item = m_editFrame->GetItem( rcItem->GetAuxItemID() );
  303. aVariant = item->GetSelectMenuText( m_editFrame );
  304. }
  305. break;
  306. case RC_TREE_NODE::AUX_ITEM2:
  307. {
  308. EDA_ITEM* item = m_editFrame->GetItem( rcItem->GetAuxItem2ID() );
  309. aVariant = item->GetSelectMenuText( m_editFrame );
  310. }
  311. break;
  312. case RC_TREE_NODE::AUX_ITEM3:
  313. {
  314. EDA_ITEM* item = m_editFrame->GetItem( rcItem->GetAuxItem3ID() );
  315. aVariant = item->GetSelectMenuText( m_editFrame );
  316. }
  317. break;
  318. }
  319. }
  320. bool RC_TREE_MODEL::GetAttr( wxDataViewItem const& aItem,
  321. unsigned int aCol,
  322. wxDataViewItemAttr& aAttr ) const
  323. {
  324. const RC_TREE_NODE* node = ToNode( aItem );
  325. wxASSERT( node );
  326. bool ret = false;
  327. bool heading = node->m_Type == RC_TREE_NODE::MARKER;
  328. if( heading )
  329. {
  330. aAttr.SetBold( true );
  331. ret = true;
  332. }
  333. if( node->m_RcItem->GetParent()
  334. && node->m_RcItem->GetParent()->GetSeverity() == RPT_SEVERITY_EXCLUSION )
  335. {
  336. wxColour textColour = wxSystemSettings::GetColour( wxSYS_COLOUR_LISTBOXTEXT );
  337. double brightness = KIGFX::COLOR4D( textColour ).GetBrightness();
  338. if( brightness > 0.5 )
  339. {
  340. int lightness = static_cast<int>( brightness * ( heading ? 50 : 60 ) );
  341. aAttr.SetColour( textColour.ChangeLightness( lightness ) );
  342. }
  343. else
  344. {
  345. aAttr.SetColour( textColour.ChangeLightness( heading ? 170 : 165 ) );
  346. }
  347. aAttr.SetItalic( true ); // Strikethrough would be better, if wxWidgets supported it
  348. ret = true;
  349. }
  350. return ret;
  351. }
  352. void RC_TREE_MODEL::ValueChanged( const RC_TREE_NODE* aNode )
  353. {
  354. if( aNode->m_Type == RC_TREE_NODE::MAIN_ITEM || aNode->m_Type == RC_TREE_NODE::AUX_ITEM )
  355. {
  356. ValueChanged( aNode->m_Parent );
  357. }
  358. if( aNode->m_Type == RC_TREE_NODE::MARKER )
  359. {
  360. wxDataViewModel::ValueChanged( ToItem( aNode ), 0 );
  361. for( const RC_TREE_NODE* child : aNode->m_Children )
  362. wxDataViewModel::ValueChanged( ToItem( child ), 0 );
  363. }
  364. }
  365. void RC_TREE_MODEL::DeleteCurrentItem( bool aDeep )
  366. {
  367. DeleteItems( true, true, aDeep );
  368. }
  369. void RC_TREE_MODEL::DeleteItems( bool aCurrentOnly, bool aIncludeExclusions, bool aDeep )
  370. {
  371. RC_TREE_NODE* current_node = m_view ? ToNode( m_view->GetCurrentItem() ) : nullptr;
  372. const std::shared_ptr<RC_ITEM> current_item = current_node ? current_node->m_RcItem : nullptr;
  373. /// Keep a vector of elements to free after wxWidgets is definitely done accessing them
  374. std::vector<RC_TREE_NODE*> to_delete;
  375. if( aCurrentOnly && !current_item )
  376. {
  377. wxBell();
  378. return;
  379. }
  380. int lastGood = -1;
  381. bool itemDeleted = false;
  382. if( m_view )
  383. {
  384. m_view->UnselectAll();
  385. wxSafeYield();
  386. m_view->Freeze();
  387. }
  388. if( !m_rcItemsProvider )
  389. return;
  390. for( int i = m_rcItemsProvider->GetCount() - 1; i >= 0; --i )
  391. {
  392. std::shared_ptr<RC_ITEM> rcItem = m_rcItemsProvider->GetItem( i );
  393. MARKER_BASE* marker = rcItem->GetParent();
  394. bool excluded = false;
  395. if( marker && marker->GetSeverity() == RPT_SEVERITY_EXCLUSION )
  396. excluded = true;
  397. if( aCurrentOnly && itemDeleted && lastGood >= 0 )
  398. break;
  399. if( aCurrentOnly && rcItem != current_item )
  400. {
  401. lastGood = i;
  402. continue;
  403. }
  404. if( excluded && !aIncludeExclusions )
  405. continue;
  406. if( i < (int) m_tree.size() ) // Careful; tree is truncated for large datasets
  407. {
  408. wxDataViewItem markerItem = ToItem( m_tree[i] );
  409. wxDataViewItemArray childItems;
  410. wxDataViewItem parentItem = ToItem( m_tree[i]->m_Parent );
  411. for( RC_TREE_NODE* child : m_tree[i]->m_Children )
  412. {
  413. childItems.push_back( ToItem( child ) );
  414. to_delete.push_back( child );
  415. }
  416. m_tree[i]->m_Children.clear();
  417. ItemsDeleted( markerItem, childItems );
  418. to_delete.push_back( m_tree[i] );
  419. m_tree.erase( m_tree.begin() + i );
  420. ItemDeleted( parentItem, markerItem );
  421. }
  422. // Only deep delete the current item here; others will be done by the caller, which
  423. // can more efficiently delete all markers on the board.
  424. m_rcItemsProvider->DeleteItem( i, aDeep && aCurrentOnly );
  425. if( lastGood > i )
  426. lastGood--;
  427. itemDeleted = true;
  428. }
  429. if( m_view && aCurrentOnly && lastGood >= 0 )
  430. m_view->Select( ToItem( m_tree[ lastGood ] ) );
  431. for( RC_TREE_NODE* item : to_delete )
  432. delete( item );
  433. if( m_view )
  434. m_view->Thaw();
  435. }
  436. void RC_TREE_MODEL::PrevMarker()
  437. {
  438. RC_TREE_NODE* currentNode = ToNode( m_view->GetCurrentItem() );
  439. RC_TREE_NODE* prevMarker = nullptr;
  440. while( currentNode && currentNode->m_Type != RC_TREE_NODE::MARKER )
  441. currentNode = currentNode->m_Parent;
  442. for( RC_TREE_NODE* candidate : m_tree )
  443. {
  444. if( candidate == currentNode )
  445. break;
  446. else
  447. prevMarker = candidate;
  448. }
  449. if( prevMarker )
  450. m_view->Select( ToItem( prevMarker ) );
  451. }
  452. void RC_TREE_MODEL::NextMarker()
  453. {
  454. RC_TREE_NODE* currentNode = ToNode( m_view->GetCurrentItem() );
  455. while( currentNode && currentNode->m_Type != RC_TREE_NODE::MARKER )
  456. currentNode = currentNode->m_Parent;
  457. RC_TREE_NODE* nextMarker = nullptr;
  458. bool trigger = currentNode == nullptr;
  459. for( RC_TREE_NODE* candidate : m_tree )
  460. {
  461. if( candidate == currentNode )
  462. {
  463. trigger = true;
  464. }
  465. else if( trigger )
  466. {
  467. nextMarker = candidate;
  468. break;
  469. }
  470. }
  471. if( nextMarker )
  472. m_view->Select( ToItem( nextMarker ) );
  473. }
  474. void RC_TREE_MODEL::SelectMarker( const MARKER_BASE* aMarker )
  475. {
  476. for( RC_TREE_NODE* candidate : m_tree )
  477. {
  478. if( candidate->m_RcItem->GetParent() == aMarker )
  479. {
  480. m_view->Select( ToItem( candidate ) );
  481. return;
  482. }
  483. }
  484. }
  485. void RC_TREE_MODEL::CenterMarker( const MARKER_BASE* aMarker )
  486. {
  487. for( RC_TREE_NODE* candidate : m_tree )
  488. {
  489. if( candidate->m_RcItem->GetParent() == aMarker )
  490. {
  491. m_view->EnsureVisible( ToItem( candidate ) );
  492. return;
  493. }
  494. }
  495. }
  496. void RC_TREE_MODEL::onSizeView( wxSizeEvent& aEvent )
  497. {
  498. int width = m_view->GetMainWindow()->GetRect().GetWidth() - WX_DATAVIEW_WINDOW_PADDING;
  499. if( m_view->GetColumnCount() > 0 )
  500. m_view->GetColumn( 0 )->SetWidth( width );
  501. // Pass size event to other widgets
  502. aEvent.Skip();
  503. }