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.

1121 lines
35 KiB

2 years ago
2 years ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
9 months ago
9 months ago
9 months ago
12 months ago
12 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
12 months ago
12 months ago
12 months ago
12 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
9 months ago
9 months ago
9 months ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2023 <author>
  5. * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software: you can redistribute it and/or modify it
  8. * under the terms of the GNU General Public License as published by the
  9. * Free Software Foundation, either version 3 of the License, or (at your
  10. * option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful, but
  13. * WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License along
  18. * with this program. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. #include <wx/string.h>
  21. #include <wx/debug.h>
  22. #include <wx/grid.h>
  23. #include <common.h>
  24. #include <widgets/wx_grid.h>
  25. #include <sch_reference_list.h>
  26. #include <sch_commit.h>
  27. #include <sch_screen.h>
  28. #include "string_utils.h"
  29. #include "fields_data_model.h"
  30. const wxString FIELDS_EDITOR_GRID_DATA_MODEL::QUANTITY_VARIABLE = wxS( "${QUANTITY}" );
  31. const wxString FIELDS_EDITOR_GRID_DATA_MODEL::ITEM_NUMBER_VARIABLE = wxS( "${ITEM_NUMBER}" );
  32. void FIELDS_EDITOR_GRID_DATA_MODEL::AddColumn( const wxString& aFieldName, const wxString& aLabel,
  33. bool aAddedByUser )
  34. {
  35. // Don't add a field twice
  36. if( GetFieldNameCol( aFieldName ) != -1 )
  37. return;
  38. m_cols.push_back( { aFieldName, aLabel, aAddedByUser, false, false } );
  39. for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
  40. {
  41. if( SCH_SYMBOL* symbol = m_symbolsList[i].GetSymbol() )
  42. updateDataStoreSymbolField( *symbol, aFieldName );
  43. }
  44. }
  45. void FIELDS_EDITOR_GRID_DATA_MODEL::updateDataStoreSymbolField( const SCH_SYMBOL& aSymbol,
  46. const wxString& aFieldName )
  47. {
  48. if( isAttribute( aFieldName ) )
  49. {
  50. m_dataStore[aSymbol.m_Uuid][aFieldName] = getAttributeValue( aSymbol, aFieldName );
  51. }
  52. else if( const SCH_FIELD* field = aSymbol.GetField( aFieldName ) )
  53. {
  54. if( field->IsPrivate() )
  55. {
  56. m_dataStore[aSymbol.m_Uuid][aFieldName] = wxEmptyString;
  57. return;
  58. }
  59. wxString value = aSymbol.Schematic()->ConvertKIIDsToRefs( field->GetText() );
  60. m_dataStore[aSymbol.m_Uuid][aFieldName] = value;
  61. }
  62. else if( IsTextVar( aFieldName ) )
  63. {
  64. // Handle fields with variables as names that are not present in the symbol
  65. // by giving them the correct value
  66. m_dataStore[aSymbol.m_Uuid][aFieldName] = aFieldName;
  67. }
  68. else
  69. {
  70. m_dataStore[aSymbol.m_Uuid][aFieldName] = wxEmptyString;
  71. }
  72. }
  73. void FIELDS_EDITOR_GRID_DATA_MODEL::RemoveColumn( int aCol )
  74. {
  75. for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
  76. {
  77. if( SCH_SYMBOL* symbol = m_symbolsList[i].GetSymbol() )
  78. m_dataStore[symbol->m_Uuid].erase( m_cols[aCol].m_fieldName );
  79. }
  80. m_cols.erase( m_cols.begin() + aCol );
  81. }
  82. void FIELDS_EDITOR_GRID_DATA_MODEL::RenameColumn( int aCol, const wxString& newName )
  83. {
  84. for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
  85. {
  86. SCH_SYMBOL* symbol = m_symbolsList[i].GetSymbol();
  87. // Careful; field may have already been renamed from another sheet instance
  88. if( auto node = m_dataStore[symbol->m_Uuid].extract( m_cols[aCol].m_fieldName ) )
  89. {
  90. node.key() = newName;
  91. m_dataStore[symbol->m_Uuid].insert( std::move( node ) );
  92. }
  93. }
  94. m_cols[aCol].m_fieldName = newName;
  95. m_cols[aCol].m_label = newName;
  96. }
  97. int FIELDS_EDITOR_GRID_DATA_MODEL::GetFieldNameCol( const wxString& aFieldName ) const
  98. {
  99. for( size_t i = 0; i < m_cols.size(); i++ )
  100. {
  101. if( m_cols[i].m_fieldName == aFieldName )
  102. return static_cast<int>( i );
  103. }
  104. return -1;
  105. }
  106. std::vector<BOM_FIELD> FIELDS_EDITOR_GRID_DATA_MODEL::GetFieldsOrdered()
  107. {
  108. std::vector<BOM_FIELD> fields;
  109. for( const DATA_MODEL_COL& col : m_cols )
  110. fields.push_back( { col.m_fieldName, col.m_label, col.m_show, col.m_group } );
  111. return fields;
  112. }
  113. void FIELDS_EDITOR_GRID_DATA_MODEL::SetFieldsOrder( const std::vector<wxString>& aNewOrder )
  114. {
  115. size_t foundCount = 0;
  116. wxCHECK( aNewOrder.size() == m_cols.size(), /* void */ );
  117. for( const wxString& newField : aNewOrder )
  118. {
  119. for( size_t i = 0; i < m_cols.size(); i++ )
  120. {
  121. if( m_cols[i].m_fieldName == newField )
  122. {
  123. std::swap( m_cols[foundCount], m_cols[i] );
  124. foundCount++;
  125. }
  126. }
  127. }
  128. }
  129. wxString FIELDS_EDITOR_GRID_DATA_MODEL::GetValue( int aRow, int aCol )
  130. {
  131. if( ColIsReference( aCol ) )
  132. {
  133. // Poor-man's tree controls
  134. if( m_rows[aRow].m_Flag == GROUP_COLLAPSED )
  135. return wxT( "> " ) + GetValue( m_rows[aRow], aCol );
  136. else if( m_rows[aRow].m_Flag == GROUP_EXPANDED )
  137. return wxT( "v " ) + GetValue( m_rows[aRow], aCol );
  138. else if( m_rows[aRow].m_Flag == CHILD_ITEM )
  139. return wxT( " " ) + GetValue( m_rows[aRow], aCol );
  140. else
  141. return wxT( " " ) + GetValue( m_rows[aRow], aCol );
  142. }
  143. else
  144. {
  145. return GetValue( m_rows[aRow], aCol );
  146. }
  147. }
  148. wxGridCellAttr* FIELDS_EDITOR_GRID_DATA_MODEL::GetAttr( int aRow, int aCol,
  149. wxGridCellAttr::wxAttrKind aKind )
  150. {
  151. if( GetColFieldName( aCol ) == GetCanonicalFieldName( FIELD_T::DATASHEET )
  152. || IsURL( GetValue( m_rows[aRow], aCol ) ) )
  153. {
  154. if( m_urlEditor )
  155. {
  156. m_urlEditor->IncRef();
  157. return enhanceAttr( m_urlEditor, aRow, aCol, aKind );
  158. }
  159. }
  160. if( m_colAttrs[aCol] )
  161. {
  162. m_colAttrs[aCol]->IncRef();
  163. return enhanceAttr( m_colAttrs[aCol], aRow, aCol, aKind );
  164. }
  165. return nullptr;
  166. }
  167. wxString FIELDS_EDITOR_GRID_DATA_MODEL::GetValue( const DATA_MODEL_ROW& group, int aCol,
  168. const wxString& refDelimiter,
  169. const wxString& refRangeDelimiter,
  170. bool resolveVars,
  171. bool listMixedValues )
  172. {
  173. std::vector<SCH_REFERENCE> references;
  174. std::set<wxString> mixedValues;
  175. wxString fieldValue;
  176. for( const SCH_REFERENCE& ref : group.m_Refs )
  177. {
  178. if( ColIsReference( aCol ) || ColIsQuantity( aCol ) || ColIsItemNumber( aCol ) )
  179. {
  180. references.push_back( ref );
  181. }
  182. else // Other columns are either a single value or ROW_MULTI_ITEMS
  183. {
  184. const KIID& symbolID = ref.GetSymbol()->m_Uuid;
  185. if( !m_dataStore.count( symbolID )
  186. || !m_dataStore[symbolID].count( m_cols[aCol].m_fieldName ) )
  187. {
  188. return INDETERMINATE_STATE;
  189. }
  190. wxString refFieldValue;
  191. // Only resolve vars on actual variables, otherwise we want to get
  192. // our values out of the datastore so we can show/export un-applied values
  193. if( resolveVars
  194. && ( IsTextVar( m_cols[aCol].m_fieldName )
  195. || IsTextVar( m_dataStore[symbolID][m_cols[aCol].m_fieldName] ) ) )
  196. {
  197. refFieldValue = getFieldShownText( ref, m_cols[aCol].m_fieldName );
  198. }
  199. else
  200. {
  201. refFieldValue = m_dataStore[symbolID][m_cols[aCol].m_fieldName];
  202. }
  203. if( listMixedValues )
  204. mixedValues.insert( refFieldValue );
  205. else if( &ref == &group.m_Refs.front() )
  206. fieldValue = refFieldValue;
  207. else if( fieldValue != refFieldValue )
  208. return INDETERMINATE_STATE;
  209. }
  210. }
  211. if( listMixedValues )
  212. {
  213. fieldValue = wxEmptyString;
  214. for( const wxString& value : mixedValues )
  215. {
  216. if( value.IsEmpty() )
  217. continue;
  218. else if( fieldValue.IsEmpty() )
  219. fieldValue = value;
  220. else
  221. fieldValue += "," + value;
  222. }
  223. }
  224. if( ColIsReference( aCol ) || ColIsQuantity( aCol ) || ColIsItemNumber( aCol ) )
  225. {
  226. // Remove duplicates (other units of multi-unit parts)
  227. std::sort( references.begin(), references.end(),
  228. []( const SCH_REFERENCE& l, const SCH_REFERENCE& r ) -> bool
  229. {
  230. wxString l_ref( l.GetRef() << l.GetRefNumber() );
  231. wxString r_ref( r.GetRef() << r.GetRefNumber() );
  232. return StrNumCmp( l_ref, r_ref, true ) < 0;
  233. } );
  234. auto logicalEnd = std::unique( references.begin(), references.end(),
  235. []( const SCH_REFERENCE& l, const SCH_REFERENCE& r ) -> bool
  236. {
  237. // If unannotated then we can't tell what units belong together
  238. // so we have to leave them all
  239. if( l.GetRefNumber() == wxT( "?" ) )
  240. return false;
  241. wxString l_ref( l.GetRef() << l.GetRefNumber() );
  242. wxString r_ref( r.GetRef() << r.GetRefNumber() );
  243. return l_ref == r_ref;
  244. } );
  245. references.erase( logicalEnd, references.end() );
  246. }
  247. if( ColIsReference( aCol ) )
  248. fieldValue = SCH_REFERENCE_LIST::Shorthand( references, refDelimiter, refRangeDelimiter );
  249. else if( ColIsQuantity( aCol ) )
  250. fieldValue = wxString::Format( wxT( "%d" ), (int) references.size() );
  251. else if( ColIsItemNumber( aCol ) && group.m_Flag != CHILD_ITEM )
  252. fieldValue = wxString::Format( wxT( "%d" ), group.m_ItemNumber );
  253. return fieldValue;
  254. }
  255. void FIELDS_EDITOR_GRID_DATA_MODEL::SetValue( int aRow, int aCol, const wxString& aValue )
  256. {
  257. wxCHECK_RET( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), wxS( "Invalid column number" ) );
  258. // Can't modify references or text variables column, e.g. ${QUANTITY}
  259. if( ColIsReference( aCol )
  260. || ( IsTextVar( m_cols[aCol].m_fieldName ) && !ColIsAttribute( aCol ) ) )
  261. {
  262. return;
  263. }
  264. DATA_MODEL_ROW& rowGroup = m_rows[aRow];
  265. for( const SCH_REFERENCE& ref : rowGroup.m_Refs )
  266. m_dataStore[ref.GetSymbol()->m_Uuid][m_cols[aCol].m_fieldName] = aValue;
  267. m_edited = true;
  268. }
  269. bool FIELDS_EDITOR_GRID_DATA_MODEL::ColIsReference( int aCol )
  270. {
  271. wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
  272. return m_cols[aCol].m_fieldName == GetCanonicalFieldName( FIELD_T::REFERENCE );
  273. }
  274. bool FIELDS_EDITOR_GRID_DATA_MODEL::ColIsValue( int aCol )
  275. {
  276. wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
  277. return m_cols[aCol].m_fieldName == GetCanonicalFieldName( FIELD_T::VALUE );
  278. }
  279. bool FIELDS_EDITOR_GRID_DATA_MODEL::ColIsQuantity( int aCol )
  280. {
  281. wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
  282. return m_cols[aCol].m_fieldName == QUANTITY_VARIABLE;
  283. }
  284. bool FIELDS_EDITOR_GRID_DATA_MODEL::ColIsItemNumber( int aCol )
  285. {
  286. wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
  287. return m_cols[aCol].m_fieldName == ITEM_NUMBER_VARIABLE;
  288. }
  289. bool FIELDS_EDITOR_GRID_DATA_MODEL::ColIsAttribute( int aCol )
  290. {
  291. wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
  292. return isAttribute( m_cols[aCol].m_fieldName );
  293. }
  294. bool FIELDS_EDITOR_GRID_DATA_MODEL::cmp( const DATA_MODEL_ROW& lhGroup,
  295. const DATA_MODEL_ROW& rhGroup,
  296. FIELDS_EDITOR_GRID_DATA_MODEL* dataModel, int sortCol,
  297. bool ascending )
  298. {
  299. // Empty rows always go to the bottom, whether ascending or descending
  300. if( lhGroup.m_Refs.size() == 0 )
  301. return true;
  302. else if( rhGroup.m_Refs.size() == 0 )
  303. return false;
  304. // N.B. To meet the iterator sort conditions, we cannot simply invert the truth
  305. // to get the opposite sort. i.e. ~(a<b) != (a>b)
  306. auto local_cmp =
  307. [ ascending ]( const auto a, const auto b )
  308. {
  309. if( ascending )
  310. return a < b;
  311. else
  312. return a > b;
  313. };
  314. // Primary sort key is sortCol; secondary is always REFERENCE (column 0)
  315. wxString lhs = dataModel->GetValue( lhGroup, sortCol ).Trim( true ).Trim( false );
  316. wxString rhs = dataModel->GetValue( rhGroup, sortCol ).Trim( true ).Trim( false );
  317. if( lhs == rhs || dataModel->ColIsReference( sortCol ) )
  318. {
  319. wxString lhRef = lhGroup.m_Refs[0].GetRef() + lhGroup.m_Refs[0].GetRefNumber();
  320. wxString rhRef = rhGroup.m_Refs[0].GetRef() + rhGroup.m_Refs[0].GetRefNumber();
  321. return local_cmp( StrNumCmp( lhRef, rhRef, true ), 0 );
  322. }
  323. else
  324. {
  325. return local_cmp( ValueStringCompare( lhs, rhs ), 0 );
  326. }
  327. }
  328. void FIELDS_EDITOR_GRID_DATA_MODEL::Sort()
  329. {
  330. CollapseForSort();
  331. // We're going to sort the rows based on their first reference, so the first reference
  332. // had better be the lowest one.
  333. for( DATA_MODEL_ROW& row : m_rows )
  334. {
  335. std::sort( row.m_Refs.begin(), row.m_Refs.end(),
  336. []( const SCH_REFERENCE& lhs, const SCH_REFERENCE& rhs )
  337. {
  338. wxString lhs_ref( lhs.GetRef() << lhs.GetRefNumber() );
  339. wxString rhs_ref( rhs.GetRef() << rhs.GetRefNumber() );
  340. return StrNumCmp( lhs_ref, rhs_ref, true ) < 0;
  341. } );
  342. }
  343. std::sort( m_rows.begin(), m_rows.end(),
  344. [this]( const DATA_MODEL_ROW& lhs, const DATA_MODEL_ROW& rhs ) -> bool
  345. {
  346. return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending );
  347. } );
  348. // Time to renumber the item numbers
  349. int itemNumber = 1;
  350. for( DATA_MODEL_ROW& row : m_rows )
  351. {
  352. row.m_ItemNumber = itemNumber++;
  353. }
  354. ExpandAfterSort();
  355. }
  356. bool FIELDS_EDITOR_GRID_DATA_MODEL::unitMatch( const SCH_REFERENCE& lhRef,
  357. const SCH_REFERENCE& rhRef )
  358. {
  359. // If items are unannotated then we can't tell if they're units of the same symbol or not
  360. if( lhRef.GetRefNumber() == wxT( "?" ) )
  361. return false;
  362. return ( lhRef.GetRef() == rhRef.GetRef() && lhRef.GetRefNumber() == rhRef.GetRefNumber() );
  363. }
  364. bool FIELDS_EDITOR_GRID_DATA_MODEL::groupMatch( const SCH_REFERENCE& lhRef,
  365. const SCH_REFERENCE& rhRef )
  366. {
  367. int refCol = GetFieldNameCol( GetCanonicalFieldName( FIELD_T::REFERENCE ) );
  368. bool matchFound = false;
  369. if( refCol == -1 )
  370. return false;
  371. // First check the reference column. This can be done directly out of the
  372. // SCH_REFERENCEs as the references can't be edited in the grid.
  373. if( m_cols[refCol].m_group )
  374. {
  375. // if we're grouping by reference, then only the prefix must match
  376. if( lhRef.GetRef() != rhRef.GetRef() )
  377. return false;
  378. matchFound = true;
  379. }
  380. const KIID& lhRefID = lhRef.GetSymbol()->m_Uuid;
  381. const KIID& rhRefID = rhRef.GetSymbol()->m_Uuid;
  382. // Now check all the other columns.
  383. for( size_t i = 0; i < m_cols.size(); ++i )
  384. {
  385. //Handled already
  386. if( static_cast<int>( i ) == refCol )
  387. continue;
  388. if( !m_cols[i].m_group )
  389. continue;
  390. // If the field is a variable, we need to resolve it through the symbol
  391. // to get the actual current value, otherwise we need to pull it out of the
  392. // store so the refresh can regroup based on values that haven't been applied
  393. // to the schematic yet.
  394. wxString lh, rh;
  395. if( IsTextVar( m_cols[i].m_fieldName )
  396. || IsTextVar( m_dataStore[lhRefID][m_cols[i].m_fieldName] ) )
  397. {
  398. lh = getFieldShownText( lhRef, m_cols[i].m_fieldName );
  399. }
  400. else
  401. {
  402. lh = m_dataStore[lhRefID][m_cols[i].m_fieldName];
  403. }
  404. if( IsTextVar( m_cols[i].m_fieldName )
  405. || IsTextVar( m_dataStore[rhRefID][m_cols[i].m_fieldName] ) )
  406. {
  407. rh = getFieldShownText( rhRef, m_cols[i].m_fieldName );
  408. }
  409. else
  410. {
  411. rh = m_dataStore[rhRefID][m_cols[i].m_fieldName];
  412. }
  413. wxString fieldName = m_cols[i].m_fieldName;
  414. if( lh != rh )
  415. return false;
  416. matchFound = true;
  417. }
  418. return matchFound;
  419. }
  420. wxString FIELDS_EDITOR_GRID_DATA_MODEL::getFieldShownText( const SCH_REFERENCE& aRef,
  421. const wxString& aFieldName )
  422. {
  423. SCH_FIELD* field = aRef.GetSymbol()->GetField( aFieldName );
  424. if( field )
  425. {
  426. if( field->IsPrivate() )
  427. return wxEmptyString;
  428. else
  429. return field->GetShownText( &aRef.GetSheetPath(), false );
  430. }
  431. // Handle fields with variables as names that are not present in the symbol
  432. // by giving them the correct value by resolving against the symbol
  433. if( IsTextVar( aFieldName ) )
  434. {
  435. int depth = 0;
  436. const SCH_SHEET_PATH& path = aRef.GetSheetPath();
  437. std::function<bool( wxString* )> symbolResolver =
  438. [&]( wxString* token ) -> bool
  439. {
  440. return aRef.GetSymbol()->ResolveTextVar( &path, token, depth + 1 );
  441. };
  442. return ExpandTextVars( aFieldName, &symbolResolver );
  443. }
  444. return wxEmptyString;
  445. }
  446. bool FIELDS_EDITOR_GRID_DATA_MODEL::isAttribute( const wxString& aFieldName )
  447. {
  448. return aFieldName == wxS( "${DNP}" )
  449. || aFieldName == wxS( "${EXCLUDE_FROM_BOARD}" )
  450. || aFieldName == wxS( "${EXCLUDE_FROM_BOM}" )
  451. || aFieldName == wxS( "${EXCLUDE_FROM_SIM}" );
  452. }
  453. wxString FIELDS_EDITOR_GRID_DATA_MODEL::getAttributeValue( const SCH_SYMBOL& aSymbol,
  454. const wxString& aAttributeName )
  455. {
  456. if( aAttributeName == wxS( "${DNP}" ) )
  457. return aSymbol.GetDNP() ? wxS( "1" ) : wxS( "0" );
  458. if( aAttributeName == wxS( "${EXCLUDE_FROM_BOARD}" ) )
  459. return aSymbol.GetExcludedFromBoard() ? wxS( "1" ) : wxS( "0" );
  460. if( aAttributeName == wxS( "${EXCLUDE_FROM_BOM}" ) )
  461. return aSymbol.GetExcludedFromBOM() ? wxS( "1" ) : wxS( "0" );
  462. if( aAttributeName == wxS( "${EXCLUDE_FROM_SIM}" ) )
  463. return aSymbol.GetExcludedFromSim() ? wxS( "1" ) : wxS( "0" );
  464. return wxS( "0" );
  465. }
  466. void FIELDS_EDITOR_GRID_DATA_MODEL::setAttributeValue( SCH_SYMBOL& aSymbol,
  467. const wxString& aAttributeName,
  468. const wxString& aValue )
  469. {
  470. if( aAttributeName == wxS( "${DNP}" ) )
  471. aSymbol.SetDNP( aValue == wxS( "1" ) );
  472. else if( aAttributeName == wxS( "${EXCLUDE_FROM_BOARD}" ) )
  473. aSymbol.SetExcludedFromBoard( aValue == wxS( "1" ) );
  474. else if( aAttributeName == wxS( "${EXCLUDE_FROM_BOM}" ) )
  475. aSymbol.SetExcludedFromBOM( aValue == wxS( "1" ) );
  476. else if( aAttributeName == wxS( "${EXCLUDE_FROM_SIM}" ) )
  477. aSymbol.SetExcludedFromSim( aValue == wxS( "1" ) );
  478. }
  479. void FIELDS_EDITOR_GRID_DATA_MODEL::EnableRebuilds()
  480. {
  481. m_rebuildsEnabled = true;
  482. }
  483. void FIELDS_EDITOR_GRID_DATA_MODEL::DisableRebuilds()
  484. {
  485. m_rebuildsEnabled = false;
  486. }
  487. void FIELDS_EDITOR_GRID_DATA_MODEL::RebuildRows()
  488. {
  489. if( !m_rebuildsEnabled )
  490. return;
  491. if( GetView() )
  492. {
  493. // Commit any pending in-place edits before the row gets moved out from under
  494. // the editor.
  495. static_cast<WX_GRID*>( GetView() )->CommitPendingChanges( true );
  496. wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, 0, m_rows.size() );
  497. GetView()->ProcessTableMessage( msg );
  498. }
  499. m_rows.clear();
  500. for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
  501. {
  502. SCH_REFERENCE ref = m_symbolsList[i];
  503. if( !m_filter.IsEmpty() && !WildCompareString( m_filter, ref.GetFullRef(), false ) )
  504. continue;
  505. if( m_excludeDNP && ( ref.GetSymbol()->GetDNP()
  506. || ref.GetSheetPath().GetDNP() ) )
  507. {
  508. continue;
  509. }
  510. if( !m_includeExcluded && ( ref.GetSymbol()->GetExcludedFromBOM()
  511. || ref.GetSheetPath().GetExcludedFromBOM() ) )
  512. {
  513. continue;
  514. }
  515. // Check if the symbol if on the current sheet or, in the sheet path somewhere
  516. // depending on scope
  517. if( ( m_scope == SCOPE::SCOPE_SHEET && ref.GetSheetPath() != m_path )
  518. || ( m_scope == SCOPE::SCOPE_SHEET_RECURSIVE
  519. && !ref.GetSheetPath().IsContainedWithin( m_path ) ) )
  520. {
  521. continue;
  522. }
  523. bool matchFound = false;
  524. // Performance optimization for ungrouped case to skip the N^2 for loop
  525. if( !m_groupingEnabled && !ref.IsMultiUnit() )
  526. {
  527. m_rows.emplace_back( DATA_MODEL_ROW( ref, GROUP_SINGLETON ) );
  528. continue;
  529. }
  530. // See if we already have a row which this symbol fits into
  531. for( DATA_MODEL_ROW& row : m_rows )
  532. {
  533. // all group members must have identical refs so just use the first one
  534. SCH_REFERENCE rowRef = row.m_Refs[0];
  535. if( unitMatch( ref, rowRef ) )
  536. {
  537. matchFound = true;
  538. row.m_Refs.push_back( ref );
  539. break;
  540. }
  541. else if( m_groupingEnabled && groupMatch( ref, rowRef ) )
  542. {
  543. matchFound = true;
  544. row.m_Refs.push_back( ref );
  545. row.m_Flag = GROUP_COLLAPSED;
  546. break;
  547. }
  548. }
  549. if( !matchFound )
  550. m_rows.emplace_back( DATA_MODEL_ROW( ref, GROUP_SINGLETON ) );
  551. }
  552. if( GetView() )
  553. {
  554. wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, m_rows.size() );
  555. GetView()->ProcessTableMessage( msg );
  556. }
  557. Sort();
  558. }
  559. void FIELDS_EDITOR_GRID_DATA_MODEL::ExpandRow( int aRow )
  560. {
  561. std::vector<DATA_MODEL_ROW> children;
  562. for( SCH_REFERENCE& ref : m_rows[aRow].m_Refs )
  563. {
  564. bool matchFound = false;
  565. // See if we already have a child group which this symbol fits into
  566. for( DATA_MODEL_ROW& child : children )
  567. {
  568. // group members are by definition all matching, so just check
  569. // against the first member
  570. if( unitMatch( ref, child.m_Refs[0] ) )
  571. {
  572. matchFound = true;
  573. child.m_Refs.push_back( ref );
  574. break;
  575. }
  576. }
  577. if( !matchFound )
  578. children.emplace_back( DATA_MODEL_ROW( ref, CHILD_ITEM ) );
  579. }
  580. if( children.size() < 2 )
  581. return;
  582. std::sort( children.begin(), children.end(),
  583. [this]( const DATA_MODEL_ROW& lhs, const DATA_MODEL_ROW& rhs ) -> bool
  584. {
  585. return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending );
  586. } );
  587. m_rows[aRow].m_Flag = GROUP_EXPANDED;
  588. m_rows.insert( m_rows.begin() + aRow + 1, children.begin(), children.end() );
  589. wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, aRow, children.size() );
  590. GetView()->ProcessTableMessage( msg );
  591. }
  592. void FIELDS_EDITOR_GRID_DATA_MODEL::CollapseRow( int aRow )
  593. {
  594. auto firstChild = m_rows.begin() + aRow + 1;
  595. auto afterLastChild = firstChild;
  596. int deleted = 0;
  597. while( afterLastChild != m_rows.end() && afterLastChild->m_Flag == CHILD_ITEM )
  598. {
  599. deleted++;
  600. afterLastChild++;
  601. }
  602. m_rows[aRow].m_Flag = GROUP_COLLAPSED;
  603. m_rows.erase( firstChild, afterLastChild );
  604. wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, aRow + 1, deleted );
  605. GetView()->ProcessTableMessage( msg );
  606. }
  607. void FIELDS_EDITOR_GRID_DATA_MODEL::ExpandCollapseRow( int aRow )
  608. {
  609. DATA_MODEL_ROW& group = m_rows[aRow];
  610. if( group.m_Flag == GROUP_COLLAPSED )
  611. ExpandRow( aRow );
  612. else if( group.m_Flag == GROUP_EXPANDED )
  613. CollapseRow( aRow );
  614. }
  615. void FIELDS_EDITOR_GRID_DATA_MODEL::CollapseForSort()
  616. {
  617. for( size_t i = 0; i < m_rows.size(); ++i )
  618. {
  619. if( m_rows[i].m_Flag == GROUP_EXPANDED )
  620. {
  621. CollapseRow( i );
  622. m_rows[i].m_Flag = GROUP_COLLAPSED_DURING_SORT;
  623. }
  624. }
  625. }
  626. void FIELDS_EDITOR_GRID_DATA_MODEL::ExpandAfterSort()
  627. {
  628. for( size_t i = 0; i < m_rows.size(); ++i )
  629. {
  630. if( m_rows[i].m_Flag == GROUP_COLLAPSED_DURING_SORT )
  631. ExpandRow( i );
  632. }
  633. }
  634. void FIELDS_EDITOR_GRID_DATA_MODEL::ApplyData( SCH_COMMIT& aCommit )
  635. {
  636. for( const SCH_REFERENCE& instance : m_symbolsList )
  637. {
  638. SCH_SYMBOL& symbol = *instance.GetSymbol();
  639. SCHEMATIC_SETTINGS& settings = symbol.Schematic()->Settings();
  640. aCommit.Modify( &symbol, instance.GetSheetPath().LastScreen() );
  641. const std::map<wxString, wxString>& fieldStore = m_dataStore[symbol.m_Uuid];
  642. for( const auto& [srcName, srcValue] : fieldStore )
  643. {
  644. // Attributes bypass the field logic, so handle them first
  645. if( isAttribute( srcName ) )
  646. {
  647. setAttributeValue( symbol, srcName, srcValue );
  648. continue;
  649. }
  650. // Skip special fields with variables as names (e.g. ${QUANTITY}),
  651. // they can't be edited
  652. if( IsTextVar( srcName ) )
  653. continue;
  654. SCH_FIELD* destField = symbol.GetField( srcName );
  655. if( destField && destField->IsPrivate() )
  656. {
  657. if( srcValue.IsEmpty() )
  658. continue;
  659. else
  660. destField->SetPrivate( false );
  661. }
  662. int col = GetFieldNameCol( srcName );
  663. bool userAdded = ( col != -1 && m_cols[col].m_userAdded );
  664. // Add a not existing field if it has a value for this symbol
  665. bool createField = !destField && ( !srcValue.IsEmpty() || userAdded );
  666. if( createField )
  667. {
  668. destField = symbol.AddField( SCH_FIELD( symbol.GetPosition(), FIELD_T::USER,
  669. &symbol, srcName ) );
  670. destField->SetTextAngle( symbol.GetField( FIELD_T::REFERENCE )->GetTextAngle() );
  671. destField->SetTextSize( VECTOR2I( settings.m_DefaultTextSize,
  672. settings.m_DefaultTextSize ) );
  673. destField->SetVisible( false );
  674. }
  675. if( !destField )
  676. continue;
  677. if( destField->GetId() == FIELD_T::REFERENCE )
  678. {
  679. // Reference is not editable from this dialog
  680. continue;
  681. }
  682. destField->SetText( symbol.Schematic()->ConvertRefsToKIIDs( srcValue ) );
  683. }
  684. for( int ii = static_cast<int>( symbol.GetFields().size() ) - 1; ii >= 0; ii-- )
  685. {
  686. if( symbol.GetFields()[ii].IsMandatory() || symbol.GetFields()[ii].IsPrivate() )
  687. continue;
  688. if( fieldStore.count( symbol.GetFields()[ii].GetName() ) == 0 )
  689. symbol.GetFields().erase( symbol.GetFields().begin() + ii );
  690. }
  691. }
  692. m_edited = false;
  693. }
  694. int FIELDS_EDITOR_GRID_DATA_MODEL::GetDataWidth( int aCol )
  695. {
  696. int width = 0;
  697. if( ColIsReference( aCol ) )
  698. {
  699. for( int row = 0; row < GetNumberRows(); ++row )
  700. width = std::max( width, KIUI::GetTextSize( GetValue( row, aCol ), GetView() ).x );
  701. }
  702. else
  703. {
  704. wxString fieldName = GetColFieldName( aCol ); // symbol fieldName or Qty string
  705. for( unsigned symbolRef = 0; symbolRef < m_symbolsList.GetCount(); ++symbolRef )
  706. {
  707. const KIID& symbolID = m_symbolsList[symbolRef].GetSymbol()->m_Uuid;
  708. wxString text = m_dataStore[symbolID][fieldName];
  709. width = std::max( width, KIUI::GetTextSize( text, GetView() ).x );
  710. }
  711. }
  712. return width;
  713. }
  714. void FIELDS_EDITOR_GRID_DATA_MODEL::ApplyBomPreset( const BOM_PRESET& aPreset )
  715. {
  716. // Hide and un-group everything by default
  717. for( size_t i = 0; i < m_cols.size(); i++ )
  718. {
  719. SetShowColumn( i, false );
  720. SetGroupColumn( i, false );
  721. }
  722. std::set<wxString> seen;
  723. std::vector<wxString> order;
  724. // Set columns that are present and shown
  725. for( const BOM_FIELD& field : aPreset.fieldsOrdered )
  726. {
  727. // Ignore empty fields
  728. if( !field.name || seen.count( field.name ) )
  729. continue;
  730. seen.insert( field.name );
  731. order.emplace_back( field.name );
  732. int col = GetFieldNameCol( field.name );
  733. // Add any missing fields, if the user doesn't add any data
  734. // they won't be saved to the symbols anyway
  735. if( col == -1 )
  736. {
  737. AddColumn( field.name, field.label, true );
  738. col = GetFieldNameCol( field.name );
  739. }
  740. else
  741. {
  742. SetColLabelValue( col, field.label );
  743. }
  744. SetGroupColumn( col, field.groupBy );
  745. SetShowColumn( col, field.show );
  746. }
  747. // Set grouping columns
  748. SetGroupingEnabled( aPreset.groupSymbols );
  749. SetFieldsOrder( order );
  750. // Set our sorting
  751. int sortCol = GetFieldNameCol( aPreset.sortField );
  752. if( sortCol == -1 )
  753. sortCol = GetFieldNameCol( GetCanonicalFieldName( FIELD_T::REFERENCE ) );
  754. SetSorting( sortCol, aPreset.sortAsc );
  755. SetFilter( aPreset.filterString );
  756. SetExcludeDNP( aPreset.excludeDNP );
  757. SetIncludeExcludedFromBOM( aPreset.includeExcludedFromBOM );
  758. RebuildRows();
  759. }
  760. BOM_PRESET FIELDS_EDITOR_GRID_DATA_MODEL::GetBomSettings()
  761. {
  762. BOM_PRESET current;
  763. current.readOnly = false;
  764. current.fieldsOrdered = GetFieldsOrdered();
  765. current.sortField = GetColFieldName( GetSortCol() );
  766. current.sortAsc = GetSortAsc();
  767. current.filterString = GetFilter();
  768. current.groupSymbols = GetGroupingEnabled();
  769. current.excludeDNP = GetExcludeDNP();
  770. current.includeExcludedFromBOM = GetIncludeExcludedFromBOM();
  771. return current;
  772. }
  773. wxString FIELDS_EDITOR_GRID_DATA_MODEL::Export( const BOM_FMT_PRESET& settings )
  774. {
  775. wxString out;
  776. if( m_cols.empty() )
  777. return out;
  778. int last_col = -1;
  779. // Find the location for the line terminator
  780. for( size_t col = 0; col < m_cols.size(); col++ )
  781. {
  782. if( m_cols[col].m_show )
  783. last_col = static_cast<int>( col );
  784. }
  785. // No shown columns
  786. if( last_col == -1 )
  787. return out;
  788. auto formatField =
  789. [&]( wxString field, bool last ) -> wxString
  790. {
  791. if( !settings.keepLineBreaks )
  792. {
  793. field.Replace( wxS( "\r" ), wxS( "" ) );
  794. field.Replace( wxS( "\n" ), wxS( "" ) );
  795. }
  796. if( !settings.keepTabs )
  797. {
  798. field.Replace( wxS( "\t" ), wxS( "" ) );
  799. }
  800. if( !settings.stringDelimiter.IsEmpty() )
  801. {
  802. field.Replace( settings.stringDelimiter,
  803. settings.stringDelimiter + settings.stringDelimiter );
  804. }
  805. return settings.stringDelimiter + field + settings.stringDelimiter
  806. + ( last ? wxString( wxS( "\n" ) ) : settings.fieldDelimiter );
  807. };
  808. // Column names
  809. for( size_t col = 0; col < m_cols.size(); col++ )
  810. {
  811. if( !m_cols[col].m_show )
  812. continue;
  813. out.Append( formatField( m_cols[col].m_label, col == static_cast<size_t>( last_col ) ) );
  814. }
  815. // Data rows
  816. for( size_t row = 0; row < m_rows.size(); row++ )
  817. {
  818. // Don't output child rows
  819. if( GetRowFlags( static_cast<int>( row ) ) == CHILD_ITEM )
  820. continue;
  821. for( size_t col = 0; col < m_cols.size(); col++ )
  822. {
  823. if( !m_cols[col].m_show )
  824. continue;
  825. // Get the unannotated version of the field, e.g. no "> " or "v " by
  826. out.Append( formatField( GetExportValue( static_cast<int>( row ), static_cast<int>( col ),
  827. settings.refDelimiter, settings.refRangeDelimiter ),
  828. col == static_cast<size_t>( last_col ) ) );
  829. }
  830. }
  831. return out;
  832. }
  833. void FIELDS_EDITOR_GRID_DATA_MODEL::AddReferences( const SCH_REFERENCE_LIST& aRefs )
  834. {
  835. for( const SCH_REFERENCE& ref : aRefs )
  836. {
  837. if( !m_symbolsList.Contains( ref ) )
  838. {
  839. SCH_SYMBOL* symbol = ref.GetSymbol();
  840. m_symbolsList.AddItem( ref );
  841. // Update the fields of every reference
  842. for( const SCH_FIELD& field : symbol->GetFields() )
  843. {
  844. if( !field.IsPrivate() )
  845. {
  846. wxString name = field.GetCanonicalName();
  847. wxString value = symbol->Schematic()->ConvertKIIDsToRefs( field.GetText() );
  848. m_dataStore[symbol->m_Uuid][name] = value;
  849. }
  850. }
  851. }
  852. }
  853. }
  854. void FIELDS_EDITOR_GRID_DATA_MODEL::RemoveSymbol( const SCH_SYMBOL& aSymbol )
  855. {
  856. // The schematic event listener passes us the symbol after it has been removed,
  857. // so we can't just work with a SCH_REFERENCE_LIST like the other handlers as the
  858. // references are already gone. Instead we need to prune our list.
  859. m_dataStore[aSymbol.m_Uuid].clear();
  860. // Remove all refs that match this symbol using remove_if
  861. m_symbolsList.erase( std::remove_if( m_symbolsList.begin(), m_symbolsList.end(),
  862. [&aSymbol]( const SCH_REFERENCE& ref ) -> bool
  863. {
  864. return ref.GetSymbol()->m_Uuid == aSymbol.m_Uuid;
  865. } ),
  866. m_symbolsList.end() );
  867. }
  868. void FIELDS_EDITOR_GRID_DATA_MODEL::RemoveReferences( const SCH_REFERENCE_LIST& aRefs )
  869. {
  870. for( const SCH_REFERENCE& ref : aRefs )
  871. {
  872. int index = m_symbolsList.FindRefByFullPath( ref.GetFullPath() );
  873. if( index != -1 )
  874. {
  875. m_symbolsList.RemoveItem( index );
  876. // If we're out of instances then remove the symbol, too
  877. if( ref.GetSymbol()->GetInstances().empty() )
  878. m_dataStore.erase( ref.GetSymbol()->m_Uuid );
  879. }
  880. }
  881. }
  882. void FIELDS_EDITOR_GRID_DATA_MODEL::UpdateReferences( const SCH_REFERENCE_LIST& aRefs )
  883. {
  884. for( const SCH_REFERENCE& ref : aRefs )
  885. {
  886. // Update the fields of every reference. Do this by iterating through the data model
  887. // columns; we must have all fields in the symbol added to the data model at this point,
  888. // and some of the data model columns may be variables that are not present in the symbol
  889. for( const DATA_MODEL_COL& col : m_cols )
  890. updateDataStoreSymbolField( *ref.GetSymbol(), col.m_fieldName );
  891. if( !m_symbolsList.Contains( ref ) )
  892. m_symbolsList.AddItem( ref );
  893. }
  894. }