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.

1219 lines
35 KiB

3 years ago
3 years ago
4 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 1992-2022 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 "grid_tricks.h"
  25. #include "lib_pin.h"
  26. #include "pin_numbers.h"
  27. #include "pgm_base.h"
  28. #include <base_units.h>
  29. #include <bitmaps.h>
  30. #include <confirm.h>
  31. #include <symbol_edit_frame.h>
  32. #include <symbol_editor_settings.h>
  33. #include <kiplatform/ui.h>
  34. #include <widgets/grid_icon_text_helpers.h>
  35. #include <widgets/grid_combobox.h>
  36. #include <widgets/wx_grid.h>
  37. #include <widgets/bitmap_button.h>
  38. #include <widgets/std_bitmap_button.h>
  39. #include <settings/settings_manager.h>
  40. #include <wx/tokenzr.h>
  41. #include <string_utils.h>
  42. #define UNITS_ALL _( "ALL" )
  43. #define DEMORGAN_ALL _( "ALL" )
  44. #define DEMORGAN_STD _( "Standard" )
  45. #define DEMORGAN_ALT _( "Alternate" )
  46. void getSelectedArea( WX_GRID* aGrid, int* aRowStart, int* aRowCount )
  47. {
  48. wxGridCellCoordsArray topLeft = aGrid->GetSelectionBlockTopLeft();
  49. wxGridCellCoordsArray botRight = aGrid->GetSelectionBlockBottomRight();
  50. wxArrayInt cols = aGrid->GetSelectedCols();
  51. wxArrayInt rows = aGrid->GetSelectedRows();
  52. if( topLeft.Count() && botRight.Count() )
  53. {
  54. *aRowStart = topLeft[0].GetRow();
  55. *aRowCount = botRight[0].GetRow() - *aRowStart + 1;
  56. }
  57. else if( cols.Count() )
  58. {
  59. *aRowStart = 0;
  60. *aRowCount = aGrid->GetNumberRows();
  61. }
  62. else if( rows.Count() )
  63. {
  64. *aRowStart = rows[0];
  65. *aRowCount = rows.Count();
  66. }
  67. else
  68. {
  69. *aRowStart = aGrid->GetGridCursorRow();
  70. *aRowCount = *aRowStart >= 0 ? 1 : 0;
  71. }
  72. }
  73. class PIN_TABLE_DATA_MODEL : public wxGridTableBase
  74. {
  75. public:
  76. PIN_TABLE_DATA_MODEL( SYMBOL_EDIT_FRAME* aFrame, DIALOG_LIB_EDIT_PIN_TABLE* aPinTable,
  77. LIB_SYMBOL* aSymbol ) :
  78. m_frame( aFrame ),
  79. m_unitFilter( -1 ),
  80. m_edited( false ),
  81. m_pinTable( aPinTable ),
  82. m_symbol( aSymbol )
  83. {
  84. m_eval = std::make_unique<NUMERIC_EVALUATOR>( m_frame->GetUserUnits() );
  85. m_frame->Bind( UNITS_CHANGED, &PIN_TABLE_DATA_MODEL::onUnitsChanged, this );
  86. }
  87. ~PIN_TABLE_DATA_MODEL()
  88. {
  89. m_frame->Unbind( UNITS_CHANGED, &PIN_TABLE_DATA_MODEL::onUnitsChanged, this );
  90. }
  91. void onUnitsChanged( wxCommandEvent& aEvent )
  92. {
  93. if( GetView() )
  94. GetView()->ForceRefresh();
  95. aEvent.Skip();
  96. }
  97. void SetUnitFilter( int aFilter ) { m_unitFilter = aFilter; }
  98. int GetNumberRows() override { return (int) m_rows.size(); }
  99. int GetNumberCols() override { return COL_COUNT; }
  100. wxString GetColLabelValue( int aCol ) override
  101. {
  102. switch( aCol )
  103. {
  104. case COL_PIN_COUNT: return _( "Count" );
  105. case COL_NUMBER: return _( "Number" );
  106. case COL_NAME: return _( "Name" );
  107. case COL_TYPE: return _( "Electrical Type" );
  108. case COL_SHAPE: return _( "Graphic Style" );
  109. case COL_ORIENTATION: return _( "Orientation" );
  110. case COL_NUMBER_SIZE: return _( "Number Text Size" );
  111. case COL_NAME_SIZE: return _( "Name Text Size" );
  112. case COL_LENGTH: return _( "Length" );
  113. case COL_POSX: return _( "X Position" );
  114. case COL_POSY: return _( "Y Position" );
  115. case COL_VISIBLE: return _( "Visible" );
  116. case COL_UNIT: return _( "Unit" );
  117. case COL_DEMORGAN: return _( "De Morgan" );
  118. default: wxFAIL; return wxEmptyString;
  119. }
  120. }
  121. bool IsEmptyCell( int row, int col ) override
  122. {
  123. return false; // don't allow adjacent cell overflow, even if we are actually empty
  124. }
  125. wxString GetValue( int aRow, int aCol ) override
  126. {
  127. wxGrid* grid = GetView();
  128. if( grid->GetGridCursorRow() == aRow && grid->GetGridCursorCol() == aCol
  129. && grid->IsCellEditControlShown() )
  130. {
  131. auto it = m_evalOriginal.find( { m_rows[ aRow ], aCol } );
  132. if( it != m_evalOriginal.end() )
  133. return it->second;
  134. }
  135. return GetValue( m_rows[ aRow ], aCol, m_frame );
  136. }
  137. static wxString GetValue( const LIB_PINS& pins, int aCol, EDA_DRAW_FRAME* aParentFrame )
  138. {
  139. wxString fieldValue;
  140. if( pins.empty() )
  141. return fieldValue;
  142. for( LIB_PIN* pin : pins )
  143. {
  144. wxString val;
  145. switch( aCol )
  146. {
  147. case COL_PIN_COUNT:
  148. val << pins.size();
  149. break;
  150. case COL_NUMBER:
  151. val = pin->GetNumber();
  152. break;
  153. case COL_NAME:
  154. val = pin->GetName();
  155. break;
  156. case COL_TYPE:
  157. val = PinTypeNames()[static_cast<int>( pin->GetType() )];
  158. break;
  159. case COL_SHAPE:
  160. val = PinShapeNames()[static_cast<int>( pin->GetShape() )];
  161. break;
  162. case COL_ORIENTATION:
  163. if( PinOrientationIndex( pin->GetOrientation() ) >= 0 )
  164. val = PinOrientationNames()[ PinOrientationIndex( pin->GetOrientation() ) ];
  165. break;
  166. case COL_NUMBER_SIZE:
  167. val = aParentFrame->StringFromValue( pin->GetNumberTextSize(), true );
  168. break;
  169. case COL_NAME_SIZE:
  170. val = aParentFrame->StringFromValue( pin->GetNameTextSize(), true );
  171. break;
  172. case COL_LENGTH:
  173. val = aParentFrame->StringFromValue( pin->GetLength(), true );
  174. break;
  175. case COL_POSX:
  176. val = aParentFrame->StringFromValue( pin->GetPosition().x, true );
  177. break;
  178. case COL_POSY:
  179. val = aParentFrame->StringFromValue( -pin->GetPosition().y, true );
  180. break;
  181. case COL_VISIBLE:
  182. val = StringFromBool( pin->IsVisible() );
  183. break;
  184. case COL_UNIT:
  185. if( pin->GetUnit() )
  186. val = LIB_SYMBOL::SubReference( pin->GetUnit(), false );
  187. else
  188. val = UNITS_ALL;
  189. break;
  190. case COL_DEMORGAN:
  191. switch( pin->GetConvert() )
  192. {
  193. case LIB_ITEM::LIB_CONVERT::BASE:
  194. val = DEMORGAN_STD;
  195. break;
  196. case LIB_ITEM::LIB_CONVERT::DEMORGAN:
  197. val = DEMORGAN_ALT;
  198. break;
  199. default:
  200. val = DEMORGAN_ALL;
  201. break;
  202. }
  203. break;
  204. default:
  205. wxFAIL;
  206. break;
  207. }
  208. if( aCol == COL_NUMBER )
  209. {
  210. if( fieldValue.length() )
  211. fieldValue += wxT( ", " );
  212. fieldValue += val;
  213. }
  214. else
  215. {
  216. if( !fieldValue.Length() )
  217. fieldValue = val;
  218. else if( val != fieldValue )
  219. fieldValue = INDETERMINATE_STATE;
  220. }
  221. }
  222. return fieldValue;
  223. }
  224. void SetValue( int aRow, int aCol, const wxString &aValue ) override
  225. {
  226. if( aValue == INDETERMINATE_STATE )
  227. return;
  228. wxString value = aValue;
  229. switch( aCol )
  230. {
  231. case COL_NUMBER_SIZE:
  232. case COL_NAME_SIZE:
  233. case COL_LENGTH:
  234. case COL_POSX:
  235. case COL_POSY:
  236. m_eval->SetDefaultUnits( m_frame->GetUserUnits() );
  237. if( m_eval->Process( value ) )
  238. {
  239. m_evalOriginal[ { m_rows[ aRow ], aCol } ] = value;
  240. value = m_eval->Result();
  241. }
  242. break;
  243. default:
  244. break;
  245. }
  246. LIB_PINS pins = m_rows[ aRow ];
  247. // If the NUMBER column is edited and the pins are grouped, renumber, and add or
  248. // remove pins based on the comma separated list of pins.
  249. if( aCol == COL_NUMBER && m_pinTable->IsDisplayGrouped() )
  250. {
  251. wxStringTokenizer tokenizer( value, "," );
  252. size_t i = 0;
  253. while( tokenizer.HasMoreTokens() )
  254. {
  255. wxString pinName = tokenizer.GetNextToken();
  256. // Trim whitespace from both ends of the string
  257. pinName.Trim( true ).Trim( false );
  258. if( i < pins.size() )
  259. {
  260. // Renumber the existing pins
  261. pins.at( i )->SetNumber( pinName );
  262. }
  263. else
  264. {
  265. // Create new pins
  266. LIB_PIN* newPin = new LIB_PIN( this->m_symbol );
  267. LIB_PIN* last = pins.back();
  268. newPin->SetNumber( pinName );
  269. newPin->SetName( last->GetName() );
  270. newPin->SetOrientation( last->GetOrientation() );
  271. newPin->SetType( last->GetType() );
  272. newPin->SetShape( last->GetShape() );
  273. newPin->SetUnit( last->GetUnit() );
  274. VECTOR2I pos = last->GetPosition();
  275. auto* cfg = Pgm().GetSettingsManager().GetAppSettings<SYMBOL_EDITOR_SETTINGS>();
  276. if( last->GetOrientation() == PIN_LEFT || last->GetOrientation() == PIN_RIGHT )
  277. pos.y -= schIUScale.MilsToIU( cfg->m_Repeat.pin_step );
  278. else
  279. pos.x += schIUScale.MilsToIU( cfg->m_Repeat.pin_step );
  280. newPin->SetPosition( pos );
  281. pins.push_back( newPin );
  282. m_pinTable->AddPin( newPin );
  283. }
  284. i++;
  285. }
  286. while( pins.size() > i )
  287. {
  288. m_pinTable->RemovePin( pins.back() );
  289. pins.pop_back();
  290. }
  291. m_rows[aRow] = pins;
  292. m_edited = true;
  293. return;
  294. }
  295. for( LIB_PIN* pin : pins )
  296. {
  297. switch( aCol )
  298. {
  299. case COL_NUMBER:
  300. if( !m_pinTable->IsDisplayGrouped() )
  301. pin->SetNumber( value );
  302. break;
  303. case COL_NAME:
  304. pin->SetName( value );
  305. break;
  306. case COL_TYPE:
  307. if( PinTypeNames().Index( value ) != wxNOT_FOUND )
  308. pin->SetType( (ELECTRICAL_PINTYPE) PinTypeNames().Index( value ) );
  309. break;
  310. case COL_SHAPE:
  311. if( PinShapeNames().Index( value ) != wxNOT_FOUND )
  312. pin->SetShape( (GRAPHIC_PINSHAPE) PinShapeNames().Index( value ) );
  313. break;
  314. case COL_ORIENTATION:
  315. if( PinOrientationNames().Index( value ) != wxNOT_FOUND )
  316. pin->SetOrientation( PinOrientationCode( PinOrientationNames().Index( value ) ) );
  317. break;
  318. case COL_NUMBER_SIZE:
  319. pin->SetNumberTextSize( m_frame->ValueFromString( value ) );
  320. break;
  321. case COL_NAME_SIZE:
  322. pin->SetNameTextSize( m_frame->ValueFromString( value ) );
  323. break;
  324. case COL_LENGTH:
  325. pin->ChangeLength( m_frame->ValueFromString( value ) );
  326. break;
  327. case COL_POSX:
  328. pin->SetPosition( wxPoint( m_frame->ValueFromString( value ),
  329. pin->GetPosition().y ) );
  330. break;
  331. case COL_POSY:
  332. pin->SetPosition( wxPoint( pin->GetPosition().x,
  333. -m_frame->ValueFromString( value ) ) );
  334. break;
  335. case COL_VISIBLE:
  336. pin->SetVisible(BoolFromString( value ));
  337. break;
  338. case COL_UNIT:
  339. if( value == UNITS_ALL )
  340. {
  341. pin->SetUnit( 0 );
  342. }
  343. else
  344. {
  345. for( int i = 1; i <= m_symbol->GetUnitCount(); i++ )
  346. {
  347. if( value == LIB_SYMBOL::SubReference( i, false ) )
  348. {
  349. pin->SetUnit( i );
  350. break;
  351. }
  352. }
  353. }
  354. break;
  355. case COL_DEMORGAN:
  356. if( value == DEMORGAN_STD )
  357. pin->SetConvert( 1 );
  358. else if( value == DEMORGAN_ALT )
  359. pin->SetConvert( 2 );
  360. else
  361. pin->SetConvert( 0 );
  362. break;
  363. default:
  364. wxFAIL;
  365. break;
  366. }
  367. }
  368. m_edited = true;
  369. }
  370. static int findRow( const std::vector<LIB_PINS>& aRowSet, const wxString& aName )
  371. {
  372. for( size_t i = 0; i < aRowSet.size(); ++i )
  373. {
  374. if( aRowSet[ i ][ 0 ] && aRowSet[ i ][ 0 ]->GetName() == aName )
  375. return i;
  376. }
  377. return -1;
  378. }
  379. static bool compare( const LIB_PINS& lhs, const LIB_PINS& rhs, int sortCol, bool ascending,
  380. EDA_DRAW_FRAME* parentFrame )
  381. {
  382. wxString lhStr = GetValue( lhs, sortCol, parentFrame );
  383. wxString rhStr = GetValue( rhs, sortCol, parentFrame );
  384. if( lhStr == rhStr )
  385. {
  386. // Secondary sort key is always COL_NUMBER
  387. sortCol = COL_NUMBER;
  388. lhStr = GetValue( lhs, sortCol, parentFrame );
  389. rhStr = GetValue( rhs, sortCol, parentFrame );
  390. }
  391. bool res;
  392. // N.B. To meet the iterator sort conditions, we cannot simply invert the truth
  393. // to get the opposite sort. i.e. ~(a<b) != (a>b)
  394. auto cmp = [ ascending ]( const auto a, const auto b )
  395. {
  396. if( ascending )
  397. return a < b;
  398. else
  399. return b < a;
  400. };
  401. switch( sortCol )
  402. {
  403. case COL_NUMBER:
  404. case COL_NAME:
  405. res = cmp( PIN_NUMBERS::Compare( lhStr, rhStr ), 0 );
  406. break;
  407. case COL_NUMBER_SIZE:
  408. case COL_NAME_SIZE:
  409. res = cmp( parentFrame->ValueFromString( lhStr ),
  410. parentFrame->ValueFromString( rhStr ) );
  411. break;
  412. case COL_LENGTH:
  413. case COL_POSX:
  414. case COL_POSY:
  415. res = cmp( parentFrame->ValueFromString( lhStr ),
  416. parentFrame->ValueFromString( rhStr ) );
  417. break;
  418. case COL_VISIBLE:
  419. case COL_DEMORGAN:
  420. default:
  421. res = cmp( StrNumCmp( lhStr, rhStr ), 0 );
  422. break;
  423. }
  424. return res;
  425. }
  426. void RebuildRows( const LIB_PINS& aPins, bool groupByName, bool groupBySelection )
  427. {
  428. WX_GRID* grid = dynamic_cast<WX_GRID*>( GetView() );
  429. std::vector<LIB_PIN*> clear_flags;
  430. clear_flags.reserve( aPins.size() );
  431. if( grid )
  432. {
  433. if( groupBySelection )
  434. {
  435. for( LIB_PIN* pin : aPins )
  436. pin->ClearTempFlags();
  437. int firstSelectedRow;
  438. int selectedRowCount;
  439. getSelectedArea( grid, &firstSelectedRow, &selectedRowCount );
  440. for( int ii = 0; ii < selectedRowCount; ++ii )
  441. {
  442. for( LIB_PIN* pin : m_rows[ firstSelectedRow + ii ] )
  443. {
  444. pin->SetFlags( CANDIDATE );
  445. clear_flags.push_back( pin );
  446. }
  447. }
  448. }
  449. // Commit any pending in-place edits before the row gets moved out from under
  450. // the editor.
  451. grid->CommitPendingChanges( true );
  452. wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, 0, m_rows.size() );
  453. GetView()->ProcessTableMessage( msg );
  454. }
  455. m_rows.clear();
  456. if( groupBySelection )
  457. m_rows.emplace_back( LIB_PINS() );
  458. for( LIB_PIN* pin : aPins )
  459. {
  460. if( m_unitFilter == -1 || pin->GetUnit() == 0 || pin->GetUnit() == m_unitFilter )
  461. {
  462. int rowIndex = -1;
  463. if( groupByName )
  464. rowIndex = findRow( m_rows, pin->GetName() );
  465. else if( groupBySelection && ( pin->GetFlags() & CANDIDATE ) )
  466. rowIndex = 0;
  467. if( rowIndex < 0 )
  468. {
  469. m_rows.emplace_back( LIB_PINS() );
  470. rowIndex = m_rows.size() - 1;
  471. }
  472. m_rows[ rowIndex ].push_back( pin );
  473. }
  474. }
  475. int sortCol = 0;
  476. bool ascending = true;
  477. if( GetView() && GetView()->GetSortingColumn() != wxNOT_FOUND )
  478. {
  479. sortCol = GetView()->GetSortingColumn();
  480. ascending = GetView()->IsSortOrderAscending();
  481. }
  482. for( LIB_PINS& row : m_rows )
  483. SortPins( row );
  484. if( !groupBySelection )
  485. SortRows( sortCol, ascending );
  486. if ( GetView() )
  487. {
  488. wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, m_rows.size() );
  489. GetView()->ProcessTableMessage( msg );
  490. if( groupBySelection )
  491. GetView()->SelectRow( 0 );
  492. }
  493. for( LIB_PIN* pin : clear_flags )
  494. pin->ClearFlags( CANDIDATE );
  495. }
  496. void SortRows( int aSortCol, bool ascending )
  497. {
  498. std::sort( m_rows.begin(), m_rows.end(),
  499. [ aSortCol, ascending, this ]( const LIB_PINS& lhs, const LIB_PINS& rhs ) -> bool
  500. {
  501. return compare( lhs, rhs, aSortCol, ascending, m_frame );
  502. } );
  503. }
  504. void SortPins( LIB_PINS& aRow )
  505. {
  506. std::sort( aRow.begin(), aRow.end(),
  507. []( LIB_PIN* lhs, LIB_PIN* rhs ) -> bool
  508. {
  509. return PIN_NUMBERS::Compare( lhs->GetNumber(), rhs->GetNumber() ) < 0;
  510. } );
  511. }
  512. void AppendRow( LIB_PIN* aPin )
  513. {
  514. LIB_PINS row;
  515. row.push_back( aPin );
  516. m_rows.push_back( row );
  517. if ( GetView() )
  518. {
  519. wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, 1 );
  520. GetView()->ProcessTableMessage( msg );
  521. }
  522. }
  523. LIB_PINS RemoveRow( int aRow )
  524. {
  525. LIB_PINS removedRow = m_rows[ aRow ];
  526. m_rows.erase( m_rows.begin() + aRow );
  527. if ( GetView() )
  528. {
  529. wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, aRow, 1 );
  530. GetView()->ProcessTableMessage( msg );
  531. }
  532. return removedRow;
  533. }
  534. bool IsEdited()
  535. {
  536. return m_edited;
  537. }
  538. private:
  539. static wxString StringFromBool( bool aValue )
  540. {
  541. if( aValue )
  542. return wxT( "1" );
  543. else
  544. return wxT( "0" );
  545. }
  546. static bool BoolFromString( wxString aValue )
  547. {
  548. if( aValue == wxS( "1" ) )
  549. {
  550. return true;
  551. }
  552. else if( aValue == wxS( "0" ) )
  553. {
  554. return false;
  555. }
  556. else
  557. {
  558. wxFAIL_MSG( wxString::Format( "string '%s' can't be converted to boolean correctly, "
  559. "it will have been perceived as FALSE",
  560. aValue ) );
  561. return false;
  562. }
  563. }
  564. private:
  565. SYMBOL_EDIT_FRAME* m_frame;
  566. // Because the rows of the grid can either be a single pin or a group of pins, the
  567. // data model is a 2D vector. If we're in the single pin case, each row's LIB_PINS
  568. // contains only a single pin.
  569. std::vector<LIB_PINS> m_rows;
  570. int m_unitFilter; // 0 to show pins for all units
  571. bool m_edited;
  572. DIALOG_LIB_EDIT_PIN_TABLE* m_pinTable;
  573. LIB_SYMBOL* m_symbol; // Parent symbol that the pins belong to.
  574. std::unique_ptr<NUMERIC_EVALUATOR> m_eval;
  575. std::map< std::pair<LIB_PINS, int>, wxString > m_evalOriginal;
  576. };
  577. DIALOG_LIB_EDIT_PIN_TABLE::DIALOG_LIB_EDIT_PIN_TABLE( SYMBOL_EDIT_FRAME* parent,
  578. LIB_SYMBOL* aSymbol ) :
  579. DIALOG_LIB_EDIT_PIN_TABLE_BASE( parent ),
  580. m_editFrame( parent ),
  581. m_symbol( aSymbol )
  582. {
  583. m_dataModel = new PIN_TABLE_DATA_MODEL( m_editFrame, this, this->m_symbol );
  584. // Save original columns widths so we can do proportional sizing.
  585. for( int i = 0; i < COL_COUNT; ++i )
  586. m_originalColWidths[ i ] = m_grid->GetColSize( i );
  587. // Give a bit more room for combobox editors
  588. m_grid->SetDefaultRowSize( m_grid->GetDefaultRowSize() + 4 );
  589. m_grid->SetTable( m_dataModel );
  590. m_grid->PushEventHandler( new GRID_TRICKS( m_grid, [this]( wxCommandEvent& aEvent )
  591. {
  592. OnAddRow( aEvent );
  593. } ) );
  594. // Show/hide columns according to the user's preference
  595. SYMBOL_EDITOR_SETTINGS* cfg = parent->GetSettings();
  596. m_columnsShown = cfg->m_PinTableVisibleColumns;
  597. m_grid->ShowHideColumns( m_columnsShown );
  598. // Set special attributes
  599. wxGridCellAttr* attr;
  600. attr = new wxGridCellAttr;
  601. attr->SetReadOnly( true );
  602. m_grid->SetColAttr( COL_PIN_COUNT, attr );
  603. attr = new wxGridCellAttr;
  604. wxArrayString typeNames = PinTypeNames();
  605. typeNames.push_back( INDETERMINATE_STATE );
  606. attr->SetRenderer( new GRID_CELL_ICON_TEXT_RENDERER( PinTypeIcons(), typeNames ) );
  607. attr->SetEditor( new GRID_CELL_ICON_TEXT_POPUP( PinTypeIcons(), typeNames ) );
  608. m_grid->SetColAttr( COL_TYPE, attr );
  609. attr = new wxGridCellAttr;
  610. wxArrayString shapeNames = PinShapeNames();
  611. shapeNames.push_back( INDETERMINATE_STATE );
  612. attr->SetRenderer( new GRID_CELL_ICON_TEXT_RENDERER( PinShapeIcons(), shapeNames ) );
  613. attr->SetEditor( new GRID_CELL_ICON_TEXT_POPUP( PinShapeIcons(), shapeNames ) );
  614. m_grid->SetColAttr( COL_SHAPE, attr );
  615. attr = new wxGridCellAttr;
  616. wxArrayString orientationNames = PinOrientationNames();
  617. orientationNames.push_back( INDETERMINATE_STATE );
  618. attr->SetRenderer( new GRID_CELL_ICON_TEXT_RENDERER( PinOrientationIcons(),
  619. orientationNames ) );
  620. attr->SetEditor( new GRID_CELL_ICON_TEXT_POPUP( PinOrientationIcons(), orientationNames ) );
  621. m_grid->SetColAttr( COL_ORIENTATION, attr );
  622. attr = new wxGridCellAttr;
  623. wxArrayString unitNames;
  624. unitNames.push_back( UNITS_ALL );
  625. for( int i = 1; i <= aSymbol->GetUnitCount(); i++ )
  626. unitNames.push_back( LIB_SYMBOL::SubReference( i, false ) );
  627. attr->SetEditor( new GRID_CELL_COMBOBOX( unitNames ) );
  628. m_grid->SetColAttr( COL_UNIT, attr );
  629. attr = new wxGridCellAttr;
  630. wxArrayString demorganNames;
  631. demorganNames.push_back( DEMORGAN_ALL );
  632. demorganNames.push_back( DEMORGAN_STD );
  633. demorganNames.push_back( DEMORGAN_ALT );
  634. attr->SetEditor( new GRID_CELL_COMBOBOX( demorganNames ) );
  635. m_grid->SetColAttr( COL_DEMORGAN, attr );
  636. attr = new wxGridCellAttr;
  637. attr->SetRenderer( new wxGridCellBoolRenderer() );
  638. attr->SetEditor( new wxGridCellBoolEditor() );
  639. attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
  640. m_grid->SetColAttr( COL_VISIBLE, attr );
  641. /* Right-aligned position values look much better, but only MSW and GTK2+
  642. * currently support right-aligned textEditCtrls, so the text jumps on all
  643. * the other platforms when you edit it.
  644. attr = new wxGridCellAttr;
  645. attr->SetAlignment( wxALIGN_RIGHT, wxALIGN_TOP );
  646. m_grid->SetColAttr( COL_POSX, attr );
  647. attr = new wxGridCellAttr;
  648. attr->SetAlignment( wxALIGN_RIGHT, wxALIGN_TOP );
  649. m_grid->SetColAttr( COL_POSY, attr );
  650. */
  651. m_addButton->SetBitmap( KiBitmap( BITMAPS::small_plus ) );
  652. m_deleteButton->SetBitmap( KiBitmap( BITMAPS::small_trash ) );
  653. m_refreshButton->SetBitmap( KiBitmap( BITMAPS::small_refresh ) );
  654. m_divider1->SetIsSeparator();
  655. m_divider2->SetIsSeparator();
  656. GetSizer()->SetSizeHints(this);
  657. Centre();
  658. if( aSymbol->IsMulti() )
  659. {
  660. m_unitFilter->Append( UNITS_ALL );
  661. for( int ii = 0; ii < aSymbol->GetUnitCount(); ++ii )
  662. m_unitFilter->Append( aSymbol->GetUnitReference( ii + 1 ) );
  663. m_unitFilter->SetSelection( -1 );
  664. }
  665. else
  666. {
  667. m_cbFilterByUnit->Show( false );
  668. m_unitFilter->Show( false );
  669. }
  670. SetupStandardButtons();
  671. if( !parent->IsSymbolEditable() || parent->IsSymbolAlias() )
  672. {
  673. m_ButtonsCancel->SetDefault();
  674. m_ButtonsOK->SetLabel( _( "Read Only" ) );
  675. m_ButtonsOK->Enable( false );
  676. }
  677. m_initialized = true;
  678. m_modified = false;
  679. // Connect Events
  680. m_grid->Connect( wxEVT_GRID_COL_SORT,
  681. wxGridEventHandler( DIALOG_LIB_EDIT_PIN_TABLE::OnColSort ), nullptr, this );
  682. }
  683. DIALOG_LIB_EDIT_PIN_TABLE::~DIALOG_LIB_EDIT_PIN_TABLE()
  684. {
  685. SYMBOL_EDITOR_SETTINGS* cfg = m_editFrame->GetSettings();
  686. cfg->m_PinTableVisibleColumns = m_grid->GetShownColumns().ToStdString();
  687. // Disconnect Events
  688. m_grid->Disconnect( wxEVT_GRID_COL_SORT,
  689. wxGridEventHandler( DIALOG_LIB_EDIT_PIN_TABLE::OnColSort ), nullptr, this );
  690. // Prevents crash bug in wxGrid's d'tor
  691. m_grid->DestroyTable( m_dataModel );
  692. // Delete the GRID_TRICKS.
  693. m_grid->PopEventHandler( true );
  694. // This is our copy of the pins. If they were transferred to the part on an OK, then
  695. // m_pins will already be empty.
  696. for( LIB_PIN* pin : m_pins )
  697. delete pin;
  698. }
  699. bool DIALOG_LIB_EDIT_PIN_TABLE::TransferDataToWindow()
  700. {
  701. // Make a copy of the pins for editing
  702. std::vector<LIB_PIN*> pins = m_symbol->GetAllLibPins();
  703. for( LIB_PIN* pin : pins )
  704. m_pins.push_back( new LIB_PIN( *pin ) );
  705. m_dataModel->RebuildRows( m_pins, m_cbGroup->GetValue(), false );
  706. if( m_symbol->IsMulti() )
  707. m_grid->ShowCol( COL_UNIT );
  708. else
  709. m_grid->HideCol( COL_UNIT );
  710. if( m_editFrame->GetShowDeMorgan() )
  711. m_grid->ShowCol( COL_DEMORGAN );
  712. else
  713. m_grid->HideCol( COL_DEMORGAN );
  714. updateSummary();
  715. return true;
  716. }
  717. bool DIALOG_LIB_EDIT_PIN_TABLE::TransferDataFromWindow()
  718. {
  719. if( !m_grid->CommitPendingChanges() )
  720. return false;
  721. // Delete the part's pins
  722. std::vector<LIB_PIN*> pins = m_symbol->GetAllLibPins();
  723. for( LIB_PIN* pin : pins )
  724. m_symbol->RemoveDrawItem( pin );
  725. // Transfer our pins to the part
  726. for( LIB_PIN* pin : m_pins )
  727. {
  728. m_symbol->AddDrawItem( pin );
  729. }
  730. m_pins.clear();
  731. return true;
  732. }
  733. void DIALOG_LIB_EDIT_PIN_TABLE::OnColSort( wxGridEvent& aEvent )
  734. {
  735. int sortCol = aEvent.GetCol();
  736. bool ascending;
  737. // This is bonkers, but wxWidgets doesn't tell us ascending/descending in the
  738. // event, and if we ask it will give us pre-event info.
  739. if( m_grid->IsSortingBy( sortCol ) )
  740. // same column; invert ascending
  741. ascending = !m_grid->IsSortOrderAscending();
  742. else
  743. // different column; start with ascending
  744. ascending = true;
  745. m_dataModel->SortRows( sortCol, ascending );
  746. }
  747. void DIALOG_LIB_EDIT_PIN_TABLE::OnAddRow( wxCommandEvent& event )
  748. {
  749. if( !m_grid->CommitPendingChanges() )
  750. return;
  751. LIB_PIN* newPin = new LIB_PIN( this->m_symbol );
  752. // Copy the settings of the last pin onto the new pin.
  753. if( m_pins.size() > 0 )
  754. {
  755. LIB_PIN* last = m_pins.back();
  756. newPin->SetOrientation( last->GetOrientation() );
  757. newPin->SetType( last->GetType() );
  758. newPin->SetShape( last->GetShape() );
  759. newPin->SetUnit( last->GetUnit() );
  760. VECTOR2I pos = last->GetPosition();
  761. SYMBOL_EDITOR_SETTINGS* cfg = m_editFrame->GetSettings();
  762. if( last->GetOrientation() == PIN_LEFT || last->GetOrientation() == PIN_RIGHT )
  763. pos.y -= schIUScale.MilsToIU(cfg->m_Repeat.pin_step);
  764. else
  765. pos.x += schIUScale.MilsToIU(cfg->m_Repeat.pin_step);
  766. newPin->SetPosition( pos );
  767. }
  768. m_pins.push_back( newPin );
  769. m_dataModel->AppendRow( m_pins[ m_pins.size() - 1 ] );
  770. m_grid->MakeCellVisible( m_grid->GetNumberRows() - 1, 1 );
  771. m_grid->SetGridCursor( m_grid->GetNumberRows() - 1, 1 );
  772. m_grid->EnableCellEditControl( true );
  773. m_grid->ShowCellEditControl();
  774. updateSummary();
  775. }
  776. void DIALOG_LIB_EDIT_PIN_TABLE::AddPin( LIB_PIN* pin )
  777. {
  778. m_pins.push_back( pin );
  779. updateSummary();
  780. }
  781. void DIALOG_LIB_EDIT_PIN_TABLE::OnDeleteRow( wxCommandEvent& event )
  782. {
  783. // TODO: handle delete of multiple rows....
  784. if( !m_grid->CommitPendingChanges() )
  785. return;
  786. if( m_pins.size() == 0 ) // empty table
  787. return;
  788. int curRow = m_grid->GetGridCursorRow();
  789. if( curRow < 0 )
  790. return;
  791. LIB_PINS removedRow = m_dataModel->RemoveRow( curRow );
  792. for( LIB_PIN* pin : removedRow )
  793. m_pins.erase( std::find( m_pins.begin(), m_pins.end(), pin ) );
  794. curRow = std::min( curRow, m_grid->GetNumberRows() - 1 );
  795. m_grid->GoToCell( curRow, m_grid->GetGridCursorCol() );
  796. m_grid->SetGridCursor( curRow, m_grid->GetGridCursorCol() );
  797. m_grid->SelectRow( curRow );
  798. updateSummary();
  799. }
  800. void DIALOG_LIB_EDIT_PIN_TABLE::RemovePin( LIB_PIN* pin )
  801. {
  802. m_pins.erase( std::find( m_pins.begin(), m_pins.end(), pin ) );
  803. updateSummary();
  804. }
  805. void DIALOG_LIB_EDIT_PIN_TABLE::OnCellEdited( wxGridEvent& event )
  806. {
  807. updateSummary();
  808. }
  809. bool DIALOG_LIB_EDIT_PIN_TABLE::IsDisplayGrouped()
  810. {
  811. return m_cbGroup->GetValue();
  812. }
  813. void DIALOG_LIB_EDIT_PIN_TABLE::OnGroupSelected( wxCommandEvent& event )
  814. {
  815. m_cbGroup->SetValue( false );
  816. m_dataModel->RebuildRows( m_pins, false, true );
  817. m_grid->ShowCol( COL_PIN_COUNT );
  818. m_grid->SetColLabelAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
  819. adjustGridColumns();
  820. }
  821. void DIALOG_LIB_EDIT_PIN_TABLE::OnRebuildRows( wxCommandEvent& )
  822. {
  823. if( !m_grid->CommitPendingChanges() )
  824. return;
  825. m_dataModel->RebuildRows( m_pins, m_cbGroup->GetValue(), false );
  826. if( m_cbGroup->GetValue() )
  827. {
  828. m_grid->ShowCol( COL_PIN_COUNT );
  829. m_grid->SetColLabelAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
  830. }
  831. adjustGridColumns();
  832. }
  833. void DIALOG_LIB_EDIT_PIN_TABLE::OnFilterCheckBox( wxCommandEvent& event )
  834. {
  835. if( event.IsChecked() )
  836. {
  837. m_dataModel->SetUnitFilter( m_unitFilter->GetSelection() );
  838. }
  839. else
  840. {
  841. m_dataModel->SetUnitFilter( -1 );
  842. m_unitFilter->SetSelection( -1 );
  843. }
  844. OnRebuildRows( event );
  845. }
  846. void DIALOG_LIB_EDIT_PIN_TABLE::OnFilterChoice( wxCommandEvent& event )
  847. {
  848. m_cbFilterByUnit->SetValue( true );
  849. m_dataModel->SetUnitFilter( m_unitFilter->GetSelection() );
  850. OnRebuildRows( event );
  851. }
  852. void DIALOG_LIB_EDIT_PIN_TABLE::adjustGridColumns()
  853. {
  854. // Account for scroll bars
  855. int width = KIPLATFORM::UI::GetUnobscuredSize( m_grid ).x;
  856. wxGridUpdateLocker deferRepaintsTillLeavingScope;
  857. // The Number and Name columns must be at least wide enough to hold their contents, but
  858. // no less wide than their original widths.
  859. m_grid->AutoSizeColumn( COL_NUMBER );
  860. if( m_grid->GetColSize( COL_NUMBER ) < m_originalColWidths[ COL_NUMBER ] )
  861. m_grid->SetColSize( COL_NUMBER, m_originalColWidths[ COL_NUMBER ] );
  862. m_grid->AutoSizeColumn( COL_NAME );
  863. if( m_grid->GetColSize( COL_NAME ) < m_originalColWidths[ COL_NAME ] )
  864. m_grid->SetColSize( COL_NAME, m_originalColWidths[ COL_NAME ] );
  865. // If the grid is still wider than the columns, then stretch the Number and Name columns
  866. // to fit.
  867. for( int i = 0; i < COL_COUNT; ++i )
  868. width -= m_grid->GetColSize( i );
  869. if( width > 0 )
  870. {
  871. m_grid->SetColSize( COL_NUMBER, m_grid->GetColSize( COL_NUMBER ) + width / 2 );
  872. m_grid->SetColSize( COL_NAME, m_grid->GetColSize( COL_NAME ) + width / 2 );
  873. }
  874. }
  875. void DIALOG_LIB_EDIT_PIN_TABLE::OnSize( wxSizeEvent& event )
  876. {
  877. wxSize new_size = event.GetSize();
  878. if( m_initialized && m_size != new_size )
  879. {
  880. m_size = new_size;
  881. adjustGridColumns();
  882. }
  883. // Always propagate for a grid repaint (needed if the height changes, as well as width)
  884. event.Skip();
  885. }
  886. void DIALOG_LIB_EDIT_PIN_TABLE::OnUpdateUI( wxUpdateUIEvent& event )
  887. {
  888. wxString columnsShown = m_grid->GetShownColumns();
  889. if( columnsShown != m_columnsShown )
  890. {
  891. m_columnsShown = columnsShown;
  892. if( !m_grid->IsCellEditControlShown() )
  893. adjustGridColumns();
  894. }
  895. int firstSelectedRow;
  896. int selectedRowCount;
  897. getSelectedArea( m_grid, &firstSelectedRow, &selectedRowCount );
  898. if( ( selectedRowCount > 1 ) != m_groupSelected->IsEnabled() )
  899. m_groupSelected->Enable( selectedRowCount > 1 );
  900. }
  901. void DIALOG_LIB_EDIT_PIN_TABLE::OnCancel( wxCommandEvent& event )
  902. {
  903. Close();
  904. }
  905. void DIALOG_LIB_EDIT_PIN_TABLE::OnClose( wxCloseEvent& event )
  906. {
  907. // This is a cancel, so commit quietly as we're going to throw the results away anyway.
  908. m_grid->CommitPendingChanges( true );
  909. int retval = wxID_CANCEL;
  910. if( m_dataModel->IsEdited() )
  911. {
  912. if( HandleUnsavedChanges( this, _( "Save changes?" ),
  913. [&]() -> bool
  914. {
  915. if( TransferDataFromWindow() )
  916. {
  917. retval = wxID_OK;
  918. return true;
  919. }
  920. return false;
  921. } ) )
  922. {
  923. if( IsQuasiModal() )
  924. EndQuasiModal( retval );
  925. else
  926. EndDialog( retval );
  927. return;
  928. }
  929. else
  930. {
  931. event.Veto();
  932. return;
  933. }
  934. }
  935. // No change in dialog: we can close it
  936. if( IsQuasiModal() )
  937. EndQuasiModal( retval );
  938. else
  939. EndDialog( retval );
  940. return;
  941. }
  942. void DIALOG_LIB_EDIT_PIN_TABLE::updateSummary()
  943. {
  944. PIN_NUMBERS pinNumbers;
  945. for( LIB_PIN* pin : m_pins )
  946. {
  947. if( pin->GetNumber().Length() )
  948. pinNumbers.insert( pin->GetNumber() );
  949. }
  950. m_pin_numbers_summary->SetLabel( pinNumbers.GetSummary() );
  951. m_pin_count->SetLabel( wxString::Format( wxT( "%u" ), (unsigned) m_pins.size() ) );
  952. m_duplicate_pins->SetLabel( pinNumbers.GetDuplicates() );
  953. Layout();
  954. }