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