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.

1727 lines
51 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
4 years ago
2 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright The 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 "dialog_lib_edit_pin_table.h"
  24. #include <wx/filedlg.h>
  25. #include <wx/wfstream.h>
  26. #include <wx/msgdlg.h>
  27. #include <wx/tokenzr.h>
  28. #include "grid_tricks.h"
  29. #include <richio.h>
  30. #include <sch_pin.h>
  31. #include <pin_numbers.h>
  32. #include "pgm_base.h"
  33. #include <base_units.h>
  34. #include <bitmaps.h>
  35. #include <clipboard.h>
  36. #include <confirm.h>
  37. #include <symbol_edit_frame.h>
  38. #include <symbol_editor_settings.h>
  39. #include <io/csv.h>
  40. #include <kiplatform/ui.h>
  41. #include <widgets/grid_icon_text_helpers.h>
  42. #include <widgets/grid_combobox.h>
  43. #include <widgets/wx_grid.h>
  44. #include <widgets/bitmap_button.h>
  45. #include <widgets/std_bitmap_button.h>
  46. #include <wildcards_and_files_ext.h>
  47. #include <settings/settings_manager.h>
  48. #include <tool/action_menu.h>
  49. #include <tool/tool_manager.h>
  50. #include <tools/sch_selection_tool.h>
  51. #include <string_utils.h>
  52. #define BOOL_TRUE _HKI( "True" )
  53. #define BOOL_FALSE _HKI( "False" )
  54. #define UNITS_ALL _HKI( "ALL" )
  55. #define DEMORGAN_ALL _HKI( "ALL" )
  56. #define DEMORGAN_STD _HKI( "Standard" )
  57. #define DEMORGAN_ALT _HKI( "Alternate" )
  58. /**
  59. * Get the label for a given column in the pin table.
  60. *
  61. * This string is NOT translated.
  62. */
  63. static wxString GetPinTableColLabel( int aCol )
  64. {
  65. switch( aCol )
  66. {
  67. case COL_PIN_COUNT: return _HKI( "Count" );
  68. case COL_NUMBER: return _HKI( "Number" );
  69. case COL_NAME: return _HKI( "Name" );
  70. case COL_TYPE: return _HKI( "Electrical Type" );
  71. case COL_SHAPE: return _HKI( "Graphic Style" );
  72. case COL_ORIENTATION: return _HKI( "Orientation" );
  73. case COL_NUMBER_SIZE: return _HKI( "Number Text Size" );
  74. case COL_NAME_SIZE: return _HKI( "Name Text Size" );
  75. case COL_LENGTH: return _HKI( "Length" );
  76. case COL_POSX: return _HKI( "X Position" );
  77. case COL_POSY: return _HKI( "Y Position" );
  78. case COL_VISIBLE: return _HKI( "Visible" );
  79. case COL_UNIT: return _HKI( "Unit" );
  80. case COL_DEMORGAN: return _HKI( "De Morgan" );
  81. default: wxFAIL; return wxEmptyString;
  82. }
  83. }
  84. static bool MatchTranslationOrNative( const wxString& aStr, const wxString& aNativeLabel, bool aCaseSensitive )
  85. {
  86. return wxGetTranslation( aNativeLabel ).IsSameAs( aStr, aCaseSensitive )
  87. || aStr.IsSameAs( aNativeLabel, aCaseSensitive );
  88. }
  89. static COL_ORDER GetColTypeForString( const wxString& aStr )
  90. {
  91. for( int i = 0; i < COL_COUNT; i++ )
  92. {
  93. if( MatchTranslationOrNative( aStr, GetPinTableColLabel( i ), false ) )
  94. return (COL_ORDER) i;
  95. }
  96. return COL_COUNT;
  97. }
  98. /**
  99. * Class that handles conversion of various pin data fields into strings for display in the
  100. * UI or serialisation to formats like CSV.
  101. */
  102. class PIN_INFO_FORMATTER
  103. {
  104. public:
  105. enum class BOOL_FORMAT
  106. {
  107. ZERO_ONE,
  108. TRUE_FALSE,
  109. };
  110. PIN_INFO_FORMATTER( UNITS_PROVIDER& aUnitsProvider, bool aIncludeUnits, BOOL_FORMAT aBoolFormat,
  111. REPORTER& aReporter ) :
  112. m_unitsProvider( aUnitsProvider ),
  113. m_includeUnits( aIncludeUnits ),
  114. m_boolFormat( aBoolFormat ),
  115. m_reporter( aReporter )
  116. {
  117. }
  118. wxString Format( const SCH_PIN& aPin, int aFieldId ) const
  119. {
  120. wxString val;
  121. switch( aFieldId )
  122. {
  123. case COL_NAME:
  124. val << aPin.GetName();
  125. break;
  126. case COL_NUMBER:
  127. val << aPin.GetNumber();
  128. break;
  129. case COL_TYPE:
  130. val << PinTypeNames()[static_cast<int>( aPin.GetType() )];
  131. break;
  132. case COL_SHAPE:
  133. val << PinShapeNames()[static_cast<int>( aPin.GetShape() )];
  134. break;
  135. case COL_ORIENTATION:
  136. {
  137. const int index = PinOrientationIndex( aPin.GetOrientation() );
  138. if( index >= 0)
  139. val << PinOrientationNames()[ index ];
  140. break;
  141. }
  142. case COL_NUMBER_SIZE:
  143. val << m_unitsProvider.StringFromValue( aPin.GetNumberTextSize(), m_includeUnits );
  144. break;
  145. case COL_NAME_SIZE:
  146. val << m_unitsProvider.StringFromValue( aPin.GetNameTextSize(), m_includeUnits );
  147. break;
  148. case COL_LENGTH:
  149. val << m_unitsProvider.StringFromValue( aPin.GetLength(), m_includeUnits );
  150. break;
  151. case COL_POSX:
  152. val << m_unitsProvider.StringFromValue( aPin.GetPosition().x, m_includeUnits );
  153. break;
  154. case COL_POSY:
  155. val << m_unitsProvider.StringFromValue( aPin.GetPosition().y, m_includeUnits );
  156. break;
  157. case COL_VISIBLE:
  158. val << stringFromBool( aPin.IsVisible() );
  159. break;
  160. case COL_UNIT:
  161. if( aPin.GetUnit() )
  162. val << LIB_SYMBOL::LetterSubReference( aPin.GetUnit(), 'A' );
  163. else
  164. val << wxGetTranslation( UNITS_ALL );
  165. break;
  166. case COL_DEMORGAN:
  167. switch( aPin.GetBodyStyle() )
  168. {
  169. case BODY_STYLE::BASE:
  170. val << wxGetTranslation( DEMORGAN_STD );
  171. break;
  172. case BODY_STYLE::DEMORGAN:
  173. val << wxGetTranslation( DEMORGAN_ALT );
  174. break;
  175. default:
  176. val << wxGetTranslation( DEMORGAN_ALL );
  177. break;
  178. }
  179. break;
  180. default:
  181. wxFAIL_MSG( wxString::Format( "Invalid field id %d", aFieldId ) );
  182. break;
  183. }
  184. return val;
  185. }
  186. /**
  187. * Update the pin from the given col/string.
  188. *
  189. * How much this should follow the format is debatable, but for now it's fairly permissive
  190. * (e.g. bools import as 0/1 and no/yes).
  191. */
  192. void UpdatePin( SCH_PIN& aPin, const wxString& aValue, int aFieldId, const LIB_SYMBOL& aSymbol ) const
  193. {
  194. switch( aFieldId )
  195. {
  196. case COL_NUMBER:
  197. aPin.SetNumber( aValue );
  198. break;
  199. case COL_NAME:
  200. aPin.SetName( aValue );
  201. break;
  202. case COL_TYPE:
  203. if( PinTypeNames().Index( aValue, false ) != wxNOT_FOUND )
  204. aPin.SetType( (ELECTRICAL_PINTYPE) PinTypeNames().Index( aValue ) );
  205. break;
  206. case COL_SHAPE:
  207. if( PinShapeNames().Index( aValue, false ) != wxNOT_FOUND )
  208. aPin.SetShape( (GRAPHIC_PINSHAPE) PinShapeNames().Index( aValue ) );
  209. break;
  210. case COL_ORIENTATION:
  211. if( PinOrientationNames().Index( aValue, false ) != wxNOT_FOUND )
  212. aPin.SetOrientation( (PIN_ORIENTATION) PinOrientationNames().Index( aValue ) );
  213. break;
  214. case COL_NUMBER_SIZE:
  215. aPin.SetNumberTextSize( m_unitsProvider.ValueFromString( aValue ) );
  216. break;
  217. case COL_NAME_SIZE:
  218. aPin.SetNameTextSize( m_unitsProvider.ValueFromString( aValue ) );
  219. break;
  220. case COL_LENGTH:
  221. aPin.ChangeLength( m_unitsProvider.ValueFromString( aValue ) );
  222. break;
  223. case COL_POSX:
  224. aPin.SetPosition( VECTOR2I( m_unitsProvider.ValueFromString( aValue ),
  225. aPin.GetPosition().y ) );
  226. break;
  227. case COL_POSY:
  228. aPin.SetPosition( VECTOR2I( aPin.GetPosition().x, m_unitsProvider.ValueFromString( aValue ) ) );
  229. break;
  230. case COL_VISIBLE:
  231. aPin.SetVisible(boolFromString( aValue, m_reporter ));
  232. break;
  233. case COL_UNIT:
  234. if( MatchTranslationOrNative( aValue, UNITS_ALL, false ) )
  235. {
  236. aPin.SetUnit( 0 );
  237. }
  238. else
  239. {
  240. for( int i = 1; i <= aSymbol.GetUnitCount(); i++ )
  241. {
  242. if( aValue == LIB_SYMBOL::LetterSubReference( i, 'A' ) )
  243. {
  244. aPin.SetUnit( i );
  245. break;
  246. }
  247. }
  248. }
  249. break;
  250. case COL_DEMORGAN:
  251. if( MatchTranslationOrNative( aValue, DEMORGAN_STD, false ) )
  252. aPin.SetBodyStyle( 1 );
  253. else if( MatchTranslationOrNative( aValue, DEMORGAN_ALT, false ) )
  254. aPin.SetBodyStyle( 2 );
  255. else
  256. aPin.SetBodyStyle( 0 );
  257. break;
  258. default:
  259. wxFAIL_MSG( wxString::Format( "Invalid field id %d", aFieldId ) );
  260. break;
  261. }
  262. }
  263. private:
  264. wxString stringFromBool( bool aValue ) const
  265. {
  266. switch( m_boolFormat )
  267. {
  268. case BOOL_FORMAT::ZERO_ONE: return aValue ? wxT( "1" ) : wxT( "0" );
  269. case BOOL_FORMAT::TRUE_FALSE:
  270. return wxGetTranslation( aValue ? BOOL_TRUE : BOOL_FALSE );
  271. // no default
  272. }
  273. wxCHECK_MSG( false, wxEmptyString, "Invalid BOOL_FORMAT" );
  274. }
  275. bool boolFromString( const wxString& aValue, REPORTER& aReporter ) const
  276. {
  277. if( aValue == wxS( "1" ) )
  278. {
  279. return true;
  280. }
  281. else if( aValue == wxS( "0" ) )
  282. {
  283. return false;
  284. }
  285. else if( MatchTranslationOrNative( aValue, BOOL_TRUE, false ) )
  286. {
  287. return true;
  288. }
  289. else if( MatchTranslationOrNative( aValue, BOOL_FALSE, false ) )
  290. {
  291. return false;
  292. }
  293. aReporter.Report( wxString::Format( _( "The value '%s' can't be converted to boolean correctly, "
  294. "it has been interpreted as 'False'" ),
  295. aValue ),
  296. RPT_SEVERITY_ERROR );
  297. return false;
  298. }
  299. UNITS_PROVIDER& m_unitsProvider;
  300. bool m_includeUnits;
  301. BOOL_FORMAT m_boolFormat;
  302. REPORTER& m_reporter;
  303. };
  304. void getSelectedArea( WX_GRID* aGrid, int* aRowStart, int* aRowCount )
  305. {
  306. wxGridCellCoordsArray topLeft = aGrid->GetSelectionBlockTopLeft();
  307. wxGridCellCoordsArray botRight = aGrid->GetSelectionBlockBottomRight();
  308. wxArrayInt cols = aGrid->GetSelectedCols();
  309. wxArrayInt rows = aGrid->GetSelectedRows();
  310. if( topLeft.Count() && botRight.Count() )
  311. {
  312. *aRowStart = topLeft[0].GetRow();
  313. *aRowCount = botRight[0].GetRow() - *aRowStart + 1;
  314. }
  315. else if( cols.Count() )
  316. {
  317. *aRowStart = 0;
  318. *aRowCount = aGrid->GetNumberRows();
  319. }
  320. else if( rows.Count() )
  321. {
  322. *aRowStart = rows[0];
  323. *aRowCount = rows.Count();
  324. }
  325. else
  326. {
  327. *aRowStart = aGrid->GetGridCursorRow();
  328. *aRowCount = *aRowStart >= 0 ? 1 : 0;
  329. }
  330. }
  331. class PIN_TABLE_DATA_MODEL : public wxGridTableBase
  332. {
  333. public:
  334. PIN_TABLE_DATA_MODEL( SYMBOL_EDIT_FRAME* aFrame,
  335. DIALOG_LIB_EDIT_PIN_TABLE* aPinTable,
  336. LIB_SYMBOL* aSymbol,
  337. const std::vector<SCH_PIN*>& aOrigSelectedPins ) :
  338. m_frame( aFrame ),
  339. m_unitFilter( -1 ),
  340. m_bodyStyleFilter( -1 ),
  341. m_filterBySelection( false ),
  342. m_edited( false ),
  343. m_pinTable( aPinTable ),
  344. m_symbol( aSymbol ),
  345. m_origSelectedPins( aOrigSelectedPins )
  346. {
  347. m_eval = std::make_unique<NUMERIC_EVALUATOR>( m_frame->GetUserUnits() );
  348. m_frame->Bind( EDA_EVT_UNITS_CHANGED, &PIN_TABLE_DATA_MODEL::onUnitsChanged, this );
  349. }
  350. ~PIN_TABLE_DATA_MODEL()
  351. {
  352. m_frame->Unbind( EDA_EVT_UNITS_CHANGED, &PIN_TABLE_DATA_MODEL::onUnitsChanged, this );
  353. }
  354. void onUnitsChanged( wxCommandEvent& aEvent )
  355. {
  356. if( GetView() )
  357. GetView()->ForceRefresh();
  358. aEvent.Skip();
  359. }
  360. void SetUnitFilter( int aFilter ) { m_unitFilter = aFilter; }
  361. void SetBodyStyleFilter( int aFilter ) { m_bodyStyleFilter = aFilter; }
  362. void SetFilterBySelection( bool aFilter ) { m_filterBySelection = aFilter; }
  363. int GetNumberRows() override { return (int) m_rows.size(); }
  364. int GetNumberCols() override { return COL_COUNT; }
  365. wxString GetColLabelValue( int aCol ) override
  366. {
  367. return wxGetTranslation( GetPinTableColLabel( aCol ) );
  368. }
  369. bool IsEmptyCell( int row, int col ) override
  370. {
  371. return false; // don't allow adjacent cell overflow, even if we are actually empty
  372. }
  373. wxString GetValue( int aRow, int aCol ) override
  374. {
  375. wxGrid* grid = GetView();
  376. if( grid->GetGridCursorRow() == aRow && grid->GetGridCursorCol() == aCol
  377. && grid->IsCellEditControlShown() )
  378. {
  379. auto it = m_evalOriginal.find( { m_rows[ aRow ], aCol } );
  380. if( it != m_evalOriginal.end() )
  381. return it->second;
  382. }
  383. return GetValue( m_rows[ aRow ], aCol, m_frame );
  384. }
  385. static wxString GetValue( const std::vector<SCH_PIN*>& pins, int aCol,
  386. EDA_DRAW_FRAME* aParentFrame )
  387. {
  388. wxString fieldValue;
  389. if( pins.empty() )
  390. return fieldValue;
  391. NULL_REPORTER reporter;
  392. PIN_INFO_FORMATTER formatter( *aParentFrame, true, PIN_INFO_FORMATTER::BOOL_FORMAT::ZERO_ONE, reporter );
  393. for( const SCH_PIN* pin : pins )
  394. {
  395. wxString val;
  396. switch( aCol )
  397. {
  398. case COL_PIN_COUNT:
  399. val << pins.size();
  400. break;
  401. default:
  402. val << formatter.Format( *pin, aCol );
  403. break;
  404. }
  405. if( aCol == COL_NUMBER )
  406. {
  407. if( fieldValue.length() )
  408. fieldValue += wxT( ", " );
  409. fieldValue += val;
  410. }
  411. else
  412. {
  413. if( !fieldValue.Length() )
  414. fieldValue = val;
  415. else if( val != fieldValue )
  416. fieldValue = INDETERMINATE_STATE;
  417. }
  418. }
  419. return fieldValue;
  420. }
  421. void SetValue( int aRow, int aCol, const wxString &aValue ) override
  422. {
  423. if( aValue == INDETERMINATE_STATE )
  424. return;
  425. wxString value = aValue;
  426. switch( aCol )
  427. {
  428. case COL_NUMBER_SIZE:
  429. case COL_NAME_SIZE:
  430. case COL_LENGTH:
  431. case COL_POSX:
  432. case COL_POSY:
  433. m_eval->SetDefaultUnits( m_frame->GetUserUnits() );
  434. if( m_eval->Process( value ) )
  435. {
  436. m_evalOriginal[ { m_rows[ aRow ], aCol } ] = value;
  437. value = m_eval->Result();
  438. }
  439. break;
  440. default:
  441. break;
  442. }
  443. std::vector<SCH_PIN*> pins = m_rows[ aRow ];
  444. // If the NUMBER column is edited and the pins are grouped, renumber, and add or
  445. // remove pins based on the comma separated list of pins.
  446. if( aCol == COL_NUMBER && m_pinTable->IsDisplayGrouped() )
  447. {
  448. wxStringTokenizer tokenizer( value, "," );
  449. size_t i = 0;
  450. while( tokenizer.HasMoreTokens() )
  451. {
  452. wxString pinName = tokenizer.GetNextToken();
  453. // Trim whitespace from both ends of the string
  454. pinName.Trim( true ).Trim( false );
  455. if( i < pins.size() )
  456. {
  457. // Renumber the existing pins
  458. pins.at( i )->SetNumber( pinName );
  459. }
  460. else
  461. {
  462. // Create new pins
  463. SCH_PIN* newPin = new SCH_PIN( this->m_symbol );
  464. SCH_PIN* last = pins.back();
  465. newPin->SetNumber( pinName );
  466. newPin->SetName( last->GetName() );
  467. newPin->SetOrientation( last->GetOrientation() );
  468. newPin->SetType( last->GetType() );
  469. newPin->SetShape( last->GetShape() );
  470. newPin->SetUnit( last->GetUnit() );
  471. VECTOR2I pos = last->GetPosition();
  472. if( SYMBOL_EDITOR_SETTINGS* cfg = GetAppSettings<SYMBOL_EDITOR_SETTINGS>( "symbol_editor" ) )
  473. {
  474. if( last->GetOrientation() == PIN_ORIENTATION::PIN_LEFT
  475. || last->GetOrientation() == PIN_ORIENTATION::PIN_RIGHT )
  476. {
  477. pos.y -= schIUScale.MilsToIU( cfg->m_Repeat.pin_step );
  478. }
  479. else
  480. {
  481. pos.x += schIUScale.MilsToIU( cfg->m_Repeat.pin_step );
  482. }
  483. }
  484. newPin->SetPosition( pos );
  485. pins.push_back( newPin );
  486. m_pinTable->AddPin( newPin );
  487. }
  488. i++;
  489. }
  490. while( pins.size() > i )
  491. {
  492. m_pinTable->RemovePin( pins.back() );
  493. pins.pop_back();
  494. }
  495. m_rows[aRow] = pins;
  496. m_edited = true;
  497. return;
  498. }
  499. NULL_REPORTER reporter;
  500. PIN_INFO_FORMATTER formatter( *m_frame, true, PIN_INFO_FORMATTER::BOOL_FORMAT::ZERO_ONE, reporter );
  501. for( SCH_PIN* pin : pins )
  502. {
  503. formatter.UpdatePin( *pin, value, aCol, *m_symbol );
  504. }
  505. m_edited = true;
  506. }
  507. static int findRow( const std::vector<std::vector<SCH_PIN*>>& aRowSet, const wxString& aName )
  508. {
  509. for( size_t i = 0; i < aRowSet.size(); ++i )
  510. {
  511. if( aRowSet[ i ][ 0 ] && aRowSet[ i ][ 0 ]->GetName() == aName )
  512. return i;
  513. }
  514. return -1;
  515. }
  516. static bool compare( const std::vector<SCH_PIN*>& lhs, const std::vector<SCH_PIN*>& rhs,
  517. int sortCol, bool ascending, EDA_DRAW_FRAME* parentFrame )
  518. {
  519. wxString lhStr = GetValue( lhs, sortCol, parentFrame );
  520. wxString rhStr = GetValue( rhs, sortCol, parentFrame );
  521. if( lhStr == rhStr )
  522. {
  523. // Secondary sort key is always COL_NUMBER
  524. sortCol = COL_NUMBER;
  525. lhStr = GetValue( lhs, sortCol, parentFrame );
  526. rhStr = GetValue( rhs, sortCol, parentFrame );
  527. }
  528. bool res;
  529. // N.B. To meet the iterator sort conditions, we cannot simply invert the truth
  530. // to get the opposite sort. i.e. ~(a<b) != (a>b)
  531. auto cmp = [ ascending ]( const auto a, const auto b )
  532. {
  533. if( ascending )
  534. return a < b;
  535. else
  536. return b < a;
  537. };
  538. switch( sortCol )
  539. {
  540. case COL_NUMBER:
  541. case COL_NAME:
  542. res = cmp( PIN_NUMBERS::Compare( lhStr, rhStr ), 0 );
  543. break;
  544. case COL_NUMBER_SIZE:
  545. case COL_NAME_SIZE:
  546. res = cmp( parentFrame->ValueFromString( lhStr ),
  547. parentFrame->ValueFromString( rhStr ) );
  548. break;
  549. case COL_LENGTH:
  550. case COL_POSX:
  551. case COL_POSY:
  552. res = cmp( parentFrame->ValueFromString( lhStr ),
  553. parentFrame->ValueFromString( rhStr ) );
  554. break;
  555. case COL_VISIBLE:
  556. case COL_DEMORGAN:
  557. default:
  558. res = cmp( StrNumCmp( lhStr, rhStr ), 0 );
  559. break;
  560. }
  561. return res;
  562. }
  563. void RebuildRows( const std::vector<SCH_PIN*>& aPins, bool groupByName, bool groupBySelection )
  564. {
  565. WX_GRID* grid = dynamic_cast<WX_GRID*>( GetView() );
  566. std::vector<SCH_PIN*> clear_flags;
  567. clear_flags.reserve( aPins.size() );
  568. if( grid )
  569. {
  570. if( groupBySelection )
  571. {
  572. for( SCH_PIN* pin : aPins )
  573. pin->ClearTempFlags();
  574. int firstSelectedRow;
  575. int selectedRowCount;
  576. getSelectedArea( grid, &firstSelectedRow, &selectedRowCount );
  577. for( int ii = 0; ii < selectedRowCount; ++ii )
  578. {
  579. for( SCH_PIN* pin : m_rows[ firstSelectedRow + ii ] )
  580. {
  581. pin->SetFlags( CANDIDATE );
  582. clear_flags.push_back( pin );
  583. }
  584. }
  585. }
  586. // Commit any pending in-place edits before the row gets moved out from under
  587. // the editor.
  588. grid->CommitPendingChanges( true );
  589. wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, 0, m_rows.size() );
  590. GetView()->ProcessTableMessage( msg );
  591. }
  592. m_rows.clear();
  593. if( groupBySelection )
  594. m_rows.emplace_back( std::vector<SCH_PIN*>() );
  595. std::set<wxString> selectedNumbers;
  596. for( SCH_PIN* pin : m_origSelectedPins )
  597. {
  598. selectedNumbers.insert( pin->GetNumber() );
  599. }
  600. const auto pinIsInEditorSelection = [&]( SCH_PIN* pin )
  601. {
  602. // Quick check before we iterate the whole thing in N^2 time.
  603. // (3000^2 = FPGAs causing issues down the road).
  604. if( selectedNumbers.count( pin->GetNumber() ) == 0 )
  605. {
  606. return false;
  607. }
  608. for( SCH_PIN* selectedPin : m_origSelectedPins )
  609. {
  610. // The selected pin is in the editor, but the pins in the table
  611. // are copies. We will mark the pin as selected if it's a match
  612. // on the critical items.
  613. if( selectedPin->GetNumber() == pin->GetNumber()
  614. && selectedPin->GetName() == pin->GetName()
  615. && selectedPin->GetUnit() == pin->GetUnit()
  616. && selectedPin->GetBodyStyle() == pin->GetBodyStyle()
  617. )
  618. {
  619. return true;
  620. }
  621. }
  622. return false;
  623. };
  624. for( SCH_PIN* pin : aPins )
  625. {
  626. const bool includedByUnit =
  627. ( m_unitFilter == -1 ) || ( pin->GetUnit() == 0 ) || ( pin->GetUnit() == m_unitFilter );
  628. const bool includedByBodyStyle =
  629. ( m_bodyStyleFilter == -1 ) || ( pin->GetBodyStyle() == m_bodyStyleFilter );
  630. const bool includedBySelection = !m_filterBySelection || pinIsInEditorSelection( pin );
  631. if( includedByUnit && includedByBodyStyle && includedBySelection )
  632. {
  633. int rowIndex = -1;
  634. if( groupByName )
  635. rowIndex = findRow( m_rows, pin->GetName() );
  636. else if( groupBySelection && ( pin->GetFlags() & CANDIDATE ) )
  637. rowIndex = 0;
  638. if( rowIndex < 0 )
  639. {
  640. m_rows.emplace_back( std::vector<SCH_PIN*>() );
  641. rowIndex = m_rows.size() - 1;
  642. }
  643. m_rows[ rowIndex ].push_back( pin );
  644. }
  645. }
  646. int sortCol = 0;
  647. bool ascending = true;
  648. if( GetView() && GetView()->GetSortingColumn() != wxNOT_FOUND )
  649. {
  650. sortCol = GetView()->GetSortingColumn();
  651. ascending = GetView()->IsSortOrderAscending();
  652. }
  653. for( std::vector<SCH_PIN*>& row : m_rows )
  654. SortPins( row );
  655. if( !groupBySelection )
  656. SortRows( sortCol, ascending );
  657. if ( GetView() )
  658. {
  659. wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, (int) m_rows.size() );
  660. GetView()->ProcessTableMessage( msg );
  661. if( groupBySelection )
  662. GetView()->SelectRow( 0 );
  663. }
  664. for( SCH_PIN* pin : clear_flags )
  665. pin->ClearFlags( CANDIDATE );
  666. }
  667. void SortRows( int aSortCol, bool ascending )
  668. {
  669. std::sort( m_rows.begin(), m_rows.end(),
  670. [ aSortCol, ascending, this ]( const std::vector<SCH_PIN*>& lhs,
  671. const std::vector<SCH_PIN*>& rhs ) -> bool
  672. {
  673. return compare( lhs, rhs, aSortCol, ascending, m_frame );
  674. } );
  675. }
  676. void SortPins( std::vector<SCH_PIN*>& aRow )
  677. {
  678. std::sort( aRow.begin(), aRow.end(),
  679. []( SCH_PIN* lhs, SCH_PIN* rhs ) -> bool
  680. {
  681. return PIN_NUMBERS::Compare( lhs->GetNumber(), rhs->GetNumber() ) < 0;
  682. } );
  683. }
  684. void AppendRow( SCH_PIN* aPin )
  685. {
  686. std::vector<SCH_PIN*> row;
  687. row.push_back( aPin );
  688. m_rows.push_back( row );
  689. if ( GetView() )
  690. {
  691. wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, 1 );
  692. GetView()->ProcessTableMessage( msg );
  693. }
  694. }
  695. std::vector<SCH_PIN*> RemoveRow( int aRow )
  696. {
  697. std::vector<SCH_PIN*> removedRow = m_rows[ aRow ];
  698. m_rows.erase( m_rows.begin() + aRow );
  699. if ( GetView() )
  700. {
  701. wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, aRow, 1 );
  702. GetView()->ProcessTableMessage( msg );
  703. }
  704. return removedRow;
  705. }
  706. std::vector<SCH_PIN*> GetRowPins( int aRow )
  707. {
  708. return m_rows[ aRow ];
  709. }
  710. bool IsEdited()
  711. {
  712. return m_edited;
  713. }
  714. private:
  715. SYMBOL_EDIT_FRAME* m_frame;
  716. // Because the rows of the grid can either be a single pin or a group of pins, the
  717. // data model is a 2D vector. If we're in the single pin case, each row's SCH_PINs
  718. // contains only a single pin.
  719. std::vector<std::vector<SCH_PIN*>> m_rows;
  720. int m_unitFilter; // -1 to show pins for all units
  721. int m_bodyStyleFilter; // -1 to show all body styles
  722. bool m_filterBySelection;
  723. bool m_edited;
  724. DIALOG_LIB_EDIT_PIN_TABLE* m_pinTable;
  725. LIB_SYMBOL* m_symbol; // Parent symbol that the pins belong to.
  726. /// The pins in the symbol that are selected at dialog start
  727. const std::vector<SCH_PIN*>& m_origSelectedPins;
  728. std::unique_ptr<NUMERIC_EVALUATOR> m_eval;
  729. std::map< std::pair<std::vector<SCH_PIN*>, int>, wxString > m_evalOriginal;
  730. };
  731. class PIN_TABLE_EXPORT
  732. {
  733. public:
  734. PIN_TABLE_EXPORT( UNITS_PROVIDER& aUnitsProvider ) :
  735. m_unitsProvider( aUnitsProvider )
  736. {
  737. }
  738. void ExportData( std::vector<SCH_PIN*>& aPins, const wxString& aToFile ) const
  739. {
  740. std::vector<int> exportCols {
  741. COL_NUMBER,
  742. COL_NAME,
  743. COL_TYPE,
  744. COL_SHAPE,
  745. COL_ORIENTATION,
  746. COL_NUMBER_SIZE,
  747. COL_NAME_SIZE,
  748. COL_LENGTH,
  749. COL_POSX,
  750. COL_POSY,
  751. COL_VISIBLE,
  752. COL_UNIT,
  753. COL_DEMORGAN,
  754. };
  755. std::vector<std::vector<wxString>> exportTable;
  756. exportTable.reserve( aPins.size() + 1 );
  757. std::vector<wxString> headers;
  758. for( int col : exportCols )
  759. {
  760. headers.push_back( wxGetTranslation( GetPinTableColLabel( col ) ) );
  761. }
  762. exportTable.emplace_back( std::move( headers ) );
  763. NULL_REPORTER reporter;
  764. PIN_INFO_FORMATTER formatter( m_unitsProvider, false, PIN_INFO_FORMATTER::BOOL_FORMAT::TRUE_FALSE, reporter );
  765. for( const SCH_PIN* pin : aPins )
  766. {
  767. std::vector<wxString>& cols = exportTable.emplace_back( 0 );
  768. cols.reserve( exportCols.size() );
  769. for( int col : exportCols )
  770. {
  771. cols.emplace_back( formatter.Format( *pin, col ) );
  772. }
  773. }
  774. if( !aToFile.IsEmpty() )
  775. {
  776. wxFileOutputStream os( aToFile );
  777. CSV_WRITER writer( os );
  778. writer.WriteLines( exportTable );
  779. }
  780. else
  781. {
  782. SaveTabularDataToClipboard( exportTable );
  783. }
  784. }
  785. private:
  786. UNITS_PROVIDER& m_unitsProvider;
  787. };
  788. class PIN_TABLE_IMPORT
  789. {
  790. public:
  791. PIN_TABLE_IMPORT( EDA_BASE_FRAME& aFrame, REPORTER& aReporter ) :
  792. m_frame( aFrame ),
  793. m_reporter( aReporter )
  794. {
  795. }
  796. std::vector<std::unique_ptr<SCH_PIN>> ImportData( bool aFromFile, LIB_SYMBOL& aSym ) const
  797. {
  798. wxString path;
  799. if( aFromFile )
  800. {
  801. path = promptForFile();
  802. if( path.IsEmpty() )
  803. return {};
  804. }
  805. std::vector<std::vector<wxString>> csvData;
  806. bool ok = false;
  807. if( !path.IsEmpty() )
  808. {
  809. // Read file content
  810. wxString csvFileContent = SafeReadFile( path, "r" );
  811. ok = AutoDecodeCSV( csvFileContent, csvData );
  812. }
  813. else
  814. {
  815. ok = GetTabularDataFromClipboard( csvData );
  816. }
  817. std::vector<std::unique_ptr<SCH_PIN>> pins;
  818. PIN_INFO_FORMATTER formatter( m_frame, false, PIN_INFO_FORMATTER::BOOL_FORMAT::TRUE_FALSE, m_reporter );
  819. if( ok )
  820. {
  821. // The first thing we need to do is map the CSV columns to the pin table columns
  822. // (in case the user reorders them)
  823. std::vector<COL_ORDER> headerCols = getColOrderFromCSV( csvData[0] );
  824. for( size_t i = 1; i < csvData.size(); ++i )
  825. {
  826. std::vector<wxString>& cols = csvData[i];
  827. auto pin = std::make_unique<SCH_PIN>( &aSym );
  828. // Ignore cells that stick out to the right of the headers
  829. size_t maxCol = std::min( headerCols.size(), cols.size() );
  830. for( size_t j = 0; j < maxCol; ++j )
  831. {
  832. // Skip unrecognised columns
  833. if( headerCols[j] == COL_COUNT )
  834. continue;
  835. formatter.UpdatePin( *pin, cols[j], headerCols[j], aSym );
  836. }
  837. pins.emplace_back( std::move( pin ) );
  838. }
  839. }
  840. return pins;
  841. }
  842. private:
  843. wxString promptForFile() const
  844. {
  845. wxFileDialog dlg( &m_frame, _( "Select pin data file" ), "", "", FILEEXT::CsvTsvFileWildcard(),
  846. wxFD_OPEN | wxFD_FILE_MUST_EXIST );
  847. if( dlg.ShowModal() == wxID_CANCEL )
  848. return wxEmptyString;
  849. return dlg.GetPath();
  850. }
  851. std::vector<COL_ORDER> getColOrderFromCSV( const std::vector<wxString>& aHeaderRow ) const
  852. {
  853. std::vector<COL_ORDER> colOrder;
  854. wxArrayString unknownHeaders;
  855. for( size_t i = 0; i < aHeaderRow.size(); ++i )
  856. {
  857. COL_ORDER col = GetColTypeForString( aHeaderRow[i] );
  858. if( col >= COL_COUNT )
  859. unknownHeaders.push_back( aHeaderRow[i] );
  860. colOrder.push_back( col );
  861. }
  862. if( unknownHeaders.size() )
  863. {
  864. wxString msg = wxString::Format( _( "Unknown columns in data: %s. These columns will be ignored." ),
  865. AccumulateDescriptions( unknownHeaders ) );
  866. m_reporter.Report( msg, RPT_SEVERITY_ERROR );
  867. }
  868. return colOrder;
  869. }
  870. EDA_BASE_FRAME& m_frame;
  871. REPORTER& m_reporter;
  872. };
  873. DIALOG_LIB_EDIT_PIN_TABLE::DIALOG_LIB_EDIT_PIN_TABLE( SYMBOL_EDIT_FRAME* parent,
  874. LIB_SYMBOL* aSymbol,
  875. const std::vector<SCH_PIN*>& aSelectedPins ) :
  876. DIALOG_LIB_EDIT_PIN_TABLE_BASE( parent ),
  877. m_editFrame( parent ),
  878. m_symbol( aSymbol )
  879. {
  880. m_dataModel = new PIN_TABLE_DATA_MODEL( m_editFrame, this, this->m_symbol, aSelectedPins );
  881. // Save original columns widths so we can do proportional sizing.
  882. for( int i = 0; i < COL_COUNT; ++i )
  883. m_originalColWidths[ i ] = m_grid->GetColSize( i );
  884. m_grid->SetTable( m_dataModel );
  885. m_grid->PushEventHandler( new GRID_TRICKS( m_grid, [this]( wxCommandEvent& aEvent )
  886. {
  887. OnAddRow( aEvent );
  888. } ) );
  889. // Show/hide columns according to the user's preference
  890. if( SYMBOL_EDITOR_SETTINGS* cfg = parent->GetSettings() )
  891. {
  892. m_grid->ShowHideColumns( cfg->m_PinTableVisibleColumns );
  893. m_columnsShown = m_grid->GetShownColumns();
  894. }
  895. // Set special attributes
  896. wxGridCellAttr* attr;
  897. attr = new wxGridCellAttr;
  898. attr->SetReadOnly( true );
  899. m_grid->SetColAttr( COL_PIN_COUNT, attr );
  900. attr = new wxGridCellAttr;
  901. wxArrayString typeNames = PinTypeNames();
  902. typeNames.push_back( INDETERMINATE_STATE );
  903. attr->SetRenderer( new GRID_CELL_ICON_TEXT_RENDERER( PinTypeIcons(), typeNames ) );
  904. attr->SetEditor( new GRID_CELL_ICON_TEXT_POPUP( PinTypeIcons(), typeNames ) );
  905. m_grid->SetColAttr( COL_TYPE, attr );
  906. attr = new wxGridCellAttr;
  907. wxArrayString shapeNames = PinShapeNames();
  908. shapeNames.push_back( INDETERMINATE_STATE );
  909. attr->SetRenderer( new GRID_CELL_ICON_TEXT_RENDERER( PinShapeIcons(), shapeNames ) );
  910. attr->SetEditor( new GRID_CELL_ICON_TEXT_POPUP( PinShapeIcons(), shapeNames ) );
  911. m_grid->SetColAttr( COL_SHAPE, attr );
  912. attr = new wxGridCellAttr;
  913. wxArrayString orientationNames = PinOrientationNames();
  914. orientationNames.push_back( INDETERMINATE_STATE );
  915. attr->SetRenderer( new GRID_CELL_ICON_TEXT_RENDERER( PinOrientationIcons(),
  916. orientationNames ) );
  917. attr->SetEditor( new GRID_CELL_ICON_TEXT_POPUP( PinOrientationIcons(), orientationNames ) );
  918. m_grid->SetColAttr( COL_ORIENTATION, attr );
  919. attr = new wxGridCellAttr;
  920. wxArrayString unitNames;
  921. unitNames.push_back( wxGetTranslation( UNITS_ALL ) );
  922. for( int i = 1; i <= aSymbol->GetUnitCount(); i++ )
  923. unitNames.push_back( LIB_SYMBOL::LetterSubReference( i, 'A' ) );
  924. attr->SetEditor( new GRID_CELL_COMBOBOX( unitNames ) );
  925. m_grid->SetColAttr( COL_UNIT, attr );
  926. attr = new wxGridCellAttr;
  927. wxArrayString demorganNames;
  928. demorganNames.push_back( wxGetTranslation( DEMORGAN_ALL ) );
  929. demorganNames.push_back( wxGetTranslation( DEMORGAN_STD ) );
  930. demorganNames.push_back( wxGetTranslation( DEMORGAN_ALT ) );
  931. attr->SetEditor( new GRID_CELL_COMBOBOX( demorganNames ) );
  932. m_grid->SetColAttr( COL_DEMORGAN, attr );
  933. attr = new wxGridCellAttr;
  934. attr->SetRenderer( new wxGridCellBoolRenderer() );
  935. attr->SetEditor( new wxGridCellBoolEditor() );
  936. attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
  937. m_grid->SetColAttr( COL_VISIBLE, attr );
  938. /* Right-aligned position values look much better, but only MSW and GTK2+
  939. * currently support right-aligned textEditCtrls, so the text jumps on all
  940. * the other platforms when you edit it.
  941. attr = new wxGridCellAttr;
  942. attr->SetAlignment( wxALIGN_RIGHT, wxALIGN_TOP );
  943. m_grid->SetColAttr( COL_POSX, attr );
  944. attr = new wxGridCellAttr;
  945. attr->SetAlignment( wxALIGN_RIGHT, wxALIGN_TOP );
  946. m_grid->SetColAttr( COL_POSY, attr );
  947. */
  948. m_addButton->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) );
  949. m_deleteButton->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) );
  950. m_refreshButton->SetBitmap( KiBitmapBundle( BITMAPS::small_refresh ) );
  951. m_divider1->SetIsSeparator();
  952. GetSizer()->SetSizeHints(this);
  953. Centre();
  954. if( aSymbol->IsMulti() )
  955. {
  956. m_unitFilter->Append( wxGetTranslation( UNITS_ALL ) );
  957. for( int ii = 0; ii < aSymbol->GetUnitCount(); ++ii )
  958. m_unitFilter->Append( LIB_SYMBOL::LetterSubReference( ii + 1, 'A' ) );
  959. m_unitFilter->SetSelection( -1 );
  960. }
  961. else
  962. {
  963. m_cbFilterByUnit->Enable( false );
  964. m_unitFilter->Enable( false );
  965. }
  966. if( aSymbol->HasAlternateBodyStyle() )
  967. {
  968. m_bodyStyleFilter->Append( wxGetTranslation( DEMORGAN_ALL ) );
  969. m_bodyStyleFilter->Append( wxGetTranslation( DEMORGAN_STD ) );
  970. m_bodyStyleFilter->Append( wxGetTranslation( DEMORGAN_ALT ) );
  971. m_bodyStyleFilter->SetSelection( -1 );
  972. }
  973. else
  974. {
  975. m_cbFilterByBodyStyle->Enable( false );
  976. m_bodyStyleFilter->Enable( false );
  977. }
  978. SetupStandardButtons();
  979. if( !parent->IsSymbolEditable() || parent->IsSymbolAlias() )
  980. {
  981. m_ButtonsCancel->SetDefault();
  982. m_ButtonsOK->SetLabel( _( "Read Only" ) );
  983. m_ButtonsOK->Enable( false );
  984. }
  985. m_initialized = true;
  986. m_modified = false;
  987. // Connect Events
  988. m_grid->Connect( wxEVT_GRID_COL_SORT,
  989. wxGridEventHandler( DIALOG_LIB_EDIT_PIN_TABLE::OnColSort ), nullptr, this );
  990. }
  991. DIALOG_LIB_EDIT_PIN_TABLE::~DIALOG_LIB_EDIT_PIN_TABLE()
  992. {
  993. if( SYMBOL_EDITOR_SETTINGS* cfg = m_editFrame->GetSettings() )
  994. cfg->m_PinTableVisibleColumns = m_grid->GetShownColumnsAsString();
  995. // Disconnect Events
  996. m_grid->Disconnect( wxEVT_GRID_COL_SORT,
  997. wxGridEventHandler( DIALOG_LIB_EDIT_PIN_TABLE::OnColSort ), nullptr, this );
  998. // Prevents crash bug in wxGrid's d'tor
  999. m_grid->DestroyTable( m_dataModel );
  1000. // Delete the GRID_TRICKS.
  1001. m_grid->PopEventHandler( true );
  1002. // This is our copy of the pins. If they were transferred to the part on an OK, then
  1003. // m_pins will already be empty.
  1004. for( SCH_PIN* pin : m_pins )
  1005. delete pin;
  1006. WINDOW_THAWER thawer( m_editFrame );
  1007. m_editFrame->ClearFocus();
  1008. m_editFrame->GetCanvas()->Refresh();
  1009. }
  1010. bool DIALOG_LIB_EDIT_PIN_TABLE::TransferDataToWindow()
  1011. {
  1012. // Make a copy of the pins for editing
  1013. std::vector<SCH_PIN*> pins = m_symbol->GetPins();
  1014. for( SCH_PIN* pin : pins )
  1015. m_pins.push_back( new SCH_PIN( *pin ) );
  1016. m_dataModel->RebuildRows( m_pins, m_cbGroup->GetValue(), false );
  1017. if( m_symbol->IsMulti() )
  1018. m_grid->ShowCol( COL_UNIT );
  1019. else
  1020. m_grid->HideCol( COL_UNIT );
  1021. if( m_editFrame->GetShowDeMorgan() )
  1022. m_grid->ShowCol( COL_DEMORGAN );
  1023. else
  1024. m_grid->HideCol( COL_DEMORGAN );
  1025. updateSummary();
  1026. return true;
  1027. }
  1028. bool DIALOG_LIB_EDIT_PIN_TABLE::TransferDataFromWindow()
  1029. {
  1030. if( !m_grid->CommitPendingChanges() )
  1031. return false;
  1032. // Delete the part's pins
  1033. std::vector<SCH_PIN*> pins = m_symbol->GetPins();
  1034. for( SCH_PIN* pin : pins )
  1035. m_symbol->RemoveDrawItem( pin );
  1036. // Transfer our pins to the part
  1037. for( SCH_PIN* pin : m_pins )
  1038. m_symbol->AddDrawItem( pin );
  1039. m_pins.clear();
  1040. return true;
  1041. }
  1042. void DIALOG_LIB_EDIT_PIN_TABLE::OnColSort( wxGridEvent& aEvent )
  1043. {
  1044. int sortCol = aEvent.GetCol();
  1045. bool ascending;
  1046. // This is bonkers, but wxWidgets doesn't tell us ascending/descending in the
  1047. // event, and if we ask it will give us pre-event info.
  1048. if( m_grid->IsSortingBy( sortCol ) )
  1049. // same column; invert ascending
  1050. ascending = !m_grid->IsSortOrderAscending();
  1051. else
  1052. // different column; start with ascending
  1053. ascending = true;
  1054. m_dataModel->SortRows( sortCol, ascending );
  1055. }
  1056. void DIALOG_LIB_EDIT_PIN_TABLE::OnAddRow( wxCommandEvent& event )
  1057. {
  1058. if( !m_grid->CommitPendingChanges() )
  1059. return;
  1060. SCH_PIN* newPin = new SCH_PIN( this->m_symbol );
  1061. // Copy the settings of the last pin onto the new pin.
  1062. if( m_pins.size() > 0 )
  1063. {
  1064. SCH_PIN* last = m_pins.back();
  1065. newPin->SetOrientation( last->GetOrientation() );
  1066. newPin->SetType( last->GetType() );
  1067. newPin->SetShape( last->GetShape() );
  1068. newPin->SetUnit( last->GetUnit() );
  1069. VECTOR2I pos = last->GetPosition();
  1070. SYMBOL_EDITOR_SETTINGS* cfg = m_editFrame->GetSettings();
  1071. if( last->GetOrientation() == PIN_ORIENTATION::PIN_LEFT
  1072. || last->GetOrientation() == PIN_ORIENTATION::PIN_RIGHT )
  1073. {
  1074. pos.y -= schIUScale.MilsToIU( cfg->m_Repeat.pin_step );
  1075. }
  1076. else
  1077. {
  1078. pos.x += schIUScale.MilsToIU( cfg->m_Repeat.pin_step );
  1079. }
  1080. newPin->SetPosition( pos );
  1081. }
  1082. m_pins.push_back( newPin );
  1083. m_dataModel->AppendRow( m_pins[ m_pins.size() - 1 ] );
  1084. m_grid->MakeCellVisible( m_grid->GetNumberRows() - 1, 1 );
  1085. m_grid->SetGridCursor( m_grid->GetNumberRows() - 1, 1 );
  1086. m_grid->EnableCellEditControl( true );
  1087. m_grid->ShowCellEditControl();
  1088. updateSummary();
  1089. }
  1090. void DIALOG_LIB_EDIT_PIN_TABLE::AddPin( SCH_PIN* pin )
  1091. {
  1092. m_pins.push_back( pin );
  1093. updateSummary();
  1094. }
  1095. void DIALOG_LIB_EDIT_PIN_TABLE::OnDeleteRow( wxCommandEvent& event )
  1096. {
  1097. // TODO: handle delete of multiple rows....
  1098. if( !m_grid->CommitPendingChanges() )
  1099. return;
  1100. if( m_pins.size() == 0 ) // empty table
  1101. return;
  1102. int curRow = m_grid->GetGridCursorRow();
  1103. if( curRow < 0 )
  1104. return;
  1105. // move the selection first because wx internally will try to reselect the row we deleted in
  1106. // out of order events
  1107. int nextSelRow = std::max( curRow-1, 0 );
  1108. m_grid->GoToCell( nextSelRow, m_grid->GetGridCursorCol() );
  1109. m_grid->SetGridCursor( nextSelRow, m_grid->GetGridCursorCol() );
  1110. m_grid->SelectRow( nextSelRow );
  1111. std::vector<SCH_PIN*> removedRow = m_dataModel->RemoveRow( curRow );
  1112. for( SCH_PIN* pin : removedRow )
  1113. m_pins.erase( std::find( m_pins.begin(), m_pins.end(), pin ) );
  1114. updateSummary();
  1115. }
  1116. void DIALOG_LIB_EDIT_PIN_TABLE::RemovePin( SCH_PIN* pin )
  1117. {
  1118. m_pins.erase( std::find( m_pins.begin(), m_pins.end(), pin ) );
  1119. updateSummary();
  1120. }
  1121. void DIALOG_LIB_EDIT_PIN_TABLE::OnCellEdited( wxGridEvent& event )
  1122. {
  1123. updateSummary();
  1124. }
  1125. void DIALOG_LIB_EDIT_PIN_TABLE::OnCellSelected( wxGridEvent& event )
  1126. {
  1127. SCH_PIN* pin = nullptr;
  1128. if( event.GetRow() >= 0 && event.GetRow() < m_dataModel->GetNumberRows() )
  1129. {
  1130. const std::vector<SCH_PIN*>& pins = m_dataModel->GetRowPins( event.GetRow() );
  1131. if( pins.size() == 1 && m_editFrame->GetCurSymbol() )
  1132. {
  1133. for( SCH_PIN* candidate : m_editFrame->GetCurSymbol()->GetPins() )
  1134. {
  1135. if( candidate->GetNumber() == pins.at( 0 )->GetNumber() )
  1136. {
  1137. pin = candidate;
  1138. break;
  1139. }
  1140. }
  1141. }
  1142. }
  1143. WINDOW_THAWER thawer( m_editFrame );
  1144. m_editFrame->FocusOnItem( pin );
  1145. m_editFrame->GetCanvas()->Refresh();
  1146. }
  1147. bool DIALOG_LIB_EDIT_PIN_TABLE::IsDisplayGrouped()
  1148. {
  1149. return m_cbGroup->GetValue();
  1150. }
  1151. void DIALOG_LIB_EDIT_PIN_TABLE::OnGroupSelected( wxCommandEvent& event )
  1152. {
  1153. m_cbGroup->SetValue( false );
  1154. m_dataModel->RebuildRows( m_pins, false, true );
  1155. m_grid->ShowCol( COL_PIN_COUNT );
  1156. m_grid->SetColLabelAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
  1157. adjustGridColumns();
  1158. }
  1159. void DIALOG_LIB_EDIT_PIN_TABLE::OnRebuildRows( wxCommandEvent& )
  1160. {
  1161. if( !m_grid->CommitPendingChanges() )
  1162. return;
  1163. m_dataModel->RebuildRows( m_pins, m_cbGroup->GetValue(), false );
  1164. if( m_cbGroup->GetValue() )
  1165. {
  1166. m_grid->ShowCol( COL_PIN_COUNT );
  1167. m_grid->SetColLabelAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
  1168. }
  1169. adjustGridColumns();
  1170. }
  1171. void DIALOG_LIB_EDIT_PIN_TABLE::OnFilterCheckBox( wxCommandEvent& event )
  1172. {
  1173. if( event.GetEventObject() == m_cbFilterByUnit )
  1174. {
  1175. if( event.IsChecked() )
  1176. {
  1177. if( m_unitFilter->GetSelection() == -1 )
  1178. {
  1179. m_unitFilter->SetSelection( 0 );
  1180. }
  1181. m_dataModel->SetUnitFilter( m_unitFilter->GetSelection() );
  1182. }
  1183. else
  1184. {
  1185. m_dataModel->SetUnitFilter( -1 );
  1186. m_unitFilter->SetSelection( -1 );
  1187. }
  1188. }
  1189. else if( event.GetEventObject() == m_cbFilterByBodyStyle )
  1190. {
  1191. if( event.IsChecked() )
  1192. {
  1193. if( m_bodyStyleFilter->GetSelection() == -1 )
  1194. {
  1195. m_bodyStyleFilter->SetSelection( 0 );
  1196. }
  1197. m_dataModel->SetBodyStyleFilter( m_bodyStyleFilter->GetSelection() );
  1198. }
  1199. else
  1200. {
  1201. m_dataModel->SetBodyStyleFilter( -1 );
  1202. m_bodyStyleFilter->SetSelection( -1 );
  1203. }
  1204. }
  1205. else if( event.GetEventObject() == m_cbFilterSelected )
  1206. {
  1207. m_dataModel->SetFilterBySelection( event.IsChecked() );
  1208. }
  1209. OnRebuildRows( event );
  1210. }
  1211. void DIALOG_LIB_EDIT_PIN_TABLE::OnFilterChoice( wxCommandEvent& event )
  1212. {
  1213. if( event.GetEventObject() == m_unitFilter )
  1214. {
  1215. m_cbFilterByUnit->SetValue( true );
  1216. m_dataModel->SetUnitFilter( m_unitFilter->GetSelection() );
  1217. }
  1218. else if( event.GetEventObject() == m_bodyStyleFilter )
  1219. {
  1220. m_cbFilterByBodyStyle->SetValue( true );
  1221. m_dataModel->SetBodyStyleFilter( m_bodyStyleFilter->GetSelection() );
  1222. }
  1223. OnRebuildRows( event );
  1224. }
  1225. void DIALOG_LIB_EDIT_PIN_TABLE::OnImportButtonClick( wxCommandEvent& event )
  1226. {
  1227. bool fromFile = event.GetEventObject() == m_btnImportFromFile;
  1228. bool replaceAll = m_rbReplaceAll->GetValue();
  1229. WX_STRING_REPORTER reporter;
  1230. PIN_TABLE_IMPORT importer( *m_editFrame, reporter );
  1231. std::vector<std::unique_ptr<SCH_PIN>> newPins = importer.ImportData( fromFile, *m_symbol );
  1232. if( reporter.HasMessage() )
  1233. {
  1234. int ret = wxMessageBox( reporter.GetMessages(), _( "Errors" ), wxOK | wxCANCEL | wxICON_ERROR, this );
  1235. // Give the user the option to cancel the import on errors.
  1236. if( ret == wxCANCEL )
  1237. return;
  1238. }
  1239. if( !newPins.size() )
  1240. {
  1241. return;
  1242. }
  1243. if( replaceAll )
  1244. {
  1245. // This is quite a dance with a segfault without smart pointers
  1246. for( SCH_PIN* pin : m_pins )
  1247. delete pin;
  1248. m_pins.clear();
  1249. }
  1250. for( auto& newPin : newPins )
  1251. {
  1252. m_pins.push_back( newPin.release() );
  1253. }
  1254. m_cbGroup->SetValue( false );
  1255. m_dataModel->RebuildRows( m_pins, false, false );
  1256. updateSummary();
  1257. }
  1258. void DIALOG_LIB_EDIT_PIN_TABLE::OnExportButtonClick( wxCommandEvent& event )
  1259. {
  1260. bool toFile = event.GetEventObject() == m_btnExportToFile;
  1261. bool onlyShown = m_rbExportOnlyShownPins->GetValue();
  1262. wxString filePath = wxEmptyString;
  1263. if( toFile )
  1264. {
  1265. wxFileName fn( m_symbol->GetName() );
  1266. fn.SetExt( FILEEXT::CsvFileExtension );
  1267. wxFileDialog dlg( this, _( "Select pin data file" ), "", fn.GetFullName(), FILEEXT::CsvFileWildcard(),
  1268. wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
  1269. if( dlg.ShowModal() == wxID_CANCEL )
  1270. return;
  1271. filePath = dlg.GetPath();
  1272. }
  1273. std::vector<SCH_PIN*> pinsToExport;
  1274. if( onlyShown )
  1275. {
  1276. for( int i = 0; i < m_dataModel->GetNumberRows(); ++i )
  1277. {
  1278. for( SCH_PIN* pin : m_dataModel->GetRowPins( i ) )
  1279. {
  1280. pinsToExport.push_back( pin );
  1281. }
  1282. }
  1283. }
  1284. else
  1285. {
  1286. pinsToExport = m_pins;
  1287. }
  1288. PIN_TABLE_EXPORT exporter( *m_editFrame );
  1289. exporter.ExportData( pinsToExport, filePath );
  1290. }
  1291. void DIALOG_LIB_EDIT_PIN_TABLE::adjustGridColumns()
  1292. {
  1293. // Account for scroll bars
  1294. int width = KIPLATFORM::UI::GetUnobscuredSize( m_grid ).x;
  1295. wxGridUpdateLocker deferRepaintsTillLeavingScope;
  1296. // The Number and Name columns must be at least wide enough to hold their contents, but
  1297. // no less wide than their original widths.
  1298. m_grid->AutoSizeColumn( COL_NUMBER );
  1299. if( m_grid->GetColSize( COL_NUMBER ) < m_originalColWidths[ COL_NUMBER ] )
  1300. m_grid->SetColSize( COL_NUMBER, m_originalColWidths[ COL_NUMBER ] );
  1301. m_grid->AutoSizeColumn( COL_NAME );
  1302. if( m_grid->GetColSize( COL_NAME ) < m_originalColWidths[ COL_NAME ] )
  1303. m_grid->SetColSize( COL_NAME, m_originalColWidths[ COL_NAME ] );
  1304. // If the grid is still wider than the columns, then stretch the Number and Name columns
  1305. // to fit.
  1306. for( int i = 0; i < COL_COUNT; ++i )
  1307. width -= m_grid->GetColSize( i );
  1308. if( width > 0 )
  1309. {
  1310. m_grid->SetColSize( COL_NUMBER, m_grid->GetColSize( COL_NUMBER ) + width / 2 );
  1311. m_grid->SetColSize( COL_NAME, m_grid->GetColSize( COL_NAME ) + width / 2 );
  1312. }
  1313. }
  1314. void DIALOG_LIB_EDIT_PIN_TABLE::OnSize( wxSizeEvent& event )
  1315. {
  1316. wxSize new_size = event.GetSize();
  1317. if( m_initialized && m_size != new_size )
  1318. {
  1319. m_size = new_size;
  1320. adjustGridColumns();
  1321. }
  1322. // Always propagate for a grid repaint (needed if the height changes, as well as width)
  1323. event.Skip();
  1324. }
  1325. void DIALOG_LIB_EDIT_PIN_TABLE::OnUpdateUI( wxUpdateUIEvent& event )
  1326. {
  1327. std::bitset<64> columnsShown = m_grid->GetShownColumns();
  1328. if( columnsShown != m_columnsShown )
  1329. {
  1330. m_columnsShown = columnsShown;
  1331. if( !m_grid->IsCellEditControlShown() )
  1332. adjustGridColumns();
  1333. }
  1334. int firstSelectedRow;
  1335. int selectedRowCount;
  1336. getSelectedArea( m_grid, &firstSelectedRow, &selectedRowCount );
  1337. if( ( selectedRowCount > 1 ) != m_groupSelected->IsEnabled() )
  1338. m_groupSelected->Enable( selectedRowCount > 1 );
  1339. }
  1340. void DIALOG_LIB_EDIT_PIN_TABLE::OnCancel( wxCommandEvent& event )
  1341. {
  1342. Close();
  1343. }
  1344. void DIALOG_LIB_EDIT_PIN_TABLE::OnClose( wxCloseEvent& event )
  1345. {
  1346. // This is a cancel, so commit quietly as we're going to throw the results away anyway.
  1347. m_grid->CommitPendingChanges( true );
  1348. int retval = wxID_CANCEL;
  1349. if( m_dataModel->IsEdited() )
  1350. {
  1351. if( HandleUnsavedChanges( this, _( "Save changes?" ),
  1352. [&]() -> bool
  1353. {
  1354. if( TransferDataFromWindow() )
  1355. {
  1356. retval = wxID_OK;
  1357. return true;
  1358. }
  1359. return false;
  1360. } ) )
  1361. {
  1362. if( IsQuasiModal() )
  1363. EndQuasiModal( retval );
  1364. else
  1365. EndDialog( retval );
  1366. return;
  1367. }
  1368. else
  1369. {
  1370. event.Veto();
  1371. return;
  1372. }
  1373. }
  1374. // No change in dialog: we can close it
  1375. if( IsQuasiModal() )
  1376. EndQuasiModal( retval );
  1377. else
  1378. EndDialog( retval );
  1379. return;
  1380. }
  1381. void DIALOG_LIB_EDIT_PIN_TABLE::updateSummary()
  1382. {
  1383. PIN_NUMBERS pinNumbers;
  1384. for( SCH_PIN* pin : m_pins )
  1385. {
  1386. if( pin->GetNumber().Length() )
  1387. pinNumbers.insert( pin->GetNumber() );
  1388. }
  1389. m_pin_numbers_summary->SetLabel( pinNumbers.GetSummary() );
  1390. m_pin_count->SetLabel( wxString::Format( wxT( "%u" ), (unsigned) m_pins.size() ) );
  1391. m_duplicate_pins->SetLabel( pinNumbers.GetDuplicates() );
  1392. Layout();
  1393. }