/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2018 CERN * Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors. * @author Jon Evans * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include #include #include #include #include "dialog_bus_manager.h" #include #include #include BEGIN_EVENT_TABLE( DIALOG_BUS_MANAGER, DIALOG_SHIM ) EVT_BUTTON( wxID_OK, DIALOG_BUS_MANAGER::OnOkClick ) EVT_BUTTON( wxID_CANCEL, DIALOG_BUS_MANAGER::OnCancelClick ) END_EVENT_TABLE() DIALOG_BUS_MANAGER::DIALOG_BUS_MANAGER( SCH_EDIT_FRAME* aParent ) : DIALOG_SHIM( aParent, wxID_ANY, _( "Bus Definitions" ), wxDefaultPosition, wxSize( 640, 480 ), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ), m_parent( aParent ) { auto sizer = new wxBoxSizer( wxVERTICAL ); auto buttons = new wxStdDialogButtonSizer(); buttons->AddButton( new wxButton( this, wxID_OK ) ); buttons->AddButton( new wxButton( this, wxID_CANCEL ) ); buttons->Realize(); auto top_container = new wxBoxSizer( wxHORIZONTAL ); auto left_pane = new wxBoxSizer( wxVERTICAL ); auto right_pane = new wxBoxSizer( wxVERTICAL ); // Left pane: alias list auto lbl_aliases = new wxStaticText( this, wxID_ANY, _( "Bus Aliases" ), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT ); m_bus_list_view = new wxListView( this, wxID_ANY, wxDefaultPosition, wxSize( 300, 300 ), wxLC_ALIGN_LEFT | wxLC_NO_HEADER | wxLC_REPORT | wxLC_SINGLE_SEL ); m_bus_list_view->InsertColumn( 0, "" ); auto lbl_alias_edit = new wxStaticText( this, wxID_ANY, _( "Alias Name" ), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT ); m_bus_edit = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER ); auto left_button_sizer = new wxBoxSizer( wxHORIZONTAL ); m_btn_add_bus = new wxButton( this, wxID_ANY, _( "Add" ) ); m_btn_rename_bus = new wxButton( this, wxID_ANY, _( "Rename" ) ); m_btn_remove_bus = new wxButton( this, wxID_ANY, _( "Remove" ) ); left_button_sizer->Add( m_btn_add_bus ); left_button_sizer->Add( m_btn_rename_bus ); left_button_sizer->Add( m_btn_remove_bus ); left_pane->Add( lbl_aliases, 0, wxEXPAND | wxALL, 5 ); left_pane->Add( m_bus_list_view, 1, wxEXPAND | wxALL, 5 ); left_pane->Add( lbl_alias_edit, 0, wxEXPAND | wxALL, 5 ); left_pane->Add( m_bus_edit, 0, wxEXPAND | wxALL, 5 ); left_pane->Add( left_button_sizer, 0, wxEXPAND | wxALL, 5 ); // Right pane: signal list auto lbl_signals = new wxStaticText( this, wxID_ANY, _( "Alias Members" ), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT ); m_signal_list_view = new wxListView( this, wxID_ANY, wxDefaultPosition, wxSize( 300, 300 ), wxLC_ALIGN_LEFT | wxLC_NO_HEADER | wxLC_REPORT | wxLC_SINGLE_SEL ); m_signal_list_view->InsertColumn( 0, "" ); auto lbl_signal_edit = new wxStaticText( this, wxID_ANY, _( "Member Name" ), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT ); m_signal_edit = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER ); auto right_button_sizer = new wxBoxSizer( wxHORIZONTAL ); m_btn_add_signal = new wxButton( this, wxID_ANY, _( "Add" ) ); m_btn_rename_signal = new wxButton( this, wxID_ANY, _( "Rename" ) ); m_btn_remove_signal = new wxButton( this, wxID_ANY, _( "Remove" ) ); right_button_sizer->Add( m_btn_add_signal ); right_button_sizer->Add( m_btn_rename_signal ); right_button_sizer->Add( m_btn_remove_signal ); right_pane->Add( lbl_signals, 0, wxEXPAND | wxALL, 5 ); right_pane->Add( m_signal_list_view, 1, wxEXPAND | wxALL, 5 ); right_pane->Add( lbl_signal_edit, 0, wxEXPAND | wxALL, 5 ); right_pane->Add( m_signal_edit, 0, wxEXPAND | wxALL, 5 ); right_pane->Add( right_button_sizer, 0, wxEXPAND | wxALL, 5 ); top_container->Add( left_pane, 1, wxEXPAND ); top_container->Add( right_pane, 1, wxEXPAND ); sizer->Add( top_container, 1, wxEXPAND | wxALL, 5 ); sizer->Add( buttons, 0, wxEXPAND | wxBOTTOM, 10 ); SetSizer( sizer ); // Setup validators wxTextValidator validator; validator.SetStyle( wxFILTER_EXCLUDE_CHAR_LIST ); validator.SetCharExcludes( "\r\n\t " ); m_bus_edit->SetValidator( validator ); // Allow spaces in the signal edit, so that you can type in a list of // signals in the box and it can automatically split them when you add. validator.SetCharExcludes( "\r\n\t" ); m_signal_edit->SetValidator( validator ); // Setup events Bind( wxEVT_INIT_DIALOG, &DIALOG_BUS_MANAGER::OnInitDialog, this ); m_bus_list_view->Connect( wxEVT_COMMAND_LIST_ITEM_DESELECTED, wxListEventHandler( DIALOG_BUS_MANAGER::OnSelectBus ), nullptr, this ); m_bus_list_view->Connect( wxEVT_COMMAND_LIST_ITEM_SELECTED, wxListEventHandler( DIALOG_BUS_MANAGER::OnSelectBus ), nullptr, this ); m_signal_list_view->Connect( wxEVT_COMMAND_LIST_ITEM_DESELECTED, wxListEventHandler( DIALOG_BUS_MANAGER::OnSelectSignal ), nullptr, this ); m_signal_list_view->Connect( wxEVT_COMMAND_LIST_ITEM_SELECTED, wxListEventHandler( DIALOG_BUS_MANAGER::OnSelectSignal ), nullptr, this ); m_btn_add_bus->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_BUS_MANAGER::OnAddBus ), nullptr, this ); m_btn_rename_bus->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_BUS_MANAGER::OnRenameBus ), nullptr, this ); m_btn_remove_bus->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_BUS_MANAGER::OnRemoveBus ), nullptr, this ); m_signal_edit->Connect( wxEVT_TEXT_ENTER, wxCommandEventHandler( DIALOG_BUS_MANAGER::OnAddSignal ), nullptr, this ); m_btn_add_signal->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_BUS_MANAGER::OnAddSignal ), nullptr, this ); m_btn_rename_signal->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_BUS_MANAGER::OnRenameSignal ), nullptr, this ); m_btn_remove_signal->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_BUS_MANAGER::OnRemoveSignal ), nullptr, this ); m_bus_edit->Connect( wxEVT_TEXT_ENTER, wxCommandEventHandler( DIALOG_BUS_MANAGER::OnAddBus ), nullptr, this ); // Set initial UI state m_btn_rename_bus->Disable(); m_btn_remove_bus->Disable(); m_btn_add_signal->Disable(); m_btn_rename_signal->Disable(); m_btn_remove_signal->Disable(); m_bus_edit->SetHint( _( "Bus Alias Name" ) ); m_signal_edit->SetHint( _( "Net or Bus Name" ) ); finishDialogSettings(); } void DIALOG_BUS_MANAGER::OnInitDialog( wxInitDialogEvent& aEvent ) { TransferDataToWindow(); } bool DIALOG_BUS_MANAGER::TransferDataToWindow() { m_aliases.clear(); m_screens.clear(); SCH_SCREENS screens( m_parent->Schematic().Root() ); std::vector< std::shared_ptr > original_aliases; // collect aliases from each open sheet for( SCH_SCREEN* screen = screens.GetFirst(); screen != nullptr; screen = screens.GetNext() ) { std::unordered_set> sheet_aliases = screen->GetBusAliases(); original_aliases.insert( original_aliases.end(), sheet_aliases.begin(), sheet_aliases.end() ); } original_aliases.erase( std::unique( original_aliases.begin(), original_aliases.end() ), original_aliases.end() ); // clone into a temporary working set int idx = 0; for( const std::shared_ptr& alias : original_aliases ) { m_screens.insert( alias->GetParent() ); m_aliases.push_back( alias->Clone() ); auto text = getAliasDisplayText( alias ); m_bus_list_view->InsertItem( idx, text ); m_bus_list_view->SetItemPtrData( idx, wxUIntPtr( m_aliases[idx].get() ) ); idx++; } m_bus_list_view->SetColumnWidth( 0, -1 ); return true; } void DIALOG_BUS_MANAGER::OnOkClick( wxCommandEvent& aEvent ) { if( TransferDataFromWindow() ) { ( ( SCH_EDIT_FRAME* )GetParent() )->OnModify(); EndModal( wxID_OK ); } } void DIALOG_BUS_MANAGER::OnCancelClick( wxCommandEvent& aEvent ) { EndModal( wxID_CANCEL ); } bool DIALOG_BUS_MANAGER::TransferDataFromWindow() { for( SCH_SCREEN* screen : m_screens ) screen->ClearBusAliases(); for( const std::shared_ptr& alias : m_aliases ) alias->GetParent()->AddBusAlias( alias ); return true; } void DIALOG_BUS_MANAGER::OnSelectBus( wxListEvent& event ) { if( event.GetEventType() == wxEVT_COMMAND_LIST_ITEM_SELECTED ) { auto alias = m_aliases[ event.GetIndex() ]; if( m_active_alias != alias ) { m_active_alias = alias; m_bus_edit->ChangeValue( alias->GetName() ); m_btn_rename_bus->Enable(); m_btn_remove_bus->Enable(); auto members = alias->Members(); // TODO(JE) Clear() seems to be clearing the hint, contrary to // the wx documentation. m_signal_edit->Clear(); m_signal_list_view->DeleteAllItems(); for( unsigned i = 0; i < members.size(); i++ ) { m_signal_list_view->InsertItem( i, members[i] ); } m_signal_list_view->SetColumnWidth( 0, -1 ); m_btn_add_signal->Enable(); m_btn_rename_signal->Disable(); m_btn_remove_signal->Disable(); } } else { m_active_alias = nullptr; m_bus_edit->Clear(); m_signal_edit->Clear(); m_signal_list_view->DeleteAllItems(); m_btn_rename_bus->Disable(); m_btn_remove_bus->Disable(); m_btn_add_signal->Disable(); m_btn_rename_signal->Disable(); m_btn_remove_signal->Disable(); } } void DIALOG_BUS_MANAGER::OnSelectSignal( wxListEvent& event ) { if( event.GetEventType() == wxEVT_COMMAND_LIST_ITEM_SELECTED ) { m_signal_edit->ChangeValue( event.GetText() ); m_btn_rename_signal->Enable(); m_btn_remove_signal->Enable(); } else { m_signal_edit->Clear(); m_btn_rename_signal->Disable(); m_btn_remove_signal->Disable(); } } void DIALOG_BUS_MANAGER::OnAddBus( wxCommandEvent& aEvent ) { // If there is an active alias, then check that the user actually // changed the text in the edit box (we can't have duplicate aliases) auto new_name = m_bus_edit->GetValue(); if( new_name.Length() == 0 ) { return; } for( const auto& alias : m_aliases ) { if( alias->GetName() == new_name ) { // TODO(JE) display error? return; } } if( !m_active_alias || ( m_active_alias && m_active_alias->GetName().Cmp( new_name ) ) ) { // The values are different; create a new alias auto alias = std::make_shared(); alias->SetName( new_name ); // New aliases get stored on the currently visible sheet alias->SetParent( static_cast( GetParent() )->GetScreen() ); auto text = getAliasDisplayText( alias ); m_aliases.push_back( alias ); long idx = m_bus_list_view->InsertItem( m_aliases.size() - 1, text ); m_bus_list_view->SetColumnWidth( 0, -1 ); m_bus_list_view->Select( idx ); m_bus_edit->Clear(); } else { // TODO(JE) Check about desired result here. // Maybe warn the user? Or just do nothing } } void DIALOG_BUS_MANAGER::OnRenameBus( wxCommandEvent& aEvent ) { // We should only get here if there is an active alias wxASSERT( m_active_alias ); m_active_alias->SetName( m_bus_edit->GetValue() ); long idx = m_bus_list_view->FindItem( -1, wxUIntPtr( m_active_alias.get() ) ); wxASSERT( idx >= 0 ); m_bus_list_view->SetItemText( idx, getAliasDisplayText( m_active_alias ) ); } void DIALOG_BUS_MANAGER::OnRemoveBus( wxCommandEvent& aEvent ) { // We should only get here if there is an active alias wxASSERT( m_active_alias ); long i = m_bus_list_view->GetFirstSelected(); wxASSERT( m_active_alias == m_aliases[ i ] ); m_bus_list_view->DeleteItem( i ); m_bus_list_view->Update(); m_aliases.erase( m_aliases.begin() + i ); m_bus_edit->Clear(); m_active_alias = nullptr; auto evt = wxListEvent( wxEVT_COMMAND_LIST_ITEM_DESELECTED ); OnSelectBus( evt ); } void DIALOG_BUS_MANAGER::OnAddSignal( wxCommandEvent& aEvent ) { auto name_list = m_signal_edit->GetValue(); if( !m_active_alias || name_list.Length() == 0 ) { return; } // String collecting net names that were not added to the bus wxString notAdded; // Parse a space-separated list and add each one wxStringTokenizer tok( name_list, " " ); while( tok.HasMoreTokens() ) { auto name = tok.GetNextToken(); if( !m_active_alias->Contains( name ) ) { m_active_alias->AddMember( name ); long idx = m_signal_list_view->InsertItem( m_active_alias->GetMemberCount() - 1, name ); m_signal_list_view->SetColumnWidth( 0, -1 ); m_signal_list_view->Select( idx ); } else { // Some of the requested net names were not added to the list, so keep them for editing notAdded = notAdded.IsEmpty() ? name : notAdded + " " + name; } } m_signal_edit->SetValue( notAdded ); m_signal_edit->SetInsertionPointEnd(); } void DIALOG_BUS_MANAGER::OnRenameSignal( wxCommandEvent& aEvent ) { // We should only get here if there is an active alias wxASSERT( m_active_alias ); auto new_name = m_signal_edit->GetValue(); long idx = m_signal_list_view->GetFirstSelected(); wxASSERT( idx >= 0 ); auto old_name = m_active_alias->Members()[ idx ]; // User could have typed a space here, so check first if( new_name.Find( " " ) != wxNOT_FOUND ) { // TODO(JE) error feedback m_signal_edit->ChangeValue( old_name ); return; } m_active_alias->Members()[ idx ] = new_name; m_signal_list_view->SetItemText( idx, new_name ); m_signal_list_view->SetColumnWidth( 0, -1 ); } void DIALOG_BUS_MANAGER::OnRemoveSignal( wxCommandEvent& aEvent ) { // We should only get here if there is an active alias wxASSERT( m_active_alias ); long idx = m_signal_list_view->GetFirstSelected(); wxASSERT( idx >= 0 ); m_active_alias->Members().erase( m_active_alias->Members().begin() + idx ); m_signal_list_view->DeleteItem( idx ); m_signal_edit->Clear(); m_btn_rename_signal->Disable(); m_btn_remove_signal->Disable(); } wxString DIALOG_BUS_MANAGER::getAliasDisplayText( std::shared_ptr< BUS_ALIAS > aAlias ) { wxString name = aAlias->GetName(); wxFileName sheet_name( aAlias->GetParent()->GetFileName() ); name += _T( " (" ) + sheet_name.GetFullName() + _T( ")" ); return name; } // see invoke_sch_dialog.h void InvokeDialogBusManager( SCH_EDIT_FRAME* aCaller ) { DIALOG_BUS_MANAGER dlg( aCaller ); dlg.ShowModal(); }