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.

519 lines
17 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2018 CERN
  5. * @author Jon Evans <jon@craftyjon.com>
  6. *
  7. * This program is free software: you can redistribute it and/or modify it
  8. * under the terms of the GNU General Public License as published by the
  9. * Free Software Foundation, either version 3 of the License, or (at your
  10. * option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful, but
  13. * WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License along
  18. * with this program. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. #include <wx/tokenzr.h>
  21. #include <invoke_sch_dialog.h>
  22. #include <sch_sheet_path.h>
  23. #include <schematic.h>
  24. #include "dialog_bus_manager.h"
  25. BEGIN_EVENT_TABLE( DIALOG_BUS_MANAGER, DIALOG_SHIM )
  26. EVT_BUTTON( wxID_OK, DIALOG_BUS_MANAGER::OnOkClick )
  27. EVT_BUTTON( wxID_CANCEL, DIALOG_BUS_MANAGER::OnCancelClick )
  28. END_EVENT_TABLE()
  29. DIALOG_BUS_MANAGER::DIALOG_BUS_MANAGER( SCH_EDIT_FRAME* aParent )
  30. : DIALOG_SHIM( aParent, wxID_ANY, _( "Bus Definitions" ),
  31. wxDefaultPosition, wxSize( 640, 480 ),
  32. wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
  33. m_parent( aParent )
  34. {
  35. auto sizer = new wxBoxSizer( wxVERTICAL );
  36. auto buttons = new wxStdDialogButtonSizer();
  37. buttons->AddButton( new wxButton( this, wxID_OK ) );
  38. buttons->AddButton( new wxButton( this, wxID_CANCEL ) );
  39. buttons->Realize();
  40. auto top_container = new wxBoxSizer( wxHORIZONTAL );
  41. auto left_pane = new wxBoxSizer( wxVERTICAL );
  42. auto right_pane = new wxBoxSizer( wxVERTICAL );
  43. // Left pane: alias list
  44. auto lbl_aliases = new wxStaticText( this, wxID_ANY, _( "Bus Aliases" ),
  45. wxDefaultPosition, wxDefaultSize,
  46. wxALIGN_LEFT );
  47. m_bus_list_view = new wxListView( this, wxID_ANY, wxDefaultPosition,
  48. wxSize( 300, 300 ), wxLC_ALIGN_LEFT |
  49. wxLC_NO_HEADER | wxLC_REPORT |
  50. wxLC_SINGLE_SEL );
  51. m_bus_list_view->InsertColumn( 0, "" );
  52. auto lbl_alias_edit = new wxStaticText( this, wxID_ANY, _( "Alias Name" ),
  53. wxDefaultPosition, wxDefaultSize,
  54. wxALIGN_LEFT );
  55. m_bus_edit = new wxTextCtrl( this, wxID_ANY, wxEmptyString,
  56. wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER );
  57. auto left_button_sizer = new wxBoxSizer( wxHORIZONTAL );
  58. m_btn_add_bus = new wxButton( this, wxID_ANY, _( "Add" ) );
  59. m_btn_rename_bus = new wxButton( this, wxID_ANY, _( "Rename" ) );
  60. m_btn_remove_bus = new wxButton( this, wxID_ANY, _( "Remove" ) );
  61. left_button_sizer->Add( m_btn_add_bus );
  62. left_button_sizer->Add( m_btn_rename_bus );
  63. left_button_sizer->Add( m_btn_remove_bus );
  64. left_pane->Add( lbl_aliases, 0, wxEXPAND | wxALL, 5 );
  65. left_pane->Add( m_bus_list_view, 1, wxEXPAND | wxALL, 5 );
  66. left_pane->Add( lbl_alias_edit, 0, wxEXPAND | wxALL, 5 );
  67. left_pane->Add( m_bus_edit, 0, wxEXPAND | wxALL, 5 );
  68. left_pane->Add( left_button_sizer, 0, wxEXPAND | wxALL, 5 );
  69. // Right pane: signal list
  70. auto lbl_signals = new wxStaticText( this, wxID_ANY, _( "Alias Members" ),
  71. wxDefaultPosition, wxDefaultSize,
  72. wxALIGN_LEFT );
  73. m_signal_list_view = new wxListView( this, wxID_ANY, wxDefaultPosition,
  74. wxSize( 300, 300 ), wxLC_ALIGN_LEFT |
  75. wxLC_NO_HEADER | wxLC_REPORT |
  76. wxLC_SINGLE_SEL );
  77. m_signal_list_view->InsertColumn( 0, "" );
  78. auto lbl_signal_edit = new wxStaticText( this, wxID_ANY, _( "Member Name" ),
  79. wxDefaultPosition, wxDefaultSize,
  80. wxALIGN_LEFT );
  81. m_signal_edit = new wxTextCtrl( this, wxID_ANY, wxEmptyString,
  82. wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER );
  83. auto right_button_sizer = new wxBoxSizer( wxHORIZONTAL );
  84. m_btn_add_signal = new wxButton( this, wxID_ANY, _( "Add" ) );
  85. m_btn_rename_signal = new wxButton( this, wxID_ANY, _( "Rename" ) );
  86. m_btn_remove_signal = new wxButton( this, wxID_ANY, _( "Remove" ) );
  87. right_button_sizer->Add( m_btn_add_signal );
  88. right_button_sizer->Add( m_btn_rename_signal );
  89. right_button_sizer->Add( m_btn_remove_signal );
  90. right_pane->Add( lbl_signals, 0, wxEXPAND | wxALL, 5 );
  91. right_pane->Add( m_signal_list_view, 1, wxEXPAND | wxALL, 5 );
  92. right_pane->Add( lbl_signal_edit, 0, wxEXPAND | wxALL, 5 );
  93. right_pane->Add( m_signal_edit, 0, wxEXPAND | wxALL, 5 );
  94. right_pane->Add( right_button_sizer, 0, wxEXPAND | wxALL, 5 );
  95. top_container->Add( left_pane, 1, wxEXPAND );
  96. top_container->Add( right_pane, 1, wxEXPAND );
  97. sizer->Add( top_container, 1, wxEXPAND | wxALL, 5 );
  98. sizer->Add( buttons, 0, wxEXPAND | wxBOTTOM, 10 );
  99. SetSizer( sizer );
  100. // Setup validators
  101. wxTextValidator validator;
  102. validator.SetStyle( wxFILTER_EXCLUDE_CHAR_LIST );
  103. validator.SetCharExcludes( "\r\n\t " );
  104. m_bus_edit->SetValidator( validator );
  105. // Allow spaces in the signal edit, so that you can type in a list of
  106. // signals in the box and it can automatically split them when you add.
  107. validator.SetCharExcludes( "\r\n\t" );
  108. m_signal_edit->SetValidator( validator );
  109. // Setup events
  110. Bind( wxEVT_INIT_DIALOG, &DIALOG_BUS_MANAGER::OnInitDialog, this );
  111. m_bus_list_view->Connect( wxEVT_COMMAND_LIST_ITEM_DESELECTED,
  112. wxListEventHandler( DIALOG_BUS_MANAGER::OnSelectBus ), NULL, this );
  113. m_bus_list_view->Connect( wxEVT_COMMAND_LIST_ITEM_SELECTED,
  114. wxListEventHandler( DIALOG_BUS_MANAGER::OnSelectBus ), NULL, this );
  115. m_signal_list_view->Connect( wxEVT_COMMAND_LIST_ITEM_DESELECTED,
  116. wxListEventHandler( DIALOG_BUS_MANAGER::OnSelectSignal ), NULL, this );
  117. m_signal_list_view->Connect( wxEVT_COMMAND_LIST_ITEM_SELECTED,
  118. wxListEventHandler( DIALOG_BUS_MANAGER::OnSelectSignal ), NULL, this );
  119. m_btn_add_bus->Connect( wxEVT_COMMAND_BUTTON_CLICKED,
  120. wxCommandEventHandler( DIALOG_BUS_MANAGER::OnAddBus ), NULL, this );
  121. m_btn_rename_bus->Connect( wxEVT_COMMAND_BUTTON_CLICKED,
  122. wxCommandEventHandler( DIALOG_BUS_MANAGER::OnRenameBus ), NULL, this );
  123. m_btn_remove_bus->Connect( wxEVT_COMMAND_BUTTON_CLICKED,
  124. wxCommandEventHandler( DIALOG_BUS_MANAGER::OnRemoveBus ), NULL, this );
  125. m_signal_edit->Connect( wxEVT_TEXT_ENTER,
  126. wxCommandEventHandler( DIALOG_BUS_MANAGER::OnAddSignal ), NULL, this );
  127. m_btn_add_signal->Connect( wxEVT_COMMAND_BUTTON_CLICKED,
  128. wxCommandEventHandler( DIALOG_BUS_MANAGER::OnAddSignal ), NULL, this );
  129. m_btn_rename_signal->Connect( wxEVT_COMMAND_BUTTON_CLICKED,
  130. wxCommandEventHandler( DIALOG_BUS_MANAGER::OnRenameSignal ), NULL, this );
  131. m_btn_remove_signal->Connect( wxEVT_COMMAND_BUTTON_CLICKED,
  132. wxCommandEventHandler( DIALOG_BUS_MANAGER::OnRemoveSignal ), NULL, this );
  133. m_bus_edit->Connect( wxEVT_TEXT_ENTER,
  134. wxCommandEventHandler( DIALOG_BUS_MANAGER::OnAddBus ), NULL, this );
  135. // Set initial UI state
  136. m_btn_rename_bus->Disable();
  137. m_btn_remove_bus->Disable();
  138. m_btn_add_signal->Disable();
  139. m_btn_rename_signal->Disable();
  140. m_btn_remove_signal->Disable();
  141. m_bus_edit->SetHint( _( "Bus Alias Name" ) );
  142. m_signal_edit->SetHint( _( "Net or Bus Name" ) );
  143. FinishDialogSettings();
  144. }
  145. void DIALOG_BUS_MANAGER::OnInitDialog( wxInitDialogEvent& aEvent )
  146. {
  147. TransferDataToWindow();
  148. }
  149. bool DIALOG_BUS_MANAGER::TransferDataToWindow()
  150. {
  151. m_aliases.clear();
  152. const SCH_SHEET_LIST& sheets = m_parent->Schematic().GetSheets();
  153. std::vector< std::shared_ptr< BUS_ALIAS > > original_aliases;
  154. // collect aliases from each open sheet
  155. for( unsigned i = 0; i < sheets.size(); i++ )
  156. {
  157. auto sheet_aliases = sheets[i].LastScreen()->GetBusAliases();
  158. original_aliases.insert( original_aliases.end(), sheet_aliases.begin(),
  159. sheet_aliases.end() );
  160. }
  161. original_aliases.erase( std::unique( original_aliases.begin(),
  162. original_aliases.end() ),
  163. original_aliases.end() );
  164. // clone into a temporary working set
  165. int idx = 0;
  166. for( const auto& alias : original_aliases )
  167. {
  168. m_aliases.push_back( alias->Clone() );
  169. auto text = getAliasDisplayText( alias );
  170. m_bus_list_view->InsertItem( idx, text );
  171. m_bus_list_view->SetItemPtrData( idx, wxUIntPtr( m_aliases[idx].get() ) );
  172. idx++;
  173. }
  174. m_bus_list_view->SetColumnWidth( 0, -1 );
  175. return true;
  176. }
  177. void DIALOG_BUS_MANAGER::OnOkClick( wxCommandEvent& aEvent )
  178. {
  179. if( TransferDataFromWindow() )
  180. {
  181. ( ( SCH_EDIT_FRAME* )GetParent() )->OnModify();
  182. EndModal( wxID_OK );
  183. }
  184. }
  185. void DIALOG_BUS_MANAGER::OnCancelClick( wxCommandEvent& aEvent )
  186. {
  187. EndModal( wxID_CANCEL );
  188. }
  189. bool DIALOG_BUS_MANAGER::TransferDataFromWindow()
  190. {
  191. // Since we have a clone of all the data, and it is from potentially
  192. // multiple screens, the way this works is to rebuild each screen's aliases.
  193. // A list of screens is stored here so that the screen's alias list is only
  194. // cleared once.
  195. std::unordered_set< SCH_SCREEN* > cleared_list;
  196. for( const auto& alias : m_aliases )
  197. {
  198. auto screen = alias->GetParent();
  199. if( cleared_list.count( screen ) == 0 )
  200. {
  201. screen->ClearBusAliases();
  202. cleared_list.insert( screen );
  203. }
  204. screen->AddBusAlias( alias );
  205. }
  206. return true;
  207. }
  208. void DIALOG_BUS_MANAGER::OnSelectBus( wxListEvent& event )
  209. {
  210. if( event.GetEventType() == wxEVT_COMMAND_LIST_ITEM_SELECTED )
  211. {
  212. auto alias = m_aliases[ event.GetIndex() ];
  213. if( m_active_alias != alias )
  214. {
  215. m_active_alias = alias;
  216. m_bus_edit->ChangeValue( alias->GetName() );
  217. m_btn_add_bus->Disable();
  218. m_btn_rename_bus->Enable();
  219. m_btn_remove_bus->Enable();
  220. auto members = alias->Members();
  221. // TODO(JE) Clear() seems to be clearing the hint, contrary to
  222. // the wx documentation.
  223. m_signal_edit->Clear();
  224. m_signal_list_view->DeleteAllItems();
  225. for( unsigned i = 0; i < members.size(); i++ )
  226. {
  227. m_signal_list_view->InsertItem( i, members[i] );
  228. }
  229. m_signal_list_view->SetColumnWidth( 0, -1 );
  230. m_btn_add_signal->Enable();
  231. m_btn_rename_signal->Disable();
  232. m_btn_remove_signal->Disable();
  233. }
  234. }
  235. else
  236. {
  237. m_active_alias = NULL;
  238. m_bus_edit->Clear();
  239. m_signal_edit->Clear();
  240. m_signal_list_view->DeleteAllItems();
  241. m_btn_add_bus->Enable();
  242. m_btn_rename_bus->Disable();
  243. m_btn_remove_bus->Disable();
  244. m_btn_add_signal->Disable();
  245. m_btn_rename_signal->Disable();
  246. m_btn_remove_signal->Disable();
  247. }
  248. }
  249. void DIALOG_BUS_MANAGER::OnSelectSignal( wxListEvent& event )
  250. {
  251. if( event.GetEventType() == wxEVT_COMMAND_LIST_ITEM_SELECTED )
  252. {
  253. m_signal_edit->ChangeValue( event.GetText() );
  254. m_btn_rename_signal->Enable();
  255. m_btn_remove_signal->Enable();
  256. }
  257. else
  258. {
  259. m_signal_edit->Clear();
  260. m_btn_rename_signal->Disable();
  261. m_btn_remove_signal->Disable();
  262. }
  263. }
  264. void DIALOG_BUS_MANAGER::OnAddBus( wxCommandEvent& aEvent )
  265. {
  266. // If there is an active alias, then check that the user actually
  267. // changed the text in the edit box (we can't have duplicate aliases)
  268. auto new_name = m_bus_edit->GetValue();
  269. if( new_name.Length() == 0 )
  270. {
  271. return;
  272. }
  273. for( const auto& alias : m_aliases )
  274. {
  275. if( alias->GetName() == new_name )
  276. {
  277. // TODO(JE) display error?
  278. return;
  279. }
  280. }
  281. if( !m_active_alias ||
  282. ( m_active_alias && m_active_alias->GetName().Cmp( new_name ) ) )
  283. {
  284. // The values are different; create a new alias
  285. auto alias = std::make_shared<BUS_ALIAS>();
  286. alias->SetName( new_name );
  287. // New aliases get stored on the currently visible sheet
  288. alias->SetParent( static_cast<SCH_EDIT_FRAME*>( GetParent() )->GetScreen() );
  289. auto text = getAliasDisplayText( alias );
  290. m_aliases.push_back( alias );
  291. long idx = m_bus_list_view->InsertItem( m_aliases.size() - 1, text );
  292. m_bus_list_view->SetColumnWidth( 0, -1 );
  293. m_bus_list_view->Select( idx );
  294. m_bus_edit->Clear();
  295. }
  296. else
  297. {
  298. // TODO(JE) Check about desired result here.
  299. // Maybe warn the user? Or just do nothing
  300. }
  301. }
  302. void DIALOG_BUS_MANAGER::OnRenameBus( wxCommandEvent& aEvent )
  303. {
  304. // We should only get here if there is an active alias
  305. wxASSERT( m_active_alias );
  306. m_active_alias->SetName( m_bus_edit->GetValue() );
  307. long idx = m_bus_list_view->FindItem( -1, wxUIntPtr( m_active_alias.get() ) );
  308. wxASSERT( idx >= 0 );
  309. m_bus_list_view->SetItemText( idx, getAliasDisplayText( m_active_alias ) );
  310. }
  311. void DIALOG_BUS_MANAGER::OnRemoveBus( wxCommandEvent& aEvent )
  312. {
  313. // We should only get here if there is an active alias
  314. wxASSERT( m_active_alias );
  315. long i = m_bus_list_view->GetFirstSelected();
  316. wxASSERT( m_active_alias == m_aliases[ i ] );
  317. m_bus_list_view->DeleteItem( i );
  318. m_aliases.erase( m_aliases.begin() + i );
  319. m_bus_edit->Clear();
  320. m_active_alias = NULL;
  321. auto evt = wxListEvent( wxEVT_COMMAND_LIST_ITEM_DESELECTED );
  322. OnSelectBus( evt );
  323. }
  324. void DIALOG_BUS_MANAGER::OnAddSignal( wxCommandEvent& aEvent )
  325. {
  326. auto name_list = m_signal_edit->GetValue();
  327. if( !m_active_alias || name_list.Length() == 0 )
  328. {
  329. return;
  330. }
  331. // String collecting net names that were not added to the bus
  332. wxString notAdded;
  333. // Parse a space-separated list and add each one
  334. wxStringTokenizer tok( name_list, " " );
  335. while( tok.HasMoreTokens() )
  336. {
  337. auto name = tok.GetNextToken();
  338. if( !m_active_alias->Contains( name ) )
  339. {
  340. m_active_alias->AddMember( name );
  341. long idx = m_signal_list_view->InsertItem(
  342. m_active_alias->GetMemberCount() - 1, name );
  343. m_signal_list_view->SetColumnWidth( 0, -1 );
  344. m_signal_list_view->Select( idx );
  345. }
  346. else
  347. {
  348. // Some of the requested net names were not added to the list, so keep them for editing
  349. notAdded = notAdded.IsEmpty() ? name : notAdded + " " + name;
  350. }
  351. }
  352. m_signal_edit->SetValue( notAdded );
  353. m_signal_edit->SetInsertionPointEnd();
  354. }
  355. void DIALOG_BUS_MANAGER::OnRenameSignal( wxCommandEvent& aEvent )
  356. {
  357. // We should only get here if there is an active alias
  358. wxASSERT( m_active_alias );
  359. auto new_name = m_signal_edit->GetValue();
  360. long idx = m_signal_list_view->GetFirstSelected();
  361. wxASSERT( idx >= 0 );
  362. auto old_name = m_active_alias->Members()[ idx ];
  363. // User could have typed a space here, so check first
  364. if( new_name.Find( " " ) != wxNOT_FOUND )
  365. {
  366. // TODO(JE) error feedback
  367. m_signal_edit->ChangeValue( old_name );
  368. return;
  369. }
  370. m_active_alias->Members()[ idx ] = new_name;
  371. m_signal_list_view->SetItemText( idx, new_name );
  372. m_signal_list_view->SetColumnWidth( 0, -1 );
  373. }
  374. void DIALOG_BUS_MANAGER::OnRemoveSignal( wxCommandEvent& aEvent )
  375. {
  376. // We should only get here if there is an active alias
  377. wxASSERT( m_active_alias );
  378. long idx = m_signal_list_view->GetFirstSelected();
  379. wxASSERT( idx >= 0 );
  380. m_active_alias->Members().erase( m_active_alias->Members().begin() + idx );
  381. m_signal_list_view->DeleteItem( idx );
  382. m_signal_edit->Clear();
  383. m_btn_rename_signal->Disable();
  384. m_btn_remove_signal->Disable();
  385. }
  386. wxString DIALOG_BUS_MANAGER::getAliasDisplayText( std::shared_ptr< BUS_ALIAS > aAlias )
  387. {
  388. wxString name = aAlias->GetName();
  389. wxFileName sheet_name( aAlias->GetParent()->GetFileName() );
  390. name += _T( " (" ) + sheet_name.GetFullName() + _T( ")" );
  391. return name;
  392. }
  393. // see invoke_sch_dialog.h
  394. void InvokeDialogBusManager( SCH_EDIT_FRAME* aCaller )
  395. {
  396. DIALOG_BUS_MANAGER dlg( aCaller );
  397. dlg.ShowModal();
  398. }