kicad/eeschema/dialogs/dialog_symbol_properties.cpp

1225 lines
38 KiB
C++
Raw Normal View History

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2004-2022 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_symbol_properties.h"
#include <memory>
#include <bitmaps.h>
#include <wx/tooltip.h>
#include <grid_tricks.h>
#include <confirm.h>
2021-09-14 22:45:14 +00:00
#include <kiface_base.h>
#include <pin_numbers.h>
#include <string_utils.h>
#include <menus_helpers.h>
#include <kiplatform/ui.h>
#include <widgets/grid_icon_text_helpers.h>
#include <widgets/grid_combobox.h>
#include <widgets/wx_grid.h>
#include <settings/settings_manager.h>
#include <ee_collectors.h>
#include <symbol_library.h>
#include <fields_grid_table.h>
#include <sch_edit_frame.h>
#include <sch_reference_list.h>
2020-05-24 14:55:37 +00:00
#include <schematic.h>
#include <tool/tool_manager.h>
#include <tool/actions.h>
#include <math/vector2d.h>
2016-08-11 12:42:13 +00:00
#ifdef KICAD_SPICE
#include <dialog_sim_model.h>
2016-08-11 12:42:13 +00:00
#endif /* KICAD_SPICE */
wxDEFINE_EVENT( SYMBOL_DELAY_FOCUS, wxCommandEvent );
wxDEFINE_EVENT( SYMBOL_DELAY_SELECTION, wxCommandEvent );
enum PIN_TABLE_COL_ORDER
{
COL_NUMBER,
COL_BASE_NAME,
COL_ALT_NAME,
COL_TYPE,
COL_SHAPE,
COL_COUNT // keep as last
};
class SCH_PIN_TABLE_DATA_MODEL : public wxGridTableBase, public std::vector<SCH_PIN>
{
protected:
std::vector<wxGridCellAttr*> m_nameAttrs;
wxGridCellAttr* m_readOnlyAttr;
wxGridCellAttr* m_typeAttr;
wxGridCellAttr* m_shapeAttr;
public:
SCH_PIN_TABLE_DATA_MODEL() :
m_readOnlyAttr( nullptr ),
m_typeAttr( nullptr ),
m_shapeAttr( nullptr )
{
}
~SCH_PIN_TABLE_DATA_MODEL()
{
for( wxGridCellAttr* attr : m_nameAttrs )
attr->DecRef();
m_readOnlyAttr->DecRef();
m_typeAttr->DecRef();
m_shapeAttr->DecRef();
}
void BuildAttrs()
{
for( wxGridCellAttr* attr : m_nameAttrs )
attr->DecRef();
m_nameAttrs.clear();
if( m_readOnlyAttr )
m_readOnlyAttr->DecRef();
m_readOnlyAttr = new wxGridCellAttr;
m_readOnlyAttr->SetReadOnly( true );
for( const SCH_PIN& pin : *this )
{
LIB_PIN* lib_pin = pin.GetLibPin();
wxGridCellAttr* attr = nullptr;
if( lib_pin->GetAlternates().empty() )
{
attr = new wxGridCellAttr;
attr->SetReadOnly( true );
}
else
{
wxArrayString choices;
choices.push_back( lib_pin->GetName() );
for( const std::pair<const wxString, LIB_PIN::ALT>& alt : lib_pin->GetAlternates() )
choices.push_back( alt.first );
attr = new wxGridCellAttr();
attr->SetEditor( new GRID_CELL_COMBOBOX( choices ) );
}
m_nameAttrs.push_back( attr );
}
if( m_typeAttr )
m_typeAttr->DecRef();
m_typeAttr = new wxGridCellAttr;
m_typeAttr->SetRenderer( new GRID_CELL_ICON_TEXT_RENDERER( PinTypeIcons(),
PinTypeNames() ) );
m_typeAttr->SetReadOnly( true );
if( m_shapeAttr )
m_shapeAttr->DecRef();
m_shapeAttr = new wxGridCellAttr;
m_shapeAttr->SetRenderer( new GRID_CELL_ICON_TEXT_RENDERER( PinShapeIcons(),
PinShapeNames() ) );
m_shapeAttr->SetReadOnly( true );
}
int GetNumberRows() override { return (int) size(); }
int GetNumberCols() override { return COL_COUNT; }
wxString GetColLabelValue( int aCol ) override
{
switch( aCol )
{
case COL_NUMBER: return _( "Number" );
case COL_BASE_NAME: return _( "Base Name" );
case COL_ALT_NAME: return _( "Alternate Assignment" );
case COL_TYPE: return _( "Electrical Type" );
case COL_SHAPE: return _( "Graphic Style" );
default: wxFAIL; return wxEmptyString;
}
}
bool IsEmptyCell( int row, int col ) override
{
return false; // don't allow adjacent cell overflow, even if we are actually empty
}
wxString GetValue( int aRow, int aCol ) override
{
return GetValue( at( aRow ), aCol );
}
static wxString GetValue( const SCH_PIN& aPin, int aCol )
{
switch( aCol )
{
case COL_NUMBER: return aPin.GetNumber();
case COL_BASE_NAME: return aPin.GetLibPin()->GetName();
case COL_ALT_NAME: return aPin.GetAlt();
case COL_TYPE: return PinTypeNames()[static_cast<int>( aPin.GetType() )];
case COL_SHAPE: return PinShapeNames()[static_cast<int>( aPin.GetShape() )];
default: wxFAIL; return wxEmptyString;
}
}
wxGridCellAttr* GetAttr( int aRow, int aCol, wxGridCellAttr::wxAttrKind ) override
{
switch( aCol )
{
case COL_NUMBER:
case COL_BASE_NAME:
m_readOnlyAttr->IncRef();
return m_readOnlyAttr;
case COL_ALT_NAME:
m_nameAttrs[ aRow ]->IncRef();
return m_nameAttrs[ aRow ];
case COL_TYPE:
m_typeAttr->IncRef();
return m_typeAttr;
case COL_SHAPE:
m_shapeAttr->IncRef();
return m_shapeAttr;
default:
wxFAIL;
return nullptr;
}
}
void SetValue( int aRow, int aCol, const wxString &aValue ) override
{
switch( aCol )
{
case COL_ALT_NAME:
if( aValue == at( aRow ).GetLibPin()->GetName() )
at( aRow ).SetAlt( wxEmptyString );
else
at( aRow ).SetAlt( aValue );
break;
case COL_NUMBER:
case COL_BASE_NAME:
case COL_TYPE:
case COL_SHAPE:
// Read-only.
break;
default:
wxFAIL;
break;
}
}
static bool compare( const SCH_PIN& lhs, const SCH_PIN& rhs, int sortCol, bool ascending )
{
wxString lhStr = GetValue( lhs, sortCol );
wxString rhStr = GetValue( rhs, sortCol );
if( lhStr == rhStr )
{
// Secondary sort key is always COL_NUMBER
sortCol = COL_NUMBER;
lhStr = GetValue( lhs, sortCol );
rhStr = GetValue( rhs, sortCol );
}
bool res;
// N.B. To meet the iterator sort conditions, we cannot simply invert the truth
// to get the opposite sort. i.e. ~(a<b) != (a>b)
auto cmp = [ ascending ]( const auto a, const auto b )
{
if( ascending )
return a < b;
else
return b < a;
};
switch( sortCol )
{
case COL_NUMBER:
case COL_BASE_NAME:
case COL_ALT_NAME:
res = cmp( PIN_NUMBERS::Compare( lhStr, rhStr ), 0 );
break;
case COL_TYPE:
case COL_SHAPE:
res = cmp( lhStr.CmpNoCase( rhStr ), 0 );
break;
default:
res = cmp( StrNumCmp( lhStr, rhStr ), 0 );
break;
}
return res;
}
void SortRows( int aSortCol, bool ascending )
{
std::sort( begin(), end(),
[ aSortCol, ascending ]( const SCH_PIN& lhs, const SCH_PIN& rhs ) -> bool
{
return compare( lhs, rhs, aSortCol, ascending );
} );
}
};
DIALOG_SYMBOL_PROPERTIES::DIALOG_SYMBOL_PROPERTIES( SCH_EDIT_FRAME* aParent,
2021-06-10 14:10:55 +00:00
SCH_SYMBOL* aSymbol ) :
DIALOG_SYMBOL_PROPERTIES_BASE( aParent ),
2021-06-10 14:10:55 +00:00
m_symbol( nullptr ),
m_part( nullptr ),
m_fields( nullptr ),
m_dataModel( nullptr )
{
2021-06-10 14:10:55 +00:00
m_symbol = aSymbol;
m_part = m_symbol->GetLibSymbolRef().get();
2020-09-03 11:47:51 +00:00
// GetLibSymbolRef() now points to the cached part in the schematic, which should always be
// there for usual cases, but can be null when opening old schematics not storing the part
// so we need to handle m_part == nullptr
// wxASSERT( m_part );
2020-09-03 11:47:51 +00:00
m_fields = new FIELDS_GRID_TABLE<SCH_FIELD>( this, aParent, m_fieldsGrid, m_symbol );
m_editorShown = false;
m_lastRequestedSize = wxSize( 0, 0 );
2016-08-11 12:42:13 +00:00
#ifndef KICAD_SPICE
m_spiceFieldsButton->Hide();
2016-08-11 12:42:13 +00:00
#endif /* not KICAD_SPICE */
// disable some options inside the edit dialog which can cause problems while dragging
2021-06-10 14:10:55 +00:00
if( m_symbol->IsDragging() )
{
m_orientationLabel->Disable();
m_orientationCtrl->Disable();
m_mirrorLabel->Disable();
m_mirrorCtrl->Disable();
}
// Give a bit more room for combobox editors
m_fieldsGrid->SetDefaultRowSize( m_fieldsGrid->GetDefaultRowSize() + 4 );
m_pinGrid->SetDefaultRowSize( m_pinGrid->GetDefaultRowSize() + 4 );
m_fieldsGrid->SetTable( m_fields );
m_fieldsGrid->PushEventHandler( new FIELDS_GRID_TRICKS( m_fieldsGrid, this ) );
m_fieldsGrid->SetSelectionMode( wxGrid::wxGridSelectRows );
// Show/hide columns according to user's preference
EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() );
if( cfg )
{
m_shownColumns = cfg->m_Appearance.edit_symbol_visible_columns;
m_fieldsGrid->ShowHideColumns( m_shownColumns );
}
2008-11-24 21:06:50 +00:00
if( m_part && m_part->HasConversion() )
{
// DeMorgan conversions are a subclass of alternate pin assignments, so don't allow
// free-form alternate assignments as well. (We won't know how to map the alternates
// back and forth when the conversion is changed.)
m_pinTablePage->Disable();
m_pinTablePage->SetToolTip( _( "Alternate pin assignments are not available for De Morgan "
"symbols." ) );
}
else
{
m_dataModel = new SCH_PIN_TABLE_DATA_MODEL();
// Make a copy of the pins for editing
2021-06-10 14:10:55 +00:00
for( const std::unique_ptr<SCH_PIN>& pin : m_symbol->GetRawPins() )
m_dataModel->push_back( *pin );
m_dataModel->SortRows( COL_NUMBER, true );
m_dataModel->BuildAttrs();
m_pinGrid->SetTable( m_dataModel );
}
m_pinGrid->PushEventHandler( new GRID_TRICKS( m_pinGrid ) );
m_pinGrid->SetSelectionMode( wxGrid::wxGridSelectRows );
wxToolTip::Enable( true );
SetupStandardButtons();
// Configure button logos
m_bpAdd->SetBitmap( KiBitmap( BITMAPS::small_plus ) );
m_bpDelete->SetBitmap( KiBitmap( BITMAPS::small_trash ) );
m_bpMoveUp->SetBitmap( KiBitmap( BITMAPS::small_up ) );
m_bpMoveDown->SetBitmap( KiBitmap( BITMAPS::small_down ) );
// wxFormBuilder doesn't include this event...
m_fieldsGrid->Connect( wxEVT_GRID_CELL_CHANGING,
wxGridEventHandler( DIALOG_SYMBOL_PROPERTIES::OnGridCellChanging ),
nullptr, this );
m_pinGrid->Connect( wxEVT_GRID_COL_SORT,
wxGridEventHandler( DIALOG_SYMBOL_PROPERTIES::OnPinTableColSort ),
nullptr, this );
Connect( SYMBOL_DELAY_FOCUS,
wxCommandEventHandler( DIALOG_SYMBOL_PROPERTIES::HandleDelayedFocus ), nullptr, this );
Connect( SYMBOL_DELAY_SELECTION,
wxCommandEventHandler( DIALOG_SYMBOL_PROPERTIES::HandleDelayedSelection ), nullptr,
this );
QueueEvent( new wxCommandEvent( SYMBOL_DELAY_SELECTION ) );
wxCommandEvent *evt = new wxCommandEvent( SYMBOL_DELAY_FOCUS );
evt->SetClientData( new VECTOR2I( REFERENCE_FIELD, FDC_VALUE ) );
QueueEvent( evt );
2020-11-16 11:16:44 +00:00
finishDialogSettings();
}
DIALOG_SYMBOL_PROPERTIES::~DIALOG_SYMBOL_PROPERTIES()
{
EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() );
if( cfg )
cfg->m_Appearance.edit_symbol_visible_columns = m_fieldsGrid->GetShownColumns();
// Prevents crash bug in wxGrid's d'tor
m_fieldsGrid->DestroyTable( m_fields );
if( m_dataModel )
m_pinGrid->DestroyTable( m_dataModel );
m_fieldsGrid->Disconnect( wxEVT_GRID_CELL_CHANGING,
wxGridEventHandler( DIALOG_SYMBOL_PROPERTIES::OnGridCellChanging ),
nullptr, this );
m_pinGrid->Disconnect( wxEVT_GRID_COL_SORT,
wxGridEventHandler( DIALOG_SYMBOL_PROPERTIES::OnPinTableColSort ),
nullptr, this );
// Delete the GRID_TRICKS.
m_fieldsGrid->PopEventHandler( true );
m_pinGrid->PopEventHandler( true );
}
2008-11-24 21:06:50 +00:00
SCH_EDIT_FRAME* DIALOG_SYMBOL_PROPERTIES::GetParent()
{
return dynamic_cast<SCH_EDIT_FRAME*>( wxDialog::GetParent() );
}
bool DIALOG_SYMBOL_PROPERTIES::TransferDataToWindow()
{
if( !wxDialog::TransferDataToWindow() )
return false;
std::set<wxString> defined;
// Push a copy of each field into m_updateFields
2021-06-10 14:10:55 +00:00
for( int i = 0; i < m_symbol->GetFieldCount(); ++i )
{
2021-06-10 14:10:55 +00:00
SCH_FIELD field( m_symbol->GetFields()[i] );
// change offset to be symbol-relative
2021-06-10 14:10:55 +00:00
field.Offset( -m_symbol->GetPosition() );
defined.insert( field.GetName() );
m_fields->push_back( field );
}
// Add in any template fieldnames not yet defined:
for( const TEMPLATE_FIELDNAME& templateFieldname :
GetParent()->Schematic().Settings().m_TemplateFieldNames.GetTemplateFieldNames() )
{
if( defined.count( templateFieldname.m_Name ) <= 0 )
{
2021-06-10 14:10:55 +00:00
SCH_FIELD field( wxPoint( 0, 0 ), -1, m_symbol, templateFieldname.m_Name );
field.SetVisible( templateFieldname.m_Visible );
m_fields->push_back( field );
}
}
// notify the grid
wxGridTableMessage msg( m_fields, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, m_fields->size() );
m_fieldsGrid->ProcessTableMessage( msg );
AdjustFieldsGridColumns();
2021-06-10 14:10:55 +00:00
// If a multi-unit symbol, set up the unit selector and interchangeable checkbox.
if( m_symbol->GetUnitCount() > 1 )
{
2021-06-10 14:10:55 +00:00
for( int ii = 1; ii <= m_symbol->GetUnitCount(); ii++ )
2021-06-10 18:51:46 +00:00
m_unitChoice->Append( LIB_SYMBOL::SubReference( ii, false ) );
2021-06-10 14:10:55 +00:00
if( m_symbol->GetUnit() <= ( int )m_unitChoice->GetCount() )
m_unitChoice->SetSelection( m_symbol->GetUnit() - 1 );
}
else
{
m_unitLabel->Enable( false );
m_unitChoice->Enable( false );
}
if( m_part && m_part->HasConversion() )
{
2021-06-10 14:10:55 +00:00
if( m_symbol->GetConvert() > LIB_ITEM::LIB_CONVERT::BASE )
m_cbAlternateSymbol->SetValue( true );
}
else
2020-09-03 11:47:51 +00:00
{
m_cbAlternateSymbol->Enable( false );
2020-09-03 11:47:51 +00:00
}
// Set the symbol orientation and mirroring.
2021-06-10 14:10:55 +00:00
int orientation = m_symbol->GetOrientation() & ~( SYM_MIRROR_X | SYM_MIRROR_Y );
switch( orientation )
{
default:
2021-06-10 14:10:55 +00:00
case SYM_ORIENT_0: m_orientationCtrl->SetSelection( 0 ); break;
case SYM_ORIENT_90: m_orientationCtrl->SetSelection( 1 ); break;
case SYM_ORIENT_270: m_orientationCtrl->SetSelection( 2 ); break;
case SYM_ORIENT_180: m_orientationCtrl->SetSelection( 3 ); break;
}
2021-06-10 14:10:55 +00:00
int mirror = m_symbol->GetOrientation() & ( SYM_MIRROR_X | SYM_MIRROR_Y );
switch( mirror )
{
default: m_mirrorCtrl->SetSelection( 0 ) ; break;
2021-06-10 14:10:55 +00:00
case SYM_MIRROR_X: m_mirrorCtrl->SetSelection( 1 ); break;
case SYM_MIRROR_Y: m_mirrorCtrl->SetSelection( 2 ); break;
}
2021-06-10 14:10:55 +00:00
m_cbExcludeFromBom->SetValue( !m_symbol->GetIncludeInBom() );
m_cbExcludeFromBoard->SetValue( !m_symbol->GetIncludeOnBoard() );
if( m_part )
{
m_ShowPinNumButt->SetValue( m_part->ShowPinNumbers() );
m_ShowPinNameButt->SetValue( m_part->ShowPinNames() );
}
2021-06-10 14:10:55 +00:00
// Set the symbol's library name.
m_tcLibraryID->SetValue( UnescapeString( m_symbol->GetLibId().Format() ) );
Layout();
m_fieldsGrid->Layout();
wxSafeYield();
return true;
}
2008-11-24 21:06:50 +00:00
void DIALOG_SYMBOL_PROPERTIES::OnEditSpiceModel( wxCommandEvent& event )
{
2016-08-11 12:42:13 +00:00
#ifdef KICAD_SPICE
int diff = m_fields->size();
DIALOG_SIM_MODEL dialog( this, *m_symbol, *m_fields );
if( dialog.ShowModal() != wxID_OK )
return;
2020-03-25 18:06:18 +00:00
diff = (int) m_fields->size() - diff;
2008-11-24 21:06:50 +00:00
if( diff > 0 )
{
wxGridTableMessage msg( m_fields, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, diff );
m_fieldsGrid->ProcessTableMessage( msg );
}
else if( diff < 0 )
{
wxGridTableMessage msg( m_fields, wxGRIDTABLE_NOTIFY_ROWS_DELETED, 0, -diff );
m_fieldsGrid->ProcessTableMessage( msg );
}
OnModify();
m_fieldsGrid->ForceRefresh();
#endif /* KICAD_SPICE */
2008-11-24 21:06:50 +00:00
}
void DIALOG_SYMBOL_PROPERTIES::OnCancelButtonClick( wxCommandEvent& event )
{
// Running the Footprint Browser gums up the works and causes the automatic cancel
// stuff to no longer work. So we do it here ourselves.
EndQuasiModal( wxID_CANCEL );
}
bool DIALOG_SYMBOL_PROPERTIES::Validate()
2014-09-15 12:06:00 +00:00
{
LIB_ID id;
if( !m_fieldsGrid->CommitPendingChanges() || !m_fieldsGrid->Validate() )
return false;
2021-06-10 14:10:55 +00:00
if( !SCH_SYMBOL::IsReferenceStringValid( m_fields->at( REFERENCE_FIELD ).GetText() ) )
{
DisplayErrorMessage( this, _( "References must start with a letter." ) );
wxCommandEvent *evt = new wxCommandEvent( SYMBOL_DELAY_FOCUS );
evt->SetClientData( new VECTOR2I( REFERENCE_FIELD, FDC_VALUE ) );
QueueEvent( evt );
return false;
}
// Check for missing field names.
for( size_t i = MANDATORY_FIELDS; i < m_fields->size(); ++i )
{
SCH_FIELD& field = m_fields->at( i );
wxString fieldName = field.GetName( false );
if( fieldName.IsEmpty() )
2008-11-26 00:20:16 +00:00
{
DisplayErrorMessage( this, _( "Fields must have a name." ) );
wxCommandEvent *evt = new wxCommandEvent( SYMBOL_DELAY_FOCUS );
evt->SetClientData( new VECTOR2I( i, FDC_VALUE ) );
QueueEvent( evt );
return false;
2008-11-26 00:20:16 +00:00
}
}
return true;
}
bool DIALOG_SYMBOL_PROPERTIES::TransferDataFromWindow()
{
if( !wxDialog::TransferDataFromWindow() ) // Calls our Validate() method.
return false;
if( !m_fieldsGrid->CommitPendingChanges() )
return false;
if( !m_pinGrid->CommitPendingChanges() )
return false;
SCH_SCREEN* currentScreen = GetParent()->GetScreen();
wxCHECK( currentScreen, false );
// This needs to be done before the LIB_ID is changed to prevent stale library symbols in
// the schematic file.
2021-06-10 14:10:55 +00:00
currentScreen->Remove( m_symbol );
// save old cmp in undo list if not already in edit, or moving ...
2021-06-10 14:10:55 +00:00
if( m_symbol->GetEditFlags() == 0 )
GetParent()->SaveCopyInUndoList( currentScreen, m_symbol, UNDO_REDO::CHANGED, false );
// Save current flags which could be modified by next change settings
2021-06-10 14:10:55 +00:00
EDA_ITEM_FLAGS flags = m_symbol->GetFlags();
// For symbols with multiple shapes (De Morgan representation) Set the selected shape:
if( m_cbAlternateSymbol->IsEnabled() && m_cbAlternateSymbol->GetValue() )
2021-06-10 14:10:55 +00:00
m_symbol->SetConvert( LIB_ITEM::LIB_CONVERT::DEMORGAN );
else
2021-06-10 14:10:55 +00:00
m_symbol->SetConvert( LIB_ITEM::LIB_CONVERT::BASE );
//Set the part selection in multiple part per package
int unit_selection = m_unitChoice->IsEnabled() ? m_unitChoice->GetSelection() + 1 : 1;
2021-06-10 14:10:55 +00:00
m_symbol->SetUnitSelection( &GetParent()->GetCurrentSheet(), unit_selection );
m_symbol->SetUnit( unit_selection );
switch( m_orientationCtrl->GetSelection() )
2008-11-26 00:20:16 +00:00
{
2021-06-10 14:10:55 +00:00
case 0: m_symbol->SetOrientation( SYM_ORIENT_0 ); break;
case 1: m_symbol->SetOrientation( SYM_ORIENT_90 ); break;
case 2: m_symbol->SetOrientation( SYM_ORIENT_270 ); break;
case 3: m_symbol->SetOrientation( SYM_ORIENT_180 ); break;
2008-11-26 00:20:16 +00:00
}
switch( m_mirrorCtrl->GetSelection() )
2008-11-26 00:20:16 +00:00
{
case 0: break;
2021-06-10 14:10:55 +00:00
case 1: m_symbol->SetOrientation( SYM_MIRROR_X ); break;
case 2: m_symbol->SetOrientation( SYM_MIRROR_Y ); break;
2008-11-26 00:20:16 +00:00
}
if( m_part )
{
m_part->SetShowPinNames( m_ShowPinNameButt->GetValue() );
m_part->SetShowPinNumbers( m_ShowPinNumButt->GetValue() );
}
// Restore m_Flag modified by SetUnit() and other change settings
2021-06-10 14:10:55 +00:00
m_symbol->ClearFlags();
m_symbol->SetFlags( flags );
// change all field positions from relative to absolute
for( unsigned i = 0; i < m_fields->size(); ++i )
2021-06-10 14:10:55 +00:00
m_fields->at( i ).Offset( m_symbol->GetPosition() );
2021-06-10 14:10:55 +00:00
SCH_FIELDS& fields = m_symbol->GetFields();
fields.clear();
for( size_t i = 0; i < m_fields->size(); ++i )
fields.push_back( m_fields->at( i ) );
// Reference has a specific initialization, depending on the current active sheet
2021-06-10 14:10:55 +00:00
// because for a given symbol, in a complex hierarchy, there are more than one
// reference.
2021-06-10 14:10:55 +00:00
m_symbol->SetRef( &GetParent()->GetCurrentSheet(), m_fields->at( REFERENCE_FIELD ).GetText() );
// Similar for Value and Footprint, except that the GUI behaviour is that they are kept
// in sync between multiple instances.
2021-06-10 14:10:55 +00:00
m_symbol->SetValue( m_fields->at( VALUE_FIELD ).GetText() );
m_symbol->SetFootprint( m_fields->at( FOOTPRINT_FIELD ).GetText() );
2021-06-10 14:10:55 +00:00
m_symbol->SetIncludeInBom( !m_cbExcludeFromBom->IsChecked() );
m_symbol->SetIncludeOnBoard( !m_cbExcludeFromBoard->IsChecked() );
// Update any assignments
if( m_dataModel )
{
for( const SCH_PIN& model_pin : *m_dataModel )
{
// map from the edited copy back to the "real" pin in the symbol.
SCH_PIN* src_pin = m_symbol->GetPin( model_pin.GetNumber() );
if( src_pin )
src_pin->SetAlt( model_pin.GetAlt() );
}
}
// Keep fields other than the reference, include/exclude flags, and alternate pin assignements
// in sync in multi-unit parts.
2021-06-10 14:10:55 +00:00
if( m_symbol->GetUnitCount() > 1 && m_symbol->IsAnnotated( &GetParent()->GetCurrentSheet() ) )
{
2021-06-10 14:10:55 +00:00
wxString ref = m_symbol->GetRef( &GetParent()->GetCurrentSheet() );
int unit = m_symbol->GetUnit();
LIB_ID libId = m_symbol->GetLibId();
for( SCH_SHEET_PATH& sheet : GetParent()->Schematic().GetSheets() )
{
2021-06-10 14:10:55 +00:00
SCH_SCREEN* screen = sheet.LastScreen();
std::vector<SCH_SYMBOL*> otherUnits;
constexpr bool appendUndo = true;
CollectOtherUnits( ref, unit, libId, sheet, &otherUnits );
2021-06-10 14:10:55 +00:00
for( SCH_SYMBOL* otherUnit : otherUnits )
{
GetParent()->SaveCopyInUndoList( screen, otherUnit, UNDO_REDO::CHANGED,
appendUndo );
2020-11-12 21:31:41 +00:00
otherUnit->SetValue( m_fields->at( VALUE_FIELD ).GetText() );
otherUnit->SetFootprint( m_fields->at( FOOTPRINT_FIELD ).GetText() );
for( size_t ii = DATASHEET_FIELD; ii < m_fields->size(); ++ii )
{
SCH_FIELD* otherField = otherUnit->FindField( m_fields->at( ii ).GetName() );
if( otherField )
{
otherField->SetText( m_fields->at( ii ).GetText() );
}
else
{
SCH_FIELD* newField = otherUnit->AddField( m_fields->at( ii ) );
const_cast<KIID&>( newField->m_Uuid ) = KIID();
}
}
for( size_t ii = otherUnit->GetFields().size() - 1; ii > DATASHEET_FIELD; ii-- )
{
SCH_FIELD& otherField = otherUnit->GetFields().at( ii );
if( !m_symbol->FindField( otherField.GetName() ) )
otherUnit->GetFields().erase( otherUnit->GetFields().begin() + ii );
}
otherUnit->SetIncludeInBom( !m_cbExcludeFromBom->IsChecked() );
otherUnit->SetIncludeOnBoard( !m_cbExcludeFromBoard->IsChecked() );
if( m_dataModel )
{
for( const SCH_PIN& model_pin : *m_dataModel )
{
SCH_PIN* src_pin = otherUnit->GetPin( model_pin.GetNumber() );
if( src_pin )
src_pin->SetAlt( model_pin.GetAlt() );
}
}
GetParent()->UpdateItem( otherUnit, false, true );
}
}
}
if( m_cbMakeDefaultInstance->IsChecked() )
{
SYMBOL_INSTANCE_REFERENCE defaultInstance;
defaultInstance.m_Unit = unit_selection;
defaultInstance.m_Reference = m_fields->at( REFERENCE_FIELD ).GetText();
defaultInstance.m_Value = m_fields->at( VALUE_FIELD ).GetText();
defaultInstance.m_Footprint = m_fields->at( FOOTPRINT_FIELD ).GetText();
m_symbol->SetDefaultInstance( defaultInstance );
}
2021-06-10 14:10:55 +00:00
currentScreen->Append( m_symbol );
GetParent()->TestDanglingEnds();
GetParent()->UpdateItem( m_symbol, false, true );
GetParent()->OnModify();
// This must go after OnModify() so that the connectivity graph will have been updated.
GetParent()->GetToolManager()->PostEvent( EVENTS::SelectedItemsModified );
2008-11-26 00:20:16 +00:00
return true;
2008-11-26 00:20:16 +00:00
}
void DIALOG_SYMBOL_PROPERTIES::OnGridCellChanging( wxGridEvent& event )
{
wxGridCellEditor* editor = m_fieldsGrid->GetCellEditor( event.GetRow(), event.GetCol() );
wxControl* control = editor->GetControl();
if( control && control->GetValidator() && !control->GetValidator()->Validate( control ) )
{
event.Veto();
wxCommandEvent *evt = new wxCommandEvent( SYMBOL_DELAY_FOCUS );
evt->SetClientData( new VECTOR2I( event.GetRow(), event.GetCol() ) );
QueueEvent( evt );
}
else if( event.GetCol() == FDC_NAME )
{
wxString newName = event.GetString();
for( int i = 0; i < m_fieldsGrid->GetNumberRows(); ++i )
{
if( i == event.GetRow() )
continue;
if( newName.CmpNoCase( m_fieldsGrid->GetCellValue( i, FDC_NAME ) ) == 0 )
{
DisplayError( this, wxString::Format( _( "Field name '%s' already in use." ),
newName ) );
event.Veto();
wxCommandEvent *evt = new wxCommandEvent( SYMBOL_DELAY_FOCUS );
evt->SetClientData( new VECTOR2I( event.GetRow(), event.GetCol() ) );
QueueEvent( evt );
}
}
}
editor->DecRef();
}
void DIALOG_SYMBOL_PROPERTIES::OnGridEditorShown( wxGridEvent& aEvent )
{
2020-11-12 21:31:41 +00:00
if( aEvent.GetRow() == REFERENCE_FIELD && aEvent.GetCol() == FDC_VALUE )
QueueEvent( new wxCommandEvent( SYMBOL_DELAY_SELECTION ) );
m_editorShown = true;
}
void DIALOG_SYMBOL_PROPERTIES::OnGridEditorHidden( wxGridEvent& aEvent )
{
m_editorShown = false;
}
void DIALOG_SYMBOL_PROPERTIES::OnAddField( wxCommandEvent& event )
{
if( !m_fieldsGrid->CommitPendingChanges() )
return;
2021-06-10 14:10:55 +00:00
SCHEMATIC_SETTINGS& settings = m_symbol->Schematic()->Settings();
int fieldID = m_fields->size();
2021-06-10 14:10:55 +00:00
SCH_FIELD newField( wxPoint( 0, 0 ), fieldID, m_symbol,
TEMPLATE_FIELDNAME::GetDefaultFieldName( fieldID, DO_TRANSLATE ) );
2020-11-12 21:31:41 +00:00
newField.SetTextAngle( m_fields->at( REFERENCE_FIELD ).GetTextAngle() );
newField.SetTextSize( wxSize( settings.m_DefaultTextSize, settings.m_DefaultTextSize ) );
m_fields->push_back( newField );
// notify the grid
wxGridTableMessage msg( m_fields, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, 1 );
m_fieldsGrid->ProcessTableMessage( msg );
m_fieldsGrid->MakeCellVisible( (int) m_fields->size() - 1, 0 );
m_fieldsGrid->SetGridCursor( (int) m_fields->size() - 1, 0 );
m_fieldsGrid->EnableCellEditControl();
m_fieldsGrid->ShowCellEditControl();
OnModify();
}
void DIALOG_SYMBOL_PROPERTIES::OnDeleteField( wxCommandEvent& event )
{
wxArrayInt selectedRows = m_fieldsGrid->GetSelectedRows();
if( selectedRows.empty() && m_fieldsGrid->GetGridCursorRow() >= 0 )
selectedRows.push_back( m_fieldsGrid->GetGridCursorRow() );
if( selectedRows.empty() )
return;
for( int row : selectedRows )
{
if( row < MANDATORY_FIELDS )
{
DisplayError( this, wxString::Format( _( "The first %d fields are mandatory." ),
MANDATORY_FIELDS ) );
return;
}
}
m_fieldsGrid->CommitPendingChanges( true /* quiet mode */ );
// Reverse sort so deleting a row doesn't change the indexes of the other rows.
selectedRows.Sort( []( int* first, int* second ) { return *second - *first; } );
for( int row : selectedRows )
{
m_fields->erase( m_fields->begin() + row );
// notify the grid
wxGridTableMessage msg( m_fields, wxGRIDTABLE_NOTIFY_ROWS_DELETED, row, 1 );
m_fieldsGrid->ProcessTableMessage( msg );
if( m_fieldsGrid->GetNumberRows() > 0 )
{
m_fieldsGrid->MakeCellVisible( std::max( 0, row-1 ), m_fieldsGrid->GetGridCursorCol() );
m_fieldsGrid->SetGridCursor( std::max( 0, row-1 ), m_fieldsGrid->GetGridCursorCol() );
}
}
OnModify();
}
void DIALOG_SYMBOL_PROPERTIES::OnMoveUp( wxCommandEvent& event )
2008-11-26 00:20:16 +00:00
{
if( !m_fieldsGrid->CommitPendingChanges() )
return;
2008-11-26 00:20:16 +00:00
int i = m_fieldsGrid->GetGridCursorRow();
2008-11-26 00:20:16 +00:00
if( i > MANDATORY_FIELDS )
{
SCH_FIELD tmp = m_fields->at( (unsigned) i );
m_fields->erase( m_fields->begin() + i, m_fields->begin() + i + 1 );
m_fields->insert( m_fields->begin() + i - 1, tmp );
m_fieldsGrid->ForceRefresh();
2008-11-26 00:20:16 +00:00
m_fieldsGrid->SetGridCursor( i - 1, m_fieldsGrid->GetGridCursorCol() );
m_fieldsGrid->MakeCellVisible( m_fieldsGrid->GetGridCursorRow(),
m_fieldsGrid->GetGridCursorCol() );
OnModify();
}
else
{
wxBell();
}
2008-11-26 00:20:16 +00:00
}
void DIALOG_SYMBOL_PROPERTIES::OnMoveDown( wxCommandEvent& event )
2008-11-26 00:20:16 +00:00
{
if( !m_fieldsGrid->CommitPendingChanges() )
return;
int i = m_fieldsGrid->GetGridCursorRow();
if( i >= MANDATORY_FIELDS && i < m_fieldsGrid->GetNumberRows() - 1 )
{
SCH_FIELD tmp = m_fields->at( (unsigned) i );
m_fields->erase( m_fields->begin() + i, m_fields->begin() + i + 1 );
m_fields->insert( m_fields->begin() + i + 1, tmp );
m_fieldsGrid->ForceRefresh();
m_fieldsGrid->SetGridCursor( i + 1, m_fieldsGrid->GetGridCursorCol() );
m_fieldsGrid->MakeCellVisible( m_fieldsGrid->GetGridCursorRow(),
m_fieldsGrid->GetGridCursorCol() );
OnModify();
}
else
Make the new schematic and symbol library file formats the default. This is a very large and potentially disruptive change so this will be an unusually long and detailed commit message. The new file formats are now the default in both the schematic and symbol library editors. Existing symbol libraries will be saved in their current format until new features are added to library symbols. Once this happens, both the legacy schematic and symbol file formats will be no longer be savable and existing libraries will have to be converted. Saving to the legacy file formats is still available for round robin testing and should not be used for normal editing. When loading the legacy schematic file, it is imperative that the schematic library symbols are rescued and/or remapped to valid library identifiers. Otherwise, there will be no way to link to the original library symbol and the user will be required manually set the library identifier. The cached symbol will be saved in the schematic file so the last library symbol in the cache will still be used but there will be no way to update it from the original library. The next save after loading a legacy schematic file will be converted to the s-expression file format. Schematics with hierarchical sheets will automatically have all sheet file name extensions changed to .kicad_sym and saved to the new format as well. Appending schematics requires that the schematic to append has already been converted to the new file format. This is required to ensure that library symbols are guaranteed to be valid for the appended schematic. The schematic symbol library symbol link resolution has been moved out of the SCH_COMPONENT object and move into the SCH_SCREEN object that owns the symbol. This was done to ensure that there is a single place where the library symbol links get resolved rather than the dozen or so different code paths that previously existed. It also removes the necessity of the SCH_COMPONENT object of requiring any knowledge of the symbol library table and/or the cache library. When opening an s-expression schematic, the legacy cache library is not loaded so any library symbols not rescued cannot be loaded. Broken library symbol links will have to be manually resolved by adding the cache library to the symbol library table and changing the links in the schematic symbol. Now that the library symbols are embedded in the schematic file, the SCH_SCREEN object maintains the list of library symbols for the schematic automatically. No external manipulation of this library cache should ever occur. ADDED: S-expression schematic and symbol library file formats.
2020-04-16 16:43:50 +00:00
{
wxBell();
Make the new schematic and symbol library file formats the default. This is a very large and potentially disruptive change so this will be an unusually long and detailed commit message. The new file formats are now the default in both the schematic and symbol library editors. Existing symbol libraries will be saved in their current format until new features are added to library symbols. Once this happens, both the legacy schematic and symbol file formats will be no longer be savable and existing libraries will have to be converted. Saving to the legacy file formats is still available for round robin testing and should not be used for normal editing. When loading the legacy schematic file, it is imperative that the schematic library symbols are rescued and/or remapped to valid library identifiers. Otherwise, there will be no way to link to the original library symbol and the user will be required manually set the library identifier. The cached symbol will be saved in the schematic file so the last library symbol in the cache will still be used but there will be no way to update it from the original library. The next save after loading a legacy schematic file will be converted to the s-expression file format. Schematics with hierarchical sheets will automatically have all sheet file name extensions changed to .kicad_sym and saved to the new format as well. Appending schematics requires that the schematic to append has already been converted to the new file format. This is required to ensure that library symbols are guaranteed to be valid for the appended schematic. The schematic symbol library symbol link resolution has been moved out of the SCH_COMPONENT object and move into the SCH_SCREEN object that owns the symbol. This was done to ensure that there is a single place where the library symbol links get resolved rather than the dozen or so different code paths that previously existed. It also removes the necessity of the SCH_COMPONENT object of requiring any knowledge of the symbol library table and/or the cache library. When opening an s-expression schematic, the legacy cache library is not loaded so any library symbols not rescued cannot be loaded. Broken library symbol links will have to be manually resolved by adding the cache library to the symbol library table and changing the links in the schematic symbol. Now that the library symbols are embedded in the schematic file, the SCH_SCREEN object maintains the list of library symbols for the schematic automatically. No external manipulation of this library cache should ever occur. ADDED: S-expression schematic and symbol library file formats.
2020-04-16 16:43:50 +00:00
}
}
void DIALOG_SYMBOL_PROPERTIES::OnEditSymbol( wxCommandEvent& )
{
if( TransferDataFromWindow() )
EndQuasiModal( SYMBOL_PROPS_EDIT_SCHEMATIC_SYMBOL );
}
void DIALOG_SYMBOL_PROPERTIES::OnEditLibrarySymbol( wxCommandEvent& )
{
if( TransferDataFromWindow() )
EndQuasiModal( SYMBOL_PROPS_EDIT_LIBRARY_SYMBOL );
}
void DIALOG_SYMBOL_PROPERTIES::OnUpdateSymbol( wxCommandEvent& )
{
if( TransferDataFromWindow() )
EndQuasiModal( SYMBOL_PROPS_WANT_UPDATE_SYMBOL );
}
void DIALOG_SYMBOL_PROPERTIES::OnExchangeSymbol( wxCommandEvent& )
{
if( TransferDataFromWindow() )
EndQuasiModal( SYMBOL_PROPS_WANT_EXCHANGE_SYMBOL );
2008-11-24 21:06:50 +00:00
}
void DIALOG_SYMBOL_PROPERTIES::OnPinTableCellEdited( wxGridEvent& aEvent )
{
int row = aEvent.GetRow();
if( m_pinGrid->GetCellValue( row, COL_ALT_NAME )
== m_dataModel->GetValue( row, COL_BASE_NAME ) )
{
m_dataModel->SetValue( row, COL_ALT_NAME, wxEmptyString );
}
// These are just to get the cells refreshed
m_dataModel->SetValue( row, COL_TYPE, m_dataModel->GetValue( row, COL_TYPE ) );
m_dataModel->SetValue( row, COL_SHAPE, m_dataModel->GetValue( row, COL_SHAPE ) );
OnModify();
}
void DIALOG_SYMBOL_PROPERTIES::OnPinTableColSort( wxGridEvent& aEvent )
{
int sortCol = aEvent.GetCol();
bool ascending;
// This is bonkers, but wxWidgets doesn't tell us ascending/descending in the
// event, and if we ask it will give us pre-event info.
if( m_pinGrid->IsSortingBy( sortCol ) )
// same column; invert ascending
ascending = !m_pinGrid->IsSortOrderAscending();
else
// different column; start with ascending
ascending = true;
m_dataModel->SortRows( sortCol, ascending );
m_dataModel->BuildAttrs();
}
void DIALOG_SYMBOL_PROPERTIES::AdjustFieldsGridColumns()
2008-11-24 21:06:50 +00:00
{
wxGridUpdateLocker deferRepaintsTillLeavingScope( m_fieldsGrid );
// Account for scroll bars
int fieldsWidth = KIPLATFORM::UI::GetUnobscuredSize( m_fieldsGrid ).x;
m_fieldsGrid->AutoSizeColumn( 0 );
int fixedColsWidth = m_fieldsGrid->GetColSize( 0 );
2008-11-24 21:06:50 +00:00
for( int i = 2; i < m_fieldsGrid->GetNumberCols(); i++ )
fixedColsWidth += m_fieldsGrid->GetColSize( i );
2008-11-24 21:06:50 +00:00
int colSize = std::max( fieldsWidth - fixedColsWidth, -1 );
colSize = ( colSize == 0 ) ? -1 : colSize; // don't hide the column!
m_fieldsGrid->SetColSize( 1, colSize );
}
void DIALOG_SYMBOL_PROPERTIES::AdjustPinsGridColumns()
{
wxGridUpdateLocker deferRepaintsTillLeavingScope( m_pinGrid );
// Account for scroll bars
int pinTblWidth = KIPLATFORM::UI::GetUnobscuredSize( m_pinGrid ).x;
// Stretch the Base Name and Alternate Assignment columns to fit.
for( int i = 0; i < COL_COUNT; ++i )
{
if( i != COL_BASE_NAME && i != COL_ALT_NAME )
pinTblWidth -= m_pinGrid->GetColSize( i );
}
2008-11-24 21:06:50 +00:00
m_pinGrid->SetColSize( COL_BASE_NAME, pinTblWidth / 2 );
m_pinGrid->SetColSize( COL_ALT_NAME, pinTblWidth / 2 );
}
void DIALOG_SYMBOL_PROPERTIES::OnUpdateUI( wxUpdateUIEvent& event )
{
wxString shownColumns = m_fieldsGrid->GetShownColumns();
if( shownColumns != m_shownColumns )
{
m_shownColumns = shownColumns;
if( !m_fieldsGrid->IsCellEditControlShown() )
AdjustFieldsGridColumns();
2008-11-26 00:20:16 +00:00
}
}
void DIALOG_SYMBOL_PROPERTIES::HandleDelayedFocus( wxCommandEvent& event )
{
VECTOR2I *loc = static_cast<VECTOR2I*>( event.GetClientData() );
wxCHECK_RET( loc, wxT( "Missing focus cell location" ) );
// Handle a delayed focus
m_fieldsGrid->SetFocus();
m_fieldsGrid->MakeCellVisible( loc->x, loc->y );
m_fieldsGrid->SetGridCursor( loc->x, loc->y );
m_fieldsGrid->EnableCellEditControl( true );
m_fieldsGrid->ShowCellEditControl();
delete loc;
}
void DIALOG_SYMBOL_PROPERTIES::HandleDelayedSelection( wxCommandEvent& event )
{
// Handle a delayed selection
wxGridCellEditor* cellEditor = m_fieldsGrid->GetCellEditor( REFERENCE_FIELD, FDC_VALUE );
if( wxTextEntry* txt = dynamic_cast<wxTextEntry*>( cellEditor->GetControl() ) )
KIUI::SelectReferenceNumber( txt );
cellEditor->DecRef(); // we're done; must release
}
void DIALOG_SYMBOL_PROPERTIES::OnSizeFieldsGrid( wxSizeEvent& event )
{
2021-12-17 04:00:24 +00:00
wxSize new_size = event.GetSize();
if( ( !m_editorShown || m_lastRequestedSize != new_size ) && m_fieldsSize != new_size )
{
2021-12-17 04:00:24 +00:00
m_fieldsSize = new_size;
AdjustFieldsGridColumns();
}
// We store this value to check whether the dialog is changing size. This might indicate
// that the user is scaling the dialog with an editor shown. Some editors do not close
// (at least on GTK) when the user drags a dialog corner
m_lastRequestedSize = new_size;
// Always propagate for a grid repaint (needed if the height changes, as well as width)
event.Skip();
}
void DIALOG_SYMBOL_PROPERTIES::OnSizePinsGrid( wxSizeEvent& event )
{
2021-12-17 04:00:24 +00:00
wxSize new_size = event.GetSize();
2021-12-17 04:00:24 +00:00
if( m_pinsSize != new_size )
{
2021-12-17 04:00:24 +00:00
m_pinsSize = new_size;
AdjustPinsGridColumns();
}
// Always propagate for a grid repaint (needed if the height changes, as well as width)
event.Skip();
}
void DIALOG_SYMBOL_PROPERTIES::OnInitDlg( wxInitDialogEvent& event )
{
TransferDataToWindow();
// Now all widgets have the size fixed, call FinishDialogSettings
2020-11-16 11:16:44 +00:00
finishDialogSettings();
}
void DIALOG_SYMBOL_PROPERTIES::OnCheckBox( wxCommandEvent& event )
{
OnModify();
}
void DIALOG_SYMBOL_PROPERTIES::OnUnitChoice( wxCommandEvent& event )
{
if( m_dataModel )
{
int unit_selection = m_unitChoice->GetSelection() + 1;
// We need to select a new unit to build the new unit pin list
// but we should not change the symbol, so the initial unit will be selected
// after rebuilding the pin list
int old_unit = m_symbol->GetUnit();
m_symbol->SetUnit( unit_selection );
// Rebuild a copy of the pins of the new unit for editing
m_dataModel->clear();
for( const std::unique_ptr<SCH_PIN>& pin : m_symbol->GetRawPins() )
m_dataModel->push_back( *pin );
m_dataModel->SortRows( COL_NUMBER, true );
m_dataModel->BuildAttrs();
m_symbol->SetUnit(old_unit );
}
OnModify();
}
void DIALOG_SYMBOL_PROPERTIES::onUpdateEditSymbol( wxUpdateUIEvent& event )
{
event.Enable( m_symbol && m_symbol->GetLibSymbolRef() );
}
void DIALOG_SYMBOL_PROPERTIES::onUpdateEditLibrarySymbol( wxUpdateUIEvent& event )
{
event.Enable( m_symbol && m_symbol->GetLibSymbolRef() );
}