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.

417 lines
12 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2020 KiCad Developers, see AUTHORS.txt for contributors.
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU General Public License
  8. * as published by the Free Software Foundation; either version 2
  9. * of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; if not, you may find one here:
  18. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  19. * or you may search the http://www.gnu.org website for the version 2 license,
  20. * or you may write to the Free Software Foundation, Inc.,
  21. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  22. */
  23. #include "dialog_sch_pin_table.h"
  24. #include <grid_tricks.h>
  25. #include <pin_number.h>
  26. #include <sch_edit_frame.h>
  27. #include <confirm.h>
  28. #include <lib_edit_frame.h>
  29. #include <widgets/grid_icon_text_helpers.h>
  30. #include <widgets/wx_grid.h>
  31. #include <settings/settings_manager.h>
  32. #include <widgets/grid_combobox.h>
  33. class SCH_PIN_TABLE_DATA_MODEL : public wxGridTableBase, public std::vector<SCH_PIN>
  34. {
  35. protected:
  36. std::vector<wxGridCellAttr*> m_nameAttrs;
  37. wxGridCellAttr* m_numberAttr;
  38. wxGridCellAttr* m_typeAttr;
  39. wxGridCellAttr* m_shapeAttr;
  40. public:
  41. SCH_PIN_TABLE_DATA_MODEL() :
  42. m_numberAttr( nullptr ),
  43. m_typeAttr( nullptr ),
  44. m_shapeAttr( nullptr )
  45. {
  46. }
  47. ~SCH_PIN_TABLE_DATA_MODEL()
  48. {
  49. for( wxGridCellAttr* attr : m_nameAttrs )
  50. attr->DecRef();
  51. m_numberAttr->DecRef();
  52. m_typeAttr->DecRef();
  53. m_shapeAttr->DecRef();
  54. }
  55. void BuildAttrs()
  56. {
  57. m_numberAttr = new wxGridCellAttr;
  58. m_numberAttr->SetReadOnly( true );
  59. for( const SCH_PIN& pin : *this )
  60. {
  61. wxArrayString choices;
  62. LIB_PIN* lib_pin = pin.GetLibPin();
  63. choices.push_back( lib_pin->GetName() );
  64. for( const std::pair<const wxString, LIB_PIN::ALT>& alt : lib_pin->GetAlternates() )
  65. choices.push_back( alt.first );
  66. wxGridCellAttr* attr = new wxGridCellAttr();
  67. attr->SetEditor( new GRID_CELL_COMBOBOX( choices ) );
  68. m_nameAttrs.push_back( attr );
  69. }
  70. m_typeAttr = new wxGridCellAttr;
  71. m_typeAttr->SetRenderer( new GRID_CELL_ICON_TEXT_RENDERER( PinTypeIcons(), PinTypeNames() ) );
  72. m_typeAttr->SetReadOnly( true );
  73. m_shapeAttr = new wxGridCellAttr;
  74. m_shapeAttr->SetRenderer( new GRID_CELL_ICON_TEXT_RENDERER( PinShapeIcons(), PinShapeNames() ) );
  75. m_shapeAttr->SetReadOnly( true );
  76. }
  77. int GetNumberRows() override { return (int) size(); }
  78. int GetNumberCols() override { return COL_COUNT; }
  79. wxString GetColLabelValue( int aCol ) override
  80. {
  81. switch( aCol )
  82. {
  83. case COL_NUMBER: return _( "Number" );
  84. case COL_NAME: return _( "Name" );
  85. case COL_TYPE: return _( "Electrical Type" );
  86. case COL_SHAPE: return _( "Graphic Style" );
  87. default: wxFAIL; return wxEmptyString;
  88. }
  89. }
  90. bool IsEmptyCell( int row, int col ) override
  91. {
  92. return false; // don't allow adjacent cell overflow, even if we are actually empty
  93. }
  94. wxString GetValue( int aRow, int aCol ) override
  95. {
  96. return GetValue( at( aRow ), aCol );
  97. }
  98. static wxString GetValue( const SCH_PIN& aPin, int aCol )
  99. {
  100. switch( aCol )
  101. {
  102. case COL_NUMBER: return aPin.GetNumber();
  103. case COL_NAME: return aPin.GetName();
  104. case COL_TYPE: return PinTypeNames()[static_cast<int>( aPin.GetType() )];
  105. case COL_SHAPE: return PinShapeNames()[static_cast<int>( aPin.GetShape() )];
  106. default: wxFAIL; return wxEmptyString;
  107. }
  108. }
  109. wxGridCellAttr* GetAttr( int aRow, int aCol, wxGridCellAttr::wxAttrKind ) override
  110. {
  111. switch( aCol )
  112. {
  113. case COL_NAME:
  114. m_nameAttrs[ aRow ]->IncRef();
  115. return m_nameAttrs[ aRow ];
  116. case COL_NUMBER:
  117. m_numberAttr->IncRef();
  118. return m_numberAttr;
  119. case COL_TYPE:
  120. m_typeAttr->IncRef();
  121. return m_typeAttr;
  122. case COL_SHAPE:
  123. m_shapeAttr->IncRef();
  124. return m_shapeAttr;
  125. default:
  126. wxFAIL;
  127. return nullptr;
  128. }
  129. }
  130. void SetValue( int aRow, int aCol, const wxString &aValue ) override
  131. {
  132. switch( aCol )
  133. {
  134. case COL_NAME:
  135. if( aValue == at( aRow ).GetName() )
  136. at( aRow ).SetAlt( wxEmptyString );
  137. else
  138. at( aRow ).SetAlt( aValue );
  139. break;
  140. case COL_NUMBER:
  141. case COL_TYPE:
  142. case COL_SHAPE:
  143. // Read-only.
  144. break;
  145. default:
  146. wxFAIL;
  147. break;
  148. }
  149. }
  150. static bool compare( const SCH_PIN& lhs, const SCH_PIN& rhs, int sortCol, bool ascending )
  151. {
  152. wxString lhStr = GetValue( lhs, sortCol );
  153. wxString rhStr = GetValue( rhs, sortCol );
  154. if( lhStr == rhStr )
  155. {
  156. // Secondary sort key is always COL_NUMBER
  157. sortCol = COL_NUMBER;
  158. lhStr = GetValue( lhs, sortCol );
  159. rhStr = GetValue( rhs, sortCol );
  160. }
  161. bool res;
  162. // N.B. To meet the iterator sort conditions, we cannot simply invert the truth
  163. // to get the opposite sort. i.e. ~(a<b) != (a>b)
  164. auto cmp = [ ascending ]( const auto a, const auto b )
  165. {
  166. if( ascending )
  167. return a < b;
  168. else
  169. return b < a;
  170. };
  171. switch( sortCol )
  172. {
  173. case COL_NUMBER:
  174. case COL_NAME:
  175. res = cmp( PinNumbers::Compare( lhStr, rhStr ), 0 );
  176. break;
  177. case COL_TYPE:
  178. case COL_SHAPE:
  179. res = cmp( lhStr.CmpNoCase( rhStr ), 0 );
  180. break;
  181. default:
  182. res = cmp( StrNumCmp( lhStr, rhStr ), 0 );
  183. break;
  184. }
  185. return res;
  186. }
  187. void SortRows( int aSortCol, bool ascending )
  188. {
  189. std::sort( begin(), end(),
  190. [ aSortCol, ascending ]( const SCH_PIN& lhs, const SCH_PIN& rhs ) -> bool
  191. {
  192. return compare( lhs, rhs, aSortCol, ascending );
  193. } );
  194. }
  195. };
  196. DIALOG_SCH_PIN_TABLE::DIALOG_SCH_PIN_TABLE( SCH_EDIT_FRAME* parent, SCH_COMPONENT* aComp ) :
  197. DIALOG_SCH_PIN_TABLE_BASE( parent ),
  198. m_editFrame( parent ),
  199. m_comp( aComp )
  200. {
  201. m_dataModel = new SCH_PIN_TABLE_DATA_MODEL();
  202. // Make a copy of the pins for editing
  203. for( const std::unique_ptr<SCH_PIN>& pin : m_comp->GetRawPins() )
  204. m_dataModel->push_back( *pin );
  205. m_dataModel->SortRows( COL_NUMBER, true );
  206. m_dataModel->BuildAttrs();
  207. // Save original columns widths so we can do proportional sizing.
  208. for( int i = 0; i < COL_COUNT; ++i )
  209. m_originalColWidths[ i ] = m_grid->GetColSize( i );
  210. // Give a bit more room for combobox editors
  211. m_grid->SetDefaultRowSize( m_grid->GetDefaultRowSize() + 4 );
  212. m_grid->SetTable( m_dataModel );
  213. m_grid->PushEventHandler( new GRID_TRICKS( m_grid ) );
  214. GetSizer()->SetSizeHints(this);
  215. Centre();
  216. m_ButtonsOK->SetDefault();
  217. m_initialized = true;
  218. m_modified = false;
  219. m_width = 0;
  220. // Connect Events
  221. m_grid->Connect( wxEVT_GRID_COL_SORT, wxGridEventHandler( DIALOG_SCH_PIN_TABLE::OnColSort ), nullptr, this );
  222. }
  223. DIALOG_SCH_PIN_TABLE::~DIALOG_SCH_PIN_TABLE()
  224. {
  225. // Disconnect Events
  226. m_grid->Disconnect( wxEVT_GRID_COL_SORT, wxGridEventHandler( DIALOG_SCH_PIN_TABLE::OnColSort ), nullptr, this );
  227. // Prevents crash bug in wxGrid's d'tor
  228. m_grid->DestroyTable( m_dataModel );
  229. // Delete the GRID_TRICKS.
  230. m_grid->PopEventHandler( true );
  231. }
  232. bool DIALOG_SCH_PIN_TABLE::TransferDataToWindow()
  233. {
  234. return true;
  235. }
  236. bool DIALOG_SCH_PIN_TABLE::TransferDataFromWindow()
  237. {
  238. if( !m_grid->CommitPendingChanges() )
  239. return false;
  240. // Update any assignments
  241. for( const SCH_PIN& model_pin : *m_dataModel )
  242. {
  243. // map from the edited copy back to the "real" pin in the component
  244. SCH_PIN* src_pin = m_comp->GetPin( model_pin.GetLibPin() );
  245. src_pin->SetAlt( model_pin.GetAlt() );
  246. }
  247. return true;
  248. }
  249. void DIALOG_SCH_PIN_TABLE::OnCellEdited( wxGridEvent& aEvent )
  250. {
  251. int row = aEvent.GetRow();
  252. // These are just to get the cells refreshed
  253. m_dataModel->SetValue( row, COL_TYPE, m_dataModel->GetValue( row, COL_TYPE ) );
  254. m_dataModel->SetValue( row, COL_SHAPE, m_dataModel->GetValue( row, COL_SHAPE ) );
  255. m_modified = true;
  256. }
  257. void DIALOG_SCH_PIN_TABLE::OnColSort( wxGridEvent& aEvent )
  258. {
  259. int sortCol = aEvent.GetCol();
  260. bool ascending;
  261. // This is bonkers, but wxWidgets doesn't tell us ascending/descending in the
  262. // event, and if we ask it will give us pre-event info.
  263. if( m_grid->IsSortingBy( sortCol ) )
  264. // same column; invert ascending
  265. ascending = !m_grid->IsSortOrderAscending();
  266. else
  267. // different column; start with ascending
  268. ascending = true;
  269. m_dataModel->SortRows( sortCol, ascending );
  270. }
  271. void DIALOG_SCH_PIN_TABLE::adjustGridColumns( int aWidth )
  272. {
  273. m_width = aWidth;
  274. // Account for scroll bars
  275. aWidth -= ( m_grid->GetSize().x - m_grid->GetClientSize().x );
  276. wxGridUpdateLocker deferRepaintsTillLeavingScope;
  277. // The Number and Name columns must be at least wide enough to hold their contents, but
  278. // no less wide than their original widths.
  279. m_grid->AutoSizeColumn( COL_NUMBER );
  280. if( m_grid->GetColSize( COL_NUMBER ) < m_originalColWidths[ COL_NUMBER ] )
  281. m_grid->SetColSize( COL_NUMBER, m_originalColWidths[ COL_NUMBER ] );
  282. m_grid->AutoSizeColumn( COL_NAME );
  283. if( m_grid->GetColSize( COL_NAME ) < m_originalColWidths[ COL_NAME ] )
  284. m_grid->SetColSize( COL_NAME, m_originalColWidths[ COL_NAME ] );
  285. // If the grid is still wider than the columns, then stretch the Number and Name columns
  286. // to fit.
  287. for( int i = 0; i < COL_COUNT; ++i )
  288. aWidth -= m_grid->GetColSize( i );
  289. if( aWidth > 0 )
  290. {
  291. m_grid->SetColSize( COL_NUMBER, m_grid->GetColSize( COL_NUMBER ) + aWidth / 2 );
  292. m_grid->SetColSize( COL_NAME, m_grid->GetColSize( COL_NAME ) + aWidth / 2 );
  293. }
  294. }
  295. void DIALOG_SCH_PIN_TABLE::OnSize( wxSizeEvent& event )
  296. {
  297. auto new_size = event.GetSize().GetX();
  298. if( m_initialized && m_width != new_size )
  299. {
  300. adjustGridColumns( new_size );
  301. }
  302. // Always propagate for a grid repaint (needed if the height changes, as well as width)
  303. event.Skip();
  304. }
  305. void DIALOG_SCH_PIN_TABLE::OnCancel( wxCommandEvent& event )
  306. {
  307. Close();
  308. }
  309. void DIALOG_SCH_PIN_TABLE::OnClose( wxCloseEvent& event )
  310. {
  311. // This is a cancel, so commit quietly as we're going to throw the results away anyway.
  312. m_grid->CommitPendingChanges( true );
  313. int retval = wxCANCEL;
  314. if( m_modified && !HandleUnsavedChanges( this, _( "Save changes?" ),
  315. [&]()->bool
  316. {
  317. if( TransferDataFromWindow() )
  318. {
  319. retval = wxOK;
  320. return true;
  321. }
  322. return false;
  323. } ) )
  324. {
  325. event.Veto();
  326. return;
  327. }
  328. if( IsQuasiModal() )
  329. EndQuasiModal( retval );
  330. else
  331. EndModal( retval );
  332. }