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.

585 lines
17 KiB

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