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.

528 lines
18 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
2 years ago
2 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2010 Jean-Pierre Charras, jp.charras at wanadoo.fr
  5. * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, you may find one here:
  19. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  20. * or you may search the http://www.gnu.org website for the version 2 license,
  21. * or you may write to the Free Software Foundation, Inc.,
  22. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. */
  24. #include <bitmaps.h>
  25. #include <sch_painter.h>
  26. #include <symbol_edit_frame.h>
  27. #include <sch_pin.h>
  28. #include <dialog_pin_properties.h>
  29. #include <confirm.h>
  30. #include <kiplatform/ui.h>
  31. #include <widgets/tab_traversal.h>
  32. #include <widgets/wx_grid.h>
  33. #include <grid_tricks.h>
  34. #include <widgets/grid_icon_text_helpers.h>
  35. #include <widgets/std_bitmap_button.h>
  36. #include <wx/hyperlink.h>
  37. #include <symbol_preview_widget.h>
  38. class ALT_PIN_DATA_MODEL : public WX_GRID_TABLE_BASE, public std::vector<SCH_PIN::ALT>
  39. {
  40. public:
  41. ALT_PIN_DATA_MODEL( EDA_UNITS aUserUnits )
  42. {
  43. }
  44. int GetNumberRows() override { return (int) size(); }
  45. int GetNumberCols() override { return COL_COUNT; }
  46. wxString GetColLabelValue( int aCol ) override
  47. {
  48. switch( aCol )
  49. {
  50. case COL_NAME: return _( "Alternate Pin Name" );
  51. case COL_TYPE: return _( "Electrical Type" );
  52. case COL_SHAPE: return _( "Graphic Style" );
  53. default: wxFAIL; return wxEmptyString;
  54. }
  55. }
  56. bool IsEmptyCell( int row, int col ) override
  57. {
  58. return false; // don't allow adjacent cell overflow, even if we are actually empty
  59. }
  60. wxString GetValue( int aRow, int aCol ) override
  61. {
  62. switch( aCol )
  63. {
  64. case COL_NAME: return at( aRow ).m_Name;
  65. case COL_TYPE: return PinTypeNames()[static_cast<int>( at( aRow ).m_Type )];
  66. case COL_SHAPE: return PinShapeNames()[static_cast<int>( at( aRow ).m_Shape )];
  67. default: wxFAIL; return wxEmptyString;
  68. }
  69. }
  70. void SetValue( int aRow, int aCol, const wxString &aValue ) override
  71. {
  72. switch( aCol )
  73. {
  74. case COL_NAME:
  75. at( aRow ).m_Name = aValue;
  76. break;
  77. case COL_TYPE:
  78. if( PinTypeNames().Index( aValue ) != wxNOT_FOUND )
  79. at( aRow ).m_Type = (ELECTRICAL_PINTYPE) PinTypeNames().Index( aValue );
  80. break;
  81. case COL_SHAPE:
  82. if( PinShapeNames().Index( aValue ) != wxNOT_FOUND )
  83. at( aRow ).m_Shape = (GRAPHIC_PINSHAPE) PinShapeNames().Index( aValue );
  84. break;
  85. default:
  86. wxFAIL;
  87. break;
  88. }
  89. }
  90. void AppendRow( const SCH_PIN::ALT& aAlt )
  91. {
  92. push_back( aAlt );
  93. if ( GetView() )
  94. {
  95. wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, 1 );
  96. GetView()->ProcessTableMessage( msg );
  97. }
  98. }
  99. void RemoveRow( int aRow )
  100. {
  101. erase( begin() + aRow );
  102. if ( GetView() )
  103. {
  104. wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, aRow, 1 );
  105. GetView()->ProcessTableMessage( msg );
  106. }
  107. }
  108. };
  109. DIALOG_PIN_PROPERTIES::DIALOG_PIN_PROPERTIES( SYMBOL_EDIT_FRAME* parent, SCH_PIN* aPin,
  110. bool aFocusPinNumber ) :
  111. DIALOG_PIN_PROPERTIES_BASE( parent ),
  112. m_frame( parent ),
  113. m_pin( aPin ),
  114. m_posX( parent, m_posXLabel, m_posXCtrl, m_posXUnits ),
  115. m_posY( parent, m_posYLabel, m_posYCtrl, m_posYUnits ),
  116. m_pinLength( parent, m_pinLengthLabel, m_pinLengthCtrl, m_pinLengthUnits ),
  117. m_nameSize( parent, m_nameSizeLabel, m_nameSizeCtrl, m_nameSizeUnits ),
  118. m_numberSize( parent, m_numberSizeLabel, m_numberSizeCtrl, m_numberSizeUnits ),
  119. m_delayedFocusRow( -1 ),
  120. m_delayedFocusColumn( -1 ),
  121. m_initialized( false )
  122. {
  123. // Create a dummy symbol with a single pin for the preview widget:
  124. m_dummyParent = new LIB_SYMBOL( *static_cast<LIB_SYMBOL*>( m_pin->GetParentSymbol() ) );
  125. // Move everything in the copied symbol to unit 1; we'll use unit 2 for the dummy pin:
  126. m_dummyParent->SetUnitCount( 2, false );
  127. m_dummyParent->RunOnChildren( [&]( SCH_ITEM* child )
  128. {
  129. child->SetUnit( 1 );
  130. },
  131. RECURSE_MODE::NO_RECURSE );
  132. m_dummyPin = new SCH_PIN( *m_pin );
  133. m_dummyPin->SetUnit( 2 );
  134. m_dummyParent->AddDrawItem( m_dummyPin, false );
  135. m_dummyParent->SetShowPinNames( true );
  136. m_dummyParent->SetShowPinNumbers( true );
  137. m_previewWidget = new SYMBOL_PREVIEW_WIDGET( m_panelShowPin, &m_frame->Kiway(), false,
  138. m_frame->GetCanvas()->GetBackend() );
  139. m_previewWidget->SetLayoutDirection( wxLayout_LeftToRight );
  140. m_previewWidget->DisplayPart( m_dummyParent, m_dummyPin->GetUnit(), 0 );
  141. wxBoxSizer* previewSizer = new wxBoxSizer( wxHORIZONTAL );
  142. previewSizer->Add( m_previewWidget, 1, wxEXPAND, 5 );
  143. m_panelShowPin->SetSizer( previewSizer );
  144. const wxArrayString& orientationNames = PinOrientationNames();
  145. const std::vector<BITMAPS>& orientationIcons = PinOrientationIcons();
  146. for ( unsigned ii = 0; ii < orientationNames.GetCount(); ii++ )
  147. m_choiceOrientation->Insert( orientationNames[ii], KiBitmapBundle( orientationIcons[ii] ), ii );
  148. // We can't set the tab order through wxWidgets due to shortcomings in their mnemonics
  149. // implementation on MSW
  150. m_tabOrder = {
  151. m_textPinName,
  152. m_textPinNumber,
  153. m_choiceElectricalType,
  154. m_choiceStyle,
  155. m_posXCtrl,
  156. m_posYCtrl,
  157. m_choiceOrientation,
  158. m_pinLengthCtrl,
  159. m_nameSizeCtrl,
  160. m_numberSizeCtrl,
  161. m_checkApplyToAllParts,
  162. m_checkApplyToAllBodyStyles,
  163. m_checkShow,
  164. m_sdbSizerButtonsOK,
  165. m_sdbSizerButtonsCancel
  166. };
  167. // Default alternates turndown to whether or not alternates exist, or if we've had it open
  168. // before
  169. m_alternatesTurndown->Collapse( m_pin->GetAlternates().size() == 0 && !s_alternatesTurndownOpen );
  170. // wxwidgets doesn't call the OnCollapseChange even at init, so we update this value if
  171. // the alternates pane defaults to open
  172. if ( m_pin->GetAlternates().size() > 0 )
  173. s_alternatesTurndownOpen = true;
  174. m_alternatesDataModel = new ALT_PIN_DATA_MODEL( GetUserUnits() );
  175. // Save original columns widths so we can do proportional sizing.
  176. for( int i = 0; i < COL_COUNT; ++i )
  177. m_originalColWidths[ i ] = m_alternatesGrid->GetColSize( i );
  178. m_alternatesGrid->SetTable( m_alternatesDataModel );
  179. m_alternatesGrid->PushEventHandler( new GRID_TRICKS( m_alternatesGrid,
  180. [this]( wxCommandEvent& aEvent )
  181. {
  182. OnAddAlternate( aEvent );
  183. } ) );
  184. m_alternatesGrid->SetSelectionMode( wxGrid::wxGridSelectRows );
  185. if( aPin->GetParentSymbol()->HasAlternateBodyStyle() )
  186. {
  187. m_alternatesTurndown->Collapse();
  188. m_alternatesTurndown->Disable();
  189. m_alternatesTurndown->SetToolTip( _( "Alternate pin assignments are not available for "
  190. "De Morgan symbols." ) );
  191. }
  192. // Set special attributes
  193. wxGridCellAttr* attr;
  194. attr = new wxGridCellAttr;
  195. attr->SetRenderer( new GRID_CELL_ICON_TEXT_RENDERER( PinTypeIcons(), PinTypeNames() ) );
  196. attr->SetEditor( new GRID_CELL_ICON_TEXT_POPUP( PinTypeIcons(), PinTypeNames() ) );
  197. m_alternatesGrid->SetColAttr( COL_TYPE, attr );
  198. attr = new wxGridCellAttr;
  199. attr->SetRenderer( new GRID_CELL_ICON_TEXT_RENDERER( PinShapeIcons(), PinShapeNames() ) );
  200. attr->SetEditor( new GRID_CELL_ICON_TEXT_POPUP( PinShapeIcons(), PinShapeNames() ) );
  201. m_alternatesGrid->SetColAttr( COL_SHAPE, attr );
  202. m_addAlternate->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) );
  203. m_deleteAlternate->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) );
  204. m_addAlternate->GetParent()->Layout();
  205. SetupStandardButtons();
  206. SetInitialFocus( aFocusPinNumber ? m_textPinNumber : m_textPinName );
  207. // We should call FinishDialogSettings() when all widgets have the size fixed.
  208. // However m_infoBar is not yet initialized, so it will called later
  209. // See TransferDataToWindow()
  210. // On some window managers (Unity, XFCE) the dialog is not always raised, depending on
  211. // how it is run.
  212. Raise();
  213. m_initialized = true;
  214. }
  215. DIALOG_PIN_PROPERTIES::~DIALOG_PIN_PROPERTIES()
  216. {
  217. delete m_dummyParent;
  218. // Prevents crash bug in wxGrid's d'tor
  219. m_alternatesGrid->DestroyTable( m_alternatesDataModel );
  220. // Delete the GRID_TRICKS.
  221. m_alternatesGrid->PopEventHandler( true );
  222. }
  223. bool DIALOG_PIN_PROPERTIES::TransferDataToWindow()
  224. {
  225. if( !DIALOG_SHIM::TransferDataToWindow() )
  226. return false;
  227. m_origPos = m_pin->GetPosition();
  228. m_choiceOrientation->SetSelection( PinOrientationIndex( m_pin->GetOrientation() ) );
  229. m_choiceStyle->SetSelection( m_pin->GetShape() );
  230. m_choiceElectricalType->SetSelection( m_pin->GetType() );
  231. m_textPinName->SetValue( m_pin->GetName() );
  232. m_nameSize.SetValue( m_pin->GetNameTextSize() );
  233. m_posX.SetValue( m_origPos.x );
  234. m_posY.SetValue( m_origPos.y );
  235. m_textPinNumber->SetValue( m_pin->GetNumber() );
  236. m_numberSize.SetValue( m_pin->GetNumberTextSize() );
  237. m_pinLength.SetValue( m_pin->GetLength() );
  238. m_checkApplyToAllParts->Enable( m_pin->GetParentSymbol()->IsMulti() );
  239. m_checkApplyToAllParts->SetValue( m_pin->GetParentSymbol()->IsMulti() && m_pin->GetUnit() == 0 );
  240. m_checkApplyToAllBodyStyles->SetValue( m_pin->GetBodyStyle() == 0 );
  241. m_checkShow->SetValue( m_pin->IsVisible() );
  242. m_dummyPin->SetVisible( m_pin->IsVisible() );
  243. wxString commonUnitsToolTip;
  244. if( m_frame->m_SyncPinEdit )
  245. {
  246. wxHyperlinkCtrl* button = new wxHyperlinkCtrl( m_infoBar, wxID_ANY, _( "Exit sync pins mode" ),
  247. wxEmptyString );
  248. button->Bind( wxEVT_COMMAND_HYPERLINK,
  249. std::function<void( wxHyperlinkEvent& aEvent )>(
  250. [&]( wxHyperlinkEvent& aEvent )
  251. {
  252. m_frame->m_SyncPinEdit = false;
  253. m_infoBar->Dismiss();
  254. } ) );
  255. m_infoBar->RemoveAllButtons();
  256. m_infoBar->AddButton( button );
  257. m_infoBar->ShowMessage( getSyncPinsMessage() );
  258. commonUnitsToolTip = _( "Synchronized pins mode is enabled.\n"
  259. "Similar pins will be edited regardless of this option." );
  260. }
  261. else
  262. {
  263. commonUnitsToolTip = _( "If checked, this pin will exist in all units." );
  264. }
  265. if( !m_pin->GetParentSymbol()->IsMulti() )
  266. commonUnitsToolTip = _( "This symbol only has one unit. This control has no effect." );
  267. m_checkApplyToAllParts->SetToolTip( commonUnitsToolTip );
  268. for( const std::pair<const wxString, SCH_PIN::ALT>& alt : m_pin->GetAlternates() )
  269. m_alternatesDataModel->AppendRow( alt.second );
  270. // We can call FinishDialogSettings() now all widgets have the size fixed.
  271. finishDialogSettings();
  272. return true;
  273. }
  274. bool DIALOG_PIN_PROPERTIES::TransferDataFromWindow()
  275. {
  276. if( !m_alternatesGrid->CommitPendingChanges() )
  277. return false;
  278. // Check for missing alternate names.
  279. for( size_t i = 0; i < m_alternatesDataModel->size(); ++i )
  280. {
  281. if( m_alternatesDataModel->at( i ).m_Name.IsEmpty() )
  282. {
  283. DisplayErrorMessage( this, _( "Alternate pin definitions must have a name." ) );
  284. m_delayedFocusColumn = COL_NAME;
  285. m_delayedFocusRow = i;
  286. return false;
  287. }
  288. }
  289. if( !DIALOG_SHIM::TransferDataFromWindow() )
  290. return false;
  291. VECTOR2I newPos( m_posX.GetIntValue(), m_posY.GetIntValue() );
  292. const int standard_grid = 50;
  293. // Only show the warning if the position has been changed
  294. if( ( m_origPos != newPos )
  295. && ( ( m_posX.GetValue() % standard_grid ) || ( m_posY.GetValue() % standard_grid ) ) )
  296. {
  297. wxString msg = wxString::Format( _( "This pin is not on a %d mils grid which will make it "
  298. "difficult to connect to in the schematic.\n"
  299. "Do you wish to continue?" ),
  300. standard_grid );
  301. if( !IsOK( this, msg ) )
  302. return false;
  303. }
  304. m_pin->SetName( m_textPinName->GetValue() );
  305. m_pin->SetNumber( m_textPinNumber->GetValue() );
  306. m_pin->SetNameTextSize( m_nameSize.GetIntValue() );
  307. m_pin->SetNumberTextSize( m_numberSize.GetIntValue() );
  308. m_pin->SetOrientation( PinOrientationCode( m_choiceOrientation->GetSelection() ) );
  309. m_pin->SetPosition( newPos );
  310. m_pin->ChangeLength( m_pinLength.GetIntValue() );
  311. m_pin->SetType( m_choiceElectricalType->GetPinTypeSelection() );
  312. m_pin->SetShape( m_choiceStyle->GetPinShapeSelection() );
  313. m_pin->SetBodyStyle( m_checkApplyToAllBodyStyles->GetValue() ? 0 : m_frame->GetBodyStyle() );
  314. m_pin->SetUnit( m_checkApplyToAllParts->GetValue() ? 0 : m_frame->GetUnit() );
  315. m_pin->SetVisible( m_checkShow->GetValue() );
  316. std::map<wxString, SCH_PIN::ALT>& alternates = m_pin->GetAlternates();
  317. alternates.clear();
  318. for( const SCH_PIN::ALT& alt : *m_alternatesDataModel )
  319. alternates[ alt.m_Name ] = alt;
  320. return true;
  321. }
  322. void DIALOG_PIN_PROPERTIES::OnPropertiesChange( wxCommandEvent& event )
  323. {
  324. if( !IsShownOnScreen() ) // do nothing at init time
  325. return;
  326. m_dummyPin->SetName( m_textPinName->GetValue() );
  327. m_dummyPin->SetNumber( m_textPinNumber->GetValue() );
  328. m_dummyPin->SetNameTextSize( m_nameSize.GetIntValue() );
  329. m_dummyPin->SetNumberTextSize( m_numberSize.GetIntValue() );
  330. m_dummyPin->SetOrientation( PinOrientationCode( m_choiceOrientation->GetSelection() ) );
  331. m_dummyPin->SetLength( m_pinLength.GetIntValue() );
  332. m_dummyPin->SetType( m_choiceElectricalType->GetPinTypeSelection() );
  333. m_dummyPin->SetShape( m_choiceStyle->GetPinShapeSelection() );
  334. m_dummyPin->SetVisible( m_checkShow->GetValue() );
  335. if( event.GetEventObject() == m_checkApplyToAllParts && m_frame->m_SyncPinEdit )
  336. {
  337. m_infoBar->ShowMessage( getSyncPinsMessage() );
  338. m_infoBar->GetSizer()->Layout();
  339. }
  340. m_previewWidget->DisplayPart( m_dummyParent, m_dummyPin->GetUnit(), 0 );
  341. }
  342. wxString DIALOG_PIN_PROPERTIES::getSyncPinsMessage()
  343. {
  344. if( m_checkApplyToAllParts->GetValue() )
  345. return _( "Synchronized Pins Mode." );
  346. else if( m_pin->IsNew() )
  347. return _( "Synchronized Pins Mode. New pin will be added to all units." );
  348. else
  349. return _( "Synchronized Pins Mode. Matching pins in other units will be updated." );
  350. }
  351. void DIALOG_PIN_PROPERTIES::OnAddAlternate( wxCommandEvent& event )
  352. {
  353. if( !m_alternatesGrid->CommitPendingChanges() )
  354. return;
  355. m_alternatesGrid->OnAddRow(
  356. [&]() -> std::pair<int, int>
  357. {
  358. SCH_PIN::ALT newAlt;
  359. newAlt.m_Name = wxEmptyString;
  360. newAlt.m_Type = m_pin->GetType();
  361. newAlt.m_Shape = m_pin->GetShape();
  362. m_alternatesDataModel->AppendRow( newAlt );
  363. return { m_alternatesGrid->GetNumberRows() - 1, COL_NAME };
  364. } );
  365. }
  366. void DIALOG_PIN_PROPERTIES::OnDeleteAlternate( wxCommandEvent& event )
  367. {
  368. m_alternatesGrid->OnDeleteRows(
  369. [&]( int row )
  370. {
  371. m_alternatesDataModel->RemoveRow( row );
  372. } );
  373. }
  374. void DIALOG_PIN_PROPERTIES::adjustGridColumns()
  375. {
  376. // Account for scroll bars
  377. int width = KIPLATFORM::UI::GetUnobscuredSize( m_alternatesGrid ).x;
  378. wxGridUpdateLocker deferRepaintsTillLeavingScope;
  379. m_alternatesGrid->SetColSize( COL_TYPE, m_originalColWidths[COL_TYPE] );
  380. m_alternatesGrid->SetColSize( COL_SHAPE, m_originalColWidths[COL_SHAPE] );
  381. m_alternatesGrid->SetColSize( COL_NAME, width - m_originalColWidths[COL_TYPE] - m_originalColWidths[COL_SHAPE] );
  382. }
  383. void DIALOG_PIN_PROPERTIES::OnSize( wxSizeEvent& event )
  384. {
  385. auto new_size = event.GetSize();
  386. if( m_initialized && m_size != new_size )
  387. {
  388. m_size = new_size;
  389. adjustGridColumns();
  390. }
  391. // Always propagate for a grid repaint (needed if the height changes, as well as width)
  392. event.Skip();
  393. }
  394. void DIALOG_PIN_PROPERTIES::OnUpdateUI( wxUpdateUIEvent& event )
  395. {
  396. // Handle a delayed focus
  397. if( m_delayedFocusRow >= 0 )
  398. {
  399. m_alternatesTurndown->Collapse( false );
  400. m_alternatesGrid->SetFocus();
  401. m_alternatesGrid->MakeCellVisible( m_delayedFocusRow, m_delayedFocusColumn );
  402. m_alternatesGrid->SetGridCursor( m_delayedFocusRow, m_delayedFocusColumn );
  403. m_alternatesGrid->EnableCellEditControl( true );
  404. m_alternatesGrid->ShowCellEditControl();
  405. m_delayedFocusRow = -1;
  406. m_delayedFocusColumn = -1;
  407. }
  408. }
  409. void DIALOG_PIN_PROPERTIES::OnCollapsiblePaneChange( wxCollapsiblePaneEvent& event )
  410. {
  411. if( !event.GetCollapsed() )
  412. {
  413. wxTopLevelWindow* tlw = dynamic_cast<wxTopLevelWindow*>( wxGetTopLevelParent( this ) );
  414. if( tlw )
  415. {
  416. tlw->InvalidateBestSize();
  417. wxSize bestSize = tlw->GetBestSize();
  418. wxSize currentSize = tlw->GetSize();
  419. tlw->SetSize( wxMax( bestSize.GetWidth(), currentSize.GetWidth() ),
  420. wxMax( bestSize.GetHeight(), currentSize.GetHeight() ) );
  421. }
  422. }
  423. }