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