kicad-source/eeschema/dialogs/dialog_choose_component.cpp
Wayne Stambaugh 54f066fed7 Implement simple inheritance for library symbols.
This change completely removes the LIB_ALIAS design pattern an replaces
it by allowing LIB_PART objects to inherit from other LIB_PART objects.
The initial implementation only allows for single inheritance and only
supports the mandatory fields in the derived part because that is all
that the current symbol library file format will support.  Once the new
file format is implemented and saving to the old file format is deprecated,
more complex inheritance will be added.  The LIB_ALIAS information saved
in the document files was move into the LIB_PART object.  This change
impacts virtually every part of the schematic and symbol library editor
code so this commit message is woefully incomplete.

REMOVE: Removed the symbol aliases concept from the schematic and symbol
editors and the symbol viewer.

NEW: Replace the symbol alias concept with simple inheritance that allows
a library symbol to be derived from another library symbol.
2019-12-06 11:33:52 -05:00

520 lines
18 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2014 Henner Zeller <h.zeller@acm.org>
* Copyright (C) 2016-2019 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 2
* 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, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <dialog_choose_component.h>
#include <algorithm>
#include <wx/utils.h>
#include <wx/button.h>
#include <wx/dataview.h>
#include <wx/panel.h>
#include <wx/sizer.h>
#include <wx/splitter.h>
#include <wx/timer.h>
#include <class_library.h>
#include <sch_base_frame.h>
#include <template_fieldnames.h>
#include <symbol_lib_table.h>
#include <widgets/lib_tree.h>
#include <widgets/footprint_preview_widget.h>
#include <widgets/footprint_select_widget.h>
#include <widgets/symbol_preview_widget.h>
#include <wx/clipbrd.h>
#include <kiface_i.h>
#include <class_libentry.h>
#define SYM_CHOOSER_HSASH wxT( "SymbolChooserHSashPosition" )
#define SYM_CHOOSER_VSASH wxT( "SymbolChooserVSashPosition" )
#define SYM_CHOOSER_WIDTH_KEY wxT( "SymbolChooserWidth" )
#define SYM_CHOOSER_HEIGHT_KEY wxT( "SymbolChooserHeight" )
#define SYM_CHOOSER_KEEP_SYM_KEY wxT( "SymbolChooserKeepSymbol" )
#define SYM_CHOOSER_USE_UNITS_KEY wxT( "SymbolChooserUseUnits" )
std::mutex DIALOG_CHOOSE_COMPONENT::g_Mutex;
DIALOG_CHOOSE_COMPONENT::DIALOG_CHOOSE_COMPONENT( SCH_BASE_FRAME* aParent, const wxString& aTitle,
SYMBOL_TREE_MODEL_ADAPTER::PTR& aAdapter,
int aDeMorganConvert, bool aAllowFieldEdits,
bool aShowFootprints, bool aAllowBrowser )
: DIALOG_SHIM( aParent, wxID_ANY, aTitle, wxDefaultPosition, wxDefaultSize,
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
m_browser_button( nullptr ),
m_hsplitter( nullptr ),
m_vsplitter( nullptr ),
m_fp_sel_ctrl( nullptr ),
m_fp_preview( nullptr ),
m_tree( nullptr ),
m_details( nullptr ),
m_parent( aParent ),
m_deMorganConvert( aDeMorganConvert >= 0 ? aDeMorganConvert : 0 ),
m_allow_field_edits( aAllowFieldEdits ),
m_show_footprints( aShowFootprints ),
m_external_browser_requested( false )
{
m_config = Kiface().KifaceSettings();
auto sizer = new wxBoxSizer( wxVERTICAL );
// Use a slightly different layout, with a details pane spanning the entire window,
// if we're not showing footprints.
if( aShowFootprints )
{
m_hsplitter = new wxSplitterWindow( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
wxSP_LIVE_UPDATE );
//Avoid the splitter window being assigned as the Parent to additional windows
m_hsplitter->SetExtraStyle( wxWS_EX_TRANSIENT );
sizer->Add( m_hsplitter, 1, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 5 );
}
else
{
m_vsplitter = new wxSplitterWindow( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
wxSP_LIVE_UPDATE );
m_hsplitter = new wxSplitterWindow( m_vsplitter, wxID_ANY, wxDefaultPosition, wxDefaultSize,
wxSP_LIVE_UPDATE );
//Avoid the splitter window being assigned as the Parent to additional windows
m_hsplitter->SetExtraStyle( wxWS_EX_TRANSIENT );
auto detailsPanel = new wxPanel( m_vsplitter );
auto detailsSizer = new wxBoxSizer( wxVERTICAL );
detailsPanel->SetSizer( detailsSizer );
m_details = new wxHtmlWindow( detailsPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize,
wxHW_SCROLLBAR_AUTO );
detailsSizer->Add( m_details, 1, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 5 );
detailsPanel->Layout();
detailsSizer->Fit( detailsPanel );
m_vsplitter->SetSashGravity( 0.5 );
m_vsplitter->SetMinimumPaneSize( 20 );
m_vsplitter->SplitHorizontally( m_hsplitter, detailsPanel );
sizer->Add( m_vsplitter, 1, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 5 );
}
m_tree = new LIB_TREE( m_hsplitter, Prj().SchSymbolLibTable(), aAdapter,
LIB_TREE::WIDGETS::ALL, m_details );
m_hsplitter->SetSashGravity( 0.8 );
m_hsplitter->SetMinimumPaneSize( 20 );
m_hsplitter->SplitVertically( m_tree, ConstructRightPanel( m_hsplitter ) );
m_dbl_click_timer = new wxTimer( this );
auto buttonsSizer = new wxBoxSizer( wxHORIZONTAL );
if( aAllowBrowser )
{
m_browser_button = new wxButton( this, wxID_ANY, _( "Select with Browser" ) );
buttonsSizer->Add( m_browser_button, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5 );
}
auto sdbSizer = new wxStdDialogButtonSizer();
auto okButton = new wxButton( this, wxID_OK );
auto cancelButton = new wxButton( this, wxID_CANCEL );
sdbSizer->AddButton( okButton );
sdbSizer->AddButton( cancelButton );
sdbSizer->Realize();
buttonsSizer->Add( sdbSizer, 1, wxALL, 5 );
sizer->Add( buttonsSizer, 0, wxEXPAND | wxLEFT, 5 );
SetSizer( sizer );
Layout();
// We specify the width of the right window (m_symbol_view_panel), because specify
// the width of the left window does not work as expected when SetSashGravity() is called
m_hsplitter->SetSashPosition( m_config->Read( SYM_CHOOSER_HSASH, HorizPixelsFromDU( 220 ) ) );
if( m_vsplitter )
m_vsplitter->SetSashPosition( m_config->Read( SYM_CHOOSER_VSASH,
VertPixelsFromDU( 230 ) ) );
wxSize dlgSize( m_config->Read( SYM_CHOOSER_WIDTH_KEY, HorizPixelsFromDU( 390 ) ),
m_config->Read( SYM_CHOOSER_HEIGHT_KEY, VertPixelsFromDU( 300 ) ) );
SetSize( dlgSize );
SetInitialFocus( m_tree );
okButton->SetDefault();
Bind( wxEVT_INIT_DIALOG, &DIALOG_CHOOSE_COMPONENT::OnInitDialog, this );
Bind( wxEVT_TIMER, &DIALOG_CHOOSE_COMPONENT::OnCloseTimer, this, m_dbl_click_timer->GetId() );
Bind( COMPONENT_PRESELECTED, &DIALOG_CHOOSE_COMPONENT::OnComponentPreselected, this );
Bind( COMPONENT_SELECTED, &DIALOG_CHOOSE_COMPONENT::OnComponentSelected, this );
if( m_browser_button )
m_browser_button->Bind( wxEVT_COMMAND_BUTTON_CLICKED,
&DIALOG_CHOOSE_COMPONENT::OnUseBrowser, this );
if( m_fp_sel_ctrl )
m_fp_sel_ctrl->Bind( EVT_FOOTPRINT_SELECTED,
&DIALOG_CHOOSE_COMPONENT::OnFootprintSelected, this );
if( m_details )
m_details->Connect( wxEVT_CHAR_HOOK,
wxKeyEventHandler( DIALOG_CHOOSE_COMPONENT::OnCharHook ),
NULL, this );
}
DIALOG_CHOOSE_COMPONENT::~DIALOG_CHOOSE_COMPONENT()
{
Unbind( wxEVT_INIT_DIALOG, &DIALOG_CHOOSE_COMPONENT::OnInitDialog, this );
Unbind( wxEVT_TIMER, &DIALOG_CHOOSE_COMPONENT::OnCloseTimer, this );
Unbind( COMPONENT_PRESELECTED, &DIALOG_CHOOSE_COMPONENT::OnComponentPreselected, this );
Unbind( COMPONENT_SELECTED, &DIALOG_CHOOSE_COMPONENT::OnComponentSelected, this );
if( m_browser_button )
m_browser_button->Unbind( wxEVT_COMMAND_BUTTON_CLICKED,
&DIALOG_CHOOSE_COMPONENT::OnUseBrowser, this );
if( m_fp_sel_ctrl )
m_fp_sel_ctrl->Unbind( EVT_FOOTPRINT_SELECTED,
&DIALOG_CHOOSE_COMPONENT::OnFootprintSelected, this );
if( m_details )
m_details->Disconnect( wxEVT_CHAR_HOOK,
wxKeyEventHandler( DIALOG_CHOOSE_COMPONENT::OnCharHook ),
NULL, this );
// I am not sure the following two lines are necessary, but they will not hurt anyone
m_dbl_click_timer->Stop();
delete m_dbl_click_timer;
m_config->Write( SYM_CHOOSER_WIDTH_KEY, GetSize().x );
m_config->Write( SYM_CHOOSER_HEIGHT_KEY, GetSize().y );
m_config->Write( SYM_CHOOSER_KEEP_SYM_KEY, m_keepSymbol->GetValue() );
m_config->Write( SYM_CHOOSER_USE_UNITS_KEY, m_useUnits->GetValue() );
m_config->Write( SYM_CHOOSER_HSASH, m_hsplitter->GetSashPosition() );
if( m_vsplitter )
m_config->Write( SYM_CHOOSER_VSASH, m_vsplitter->GetSashPosition() );
}
wxPanel* DIALOG_CHOOSE_COMPONENT::ConstructRightPanel( wxWindow* aParent )
{
auto panel = new wxPanel( aParent );
auto sizer = new wxBoxSizer( wxVERTICAL );
m_symbol_preview = new SYMBOL_PREVIEW_WIDGET( panel, Kiway(),
m_parent->GetCanvas()->GetBackend() );
m_symbol_preview->SetLayoutDirection( wxLayout_LeftToRight );
if( m_show_footprints )
{
FOOTPRINT_LIST* fp_list = FOOTPRINT_LIST::GetInstance( Kiway() );
sizer->Add( m_symbol_preview, 1, wxEXPAND | wxTOP | wxBOTTOM | wxRIGHT, 5 );
if ( fp_list )
{
if( m_allow_field_edits )
m_fp_sel_ctrl = new FOOTPRINT_SELECT_WIDGET( panel, fp_list, true );
m_fp_preview = new FOOTPRINT_PREVIEW_WIDGET( panel, Kiway() );
}
if( m_fp_sel_ctrl )
sizer->Add( m_fp_sel_ctrl, 0, wxEXPAND | wxBOTTOM | wxTOP | wxRIGHT, 5 );
if( m_fp_preview )
sizer->Add( m_fp_preview, 1, wxEXPAND | wxBOTTOM | wxRIGHT, 5 );
}
else
{
sizer->Add( m_symbol_preview, 1, wxEXPAND | wxTOP | wxRIGHT, 5 );
}
m_keepSymbol = new wxCheckBox( panel, 1000, _("Multi-Symbol Placement"), wxDefaultPosition,
wxDefaultSize, wxALIGN_RIGHT );
m_keepSymbol->SetValue( m_config->ReadBool( SYM_CHOOSER_KEEP_SYM_KEY, false ) );
m_keepSymbol->SetToolTip( _( "Place multiple copies of the symbol." ) );
m_useUnits = new wxCheckBox( panel, 1000, _("Place all units"), wxDefaultPosition,
wxDefaultSize, wxALIGN_RIGHT );
m_useUnits->SetValue( m_config->ReadBool( SYM_CHOOSER_USE_UNITS_KEY, true ) );
m_useUnits->SetToolTip( _( "Sequentially place all units of the symbol." ) );
auto fgSizer = new wxFlexGridSizer( 0, 2, 0, 1 );
fgSizer->AddGrowableCol( 0 );
fgSizer->SetFlexibleDirection( wxBOTH );
fgSizer->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
fgSizer->Add( 0, 0, 1, wxEXPAND );
fgSizer->Add( m_keepSymbol, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxBOTTOM|wxRIGHT|wxLEFT, 5 );
fgSizer->Add( 0, 0, 1, wxEXPAND );
fgSizer->Add( m_useUnits, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxBOTTOM|wxRIGHT|wxLEFT, 5 );
sizer->Add( fgSizer, 0, wxALL | wxEXPAND, 5 );
panel->SetSizer( sizer );
panel->Layout();
sizer->Fit( panel );
return panel;
}
void DIALOG_CHOOSE_COMPONENT::OnInitDialog( wxInitDialogEvent& aEvent )
{
if( m_fp_preview && m_fp_preview->IsInitialized() )
{
// This hides the GAL panel and shows the status label
m_fp_preview->SetStatusText( wxEmptyString );
}
if( m_fp_sel_ctrl )
m_fp_sel_ctrl->Load( Kiway(), Prj() );
}
void DIALOG_CHOOSE_COMPONENT::OnCharHook( wxKeyEvent& e )
{
if( m_details && e.GetKeyCode() == 'C' && e.ControlDown() &&
!e.AltDown() && !e.ShiftDown() && !e.MetaDown() )
{
wxString txt = m_details->SelectionToText();
if( wxTheClipboard->Open() )
{
wxTheClipboard->SetData( new wxTextDataObject( txt ) );
wxTheClipboard->Close();
}
}
else
{
e.Skip();
}
}
LIB_ID DIALOG_CHOOSE_COMPONENT::GetSelectedLibId( int* aUnit ) const
{
return m_tree->GetSelectedLibId( aUnit );
}
void DIALOG_CHOOSE_COMPONENT::OnUseBrowser( wxCommandEvent& aEvent )
{
m_external_browser_requested = true;
EndQuasiModal( wxID_OK );
}
void DIALOG_CHOOSE_COMPONENT::OnCloseTimer( wxTimerEvent& aEvent )
{
// Hack handler because of eaten MouseUp event. See
// DIALOG_CHOOSE_COMPONENT::OnComponentSelected for the beginning
// of this spaghetti noodle.
auto state = wxGetMouseState();
if( state.LeftIsDown() )
{
// Mouse hasn't been raised yet, so fire the timer again. Otherwise the
// purpose of this timer is defeated.
m_dbl_click_timer->StartOnce( DIALOG_CHOOSE_COMPONENT::DblClickDelay );
}
else
{
EndQuasiModal( wxID_OK );
}
}
void DIALOG_CHOOSE_COMPONENT::ShowFootprintFor( LIB_ID const& aLibId )
{
if( !m_fp_preview || !m_fp_preview->IsInitialized() )
return;
LIB_PART* symbol = nullptr;
try
{
symbol = Prj().SchSymbolLibTable()->LoadSymbol( aLibId );
}
catch( const IO_ERROR& ioe )
{
wxLogError( wxString::Format( _( "Error loading symbol %s from library %s.\n\n%s" ),
aLibId.GetLibItemName().wx_str(),
aLibId.GetLibNickname().wx_str(),
ioe.What() ) );
}
if( !symbol )
return;
LIB_FIELD* fp_field = symbol->GetField( FOOTPRINT );
wxString fp_name = fp_field ? fp_field->GetFullText() : wxString( "" );
ShowFootprint( fp_name );
}
void DIALOG_CHOOSE_COMPONENT::ShowFootprint( wxString const& aName )
{
if( !m_fp_preview || !m_fp_preview->IsInitialized() )
return;
if( aName == wxEmptyString )
{
m_fp_preview->SetStatusText( _( "No footprint specified" ) );
}
else
{
LIB_ID lib_id;
if( lib_id.Parse( aName, LIB_ID::ID_PCB ) == -1 && lib_id.IsValid() )
{
m_fp_preview->ClearStatus();
m_fp_preview->CacheFootprint( lib_id );
m_fp_preview->DisplayFootprint( lib_id );
}
else
{
m_fp_preview->SetStatusText( _( "Invalid footprint specified" ) );
}
}
}
void DIALOG_CHOOSE_COMPONENT::PopulateFootprintSelector( LIB_ID const& aLibId )
{
if( !m_fp_sel_ctrl )
return;
m_fp_sel_ctrl->ClearFilters();
LIB_PART* symbol = nullptr;
if( aLibId.IsValid() )
{
try
{
symbol = Prj().SchSymbolLibTable()->LoadSymbol( aLibId );
}
catch( const IO_ERROR& ioe )
{
wxLogError( wxString::Format( _( "Error occurred loading symbol %s from library %s."
"\n\n%s" ),
aLibId.GetLibItemName().wx_str(),
aLibId.GetLibNickname().wx_str(),
ioe.What() ) );
}
}
if( symbol != nullptr )
{
LIB_PINS temp_pins;
LIB_FIELD* fp_field = symbol->GetField( FOOTPRINT );
wxString fp_name = fp_field ? fp_field->GetFullText() : wxString( "" );
symbol->GetPins( temp_pins );
m_fp_sel_ctrl->FilterByPinCount( temp_pins.size() );
m_fp_sel_ctrl->FilterByFootprintFilters( symbol->GetFootprints(), true );
m_fp_sel_ctrl->SetDefaultFootprint( fp_name );
m_fp_sel_ctrl->UpdateList();
m_fp_sel_ctrl->Enable();
}
else
{
m_fp_sel_ctrl->UpdateList();
m_fp_sel_ctrl->Disable();
}
}
void DIALOG_CHOOSE_COMPONENT::OnFootprintSelected( wxCommandEvent& aEvent )
{
m_fp_override = aEvent.GetString();
m_field_edits.erase(
std::remove_if( m_field_edits.begin(), m_field_edits.end(),
[]( std::pair<int, wxString> const& i )
{
return i.first == FOOTPRINT;
} ),
m_field_edits.end() );
m_field_edits.emplace_back( std::make_pair( FOOTPRINT, m_fp_override ) );
ShowFootprint( m_fp_override );
}
void DIALOG_CHOOSE_COMPONENT::OnComponentPreselected( wxCommandEvent& aEvent )
{
int unit = 0;
LIB_ID id = m_tree->GetSelectedLibId( &unit );
if( id.IsValid() )
{
m_symbol_preview->DisplaySymbol( id, unit );
ShowFootprintFor( id );
PopulateFootprintSelector( id );
}
else
{
m_symbol_preview->SetStatusText( _( "No symbol selected" ) );
if( m_fp_preview && m_fp_preview->IsInitialized() )
m_fp_preview->SetStatusText( wxEmptyString );
PopulateFootprintSelector( id );
}
}
void DIALOG_CHOOSE_COMPONENT::OnComponentSelected( wxCommandEvent& aEvent )
{
if( m_tree->GetSelectedLibId().IsValid() )
{
// Got a selection. We can't just end the modal dialog here, because
// wx leaks some events back to the parent window (in particular, the
// MouseUp following a double click).
//
// NOW, here's where it gets really fun. wxTreeListCtrl eats MouseUp.
// This isn't really feasible to bypass without a fully custom
// wxDataViewCtrl implementation, and even then might not be fully
// possible (docs are vague). To get around this, we use a one-shot
// timer to schedule the dialog close.
//
// See DIALOG_CHOOSE_COMPONENT::OnCloseTimer for the other end of this
// spaghetti noodle.
m_dbl_click_timer->StartOnce( DIALOG_CHOOSE_COMPONENT::DblClickDelay );
}
}