2017-03-06 14:50:48 +00:00
|
|
|
/*
|
|
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2017 Chris Pavlina <pavlina.chris@gmail.com>
|
|
|
|
* Copyright (C) 2014 Henner Zeller <h.zeller@acm.org>
|
|
|
|
* Copyright (C) 2014-2017 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 <cmp_tree_model_adapter.h>
|
|
|
|
|
|
|
|
#include <eda_pattern_match.h>
|
|
|
|
#include <wx/tokenzr.h>
|
2017-09-15 14:17:44 +00:00
|
|
|
#include <symbol_lib_table.h>
|
2017-03-06 14:50:48 +00:00
|
|
|
|
|
|
|
|
|
|
|
CMP_TREE_MODEL_ADAPTER::WIDTH_CACHE CMP_TREE_MODEL_ADAPTER::m_width_cache;
|
|
|
|
|
|
|
|
|
|
|
|
static const int kDataViewIndent = 20;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert CMP_TREE_NODE -> wxDataViewItem
|
|
|
|
*/
|
|
|
|
static wxDataViewItem ToItem( CMP_TREE_NODE const* aNode )
|
|
|
|
{
|
|
|
|
return wxDataViewItem( const_cast<void*>( static_cast<void const*>( aNode ) ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert wxDataViewItem -> CMP_TREE_NODE
|
|
|
|
*/
|
|
|
|
static CMP_TREE_NODE const* ToNode( wxDataViewItem aItem )
|
|
|
|
{
|
|
|
|
return static_cast<CMP_TREE_NODE const*>( aItem.GetID() );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert CMP_TREE_NODE's children to wxDataViewItemArray
|
|
|
|
*/
|
|
|
|
static unsigned int IntoArray( CMP_TREE_NODE const& aNode, wxDataViewItemArray& aChildren )
|
|
|
|
{
|
|
|
|
unsigned int n = 0;
|
|
|
|
|
|
|
|
for( auto const& child: aNode.Children )
|
|
|
|
{
|
|
|
|
if( child->Score > 0 )
|
|
|
|
{
|
|
|
|
aChildren.Add( ToItem( &*child ) );
|
|
|
|
++n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-09-15 14:17:44 +00:00
|
|
|
CMP_TREE_MODEL_ADAPTER::PTR CMP_TREE_MODEL_ADAPTER::Create( SYMBOL_LIB_TABLE* aLibs )
|
2017-03-06 14:50:48 +00:00
|
|
|
{
|
|
|
|
auto adapter = new CMP_TREE_MODEL_ADAPTER( aLibs );
|
|
|
|
auto container = CMP_TREE_MODEL_ADAPTER::PTR( adapter );
|
|
|
|
return container;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-09-15 14:17:44 +00:00
|
|
|
CMP_TREE_MODEL_ADAPTER::CMP_TREE_MODEL_ADAPTER( SYMBOL_LIB_TABLE* aLibs )
|
2017-03-06 14:50:48 +00:00
|
|
|
:m_filter( CMP_FILTER_NONE ),
|
|
|
|
m_show_units( true ),
|
|
|
|
m_libs( aLibs ),
|
2017-03-12 00:22:42 +00:00
|
|
|
m_preselect_unit( 0 ),
|
|
|
|
m_col_part( nullptr ),
|
|
|
|
m_col_desc( nullptr ),
|
|
|
|
m_widget( nullptr )
|
2017-03-06 14:50:48 +00:00
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
|
|
CMP_TREE_MODEL_ADAPTER::~CMP_TREE_MODEL_ADAPTER()
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
|
|
void CMP_TREE_MODEL_ADAPTER::SetFilter( CMP_FILTER_TYPE aFilter )
|
|
|
|
{
|
|
|
|
m_filter = aFilter;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CMP_TREE_MODEL_ADAPTER::ShowUnits( bool aShow )
|
|
|
|
{
|
|
|
|
m_show_units = aShow;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-09-15 14:17:44 +00:00
|
|
|
void CMP_TREE_MODEL_ADAPTER::SetPreselectNode( LIB_ID const& aLibId, int aUnit )
|
2017-03-06 14:50:48 +00:00
|
|
|
{
|
2017-09-15 14:17:44 +00:00
|
|
|
m_preselect_lib_id = aLibId;
|
2017-03-06 14:50:48 +00:00
|
|
|
m_preselect_unit = aUnit;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-09-15 14:17:44 +00:00
|
|
|
void CMP_TREE_MODEL_ADAPTER::AddLibrary( wxString const& aLibNickname )
|
2017-03-06 14:50:48 +00:00
|
|
|
{
|
2017-09-15 14:17:44 +00:00
|
|
|
bool onlyPowerSymbols = ( m_filter == CMP_FILTER_POWER );
|
|
|
|
|
|
|
|
wxArrayString aliases;
|
|
|
|
|
|
|
|
try
|
2017-03-06 14:50:48 +00:00
|
|
|
{
|
2017-09-15 14:17:44 +00:00
|
|
|
m_libs->EnumerateSymbolLib( aLibNickname, aliases, onlyPowerSymbols );
|
2017-03-06 14:50:48 +00:00
|
|
|
}
|
2017-09-15 14:17:44 +00:00
|
|
|
catch( const IO_ERROR& ioe )
|
2017-03-06 14:50:48 +00:00
|
|
|
{
|
2017-09-15 14:17:44 +00:00
|
|
|
wxLogError( wxString::Format( _( "Error occurred loading symbol library %s."
|
|
|
|
"\n\n%s" ), aLibNickname, ioe.What() ) );
|
|
|
|
return;
|
2017-03-06 14:50:48 +00:00
|
|
|
}
|
|
|
|
|
2017-09-15 14:17:44 +00:00
|
|
|
AddAliasList( aLibNickname, aliases );
|
|
|
|
|
2017-03-06 14:50:48 +00:00
|
|
|
m_tree.AssignIntrinsicRanks();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CMP_TREE_MODEL_ADAPTER::AddAliasList(
|
|
|
|
wxString const& aNodeName,
|
2017-09-15 14:17:44 +00:00
|
|
|
wxArrayString const& aAliasNameList )
|
2017-03-06 14:50:48 +00:00
|
|
|
{
|
|
|
|
std::vector<LIB_ALIAS*> alias_list;
|
|
|
|
|
|
|
|
for( const wxString& name: aAliasNameList )
|
|
|
|
{
|
2017-09-15 14:17:44 +00:00
|
|
|
LIB_ALIAS* a = nullptr;
|
2017-03-06 14:50:48 +00:00
|
|
|
|
2017-09-15 14:17:44 +00:00
|
|
|
try
|
|
|
|
{
|
|
|
|
a = m_libs->LoadSymbol( aNodeName, name );
|
|
|
|
}
|
|
|
|
catch( const IO_ERROR& ioe )
|
|
|
|
{
|
|
|
|
wxLogError( wxString::Format( _( "Error occurred loading symbol %s from library %s."
|
|
|
|
"\n\n%s" ), name, aNodeName, ioe.What() ) );
|
|
|
|
continue;
|
|
|
|
}
|
2017-03-06 14:50:48 +00:00
|
|
|
|
|
|
|
if( a )
|
|
|
|
alias_list.push_back( a );
|
|
|
|
}
|
|
|
|
|
2017-09-15 14:17:44 +00:00
|
|
|
AddAliasList( aNodeName, alias_list );
|
2017-03-06 14:50:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CMP_TREE_MODEL_ADAPTER::AddAliasList(
|
|
|
|
wxString const& aNodeName,
|
2017-09-15 14:17:44 +00:00
|
|
|
std::vector<LIB_ALIAS*> const& aAliasList )
|
2017-03-06 14:50:48 +00:00
|
|
|
{
|
|
|
|
auto& lib_node = m_tree.AddLib( aNodeName );
|
|
|
|
|
|
|
|
for( auto a: aAliasList )
|
|
|
|
{
|
|
|
|
lib_node.AddAlias( a );
|
|
|
|
}
|
|
|
|
|
|
|
|
lib_node.AssignIntrinsicRanks();
|
|
|
|
m_tree.AssignIntrinsicRanks();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CMP_TREE_MODEL_ADAPTER::UpdateSearchString( wxString const& aSearch )
|
|
|
|
{
|
|
|
|
m_tree.ResetScore();
|
|
|
|
|
|
|
|
wxStringTokenizer tokenizer( aSearch );
|
|
|
|
|
|
|
|
while( tokenizer.HasMoreTokens() )
|
|
|
|
{
|
|
|
|
const wxString term = tokenizer.GetNextToken().Lower();
|
|
|
|
EDA_COMBINED_MATCHER matcher( term );
|
|
|
|
|
|
|
|
m_tree.UpdateScore( matcher );
|
|
|
|
}
|
|
|
|
|
|
|
|
m_tree.SortNodes();
|
|
|
|
Cleared();
|
|
|
|
AttachTo( m_widget );
|
|
|
|
|
|
|
|
ShowResults() || ShowPreselect() || ShowSingleLibrary();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CMP_TREE_MODEL_ADAPTER::AttachTo( wxDataViewCtrl* aDataViewCtrl )
|
|
|
|
{
|
|
|
|
m_widget = aDataViewCtrl;
|
|
|
|
aDataViewCtrl->Freeze();
|
|
|
|
aDataViewCtrl->SetIndent( kDataViewIndent );
|
|
|
|
aDataViewCtrl->AssociateModel( this );
|
|
|
|
aDataViewCtrl->ClearColumns();
|
2017-03-07 19:59:20 +00:00
|
|
|
|
|
|
|
wxString part_head = _( "Part" );
|
|
|
|
wxString desc_head = _( "Desc" );
|
|
|
|
|
|
|
|
m_col_part = aDataViewCtrl->AppendTextColumn( part_head, 0, wxDATAVIEW_CELL_INERT,
|
|
|
|
ColWidth( m_tree, 0, part_head ) );
|
|
|
|
m_col_desc = aDataViewCtrl->AppendTextColumn( desc_head, 1, wxDATAVIEW_CELL_INERT,
|
|
|
|
ColWidth( m_tree, 1, desc_head ) );
|
2017-03-06 14:50:48 +00:00
|
|
|
aDataViewCtrl->Thaw();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-09-15 14:17:44 +00:00
|
|
|
LIB_ID CMP_TREE_MODEL_ADAPTER::GetAliasFor( wxDataViewItem aSelection ) const
|
2017-03-06 14:50:48 +00:00
|
|
|
{
|
|
|
|
auto node = ToNode( aSelection );
|
2017-09-15 14:17:44 +00:00
|
|
|
|
|
|
|
LIB_ID emptyId;
|
|
|
|
|
|
|
|
if( !node )
|
|
|
|
return emptyId;
|
|
|
|
|
|
|
|
return node->LibId;
|
2017-03-06 14:50:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int CMP_TREE_MODEL_ADAPTER::GetUnitFor( wxDataViewItem aSelection ) const
|
|
|
|
{
|
|
|
|
auto node = ToNode( aSelection );
|
|
|
|
return node ? node->Unit : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int CMP_TREE_MODEL_ADAPTER::GetComponentsCount() const
|
|
|
|
{
|
|
|
|
int n = 0;
|
|
|
|
|
|
|
|
for( auto& lib: m_tree.Children )
|
|
|
|
{
|
|
|
|
for( auto& alias: lib->Children )
|
|
|
|
{
|
|
|
|
(void) alias;
|
|
|
|
++n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CMP_TREE_MODEL_ADAPTER::HasContainerColumns( wxDataViewItem const& aItem ) const
|
|
|
|
{
|
|
|
|
return IsContainer( aItem );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CMP_TREE_MODEL_ADAPTER::IsContainer( wxDataViewItem const& aItem ) const
|
|
|
|
{
|
|
|
|
auto node = ToNode( aItem );
|
|
|
|
return node ? node->Children.size() : true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
wxDataViewItem CMP_TREE_MODEL_ADAPTER::GetParent( wxDataViewItem const& aItem ) const
|
|
|
|
{
|
|
|
|
auto node = ToNode( aItem );
|
|
|
|
auto parent = node ? node->Parent : nullptr;
|
|
|
|
|
|
|
|
// wxDataViewModel has no root node, but rather top-level elements have
|
|
|
|
// an invalid (null) parent.
|
|
|
|
if( !node || !parent || parent->Type == CMP_TREE_NODE::TYPE::ROOT )
|
|
|
|
{
|
|
|
|
return ToItem( nullptr );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return ToItem( parent );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
unsigned int CMP_TREE_MODEL_ADAPTER::GetChildren(
|
|
|
|
wxDataViewItem const& aItem,
|
|
|
|
wxDataViewItemArray& aChildren ) const
|
|
|
|
{
|
|
|
|
auto node = ( aItem.IsOk() ? ToNode( aItem ) : &m_tree );
|
|
|
|
|
|
|
|
if( node->Type != CMP_TREE_NODE::TYPE::ALIAS || m_show_units )
|
|
|
|
return IntoArray( *node, aChildren );
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CMP_TREE_MODEL_ADAPTER::GetValue(
|
|
|
|
wxVariant& aVariant,
|
|
|
|
wxDataViewItem const& aItem,
|
|
|
|
unsigned int aCol ) const
|
|
|
|
{
|
|
|
|
auto node = ToNode( aItem );
|
|
|
|
wxASSERT( node );
|
|
|
|
|
|
|
|
switch( aCol )
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
aVariant = node->Name;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
aVariant = node->Desc;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
wxFAIL_MSG( "Invalid column ID!" );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-03-27 11:54:51 +00:00
|
|
|
bool CMP_TREE_MODEL_ADAPTER::GetAttr(
|
|
|
|
wxDataViewItem const& aItem,
|
|
|
|
unsigned int aCol,
|
|
|
|
wxDataViewItemAttr& aAttr ) const
|
|
|
|
{
|
|
|
|
auto node = ToNode( aItem );
|
|
|
|
wxASSERT( node );
|
|
|
|
|
|
|
|
if( node->Type != CMP_TREE_NODE::ALIAS )
|
|
|
|
{
|
|
|
|
// Currently only aliases are formatted at all
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-09-15 14:17:44 +00:00
|
|
|
if( !node->IsRoot && aCol == 0 )
|
2017-03-27 11:54:51 +00:00
|
|
|
{
|
|
|
|
// Names of non-root aliases are italicized
|
|
|
|
aAttr.SetItalic( true );
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-03-07 19:59:20 +00:00
|
|
|
int CMP_TREE_MODEL_ADAPTER::ColWidth( CMP_TREE_NODE& aTree, int aCol, wxString const& aHeading )
|
2017-03-06 14:50:48 +00:00
|
|
|
{
|
|
|
|
const int indent = aCol ? 0 : kDataViewIndent;
|
|
|
|
|
2017-03-07 19:59:20 +00:00
|
|
|
int min_width = WidthFor( aHeading, aCol );
|
|
|
|
int width = std::max( aTree.Score > 0 ? WidthFor( aTree, aCol ) : 0, min_width );
|
|
|
|
|
|
|
|
if( aTree.Score > 0 )
|
2017-03-06 14:50:48 +00:00
|
|
|
{
|
2017-03-07 19:59:20 +00:00
|
|
|
for( auto& node: aTree.Children )
|
|
|
|
{
|
|
|
|
width = std::max( width, ColWidth( *node, aCol, aHeading ) + indent );
|
|
|
|
}
|
2017-03-06 14:50:48 +00:00
|
|
|
}
|
|
|
|
|
2017-03-07 19:59:20 +00:00
|
|
|
return width;
|
2017-03-06 14:50:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int CMP_TREE_MODEL_ADAPTER::WidthFor( CMP_TREE_NODE& aNode, int aCol )
|
|
|
|
{
|
2017-03-08 19:41:34 +00:00
|
|
|
auto result = m_width_cache.find( aNode.Name );
|
2017-03-06 14:50:48 +00:00
|
|
|
|
|
|
|
if( result != m_width_cache.end() )
|
|
|
|
{
|
|
|
|
return result->second[aCol];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int wname = m_widget->GetTextExtent( aNode.Name ).x + kDataViewIndent;
|
|
|
|
int wdesc = m_widget->GetTextExtent( aNode.Desc ).x;
|
|
|
|
|
2017-03-08 19:41:34 +00:00
|
|
|
auto& val = m_width_cache[aNode.Name];
|
|
|
|
val.push_back( wname );
|
|
|
|
val.push_back( wdesc );
|
|
|
|
return val[aCol];
|
2017-03-06 14:50:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-03-07 19:59:20 +00:00
|
|
|
int CMP_TREE_MODEL_ADAPTER::WidthFor( wxString const& aHeading, int aCol )
|
|
|
|
{
|
|
|
|
static std::vector<int> widths;
|
|
|
|
|
|
|
|
for( int i = (int) widths.size(); i <= aCol; ++i )
|
|
|
|
{
|
|
|
|
widths.push_back( 0 );
|
|
|
|
}
|
|
|
|
|
|
|
|
if( widths[aCol] == 0 )
|
|
|
|
{
|
|
|
|
widths[aCol] = m_widget->GetTextExtent( aHeading ).x;
|
|
|
|
}
|
|
|
|
|
|
|
|
return widths[aCol];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-03-22 02:42:49 +00:00
|
|
|
bool CMP_TREE_MODEL_ADAPTER::FindAndExpand(
|
|
|
|
CMP_TREE_NODE& aNode,
|
|
|
|
std::function<bool( CMP_TREE_NODE const* )> aFunc )
|
|
|
|
{
|
|
|
|
for( auto& node: aNode.Children )
|
|
|
|
{
|
|
|
|
if( aFunc( &*node ) )
|
|
|
|
{
|
|
|
|
auto item = wxDataViewItem(
|
|
|
|
const_cast<void*>( static_cast<void const*>( &*node ) ) );
|
|
|
|
m_widget->ExpandAncestors( item );
|
|
|
|
m_widget->EnsureVisible( item );
|
|
|
|
m_widget->Select( item );
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if( FindAndExpand( *node, aFunc ) )
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-03-06 14:50:48 +00:00
|
|
|
bool CMP_TREE_MODEL_ADAPTER::ShowResults()
|
|
|
|
{
|
|
|
|
return FindAndExpand( m_tree,
|
|
|
|
[]( CMP_TREE_NODE const* n )
|
|
|
|
{
|
|
|
|
return n->Type == CMP_TREE_NODE::TYPE::ALIAS && n->Score > 1;
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CMP_TREE_MODEL_ADAPTER::ShowPreselect()
|
|
|
|
{
|
2017-09-15 14:17:44 +00:00
|
|
|
if( !m_preselect_lib_id.IsValid() )
|
2017-03-06 14:50:48 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
return FindAndExpand( m_tree,
|
|
|
|
[&]( CMP_TREE_NODE const* n )
|
|
|
|
{
|
2017-03-22 02:35:35 +00:00
|
|
|
if( n->Type == CMP_TREE_NODE::ALIAS && ( n->Children.empty() || !m_preselect_unit ) )
|
2017-09-15 14:17:44 +00:00
|
|
|
return m_preselect_lib_id == n->LibId;
|
2017-03-22 02:35:35 +00:00
|
|
|
else if( n->Type == CMP_TREE_NODE::UNIT && m_preselect_unit )
|
2017-09-15 14:17:44 +00:00
|
|
|
return m_preselect_lib_id == n->Parent->LibId && m_preselect_unit == n->Unit;
|
2017-03-22 02:35:35 +00:00
|
|
|
else
|
|
|
|
return false;
|
2017-03-06 14:50:48 +00:00
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CMP_TREE_MODEL_ADAPTER::ShowSingleLibrary()
|
|
|
|
{
|
|
|
|
return FindAndExpand( m_tree,
|
|
|
|
[]( CMP_TREE_NODE const* n )
|
|
|
|
{
|
|
|
|
return n->Type == CMP_TREE_NODE::TYPE::ALIAS &&
|
|
|
|
n->Parent->Parent->Children.size() == 1;
|
|
|
|
} );
|
|
|
|
}
|