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