/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2018 CERN * Copyright (C) 2021-2024 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 #include #include "grid_tricks.h" PANEL_SETUP_BUSES::PANEL_SETUP_BUSES( wxWindow* aWindow, SCH_EDIT_FRAME* aFrame ) : PANEL_SETUP_BUSES_BASE( aWindow ), m_frame( aFrame ), m_lastAlias( 0 ), m_membersGridDirty( false ), m_errorGrid( nullptr ), m_errorRow( -1 ) { m_membersLabelTemplate = m_membersLabel->GetLabel(); m_addAlias->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) ); m_deleteAlias->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) ); m_addMember->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) ); m_removeMember->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) ); m_source->SetFont( KIUI::GetInfoFont( aWindow ) ); m_aliasesGrid->SetSelectionMode( wxGrid::wxGridSelectRows ); m_membersGrid->SetSelectionMode( wxGrid::wxGridSelectRows ); m_aliasesGrid->PushEventHandler( new GRID_TRICKS( m_aliasesGrid, [this]( wxCommandEvent& aEvent ) { OnAddAlias( aEvent ); } ) ); m_membersGrid->PushEventHandler( new GRID_TRICKS( m_membersGrid, [this]( wxCommandEvent& aEvent ) { OnAddMember( aEvent ); } ) ); m_aliasesGrid->SetUseNativeColLabels(); m_membersGrid->SetUseNativeColLabels(); // wxFormBuilder doesn't include this event... m_aliasesGrid->Connect( wxEVT_GRID_CELL_CHANGING, wxGridEventHandler( PANEL_SETUP_BUSES::OnAliasesGridCellChanging ), nullptr, this ); m_membersGrid->Connect( wxEVT_GRID_CELL_CHANGING, wxGridEventHandler( PANEL_SETUP_BUSES::OnMemberGridCellChanging ), nullptr, this ); Layout(); } PANEL_SETUP_BUSES::~PANEL_SETUP_BUSES() { // Delete the GRID_TRICKS. m_aliasesGrid->PopEventHandler( true ); m_membersGrid->PopEventHandler( true ); m_aliasesGrid->Disconnect( wxEVT_GRID_CELL_CHANGING, wxGridEventHandler( PANEL_SETUP_BUSES::OnAliasesGridCellChanging ), nullptr, this ); m_membersGrid->Disconnect( wxEVT_GRID_CELL_CHANGING, wxGridEventHandler( PANEL_SETUP_BUSES::OnMemberGridCellChanging ), nullptr, this ); } bool PANEL_SETUP_BUSES::TransferDataToWindow() { auto contains = [&]( const std::shared_ptr& alias ) -> bool { wxString aName = alias->GetName(); std::vector aMembers = alias->Members(); std::sort( aMembers.begin(), aMembers.end() ); for( const std::shared_ptr& candidate : m_aliases ) { wxString bName = candidate->GetName(); std::vector bMembers = candidate->Members(); std::sort( bMembers.begin(), bMembers.end() ); if( aName == bName && aMembers == bMembers ) return true; } return false; }; SCH_SCREENS screens( m_frame->Schematic().Root() ); // collect aliases from each open sheet for( SCH_SCREEN* screen = screens.GetFirst(); screen != nullptr; screen = screens.GetNext() ) { for( const std::shared_ptr& alias : screen->GetBusAliases() ) { if( !contains( alias ) ) m_aliases.push_back( alias->Clone() ); } } int ii = 0; m_aliasesGrid->ClearRows(); m_aliasesGrid->AppendRows( m_aliases.size() ); for( const std::shared_ptr& alias : m_aliases ) m_aliasesGrid->SetCellValue( ii++, 0, alias->GetName() ); m_membersBook->SetSelection( 1 ); return true; } bool PANEL_SETUP_BUSES::TransferDataFromWindow() { if( !m_aliasesGrid->CommitPendingChanges() || !m_membersGrid->CommitPendingChanges() ) return false; // Copy names back just in case they didn't get caught on the GridCellChanging event for( int ii = 0; ii < m_aliasesGrid->GetNumberRows(); ++ii ) m_aliases[ii]->SetName( m_aliasesGrid->GetCellValue( ii, 0 ) ); SCH_SCREENS screens( m_frame->Schematic().Root() ); for( SCH_SCREEN* screen = screens.GetFirst(); screen != nullptr; screen = screens.GetNext() ) screen->ClearBusAliases(); for( const std::shared_ptr& alias : m_aliases ) alias->GetParent()->AddBusAlias( alias ); return true; } void PANEL_SETUP_BUSES::OnAddAlias( wxCommandEvent& aEvent ) { if( !m_aliasesGrid->CommitPendingChanges() || !m_membersGrid->CommitPendingChanges() ) return; // New aliases get stored on the currently visible sheet m_aliases.push_back( std::make_shared( m_frame->GetScreen() ) ); int row = m_aliasesGrid->GetNumberRows(); m_aliasesGrid->AppendRows(); m_aliasesGrid->MakeCellVisible( row, 0 ); m_aliasesGrid->SetGridCursor( row, 0 ); m_aliasesGrid->EnableCellEditControl( true ); m_aliasesGrid->ShowCellEditControl(); } void PANEL_SETUP_BUSES::OnDeleteAlias( wxCommandEvent& aEvent ) { if( !m_aliasesGrid->CommitPendingChanges() || !m_membersGrid->CommitPendingChanges() ) return; int curRow = m_aliasesGrid->GetGridCursorRow(); if( curRow < 0 ) return; // Clear the members grid first so we don't try to write it back to a deleted alias m_membersGrid->ClearRows(); m_lastAlias = -1; m_lastAliasName = wxEmptyString; m_aliases.erase( m_aliases.begin() + curRow ); m_aliasesGrid->DeleteRows( curRow, 1 ); if( m_aliasesGrid->GetNumberRows() > 0 ) { m_aliasesGrid->MakeCellVisible( std::max( 0, curRow-1 ), 0 ); m_aliasesGrid->SelectRow( std::max( 0, curRow-1 ) ); } } void PANEL_SETUP_BUSES::OnAddMember( wxCommandEvent& aEvent ) { if( !m_membersGrid->CommitPendingChanges() ) return; int row = m_membersGrid->GetNumberRows(); m_membersGrid->AppendRows(); m_membersGrid->MakeCellVisible( row, 0 ); m_membersGrid->SetGridCursor( row, 0 ); m_membersGrid->EnableCellEditControl( true ); m_membersGrid->ShowCellEditControl(); } void PANEL_SETUP_BUSES::OnRemoveMember( wxCommandEvent& aEvent ) { if( !m_membersGrid->CommitPendingChanges() ) return; int curRow = m_membersGrid->GetGridCursorRow(); if( curRow < 0 ) return; m_membersGrid->DeleteRows( curRow, 1 ); // Update the member list of the current bus alias from the members grid const std::shared_ptr& alias = m_aliases[ m_lastAlias ]; alias->Members().clear(); for( int ii = 0; ii < m_membersGrid->GetNumberRows(); ++ii ) alias->Members().push_back( m_membersGrid->GetCellValue( ii, 0 ) ); if( m_membersGrid->GetNumberRows() > 0 ) { m_membersGrid->MakeCellVisible( std::max( 0, curRow-1 ), 0 ); m_membersGrid->SelectRow( std::max( 0, curRow-1 ) ); } } void PANEL_SETUP_BUSES::OnAliasesGridCellChanging( wxGridEvent& event ) { int row = event.GetRow(); if( row >= 0 ) { wxString name = event.GetString(); for( int ii = 0; ii < m_aliasesGrid->GetNumberRows(); ++ii ) { if( ii == event.GetRow() ) continue; if( name == m_aliasesGrid->GetCellValue( ii, 0 ) && m_aliases[ row ]->GetParent() == m_aliases[ ii ]->GetParent() ) { m_errorMsg = wxString::Format( _( "Alias name '%s' already in use." ), name ); m_errorGrid = m_aliasesGrid; m_errorRow = row; event.Veto(); return; } } m_aliases[ row ]->SetName( name ); } } void PANEL_SETUP_BUSES::OnMemberGridCellChanging( wxGridEvent& event ) { int row = event.GetRow(); if( row >= 0 ) { wxString name = event.GetString(); if( name.IsEmpty() ) { m_errorMsg = _( "Member net/alias name cannot be empty." ); m_errorGrid = m_membersGrid; m_errorRow = event.GetRow(); event.Veto(); return; } const std::shared_ptr& alias = m_aliases[ m_lastAlias ]; alias->Members().clear(); for( int ii = 0; ii < m_membersGrid->GetNumberRows(); ++ii ) { if( ii == row ) { // Parse a space-separated list and add each one wxStringTokenizer tok( name, " " ); if( tok.CountTokens() > 1 ) { m_membersGridDirty = true; Bind( wxEVT_IDLE, &PANEL_SETUP_BUSES::reloadMembersGridOnIdle, this ); } while( tok.HasMoreTokens() ) alias->Members().push_back( tok.GetNextToken() ); } else { alias->Members().push_back( m_membersGrid->GetCellValue( ii, 0 ) ); } } } } void PANEL_SETUP_BUSES::doReloadMembersGrid() { if( m_lastAlias >= 0 && m_lastAlias < m_aliasesGrid->GetNumberRows() ) { const std::shared_ptr& alias = m_aliases[ m_lastAlias ]; wxString source; wxString membersLabel; if( alias->GetParent() ) { wxFileName sheet_name( alias->GetParent()->GetFileName() ); source.Printf( wxS( "(" ) + sheet_name.GetFullName() + wxS( ")" ) ); } membersLabel.Printf( m_membersLabelTemplate, m_lastAliasName ); m_source->SetLabel( source ); m_membersLabel->SetLabel( membersLabel ); m_membersGrid->ClearRows(); m_membersGrid->AppendRows( alias->Members().size() ); int ii = 0; for( const wxString& member : alias->Members() ) m_membersGrid->SetCellValue( ii++, 0, member ); } m_membersGridDirty = false; } void PANEL_SETUP_BUSES::reloadMembersGridOnIdle( wxIdleEvent& aEvent ) { if( m_membersGridDirty ) doReloadMembersGrid(); Unbind( wxEVT_IDLE, &PANEL_SETUP_BUSES::reloadMembersGridOnIdle, this ); } void PANEL_SETUP_BUSES::OnSizeGrid( wxSizeEvent& event ) { auto setColSize = []( WX_GRID* grid ) { int colSize = std::max( grid->GetClientSize().x, grid->GetVisibleWidth( 0 ) ); if( grid->GetColSize( 0 ) != colSize ) grid->SetColSize( 0, colSize ); }; setColSize( m_aliasesGrid ); setColSize( m_membersGrid ); // Always propagate for a grid repaint (needed if the height changes, as well as width) event.Skip(); } void PANEL_SETUP_BUSES::OnUpdateUI( wxUpdateUIEvent& event ) { // Handle a grid error. This is delayed to OnUpdateUI so that we can change focus // even when the original validation was triggered from a killFocus event. if( !m_errorMsg.IsEmpty() ) { // We will re-enter this routine when the error dialog is displayed, so make // sure we don't keep putting up more dialogs. wxString errorMsg = m_errorMsg; m_errorMsg = wxEmptyString; wxWindow* topLevelParent = wxGetTopLevelParent( this ); DisplayErrorMessage( topLevelParent, errorMsg ); m_errorGrid->SetFocus(); m_errorGrid->MakeCellVisible( m_errorRow, 0 ); m_errorGrid->SetGridCursor( m_errorRow, 0 ); m_errorGrid->EnableCellEditControl( true ); m_errorGrid->ShowCellEditControl(); return; } if( !m_membersGrid->IsCellEditControlShown() ) { int row = -1; wxString aliasName; if( m_aliasesGrid->IsCellEditControlShown() ) { row = m_aliasesGrid->GetGridCursorRow(); wxGridCellEditor* cellEditor = m_aliasesGrid->GetCellEditor( row, 0 ); if( wxTextEntry* txt = dynamic_cast( cellEditor->GetControl() ) ) aliasName = txt->GetValue(); cellEditor->DecRef(); } else if( m_aliasesGrid->GetGridCursorRow() >= 0 ) { row = m_aliasesGrid->GetGridCursorRow(); aliasName = m_aliasesGrid->GetCellValue( row, 0 ); } else if( m_lastAlias >= 0 && m_lastAlias < m_aliasesGrid->GetNumberRows() ) { row = m_lastAlias; aliasName = m_lastAliasName; } if( row < 0 ) { m_membersBook->SetSelection( 1 ); } else if( row != m_lastAlias || aliasName != m_lastAliasName ) { m_lastAlias = row; m_lastAliasName = aliasName; m_membersBook->SetSelection( 0 ); m_membersBook->GetPage( 0 )->Layout(); const std::shared_ptr& alias = m_aliases[ row ]; alias->SetName( aliasName ); m_membersGridDirty = true; Bind( wxEVT_IDLE, &PANEL_SETUP_BUSES::reloadMembersGridOnIdle, this ); } } }