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.

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