kicad/eeschema/dialogs/panel_setup_buses.cpp

462 lines
14 KiB
C++

/*
* 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 <jon@craftyjon.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <widgets/wx_grid.h>
#include <widgets/std_bitmap_button.h>
#include <confirm.h>
#include <sch_edit_frame.h>
#include <schematic.h>
#include <dialogs/panel_setup_buses.h>
#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<BUS_ALIAS>& alias ) -> bool
{
wxString aName = alias->GetName();
std::vector<wxString> aMembers = alias->Members();
std::sort( aMembers.begin(), aMembers.end() );
for( const std::shared_ptr<BUS_ALIAS>& candidate : m_aliases )
{
wxString bName = candidate->GetName();
std::vector<wxString> 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<BUS_ALIAS>& 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<BUS_ALIAS>& 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<BUS_ALIAS>& 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<BUS_ALIAS>( 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<BUS_ALIAS>& 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<BUS_ALIAS>& 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<BUS_ALIAS>& 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<wxTextEntry*>( 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<BUS_ALIAS>& alias = m_aliases[ row ];
alias->SetName( aliasName );
m_membersGridDirty = true;
Bind( wxEVT_IDLE, &PANEL_SETUP_BUSES::reloadMembersGridOnIdle, this );
}
}
}