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.

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