Files
Seth Hillbrand 700427adbf Improve Advanced Configuration editor with sorting and filtering
Configuration entries are now sorted alphabetically and a search
control filters the list by key name as you type. Double-clicking
a boolean value toggles it instead of opening a text editor.
2026-02-01 07:51:06 -08:00

430 lines
12 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/log.h>
#include <wx/menu.h>
#include <wx/sizer.h>
#include <wx/settings.h>
#include <wx/srchctrl.h>
#include <widgets/wx_grid.h>
#include <algorithm>
#include <functional>
#include <memory>
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_filterCtrl = new wxSearchCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize );
m_filterCtrl->ShowCancelButton( true );
m_filterCtrl->SetDescriptiveText( _( "Filter" ) );
m_grid = new WX_GRID( this, wxID_ANY );
m_grid->CreateGrid( 0, 3 );
m_grid->SetColSize( 0, 100 ); // SetColumnAutosizer() will use these for minimum size
m_grid->SetColSize( 1, 80 );
m_grid->SetColLabelValue( 0, _( "Key" ) );
m_grid->SetColLabelValue( 1, _( "Value" ) );
m_grid->SetColLabelValue( 2, _( "Extant" ) );
m_grid->HideCol( 2 );
m_grid->SetRowLabelSize( 0 );
m_grid->SetupColumnAutosizer( 1 );
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_filterCtrl, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 5 );
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 );
m_grid->Bind( wxEVT_GRID_CELL_LEFT_DCLICK, &DIALOG_EDIT_CFG::OnCellLeftDClick, this );
m_filterCtrl->Bind( wxEVT_TEXT, &DIALOG_EDIT_CFG::OnFilterChanged, this );
m_filterCtrl->Bind( wxEVT_SEARCHCTRL_CANCEL_BTN, &DIALOG_EDIT_CFG::OnFilterChanged, this );
Bind( wxEVT_SIZE, &DIALOG_EDIT_CFG::OnSize, this );
m_contextRow = -1;
Layout();
Centre();
}
bool DIALOG_EDIT_CFG::TransferDataToWindow()
{
m_allEntries.clear();
for( const std::unique_ptr<PARAM_CFG>& entry : ADVANCED_CFG::GetCfg().GetEntries() )
{
ConfigEntry cfg;
cfg.key = entry->m_Ident;
cfg.value = paramValueString( *entry );
wxString def = paramDefaultString( *entry );
cfg.extant = ( def == cfg.value ? wxS( "0" ) : wxS( "1" ) );
m_allEntries.push_back( cfg );
}
std::sort( m_allEntries.begin(), m_allEntries.end(),
[]( const ConfigEntry& a, const ConfigEntry& b )
{
return a.key.CmpNoCase( b.key ) < 0;
} );
applyFilter();
return true;
}
bool DIALOG_EDIT_CFG::TransferDataFromWindow()
{
if( !m_grid->CommitPendingChanges() )
return false;
saveSettings();
return true;
}
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;
for( const std::unique_ptr<PARAM_CFG>& entry : ADVANCED_CFG::GetCfg().GetEntries() )
{
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 );
wxString key = m_grid->GetCellValue( row, 0 );
wxString value = m_grid->GetCellValue( row, 1 );
for( ConfigEntry& entry : m_allEntries )
{
if( entry.key == key )
{
entry.value = value;
entry.extant = wxS( "1" );
break;
}
}
}
saveSettings();
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;
for( const std::unique_ptr<PARAM_CFG>& entry : ADVANCED_CFG::GetCfg().GetEntries() )
{
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 );
}
void DIALOG_EDIT_CFG::OnFilterChanged( wxCommandEvent& aEvent )
{
m_filterText = m_filterCtrl->GetValue();
applyFilter();
aEvent.Skip();
}
void DIALOG_EDIT_CFG::applyFilter()
{
if( m_grid->GetNumberRows() > 0 )
m_grid->DeleteRows( 0, m_grid->GetNumberRows() );
for( const ConfigEntry& entry : m_allEntries )
{
if( m_filterText.IsEmpty() || entry.key.Upper().Contains( m_filterText.Upper() ) )
{
int row = m_grid->GetNumberRows();
m_grid->AppendRows( 1 );
m_grid->SetCellValue( row, 0, entry.key );
m_grid->SetCellValue( row, 1, entry.value );
m_grid->SetCellValue( row, 2, entry.extant );
m_grid->SetReadOnly( row, 2 );
updateRowAppearance( row );
}
}
}
void DIALOG_EDIT_CFG::OnCellLeftDClick( wxGridEvent& aEvent )
{
int row = aEvent.GetRow();
int col = aEvent.GetCol();
if( col != 1 )
{
aEvent.Skip();
return;
}
wxString key = m_grid->GetCellValue( row, 0 );
for( const std::unique_ptr<PARAM_CFG>& entry : ADVANCED_CFG::GetCfg().GetEntries() )
{
if( entry->m_Ident == key && entry->m_Type == paramcfg_id::PARAM_BOOL )
{
wxString currentValue = m_grid->GetCellValue( row, 1 );
bool isTrueValue = currentValue.CmpNoCase( wxS( "true" ) ) == 0
|| currentValue.CmpNoCase( wxS( "yes" ) ) == 0
|| currentValue.CmpNoCase( wxS( "1" ) ) == 0;
wxString newValue = isTrueValue ? wxS( "false" ) : wxS( "true" );
m_grid->SetCellValue( row, 1, newValue );
m_grid->SetCellValue( row, 2, wxS( "1" ) );
updateRowAppearance( row );
saveSettings();
for( ConfigEntry& cfg : m_allEntries )
{
if( cfg.key == key )
{
cfg.value = newValue;
cfg.extant = wxS( "1" );
break;
}
}
return;
}
}
aEvent.Skip();
}
void DIALOG_EDIT_CFG::OnSize( wxSizeEvent& aEvent )
{
aEvent.Skip();
}