kicad-source/kicad/dialogs/dialog_edit_cfg.cpp
Seth Hillbrand 17d9ff4fe7 Add the ability to edit advanced config
If you set the env var KICAD_EDIT_ADVANCED_CFG=1, you get a new menu
option in the KiCad project window's Edit menu.  Lets you modify the
settings for advanced config graphically without having to
remember/lookup the magic incantation
2025-08-14 15:37:02 -07:00

344 lines
10 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
*
* 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 "dialog_edit_cfg.h"
#include <advanced_config.h>
#include <config_params.h>
#include <paths.h>
#include <wx/button.h>
#include <wx/display.h>
#include <wx/fileconf.h>
#include <wx/log.h>
#include <wx/menu.h>
#include <wx/sizer.h>
#include <wx/settings.h>
#include <functional>
#include <memory>
#include <set>
static wxString paramValueString( const PARAM_CFG& aParam )
{
wxString s;
try
{
switch( aParam.m_Type )
{
case paramcfg_id::PARAM_INT: s << *static_cast<const PARAM_CFG_INT&>( aParam ).m_Pt_param; break;
case paramcfg_id::PARAM_DOUBLE:
s = wxString::FromCDouble( *static_cast<const PARAM_CFG_DOUBLE&>( aParam ).m_Pt_param );
break;
case paramcfg_id::PARAM_WXSTRING: s = *static_cast<const PARAM_CFG_WXSTRING&>( aParam ).m_Pt_param; break;
case paramcfg_id::PARAM_BOOL:
s << ( *static_cast<const PARAM_CFG_BOOL&>( aParam ).m_Pt_param ? wxS( "true" ) : wxS( "false" ) );
break;
default:
wxLogError( wxS( "Unsupported PARAM_CFG variant: " ) + wxString::Format( wxS( "%d" ), aParam.m_Type ) );
}
}
catch( ... )
{
wxLogError( wxS( "Error converting parameter value to string." ) );
}
return s;
}
static wxString paramDefaultString( const PARAM_CFG& aParam )
{
wxString s;
try
{
switch( aParam.m_Type )
{
case paramcfg_id::PARAM_INT: s << static_cast<const PARAM_CFG_INT&>( aParam ).m_Default; break;
case paramcfg_id::PARAM_DOUBLE:
s = wxString::FromCDouble( static_cast<const PARAM_CFG_DOUBLE&>( aParam ).m_Default );
break;
case paramcfg_id::PARAM_WXSTRING: s << static_cast<const PARAM_CFG_WXSTRING&>( aParam ).m_default; break;
case paramcfg_id::PARAM_BOOL:
s << ( static_cast<const PARAM_CFG_BOOL&>( aParam ).m_Default ? wxS( "true" ) : wxS( "false" ) );
break;
default: break;
}
}
catch( ... )
{
wxLogError( wxS( "Error converting parameter default value to string." ) );
}
return s;
}
static void writeParam( PARAM_CFG& aParam, const wxString& aValue )
{
switch( aParam.m_Type )
{
case paramcfg_id::PARAM_INT:
{
PARAM_CFG_INT& param = static_cast<PARAM_CFG_INT&>( aParam );
*param.m_Pt_param = wxAtoi( aValue );
break;
}
case paramcfg_id::PARAM_BOOL:
{
PARAM_CFG_BOOL& param = static_cast<PARAM_CFG_BOOL&>( aParam );
if( aValue.CmpNoCase( wxS( "true" ) ) == 0 || aValue.CmpNoCase( wxS( "yes" ) ) == 0
|| aValue.CmpNoCase( wxS( "1" ) ) == 0 )
{
*param.m_Pt_param = true;
}
else
{
*param.m_Pt_param = false;
}
break;
}
case paramcfg_id::PARAM_DOUBLE:
{
PARAM_CFG_DOUBLE& param = static_cast<PARAM_CFG_DOUBLE&>( aParam );
aValue.ToCDouble( param.m_Pt_param );
break;
}
case paramcfg_id::PARAM_WXSTRING:
{
PARAM_CFG_WXSTRING& param = static_cast<PARAM_CFG_WXSTRING&>( aParam );
*param.m_Pt_param = aValue;
break;
}
default:
wxASSERT_MSG( false,
wxS( "Unsupported PARAM_CFG variant: " ) + wxString::Format( wxS( "%d" ), aParam.m_Type ) );
}
}
DIALOG_EDIT_CFG::DIALOG_EDIT_CFG( wxWindow* aParent ) :
wxDialog( aParent, wxID_ANY, _( "Edit Advanced Configuration" ), wxDefaultPosition, wxDefaultSize,
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER )
{
m_cfgFile = wxFileName( PATHS::GetUserSettingsPath(), wxS( "kicad_advanced" ) );
m_grid = new wxGrid( this, wxID_ANY );
m_grid->CreateGrid( 0, 3 );
m_grid->SetColLabelValue( 0, _( "Key" ) );
m_grid->SetColLabelValue( 1, _( "Value" ) );
m_grid->SetColLabelValue( 2, _( "Extant" ) );
m_grid->HideCol( 2 );
m_grid->SetRowLabelSize( 0 );
loadSettings();
wxStdDialogButtonSizer* buttonSizer = new wxStdDialogButtonSizer();
wxButton* okButton = new wxButton( this, wxID_OK, _( "OK" ) );
okButton->SetDefault();
buttonSizer->AddButton( okButton );
buttonSizer->Realize();
wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL );
sizer->Add( m_grid, 1, wxEXPAND | wxALL, 5 );
sizer->Add( buttonSizer, 0, wxEXPAND | wxALL, 5 );
SetSizer( sizer );
wxDisplay display( wxDisplay::GetFromWindow( this ) );
wxRect displayRect = display.GetClientArea();
SetMinSize( wxSize( 500, 300 ) );
SetMaxSize( wxSize( displayRect.GetWidth() - 100, displayRect.GetHeight() - 100 ) );
SetSizeHints( wxSize( 700, 500 ) );
m_grid->Bind( wxEVT_GRID_CELL_CHANGED, &DIALOG_EDIT_CFG::OnCellChange, this );
m_grid->Bind( wxEVT_GRID_CELL_RIGHT_CLICK, &DIALOG_EDIT_CFG::OnCellRightClick, this );
Bind( wxEVT_SIZE, &DIALOG_EDIT_CFG::OnSize, this );
m_contextRow = -1;
Layout();
Centre();
// CallAfter ensures layout is complete before column adjustment
CallAfter(
[this]()
{
adjustColumnWidths();
} );
}
void DIALOG_EDIT_CFG::adjustColumnWidths()
{
if( !m_grid || m_grid->GetNumberRows() == 0 )
return;
m_grid->Layout();
wxSize clientSize = m_grid->GetClientSize();
int availableWidth = clientSize.GetWidth();
// Reserve space for vertical scrollbar when we have many rows
availableWidth -= wxSystemSettings::GetMetric( wxSYS_VSCROLL_X );
if( availableWidth < 200 )
availableWidth = 200;
int keyWidth = ( availableWidth * 6 ) / 10;
int valueWidth = availableWidth - keyWidth;
keyWidth = std::max( keyWidth, 100 );
valueWidth = std::max( valueWidth, 80 );
m_grid->SetColSize( 0, keyWidth );
m_grid->SetColSize( 1, valueWidth );
// Prevent wxWidgets auto-sizing from interfering
m_grid->SetColMinimalAcceptableWidth( 50 );
}
void DIALOG_EDIT_CFG::OnSize( wxSizeEvent& aEvent )
{
aEvent.Skip();
if( m_grid && m_grid->GetNumberRows() > 0 )
{
// Defer column adjustment until resize is complete
CallAfter(
[this]()
{
adjustColumnWidths();
} );
}
}
void DIALOG_EDIT_CFG::loadSettings()
{
const ADVANCED_CFG& adv = ADVANCED_CFG::GetCfg();
const std::vector<std::unique_ptr<PARAM_CFG>>& entries = adv.GetEntries();
for( const auto& entry : entries )
{
wxString value = paramValueString( *entry );
wxString def = paramDefaultString( *entry );
int row = m_grid->GetNumberRows();
m_grid->AppendRows( 1 );
m_grid->SetCellValue( row, 0, entry->m_Ident );
m_grid->SetCellValue( row, 1, value );
m_grid->SetCellValue( row, 2, def == value ? wxS( "0" ) : wxS( "1" ) );
m_grid->SetReadOnly( row, 2 );
updateRowAppearance( row );
}
}
void DIALOG_EDIT_CFG::saveSettings()
{
int rows = m_grid->GetNumberRows();
for( int row = 0; row < rows; ++row )
{
wxString key = m_grid->GetCellValue( row, 0 );
wxString val = m_grid->GetCellValue( row, 1 );
wxString ext = m_grid->GetCellValue( row, 2 );
if( key.IsEmpty() || ext != wxS( "1" ) )
continue;
const std::vector<std::unique_ptr<PARAM_CFG>>& entries = ADVANCED_CFG::GetCfg().GetEntries();
for( auto& entry : entries )
{
if( entry->m_Ident == key )
{
writeParam( *entry, val );
break;
}
}
}
ADVANCED_CFG& adv = const_cast<ADVANCED_CFG&>( ADVANCED_CFG::GetCfg() );
adv.Save();
}
void DIALOG_EDIT_CFG::OnCellChange( wxGridEvent& aEvent )
{
int row = aEvent.GetRow();
int col = aEvent.GetCol();
if( col == 0 || col == 1 )
{
m_grid->SetCellValue( row, 2, wxS( "1" ) );
updateRowAppearance( row );
}
saveSettings();
int lastRow = m_grid->GetNumberRows() - 1;
if( !m_grid->GetCellValue( lastRow, 0 ).IsEmpty() || !m_grid->GetCellValue( lastRow, 1 ).IsEmpty() )
{
m_grid->AppendRows( 1 );
m_grid->SetCellValue( m_grid->GetNumberRows() - 1, 2, wxS( "0" ) );
m_grid->SetReadOnly( m_grid->GetNumberRows() - 1, 2 );
updateRowAppearance( m_grid->GetNumberRows() - 1 );
}
aEvent.Skip();
}
void DIALOG_EDIT_CFG::OnCellRightClick( wxGridEvent& aEvent )
{
m_contextRow = aEvent.GetRow();
wxMenu menu;
menu.Append( wxID_ANY, _( "Reset to default" ) );
menu.Bind( wxEVT_MENU, &DIALOG_EDIT_CFG::OnResetDefault, this );
PopupMenu( &menu );
menu.Unbind( wxEVT_MENU, &DIALOG_EDIT_CFG::OnResetDefault, this );
}
void DIALOG_EDIT_CFG::OnResetDefault( wxCommandEvent& aEvent )
{
if( m_contextRow < 0 )
return;
wxString key = m_grid->GetCellValue( m_contextRow, 0 );
wxString def;
const ADVANCED_CFG& adv = ADVANCED_CFG::GetCfg();
const std::vector<std::unique_ptr<PARAM_CFG>>& entries = adv.GetEntries();
for( const auto& entry : entries )
{
if( entry->m_Ident == key )
{
def = paramDefaultString( *entry );
break;
}
}
m_grid->SetCellValue( m_contextRow, 1, def );
m_grid->SetCellValue( m_contextRow, 2, wxS( "0" ) );
updateRowAppearance( m_contextRow );
saveSettings();
}
void DIALOG_EDIT_CFG::updateRowAppearance( int aRow )
{
bool ext = m_grid->GetCellValue( aRow, 2 ) == wxS( "1" );
wxFont font = m_grid->GetCellFont( aRow, 0 );
font.SetWeight( ext ? wxFONTWEIGHT_BOLD : wxFONTWEIGHT_NORMAL );
m_grid->SetCellFont( aRow, 0, font );
m_grid->SetCellFont( aRow, 1, font );
}