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.

1179 lines
37 KiB

7 years ago
7 years ago
7 years ago
7 years ago
8 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2017 Oliver Walters
  5. * Copyright (C) 2017-2019 KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, you may find one here:
  19. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  20. * or you may search the http://www.gnu.org website for the version 2 license,
  21. * or you may write to the Free Software Foundation, Inc.,
  22. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. */
  24. #include <base_units.h>
  25. #include <bitmaps.h>
  26. #include <class_library.h>
  27. #include <confirm.h>
  28. #include <eda_doc.h>
  29. #include <eeschema_settings.h>
  30. #include <general.h>
  31. #include <grid_tricks.h>
  32. #include <kicad_string.h>
  33. #include <kiface_i.h>
  34. #include <refdes_utils.h>
  35. #include <sch_edit_frame.h>
  36. #include <sch_reference_list.h>
  37. #include <tools/sch_editor_control.h>
  38. #include <widgets/grid_text_button_helpers.h>
  39. #include <widgets/wx_grid.h>
  40. #include <wx/grid.h>
  41. #include <wx/msgdlg.h>
  42. #include "dialog_fields_editor_global.h"
  43. enum
  44. {
  45. MYID_SELECT_FOOTPRINT = 991, // must be within GRID_TRICKS' enum range
  46. MYID_SHOW_DATASHEET
  47. };
  48. class FIELDS_EDITOR_GRID_TRICKS : public GRID_TRICKS
  49. {
  50. public:
  51. FIELDS_EDITOR_GRID_TRICKS( DIALOG_SHIM* aParent, WX_GRID* aGrid,
  52. wxDataViewListCtrl* aFieldsCtrl ) :
  53. GRID_TRICKS( aGrid ),
  54. m_dlg( aParent ),
  55. m_fieldsCtrl( aFieldsCtrl )
  56. {}
  57. protected:
  58. void showPopupMenu( wxMenu& menu ) override
  59. {
  60. if( m_grid->GetGridCursorCol() == FOOTPRINT )
  61. {
  62. menu.Append( MYID_SELECT_FOOTPRINT, _( "Select Footprint..." ),
  63. _( "Browse for footprint" ) );
  64. menu.AppendSeparator();
  65. }
  66. else if( m_grid->GetGridCursorCol() == DATASHEET )
  67. {
  68. menu.Append( MYID_SHOW_DATASHEET, _( "Show Datasheet" ),
  69. _( "Show datasheet in browser" ) );
  70. menu.AppendSeparator();
  71. }
  72. GRID_TRICKS::showPopupMenu( menu );
  73. }
  74. void doPopupSelection( wxCommandEvent& event ) override
  75. {
  76. if( event.GetId() == MYID_SELECT_FOOTPRINT )
  77. {
  78. // pick a footprint using the footprint picker.
  79. wxString fpid = m_grid->GetCellValue( m_grid->GetGridCursorRow(), FOOTPRINT );
  80. KIWAY_PLAYER* frame = m_dlg->Kiway().Player( FRAME_FOOTPRINT_VIEWER_MODAL, true, m_dlg );
  81. if( frame->ShowModal( &fpid, m_dlg ) )
  82. m_grid->SetCellValue( m_grid->GetGridCursorRow(), FOOTPRINT, fpid );
  83. frame->Destroy();
  84. }
  85. else if (event.GetId() == MYID_SHOW_DATASHEET )
  86. {
  87. wxString datasheet_uri = m_grid->GetCellValue( m_grid->GetGridCursorRow(), DATASHEET );
  88. GetAssociatedDocument( m_dlg, datasheet_uri );
  89. }
  90. else
  91. {
  92. GRID_TRICKS::doPopupSelection( event );
  93. }
  94. if( event.GetId() >= GRIDTRICKS_FIRST_SHOWHIDE && event.GetId() < GRIDTRICKS_LAST_ID )
  95. {
  96. if( !m_grid->IsColShown( REFERENCE ) )
  97. {
  98. DisplayError( m_dlg, _( "The Reference column cannot be hidden." ) );
  99. m_grid->ShowCol( REFERENCE );
  100. }
  101. // Refresh Show checkboxes from grid columns
  102. for( int i = 0; i < m_fieldsCtrl->GetItemCount(); ++i )
  103. m_fieldsCtrl->SetToggleValue( m_grid->IsColShown( i ), i, 1 );
  104. }
  105. }
  106. DIALOG_SHIM* m_dlg;
  107. wxDataViewListCtrl* m_fieldsCtrl;
  108. };
  109. enum GROUP_TYPE
  110. {
  111. GROUP_SINGLETON,
  112. GROUP_COLLAPSED,
  113. GROUP_COLLAPSED_DURING_SORT,
  114. GROUP_EXPANDED,
  115. CHILD_ITEM
  116. };
  117. struct DATA_MODEL_ROW
  118. {
  119. DATA_MODEL_ROW( const SCH_REFERENCE& aFirstReference, GROUP_TYPE aType )
  120. {
  121. m_Refs.push_back( aFirstReference );
  122. m_Flag = aType;
  123. }
  124. GROUP_TYPE m_Flag;
  125. std::vector<SCH_REFERENCE> m_Refs;
  126. };
  127. #define FIELD_NAME_COLUMN 0
  128. #define SHOW_FIELD_COLUMN 1
  129. #define GROUP_BY_COLUMN 2
  130. #define QUANTITY_COLUMN ( GetNumberCols() - 1 )
  131. #ifdef __WXMAC__
  132. #define COLUMN_MARGIN 5
  133. #else
  134. #define COLUMN_MARGIN 15
  135. #endif
  136. class FIELDS_EDITOR_GRID_DATA_MODEL : public wxGridTableBase
  137. {
  138. protected:
  139. // The data model is fundamentally m_componentRefs X m_fieldNames.
  140. SCH_EDIT_FRAME* m_frame;
  141. SCH_REFERENCE_LIST m_componentRefs;
  142. bool m_edited;
  143. std::vector<wxString> m_fieldNames;
  144. int m_sortColumn;
  145. bool m_sortAscending;
  146. // However, the grid view can vary in two ways:
  147. // 1) the componentRefs can be grouped into fewer rows
  148. // 2) some columns can be hidden
  149. //
  150. // We handle (1) here (ie: a table row maps to a group, and the table is rebuilt
  151. // when the groupings change), and we let the wxGrid handle (2) (ie: the number
  152. // of columns is constant but are hidden/shown by the wxGrid control).
  153. std::vector< DATA_MODEL_ROW > m_rows;
  154. // Data store
  155. // A map of compID : fieldSet, where fieldSet is a map of fieldName : fieldValue
  156. std::map< timestamp_t, std::map<wxString, wxString> > m_dataStore;
  157. public:
  158. FIELDS_EDITOR_GRID_DATA_MODEL( SCH_EDIT_FRAME* aFrame, SCH_REFERENCE_LIST& aComponentList ) :
  159. m_frame( aFrame ),
  160. m_componentRefs( aComponentList ),
  161. m_edited( false ),
  162. m_sortColumn( 0 ),
  163. m_sortAscending( false )
  164. {
  165. m_componentRefs.SplitReferences();
  166. }
  167. void AddColumn( const wxString& aFieldName )
  168. {
  169. m_fieldNames.push_back( aFieldName );
  170. for( unsigned i = 0; i < m_componentRefs.GetCount(); ++i )
  171. {
  172. SCH_COMPONENT* comp = m_componentRefs[ i ].GetComp();
  173. timestamp_t compID = comp->GetTimeStamp();
  174. m_dataStore[ compID ][ aFieldName ] = comp->GetFieldText( aFieldName, m_frame );
  175. }
  176. }
  177. int GetNumberRows() override { return m_rows.size(); }
  178. // Columns are fieldNames + quantity column
  179. int GetNumberCols() override { return (int) m_fieldNames.size() + 1; }
  180. wxString GetColLabelValue( int aCol ) override
  181. {
  182. if( aCol == QUANTITY_COLUMN )
  183. return _( "Qty" );
  184. else
  185. return m_fieldNames[ aCol ];
  186. }
  187. bool IsEmptyCell( int aRow, int aCol ) override
  188. {
  189. return false; // don't allow adjacent cell overflow, even if we are actually empty
  190. }
  191. wxString GetValue( int aRow, int aCol ) override
  192. {
  193. if( aCol == REFERENCE )
  194. {
  195. // Poor-man's tree controls
  196. if( m_rows[ aRow ].m_Flag == GROUP_COLLAPSED )
  197. return wxT( "> " ) + GetValue( m_rows[ aRow ], aCol );
  198. else if (m_rows[ aRow ].m_Flag == GROUP_EXPANDED )
  199. return wxT( "v " ) + GetValue( m_rows[ aRow ], aCol );
  200. else if( m_rows[ aRow ].m_Flag == CHILD_ITEM )
  201. return wxT( " " ) + GetValue( m_rows[ aRow ], aCol );
  202. else
  203. return wxT( " " ) + GetValue( m_rows[ aRow ], aCol );
  204. }
  205. else
  206. return GetValue( m_rows[ aRow ], aCol );
  207. }
  208. std::vector<SCH_REFERENCE> GetRowReferences( int aRow )
  209. {
  210. wxCHECK( aRow < (int)m_rows.size(), std::vector<SCH_REFERENCE>() );
  211. return m_rows[ aRow ].m_Refs;
  212. }
  213. wxString GetValue( DATA_MODEL_ROW& group, int aCol )
  214. {
  215. std::vector<SCH_REFERENCE> references;
  216. wxString fieldValue;
  217. for( const auto& ref : group.m_Refs )
  218. {
  219. if( aCol == REFERENCE || aCol == QUANTITY_COLUMN )
  220. {
  221. references.push_back( ref );
  222. }
  223. else // Other columns are either a single value or ROW_MULTI_ITEMS
  224. {
  225. timestamp_t compID = ref.GetComp()->GetTimeStamp();
  226. if( !m_dataStore.count( compID ) ||
  227. !m_dataStore[ compID ].count( m_fieldNames[ aCol ] ) )
  228. return INDETERMINATE;
  229. if( &ref == &group.m_Refs.front() )
  230. fieldValue = m_dataStore[ compID ][ m_fieldNames[ aCol ] ];
  231. else if ( fieldValue != m_dataStore[ compID ][ m_fieldNames[ aCol ] ] )
  232. return INDETERMINATE;
  233. }
  234. }
  235. if( aCol == REFERENCE || aCol == QUANTITY_COLUMN )
  236. {
  237. // Remove duplicates (other units of multi-unit parts)
  238. std::sort( references.begin(), references.end(),
  239. []( const SCH_REFERENCE& l, const SCH_REFERENCE& r ) -> bool
  240. {
  241. wxString l_ref( l.GetRef() << l.GetRefNumber() );
  242. wxString r_ref( r.GetRef() << r.GetRefNumber() );
  243. return UTIL::RefDesStringCompare( l_ref, r_ref ) < 0;
  244. } );
  245. auto logicalEnd = std::unique( references.begin(), references.end(),
  246. []( const SCH_REFERENCE& l, const SCH_REFERENCE& r ) -> bool
  247. {
  248. // If unannotated then we can't tell what units belong together
  249. // so we have to leave them all
  250. if( l.GetRefNumber() == wxT( "?" ) )
  251. return false;
  252. wxString l_ref( l.GetRef() << l.GetRefNumber() );
  253. wxString r_ref( r.GetRef() << r.GetRefNumber() );
  254. return l_ref == r_ref;
  255. } );
  256. references.erase( logicalEnd, references.end() );
  257. }
  258. if( aCol == REFERENCE )
  259. {
  260. fieldValue = SCH_REFERENCE_LIST::Shorthand( references );
  261. }
  262. else if( aCol == QUANTITY_COLUMN )
  263. {
  264. fieldValue = wxString::Format( wxT( "%d" ), ( int )references.size() );
  265. }
  266. return fieldValue;
  267. }
  268. void SetValue( int aRow, int aCol, const wxString &aValue ) override
  269. {
  270. if( aCol == REFERENCE || aCol == QUANTITY_COLUMN )
  271. return; // Can't modify references or quantity
  272. DATA_MODEL_ROW& rowGroup = m_rows[ aRow ];
  273. wxString fieldName = m_fieldNames[ aCol ];
  274. for( const auto& ref : rowGroup.m_Refs )
  275. m_dataStore[ ref.GetComp()->GetTimeStamp() ][ fieldName ] = aValue;
  276. m_edited = true;
  277. }
  278. static bool cmp( const DATA_MODEL_ROW& lhGroup, const DATA_MODEL_ROW& rhGroup,
  279. FIELDS_EDITOR_GRID_DATA_MODEL* dataModel, int sortCol, bool ascending )
  280. {
  281. // Empty rows always go to the bottom, whether ascending or descending
  282. if( lhGroup.m_Refs.size() == 0 )
  283. return true;
  284. else if( rhGroup.m_Refs.size() == 0 )
  285. return false;
  286. // N.B. To meet the iterator sort conditions, we cannot simply invert the truth
  287. // to get the opposite sort. i.e. ~(a<b) != (a>b)
  288. auto local_cmp = [ ascending ]( const auto a, const auto b )
  289. {
  290. if( ascending )
  291. return a < b;
  292. else
  293. return a > b;
  294. };
  295. // Primary sort key is sortCol; secondary is always REFERENCE (column 0)
  296. wxString lhs = dataModel->GetValue( (DATA_MODEL_ROW&) lhGroup, sortCol );
  297. wxString rhs = dataModel->GetValue( (DATA_MODEL_ROW&) rhGroup, sortCol );
  298. if( lhs == rhs || sortCol == REFERENCE )
  299. {
  300. wxString lhRef = lhGroup.m_Refs[ 0 ].GetRef() + lhGroup.m_Refs[ 0 ].GetRefNumber();
  301. wxString rhRef = rhGroup.m_Refs[ 0 ].GetRef() + rhGroup.m_Refs[ 0 ].GetRefNumber();
  302. return local_cmp( UTIL::RefDesStringCompare( lhRef, rhRef ), 0 );
  303. }
  304. else
  305. return local_cmp( ValueStringCompare( lhs, rhs ), 0 );
  306. }
  307. void Sort( int aColumn, bool ascending )
  308. {
  309. if( aColumn < 0 )
  310. aColumn = 0;
  311. m_sortColumn = aColumn;
  312. m_sortAscending = ascending;
  313. CollapseForSort();
  314. std::sort( m_rows.begin(), m_rows.end(),
  315. [ this ]( const DATA_MODEL_ROW& lhs, const DATA_MODEL_ROW& rhs ) -> bool
  316. {
  317. return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending );
  318. } );
  319. ExpandAfterSort();
  320. }
  321. bool unitMatch( const SCH_REFERENCE& lhRef, const SCH_REFERENCE& rhRef )
  322. {
  323. // If items are unannotated then we can't tell if they're units of the same
  324. // component or not
  325. if( lhRef.GetRefNumber() == wxT( "?" ) )
  326. return false;
  327. return ( lhRef.GetRef() == rhRef.GetRef() && lhRef.GetRefNumber() == rhRef.GetRefNumber() );
  328. }
  329. bool groupMatch( const SCH_REFERENCE& lhRef, const SCH_REFERENCE& rhRef,
  330. wxDataViewListCtrl* fieldsCtrl )
  331. {
  332. bool matchFound = false;
  333. // First check the reference column. This can be done directly out of the
  334. // SCH_REFERENCEs as the references can't be edited in the grid.
  335. if( fieldsCtrl->GetToggleValue( REFERENCE, GROUP_BY_COLUMN ) )
  336. {
  337. // if we're grouping by reference, then only the prefix must match
  338. if( lhRef.GetRef() != rhRef.GetRef() )
  339. return false;
  340. matchFound = true;
  341. }
  342. timestamp_t lhRefID = lhRef.GetComp()->GetTimeStamp();
  343. timestamp_t rhRefID = rhRef.GetComp()->GetTimeStamp();
  344. // Now check all the other columns. This must be done out of the dataStore
  345. // for the refresh button to work after editing.
  346. for( int i = REFERENCE + 1; i < fieldsCtrl->GetItemCount(); ++i )
  347. {
  348. if( !fieldsCtrl->GetToggleValue( i, GROUP_BY_COLUMN ) )
  349. continue;
  350. wxString fieldName = fieldsCtrl->GetTextValue( i, FIELD_NAME_COLUMN );
  351. if( m_dataStore[ lhRefID ][ fieldName ] != m_dataStore[ rhRefID ][ fieldName ] )
  352. return false;
  353. matchFound = true;
  354. }
  355. return matchFound;
  356. }
  357. void RebuildRows( wxCheckBox* groupComponentsBox, wxDataViewListCtrl* fieldsCtrl )
  358. {
  359. if ( GetView() )
  360. {
  361. // Commit any pending in-place edits before the row gets moved out from under
  362. // the editor.
  363. static_cast<WX_GRID*>( GetView() )->CommitPendingChanges( true );
  364. wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, 0, m_rows.size() );
  365. GetView()->ProcessTableMessage( msg );
  366. }
  367. m_rows.clear();
  368. for( unsigned i = 0; i < m_componentRefs.GetCount(); ++i )
  369. {
  370. SCH_REFERENCE ref = m_componentRefs[ i ];
  371. bool matchFound = false;
  372. // See if we already have a row which this component fits into
  373. for( auto& row : m_rows )
  374. {
  375. // all group members must have identical refs so just use the first one
  376. SCH_REFERENCE rowRef = row.m_Refs[ 0 ];
  377. if( unitMatch( ref, rowRef ) )
  378. {
  379. matchFound = true;
  380. row.m_Refs.push_back( ref );
  381. break;
  382. }
  383. else if (groupComponentsBox->GetValue() && groupMatch( ref, rowRef, fieldsCtrl ) )
  384. {
  385. matchFound = true;
  386. row.m_Refs.push_back( ref );
  387. row.m_Flag = GROUP_COLLAPSED;
  388. break;
  389. }
  390. }
  391. if( !matchFound )
  392. m_rows.emplace_back( DATA_MODEL_ROW( ref, GROUP_SINGLETON ) );
  393. }
  394. if ( GetView() )
  395. {
  396. wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, m_rows.size() );
  397. GetView()->ProcessTableMessage( msg );
  398. }
  399. }
  400. void ExpandRow( int aRow )
  401. {
  402. std::vector<DATA_MODEL_ROW> children;
  403. for( auto& ref : m_rows[ aRow ].m_Refs )
  404. {
  405. bool matchFound = false;
  406. // See if we already have a child group which this component fits into
  407. for( auto& child : children )
  408. {
  409. // group members are by definition all matching, so just check
  410. // against the first member
  411. if( unitMatch( ref, child.m_Refs[ 0 ] ) )
  412. {
  413. matchFound = true;
  414. child.m_Refs.push_back( ref );
  415. break;
  416. }
  417. }
  418. if( !matchFound )
  419. children.emplace_back( DATA_MODEL_ROW( ref, CHILD_ITEM ) );
  420. }
  421. if( children.size() < 2 )
  422. return;
  423. std::sort( children.begin(), children.end(),
  424. [ this ] ( const DATA_MODEL_ROW& lhs, const DATA_MODEL_ROW& rhs ) -> bool
  425. {
  426. return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending );
  427. } );
  428. m_rows[ aRow ].m_Flag = GROUP_EXPANDED;
  429. m_rows.insert( m_rows.begin() + aRow + 1, children.begin(), children.end() );
  430. wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, aRow, children.size() );
  431. GetView()->ProcessTableMessage( msg );
  432. }
  433. void CollapseRow( int aRow )
  434. {
  435. auto firstChild = m_rows.begin() + aRow + 1;
  436. auto afterLastChild = firstChild;
  437. int deleted = 0;
  438. while( afterLastChild != m_rows.end() && afterLastChild->m_Flag == CHILD_ITEM )
  439. {
  440. deleted++;
  441. afterLastChild++;
  442. }
  443. m_rows[ aRow ].m_Flag = GROUP_COLLAPSED;
  444. m_rows.erase( firstChild, afterLastChild );
  445. wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, aRow + 1, deleted );
  446. GetView()->ProcessTableMessage( msg );
  447. }
  448. void ExpandCollapseRow( int aRow )
  449. {
  450. DATA_MODEL_ROW& group = m_rows[ aRow ];
  451. if( group.m_Flag == GROUP_COLLAPSED )
  452. ExpandRow( aRow );
  453. else if( group.m_Flag == GROUP_EXPANDED )
  454. CollapseRow( aRow );
  455. }
  456. void CollapseForSort()
  457. {
  458. for( size_t i = 0; i < m_rows.size(); ++i )
  459. {
  460. if( m_rows[ i ].m_Flag == GROUP_EXPANDED )
  461. {
  462. CollapseRow( i );
  463. m_rows[ i ].m_Flag = GROUP_COLLAPSED_DURING_SORT;
  464. }
  465. }
  466. }
  467. void ExpandAfterSort()
  468. {
  469. for( size_t i = 0; i < m_rows.size(); ++i )
  470. {
  471. if( m_rows[ i ].m_Flag == GROUP_COLLAPSED_DURING_SORT )
  472. ExpandRow( i );
  473. }
  474. }
  475. void ApplyData()
  476. {
  477. for( unsigned i = 0; i < m_componentRefs.GetCount(); ++i )
  478. {
  479. SCH_COMPONENT& comp = *m_componentRefs[i].GetComp();
  480. m_frame->SetCurrentSheet( m_componentRefs[i].GetSheetPath() );
  481. m_frame->SaveCopyInUndoList( &comp, UR_CHANGED, true );
  482. const std::map<wxString, wxString>& fieldStore = m_dataStore[comp.GetTimeStamp()];
  483. for( const std::pair<wxString, wxString> srcData : fieldStore )
  484. {
  485. const wxString& srcName = srcData.first;
  486. const wxString& srcValue = srcData.second;
  487. SCH_FIELD* destField = comp.FindField( srcName );
  488. if( !destField && !srcValue.IsEmpty() )
  489. {
  490. const auto compOrigin = comp.GetPosition();
  491. destField = comp.AddField( SCH_FIELD( compOrigin, -1, &comp, srcName ) );
  492. }
  493. if( !destField )
  494. {
  495. comp.RemoveField( srcName );
  496. continue;
  497. }
  498. // Reference and value fields cannot be empty. All other fields can.
  499. if( srcValue.IsEmpty()
  500. && (destField->GetId() == REFERENCE || destField->GetId() == VALUE))
  501. continue;
  502. destField->SetText( srcValue );
  503. }
  504. }
  505. m_edited = false;
  506. }
  507. int GetDataWidth( int aCol )
  508. {
  509. int width = 0;
  510. if( aCol == REFERENCE )
  511. {
  512. for( int row = 0; row < GetNumberRows(); ++row )
  513. {
  514. width = std::max( width, GetTextSize( GetValue( row, aCol ), GetView() ).x );
  515. }
  516. }
  517. else
  518. {
  519. wxString column_label = GetColLabelValue( aCol ); // component fieldName or Qty string
  520. for( unsigned compRef = 0; compRef < m_componentRefs.GetCount(); ++ compRef )
  521. {
  522. timestamp_t compId = m_componentRefs[ compRef ].GetComp()->GetTimeStamp();
  523. wxString text = m_dataStore[ compId ][ column_label ];
  524. width = std::max( width, GetTextSize( text, GetView() ).x );
  525. }
  526. }
  527. return width;
  528. }
  529. bool IsEdited()
  530. {
  531. return m_edited;
  532. }
  533. };
  534. DIALOG_FIELDS_EDITOR_GLOBAL::DIALOG_FIELDS_EDITOR_GLOBAL( SCH_EDIT_FRAME* parent ) :
  535. DIALOG_FIELDS_EDITOR_GLOBAL_BASE( parent ),
  536. m_parent( parent )
  537. {
  538. wxSize defaultDlgSize = ConvertDialogToPixels( wxSize( 600, 300 ) );
  539. // Get all components from the list of schematic sheets
  540. SCH_SHEET_LIST sheets( g_RootSheet );
  541. sheets.GetComponents( m_componentRefs, false );
  542. m_bRefresh->SetBitmap( KiBitmap( refresh_xpm ) );
  543. m_fieldsCtrl->AppendTextColumn( _( "Field" ), wxDATAVIEW_CELL_INERT, 0, wxALIGN_LEFT, 0 );
  544. m_fieldsCtrl->AppendToggleColumn( _( "Show" ), wxDATAVIEW_CELL_ACTIVATABLE, 0, wxALIGN_CENTER,
  545. 0 );
  546. m_fieldsCtrl->AppendToggleColumn( _( "Group By" ), wxDATAVIEW_CELL_ACTIVATABLE, 0,
  547. wxALIGN_CENTER, 0 );
  548. // SetWidth( wxCOL_WIDTH_AUTOSIZE ) fails here on GTK, so we calculate the title sizes and
  549. // set the column widths ourselves.
  550. auto column = m_fieldsCtrl->GetColumn( SHOW_FIELD_COLUMN );
  551. m_showColWidth = GetTextSize( column->GetTitle(), m_fieldsCtrl ).x + COLUMN_MARGIN;
  552. column->SetWidth( m_showColWidth );
  553. column = m_fieldsCtrl->GetColumn( GROUP_BY_COLUMN );
  554. m_groupByColWidth = GetTextSize( column->GetTitle(), m_fieldsCtrl ).x + COLUMN_MARGIN;
  555. column->SetWidth( m_groupByColWidth );
  556. // The fact that we're a list should keep the control from reserving space for the
  557. // expander buttons... but it doesn't. Fix by forcing the indent to 0.
  558. m_fieldsCtrl->SetIndent( 0 );
  559. m_dataModel = new FIELDS_EDITOR_GRID_DATA_MODEL( m_parent, m_componentRefs );
  560. LoadFieldNames(); // loads rows into m_fieldsCtrl and columns into m_dataModel
  561. // Now that the fields are loaded we can set the initial location of the splitter
  562. // based on the list width. Again, SetWidth( wxCOL_WIDTH_AUTOSIZE ) fails us on GTK.
  563. int nameColWidth = 0;
  564. for( int row = 0; row < m_fieldsCtrl->GetItemCount(); ++row )
  565. {
  566. const wxString& fieldName = m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN );
  567. nameColWidth = std::max( nameColWidth, GetTextSize( fieldName, m_fieldsCtrl ).x );
  568. }
  569. m_fieldsCtrl->GetColumn( FIELD_NAME_COLUMN )->SetWidth( nameColWidth );
  570. m_splitter1->SetSashPosition( nameColWidth + m_showColWidth + m_groupByColWidth + 40 );
  571. m_dataModel->RebuildRows( m_groupComponentsBox, m_fieldsCtrl );
  572. m_dataModel->Sort( 0, true );
  573. // wxGrid's column moving is buggy with native headers and this is one dialog where you'd
  574. // really like to be able to rearrange columns.
  575. m_grid->UseNativeColHeader( false );
  576. m_grid->SetTable( m_dataModel, true );
  577. // sync m_grid's column visibilities to Show checkboxes in m_fieldsCtrl
  578. for( int i = 0; i < m_fieldsCtrl->GetItemCount(); ++i )
  579. {
  580. if( m_fieldsCtrl->GetToggleValue( i, 1 ) )
  581. m_grid->ShowCol( i );
  582. else
  583. m_grid->HideCol( i );
  584. }
  585. // add Cut, Copy, and Paste to wxGrid
  586. m_grid->PushEventHandler( new FIELDS_EDITOR_GRID_TRICKS( this, m_grid, m_fieldsCtrl ) );
  587. // give a bit more room for comboboxes
  588. m_grid->SetDefaultRowSize( m_grid->GetDefaultRowSize() + 4 );
  589. // set reference column attributes
  590. wxGridCellAttr* attr = new wxGridCellAttr;
  591. attr->SetReadOnly();
  592. m_grid->SetColAttr( REFERENCE, attr );
  593. // set footprint column browse button
  594. attr = new wxGridCellAttr;
  595. attr->SetEditor( new GRID_CELL_FOOTPRINT_ID_EDITOR( this ) );
  596. m_grid->SetColAttr( FOOTPRINT, attr );
  597. // set datasheet column viewer button
  598. attr = new wxGridCellAttr;
  599. attr->SetEditor( new GRID_CELL_URL_EDITOR( this ) );
  600. m_grid->SetColAttr( DATASHEET, attr );
  601. // set quantities column attributes
  602. attr = new wxGridCellAttr;
  603. attr->SetReadOnly();
  604. m_grid->SetColAttr( m_dataModel->GetColsCount() - 1, attr );
  605. m_grid->SetColFormatNumber( m_dataModel->GetColsCount() - 1 );
  606. m_grid->AutoSizeColumns( false );
  607. for( int col = 0; col < m_grid->GetNumberCols(); ++ col )
  608. {
  609. // Columns are hidden by setting their width to 0 so if we resize them they will
  610. // become unhidden.
  611. if( m_grid->IsColShown( col ) )
  612. {
  613. int textWidth = m_dataModel->GetDataWidth( col ) + COLUMN_MARGIN;
  614. int maxWidth = defaultDlgSize.x / 3;
  615. if( col == m_grid->GetNumberCols() - 1 )
  616. m_grid->SetColSize( col, std::min( std::max( 50, textWidth ), maxWidth ) );
  617. else
  618. m_grid->SetColSize( col, std::min( std::max( 100, textWidth ), maxWidth ) );
  619. }
  620. }
  621. m_grid->SetGridCursor( 0, 1 );
  622. SetInitialFocus( m_grid );
  623. m_sdbSizer1OK->SetDefault();
  624. FinishDialogSettings();
  625. SetSize( defaultDlgSize );
  626. Center();
  627. // Connect Events
  628. m_grid->Connect( wxEVT_GRID_COL_SORT,
  629. wxGridEventHandler( DIALOG_FIELDS_EDITOR_GLOBAL::OnColSort ), NULL, this );
  630. }
  631. DIALOG_FIELDS_EDITOR_GLOBAL::~DIALOG_FIELDS_EDITOR_GLOBAL()
  632. {
  633. // Disconnect Events
  634. m_grid->Disconnect( wxEVT_GRID_COL_SORT,
  635. wxGridEventHandler( DIALOG_FIELDS_EDITOR_GLOBAL::OnColSort ), NULL, this );
  636. // Delete the GRID_TRICKS.
  637. m_grid->PopEventHandler( true );
  638. // we gave ownership of m_dataModel to the wxGrid...
  639. // Clear highlighted symbols, if any
  640. m_parent->GetCanvas()->GetView()->HighlightItem( nullptr, nullptr );
  641. m_parent->GetCanvas()->Refresh();
  642. }
  643. bool DIALOG_FIELDS_EDITOR_GLOBAL::TransferDataToWindow()
  644. {
  645. if( !wxDialog::TransferDataFromWindow() )
  646. return false;
  647. TOOL_MANAGER* toolMgr = m_parent->GetToolManager();
  648. EE_SELECTION_TOOL* selectionTool = toolMgr->GetTool<EE_SELECTION_TOOL>();
  649. EE_SELECTION& selection = selectionTool->GetSelection();
  650. SCH_COMPONENT* component = nullptr;
  651. if( selection.GetSize() == 1 )
  652. {
  653. EDA_ITEM* item = selection.Front();
  654. if( item->Type() == SCH_COMPONENT_T )
  655. component = (SCH_COMPONENT*) item;
  656. else if( item->GetParent() && item->GetParent()->Type() == SCH_COMPONENT_T )
  657. component = (SCH_COMPONENT*) item->GetParent();
  658. }
  659. if( component )
  660. {
  661. for( int row = 0; row < m_dataModel->GetNumberRows(); ++row )
  662. {
  663. std::vector<SCH_REFERENCE> references = m_dataModel->GetRowReferences( row );
  664. bool found = false;
  665. for( const SCH_REFERENCE& ref : references )
  666. {
  667. if( ref.GetComp() == component )
  668. {
  669. found = true;
  670. break;
  671. }
  672. }
  673. if( found )
  674. {
  675. m_grid->GoToCell( row, 1 );
  676. break;
  677. }
  678. }
  679. }
  680. return true;
  681. }
  682. bool DIALOG_FIELDS_EDITOR_GLOBAL::TransferDataFromWindow()
  683. {
  684. if( !m_grid->CommitPendingChanges() )
  685. return false;
  686. if( !wxDialog::TransferDataFromWindow() )
  687. return false;
  688. SCH_SHEET_PATH currentSheet = m_parent->GetCurrentSheet();
  689. m_dataModel->ApplyData();
  690. m_parent->SyncView();
  691. m_parent->OnModify();
  692. // Reset the view to where we left the user
  693. m_parent->SetCurrentSheet( currentSheet );
  694. m_parent->Refresh();
  695. return true;
  696. }
  697. void DIALOG_FIELDS_EDITOR_GLOBAL::AddField( const wxString& aName,
  698. bool defaultShow, bool defaultSortBy )
  699. {
  700. m_dataModel->AddColumn( aName );
  701. wxVector<wxVariant> fieldsCtrlRow;
  702. auto cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() );
  703. bool show = defaultShow;
  704. bool sort_by = defaultSortBy;
  705. std::string key( aName.ToUTF8() );
  706. try
  707. {
  708. show = cfg->m_FieldEditorPanel.fields_show.at( key );
  709. }
  710. catch( std::out_of_range& )
  711. {
  712. }
  713. try
  714. {
  715. show = cfg->m_FieldEditorPanel.fields_group_by.at( key );
  716. }
  717. catch( std::out_of_range& )
  718. {
  719. }
  720. fieldsCtrlRow.push_back( wxVariant( aName ) );
  721. fieldsCtrlRow.push_back( wxVariant( show ) );
  722. fieldsCtrlRow.push_back( wxVariant( sort_by ) );
  723. m_fieldsCtrl->AppendItem( fieldsCtrlRow );
  724. }
  725. /**
  726. * Constructs the rows of m_fieldsCtrl and the columns of m_dataModel from a union of all
  727. * field names in use.
  728. */
  729. void DIALOG_FIELDS_EDITOR_GLOBAL::LoadFieldNames()
  730. {
  731. std::set<wxString> userFieldNames;
  732. for( unsigned i = 0; i < m_componentRefs.GetCount(); ++i )
  733. {
  734. SCH_COMPONENT* comp = m_componentRefs[ i ].GetComp();
  735. for( int j = MANDATORY_FIELDS; j < comp->GetFieldCount(); ++j )
  736. userFieldNames.insert( comp->GetField( j )->GetName() );
  737. }
  738. // Force References to always be shown
  739. auto cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() );
  740. cfg->m_FieldEditorPanel.fields_show["Reference"] = true;
  741. // *DO NOT* use translated mandatory field names:
  742. // They are also used as keyword to find fields in component list.
  743. // Changing that is not a basic change
  744. AddField( "Reference", true, true );
  745. AddField( "Value", true, true );
  746. AddField( "Footprint", true, true );
  747. AddField( "Datasheet", true, false );
  748. for( const wxString& fieldName : userFieldNames )
  749. AddField( fieldName, true, false );
  750. // Add any templateFieldNames which aren't already present in the userFieldNames
  751. for( const TEMPLATE_FIELDNAME& templateFieldName : m_parent->GetTemplateFieldNames() )
  752. {
  753. if( userFieldNames.count( templateFieldName.m_Name ) == 0 )
  754. AddField( templateFieldName.m_Name, false, false );
  755. }
  756. }
  757. void DIALOG_FIELDS_EDITOR_GLOBAL::OnAddField( wxCommandEvent& event )
  758. {
  759. // quantities column will become new field column, so it needs to be reset
  760. auto attr = new wxGridCellAttr;
  761. m_grid->SetColAttr( m_dataModel->GetColsCount() - 1, attr );
  762. m_grid->SetColFormatCustom( m_dataModel->GetColsCount() - 1, wxGRID_VALUE_STRING );
  763. wxTextEntryDialog dlg( this, _( "New field name:" ), _( "Add Field" ) );
  764. if( dlg.ShowModal() != wxID_OK )
  765. return;
  766. wxString fieldName = dlg.GetValue();
  767. if( fieldName.IsEmpty() )
  768. {
  769. DisplayError( this, _( "Field must have a name." ) );
  770. return;
  771. }
  772. for( int i = 0; i < m_dataModel->GetNumberCols(); ++i )
  773. {
  774. if( fieldName == m_dataModel->GetColLabelValue( i ) )
  775. {
  776. DisplayError( this, wxString::Format( _( "Field name \"%s\" already in use." ),
  777. fieldName ) );
  778. return;
  779. }
  780. }
  781. std::string key( fieldName.ToUTF8() );
  782. auto cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() );
  783. cfg->m_FieldEditorPanel.fields_show[key] = true;
  784. AddField( fieldName, true, false );
  785. wxGridTableMessage msg( m_dataModel, wxGRIDTABLE_NOTIFY_COLS_INSERTED,
  786. m_fieldsCtrl->GetItemCount(), 1 );
  787. m_grid->ProcessTableMessage( msg );
  788. // set up attributes on the new quantities column
  789. attr = new wxGridCellAttr;
  790. attr->SetReadOnly();
  791. m_grid->SetColAttr( m_dataModel->GetColsCount() - 1, attr );
  792. m_grid->SetColFormatNumber( m_dataModel->GetColsCount() - 1 );
  793. m_grid->SetColSize( m_dataModel->GetColsCount() - 1, 50 );
  794. }
  795. void DIALOG_FIELDS_EDITOR_GLOBAL::OnColumnItemToggled( wxDataViewEvent& event )
  796. {
  797. auto cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() );
  798. wxDataViewItem item = event.GetItem();
  799. int row = m_fieldsCtrl->ItemToRow( item );
  800. int col = event.GetColumn();
  801. switch ( col )
  802. {
  803. default:
  804. break;
  805. case SHOW_FIELD_COLUMN:
  806. {
  807. bool value = m_fieldsCtrl->GetToggleValue( row, col );
  808. if( row == REFERENCE && !value )
  809. {
  810. DisplayError( this, _( "The Reference column cannot be hidden." ) );
  811. value = true;
  812. m_fieldsCtrl->SetToggleValue( value, row, col );
  813. }
  814. std::string fieldName( m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN ).ToUTF8() );
  815. cfg->m_FieldEditorPanel.fields_show[fieldName] = value;
  816. if( value )
  817. m_grid->ShowCol( row );
  818. else
  819. m_grid->HideCol( row ); // grid's columns map to fieldsCtrl's rows
  820. break;
  821. }
  822. case GROUP_BY_COLUMN:
  823. {
  824. bool value = m_fieldsCtrl->GetToggleValue( row, col );
  825. std::string fieldName( m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN ).ToUTF8() );
  826. cfg->m_FieldEditorPanel.fields_group_by[fieldName] = value;
  827. m_dataModel->RebuildRows( m_groupComponentsBox, m_fieldsCtrl );
  828. m_dataModel->Sort( m_grid->GetSortingColumn(), m_grid->IsSortOrderAscending() );
  829. m_grid->ForceRefresh();
  830. break;
  831. }
  832. }
  833. }
  834. void DIALOG_FIELDS_EDITOR_GLOBAL::OnGroupComponentsToggled( wxCommandEvent& event )
  835. {
  836. m_dataModel->RebuildRows( m_groupComponentsBox, m_fieldsCtrl );
  837. m_dataModel->Sort( m_grid->GetSortingColumn(), m_grid->IsSortOrderAscending() );
  838. m_grid->ForceRefresh();
  839. }
  840. void DIALOG_FIELDS_EDITOR_GLOBAL::OnColSort( wxGridEvent& aEvent )
  841. {
  842. int sortCol = aEvent.GetCol();
  843. bool ascending;
  844. // This is bonkers, but wxWidgets doesn't tell us ascending/descending in the
  845. // event, and if we ask it will give us pre-event info.
  846. if( m_grid->IsSortingBy( sortCol ) )
  847. // same column; invert ascending
  848. ascending = !m_grid->IsSortOrderAscending();
  849. else
  850. // different column; start with ascending
  851. ascending = true;
  852. m_dataModel->Sort( sortCol, ascending );
  853. m_grid->ForceRefresh();
  854. }
  855. void DIALOG_FIELDS_EDITOR_GLOBAL::OnTableValueChanged( wxGridEvent& event )
  856. {
  857. m_grid->ForceRefresh();
  858. }
  859. void DIALOG_FIELDS_EDITOR_GLOBAL::OnRegroupComponents( wxCommandEvent& event )
  860. {
  861. m_dataModel->RebuildRows( m_groupComponentsBox, m_fieldsCtrl );
  862. m_dataModel->Sort( m_grid->GetSortingColumn(), m_grid->IsSortOrderAscending() );
  863. m_grid->ForceRefresh();
  864. }
  865. void DIALOG_FIELDS_EDITOR_GLOBAL::OnTableCellClick( wxGridEvent& event )
  866. {
  867. if( event.GetCol() == REFERENCE )
  868. {
  869. m_grid->ClearSelection();
  870. m_grid->SetGridCursor( event.GetRow(), event.GetCol() );
  871. // Clear highlighted symbols, if any
  872. m_parent->GetCanvas()->GetView()->HighlightItem( nullptr, nullptr );
  873. m_parent->GetCanvas()->Refresh();
  874. m_dataModel->ExpandCollapseRow( event.GetRow() );
  875. std::vector<SCH_REFERENCE> refs = m_dataModel->GetRowReferences( event.GetRow() );
  876. // Focus Eeschema view on the component selected in the dialog
  877. if( refs.size() == 1 )
  878. {
  879. SCH_EDITOR_CONTROL* editor = m_parent->GetToolManager()->GetTool<SCH_EDITOR_CONTROL>();
  880. editor->FindComponentAndItem( refs[0].GetRef() + refs[0].GetRefNumber(), true,
  881. HIGHLIGHT_COMPONENT, wxEmptyString );
  882. }
  883. }
  884. else
  885. {
  886. event.Skip();
  887. }
  888. }
  889. void DIALOG_FIELDS_EDITOR_GLOBAL::OnTableItemContextMenu( wxGridEvent& event )
  890. {
  891. // TODO: Option to select footprint if FOOTPRINT column selected
  892. event.Skip();
  893. }
  894. void DIALOG_FIELDS_EDITOR_GLOBAL::OnSizeFieldList( wxSizeEvent& event )
  895. {
  896. int nameColWidth = event.GetSize().GetX() - m_showColWidth - m_groupByColWidth - 8;
  897. // GTK loses its head and messes these up when resizing the splitter bar:
  898. m_fieldsCtrl->GetColumn( 1 )->SetWidth( m_showColWidth );
  899. m_fieldsCtrl->GetColumn( 2 )->SetWidth( m_groupByColWidth );
  900. m_fieldsCtrl->GetColumn( 0 )->SetWidth( nameColWidth );
  901. event.Skip();
  902. }
  903. void DIALOG_FIELDS_EDITOR_GLOBAL::OnSaveAndContinue( wxCommandEvent& aEvent )
  904. {
  905. if( TransferDataFromWindow() )
  906. m_parent->SaveProject();
  907. }
  908. void DIALOG_FIELDS_EDITOR_GLOBAL::OnCancel( wxCommandEvent& event )
  909. {
  910. Close();
  911. }
  912. void DIALOG_FIELDS_EDITOR_GLOBAL::OnClose( wxCloseEvent& event )
  913. {
  914. // This is a cancel, so commit quietly as we're going to throw the results away anyway.
  915. m_grid->CommitPendingChanges( true );
  916. if( m_dataModel->IsEdited() )
  917. {
  918. if( !HandleUnsavedChanges( this, wxEmptyString,
  919. [&]()->bool { return TransferDataFromWindow(); } ) )
  920. {
  921. event.Veto();
  922. return;
  923. }
  924. }
  925. event.Skip();
  926. }