Extracted CMP_TREE_MODEL_ADAPTER_BASE class

This commit is contained in:
Maciej Suminski 2017-10-28 20:28:14 +02:00
parent 3288a5f8b8
commit 0d190639f1
8 changed files with 839 additions and 723 deletions

View File

@ -124,6 +124,7 @@ set( EESCHEMA_SRCS
getpart.cpp
cmp_tree_model.cpp
cmp_tree_model_adapter.cpp
cmp_tree_model_adapter_base.cpp
generate_alias_info.cpp
hierarch.cpp
highlight_connection.cpp

View File

@ -133,7 +133,6 @@ CMP_TREE_NODE_LIB_ID::CMP_TREE_NODE_LIB_ID( CMP_TREE_NODE* aParent, LIB_ALIAS* a
Type = LIBID;
Parent = aParent;
Type = ALIAS;
Name = aAlias->GetName();
Desc = aAlias->GetDescription();

View File

@ -26,51 +26,7 @@
#include <symbol_lib_table.h>
#include <wx/progdlg.h>
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;
}
CMP_TREE_MODEL_ADAPTER::PTR CMP_TREE_MODEL_ADAPTER::Create( SYMBOL_LIB_TABLE* aLibs )
CMP_TREE_MODEL_ADAPTER_BASE::PTR CMP_TREE_MODEL_ADAPTER::Create( SYMBOL_LIB_TABLE* aLibs )
{
auto adapter = new CMP_TREE_MODEL_ADAPTER( aLibs );
auto container = CMP_TREE_MODEL_ADAPTER::PTR( adapter );
@ -79,13 +35,7 @@ CMP_TREE_MODEL_ADAPTER::PTR CMP_TREE_MODEL_ADAPTER::Create( SYMBOL_LIB_TABLE* aL
CMP_TREE_MODEL_ADAPTER::CMP_TREE_MODEL_ADAPTER( SYMBOL_LIB_TABLE* aLibs )
:m_filter( CMP_FILTER_NONE ),
m_show_units( true ),
m_libs( aLibs ),
m_preselect_unit( 0 ),
m_col_part( nullptr ),
m_col_desc( nullptr ),
m_widget( nullptr )
: m_libs( aLibs )
{}
@ -93,28 +43,9 @@ 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;
}
void CMP_TREE_MODEL_ADAPTER::SetPreselectNode( LIB_ID const& aLibId, int aUnit )
{
m_preselect_lib_id = aLibId;
m_preselect_unit = aUnit;
}
void CMP_TREE_MODEL_ADAPTER::AddLibrary( wxString const& aLibNickname )
{
bool onlyPowerSymbols = ( m_filter == CMP_FILTER_POWER );
bool onlyPowerSymbols = ( GetFilter() == CMP_FILTER_POWER );
wxArrayString aliases;
@ -135,26 +66,6 @@ void CMP_TREE_MODEL_ADAPTER::AddLibrary( wxString const& aLibNickname )
}
void CMP_TREE_MODEL_ADAPTER::AddLibrariesWithProgress( const std::vector<wxString>& aNicknames, EDA_DRAW_FRAME* aParent )
{
auto* prg = new wxProgressDialog(
_( "Loading symbol libraries" ),
wxEmptyString,
aNicknames.size(),
aParent );
unsigned int ii = 0;
for( auto nickname : aNicknames )
{
prg->Update( ii++, wxString::Format( _( "Loading library '%s'" ), nickname ) );
AddLibrary( nickname );
}
prg->Destroy();
}
void CMP_TREE_MODEL_ADAPTER::AddAliasList(
wxString const& aNodeName,
wxArrayString const& aAliasNameList )
@ -182,319 +93,3 @@ void CMP_TREE_MODEL_ADAPTER::AddAliasList(
AddAliasList( aNodeName, alias_list );
}
void CMP_TREE_MODEL_ADAPTER::AddAliasList(
wxString const& aNodeName,
std::vector<LIB_ALIAS*> const& aAliasList )
{
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();
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 ) );
aDataViewCtrl->Thaw();
}
LIB_ID CMP_TREE_MODEL_ADAPTER::GetAliasFor( const wxDataViewItem& aSelection ) const
{
auto node = ToNode( aSelection );
LIB_ID emptyId;
if( !node )
return emptyId;
return node->LibId;
}
int CMP_TREE_MODEL_ADAPTER::GetUnitFor( const wxDataViewItem& aSelection ) const
{
auto node = ToNode( aSelection );
return node ? node->Unit : 0;
}
CMP_TREE_NODE::TYPE CMP_TREE_MODEL_ADAPTER::GetTypeFor( const wxDataViewItem& aSelection ) const
{
auto node = ToNode( aSelection );
return node ? node->Type : CMP_TREE_NODE::INVALID;
}
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::LIBID || 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!" );
}
}
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::LIBID )
{
// Currently only aliases are formatted at all
return false;
}
if( !node->IsRoot && aCol == 0 )
{
// Names of non-root aliases are italicized
aAttr.SetItalic( true );
return true;
}
else
{
return false;
}
}
int CMP_TREE_MODEL_ADAPTER::ColWidth( CMP_TREE_NODE& aTree, int aCol, wxString const& aHeading )
{
const int indent = aCol ? 0 : kDataViewIndent;
int min_width = WidthFor( aHeading, aCol );
int width = std::max( aTree.Score > 0 ? WidthFor( aTree, aCol ) : 0, min_width );
if( aTree.Score > 0 )
{
for( auto& node: aTree.Children )
{
width = std::max( width, ColWidth( *node, aCol, aHeading ) + indent );
}
}
return width;
}
int CMP_TREE_MODEL_ADAPTER::WidthFor( CMP_TREE_NODE& aNode, int aCol )
{
auto result = m_width_cache.find( aNode.Name );
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;
auto& val = m_width_cache[aNode.Name];
val.push_back( wname );
val.push_back( wdesc );
return val[aCol];
}
}
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];
}
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;
}
bool CMP_TREE_MODEL_ADAPTER::ShowResults()
{
return FindAndExpand( m_tree,
[]( CMP_TREE_NODE const* n )
{
return n->Type == CMP_TREE_NODE::TYPE::LIBID && n->Score > 1;
} );
}
bool CMP_TREE_MODEL_ADAPTER::ShowPreselect()
{
if( !m_preselect_lib_id.IsValid() )
return false;
return FindAndExpand( m_tree,
[&]( CMP_TREE_NODE const* n )
{
if( n->Type == CMP_TREE_NODE::LIBID && ( n->Children.empty() || !m_preselect_unit ) )
return m_preselect_lib_id == n->LibId;
else if( n->Type == CMP_TREE_NODE::UNIT && m_preselect_unit )
return m_preselect_lib_id == n->Parent->LibId && m_preselect_unit == n->Unit;
else
return false;
} );
}
bool CMP_TREE_MODEL_ADAPTER::ShowSingleLibrary()
{
return FindAndExpand( m_tree,
[]( CMP_TREE_NODE const* n )
{
return n->Type == CMP_TREE_NODE::TYPE::LIBID &&
n->Parent->Parent->Children.size() == 1;
} );
}

View File

@ -22,84 +22,18 @@
#ifndef _CMP_TREE_MODEL_ADAPTER_H
#define _CMP_TREE_MODEL_ADAPTER_H
#include <lib_id.h>
#include <draw_frame.h>
#include <cmp_tree_model.h>
#include <wx/hashmap.h>
#include <wx/dataview.h>
#include <vector>
#include <functional>
#include <cmp_tree_model_adapter_base.h>
class SYMBOL_LIB_TABLE;
/**
* Adapter class in the component selector Model-View-Adapter (mediated MVC)
* architecture. The other pieces are in:
*
* - Model: CMP_TREE_NODE and descendants in eeschema/cmp_tree_model.h
* - View:
* - DIALOG_CHOOSE_COMPONENT in eeschema/dialogs/dialog_choose_component.h
* - wxDataViewCtrl
*
* This adapter presents the interface specified by wxDataViewModel to the
* wxDataViewCtrl:
*
* +---+ +------------------+
* +---+ Generates | A | | VIEW |
* | M | from libs | D | wxDataViewModel |------------------|
* | O | <---------- | A | <------------------> | wxDataViewCtrl |
* | D | | P | |------------------|
* | E | <---------> | T | <------------------- | wxTextCtrl |
* | L | UpdateScore | E | UpdateSearchString() |------------------|
* +---+ | R | | |
* +---+ +------------------+
*
* Because this adapter is a wxDataViewModel, it is reference-counted by
* wxObject. To ensure this interface is used correctly, the constructor
* is private; CMP_TREE_MODEL_ADAPTER should be created by the static
* factory method CMP_TREE_MODEL_ADAPTER::Create().
*
* Quick summary of methods used to drive this class:
*
* - `SetFilter()` - set whether the view is restricted to power parts
* - `ShowUnits()` - set whether units are displayed
* - `SetPreselectNode()` - set a node to highlight when not searching
* - `AddLibrary()` - populate the model with all aliases in a library
* - `AddAliasList()` - populate the model with a specific list of aliases
*
* Quick summary of methods used by the View:
*
* - `UpdateSearchString()` - pass in the user's search text
* - `AttachTo()` - pass in the wxDataViewCtrl
* - `GetAliasFor()` - get the LIB_ALIAS* for a selected item
* - `GetUnitFor()` - get the unit for a selected item
* - `GetComponentsCount()` - count the aliases loaded
*
* Methods implemented as part of wxDataViewModel:
*
* - `HasContainerColumns()` - whether a parent item has more than one column
* - `IsContainer()` - whether an item is a parent
* - `GetParent()` - return the parent of an item, or invalid if root
* - `GetChildren()` - get the children of an item
* - `GetColumnCount()` - get the number of columns in the view
* - `GetColumnType()` - get the data type shown in each column
* - `GetValue()` - get the data shown in a cell
* - `SetValue()` - edit the data in a cell (does nothing)
* - `GetAttr()` - get any per-item formatting
* - `Compare()` - compare two rows, for sorting
* - `HasDefaultCompare()` - whether sorted by default
*/
class CMP_TREE_MODEL_ADAPTER: public wxDataViewModel
class CMP_TREE_MODEL_ADAPTER: public CMP_TREE_MODEL_ADAPTER_BASE
{
public:
/**
* Reference-counting container for a pointer to CMP_TREE_MODEL_ADAPTER.
*/
typedef wxObjectDataPtr<CMP_TREE_MODEL_ADAPTER> PTR;
//typedef wxObjectDataPtr<CMP_TREE_MODEL_ADAPTER> PTR;
/**
* Destructor. Do NOT delete this class manually; it is reference-counted
@ -115,57 +49,13 @@ public:
*/
static PTR Create( SYMBOL_LIB_TABLE* aLibs );
/**
* This enum allows a selective filtering of components to list
*/
enum CMP_FILTER_TYPE
{
CMP_FILTER_NONE, ///< no filtering
CMP_FILTER_POWER, ///< list components flagged PWR
};
/**
* Set the component filter type. Must be set before adding libraries
*
* @param aFilter if CMP_FILTER_POWER, only power parts are loaded
*/
void SetFilter( CMP_FILTER_TYPE aFilter );
/**
* Whether or not to show units. May be set at any time; updates at the next
* UpdateSearchString()
*
* @param aShow if true, units are displayed
*/
void ShowUnits( bool aShow );
/**
* Set the component name to be selected if there are no search results.
* May be set at any time; updates at the next UpdateSearchString().
*
* @param aLibId symbol #LIB_ID to be selected
* @param aUnit unit to be selected, if > 0 (0 selects the alias itself)
*/
void SetPreselectNode( LIB_ID const& aLibId, int aUnit );
/**
* Add all the components and their aliases in this library. To be called
* in the setup phase.
*
* @param aLibNickname reference to a symbol library nickname
*/
void AddLibrary( wxString const& aLibNickname );
/**
* Add all the libraries in a SYMBOL_LIB_TABLE to the model,
* displaying a progress dialog attached to the parent frame
*
* @param aNicknames is the list of library nicknames
* @param aParent is the parent window to display the progress dialog
*/
void AddLibrariesWithProgress( const std::vector<wxString>& aNicknames, EDA_DRAW_FRAME* aParent );
void AddLibrary( wxString const& aLibNickname ) override;
/**
* Add the given list of components, by name. To be called in the setup
@ -176,70 +66,9 @@ public:
*/
void AddAliasList(
wxString const& aNodeName,
wxArrayString const& aAliasNameList );
/**
* Add the given list of components by alias. To be called in the setup
* phase.
*
* @param aNodeName the parent node the components will appear under
* @param aAliasList list of aliases
*/
void AddAliasList(
wxString const& aNodeName,
std::vector<LIB_ALIAS*> const& aAliasList );
/**
* Set the search string provided by the user.
*
* @param aSearch full, unprocessed search text
*/
void UpdateSearchString( wxString const& aSearch );
/**
* Attach to a wxDataViewCtrl and initialize it. This will set up columns
* and associate the model via the adapter.
*
* @param aDataViewCtrl the view component in the dialog
*/
void AttachTo( wxDataViewCtrl* aDataViewCtrl );
/**
* Return the alias for the given item.
*
* @param aSelection item from the wxDataViewCtrl
* (see wxDataViewCtrl::GetSelection())
*
* @return alias, or nullptr if none is selected
*/
LIB_ID GetAliasFor( const wxDataViewItem& aSelection ) const;
/**
* Return the unit for the given item.
*
* @param aSelection item from the wxDataViewCtrl
* (see wxDataViewCtrl::GetSelection())
*
* @return Unit, or zero if the alias itself is selected. Return valid is
* invalid if GetAliasFor() returns nullptr.
*/
int GetUnitFor( const wxDataViewItem& aSelection ) const;
/**
* Return node type for the given item.
*
* @param aSelection item from the wxDataViewCtrl
* (see wxDataViewCtrl::GetSelection())
*
* @return Type of the selected node, might be INVALID.
*/
CMP_TREE_NODE::TYPE GetTypeFor( const wxDataViewItem& aSelection ) const;
/**
* Return the number of components loaded in the tree.
*/
int GetComponentsCount() const;
wxArrayString const& aAliasNameList ) override;
using CMP_TREE_MODEL_ADAPTER_BASE::AddAliasList;
protected:
/**
@ -247,143 +76,8 @@ protected:
*/
CMP_TREE_MODEL_ADAPTER( SYMBOL_LIB_TABLE* aLibs );
/**
* Check whether a container has columns too
*/
virtual bool HasContainerColumns( wxDataViewItem const& aItem ) const override;
/**
* Check whether an item can have children.
*/
virtual bool IsContainer( wxDataViewItem const& aItem ) const override;
/**
* Get the parent of an item.
*
* @param aItem item to get the parent of
* @return parent of aItem, or an invalid wxDataViewItem if parent is root
*/
virtual wxDataViewItem GetParent( wxDataViewItem const& aItem ) const override;
/**
* Populate a list of all the children of an item
*
* @return number of children
*/
virtual unsigned int GetChildren(
wxDataViewItem const& aItem,
wxDataViewItemArray& aChildren ) const override;
/**
* Return the number of columns in the model
*/
virtual unsigned int GetColumnCount() const override { return 2; }
/**
* Return the type of data stored in the column
*
* @return type of data as indicated by wxVariant::GetType()
*/
virtual wxString GetColumnType( unsigned int aCol ) const override { return "string"; }
/**
* Get the value of an item.
*
* @param aVariant wxVariant to receive the data
* @param aItem item whose data will be placed into aVariant
* @param aCol column number of the data
*/
virtual void GetValue(
wxVariant& aVariant,
wxDataViewItem const& aItem,
unsigned int aCol ) const override;
/**
* Set the value of an item. Does nothing - this model doesn't support
* editing.
*/
virtual bool SetValue(
wxVariant const& aVariant,
wxDataViewItem const& aItem,
unsigned int aCol ) override { return false; }
/**
* Get any formatting for an item.
*
* @param aItem item to get formatting for
* @param aCol column number of interest
* @param aAttr receiver for attributes
* @return true iff the item has non-default attributes
*/
virtual bool GetAttr(
wxDataViewItem const& aItem,
unsigned int aCol,
wxDataViewItemAttr& aAttr ) const override;
private:
CMP_FILTER_TYPE m_filter;
bool m_show_units;
SYMBOL_LIB_TABLE* m_libs;
LIB_ID m_preselect_lib_id;
int m_preselect_unit;
CMP_TREE_NODE_ROOT m_tree;
wxDataViewColumn* m_col_part;
wxDataViewColumn* m_col_desc;
wxDataViewCtrl* m_widget;
WX_DECLARE_STRING_HASH_MAP( std::vector<int>, WIDTH_CACHE );
static WIDTH_CACHE m_width_cache;
/**
* Compute the width required for the given column of a node and its
* children.
*
* @param aTree - root node of the tree
* @param aCol - column number
* @param aHeading - heading text, to set the minimum width
*/
int ColWidth( CMP_TREE_NODE& aTree, int aCol, wxString const& aHeading );
/**
* Return the width required to display a single row's aCol text.
* This is cached for efficiency as it's very slow on some platforms
* (*cough* macOS)
*/
int WidthFor( CMP_TREE_NODE& aNode, int aCol );
/**
* Return the width required to display a column's heading. This is
* cached by column number for the same reason as the width per cell.
*/
int WidthFor( wxString const& aHeading, int aCol );
/**
* Find any results worth highlighting and expand them, according to given
* criteria (f(CMP_TREE_NODE const*) -> bool)
*
* @return whether a node was expanded
*/
bool FindAndExpand(
CMP_TREE_NODE& aNode,
std::function<bool( CMP_TREE_NODE const* )> aFunc );
/**
* Find and expand successful search results
*/
bool ShowResults();
/**
* Find and expand preselected node
*/
bool ShowPreselect();
/**
* Find and expand a library if there is only one
*/
bool ShowSingleLibrary();
};
#endif // _CMP_TREE_MODEL_ADAPTER_H

View File

@ -0,0 +1,442 @@
/*
* 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_base.h>
#include <eda_pattern_match.h>
#include <wx/progdlg.h>
#include <wx/tokenzr.h>
CMP_TREE_MODEL_ADAPTER_BASE::WIDTH_CACHE CMP_TREE_MODEL_ADAPTER_BASE::m_width_cache;
static const int kDataViewIndent = 20;
/**
* Convert CMP_TREE_NODE -> wxDataViewItem
*/
wxDataViewItem CMP_TREE_MODEL_ADAPTER_BASE::ToItem( CMP_TREE_NODE const* aNode )
{
return wxDataViewItem( const_cast<void*>( static_cast<void const*>( aNode ) ) );
}
/**
* Convert wxDataViewItem -> CMP_TREE_NODE
*/
CMP_TREE_NODE const* CMP_TREE_MODEL_ADAPTER_BASE::ToNode( wxDataViewItem aItem )
{
return static_cast<CMP_TREE_NODE const*>( aItem.GetID() );
}
/**
* Convert CMP_TREE_NODE's children to wxDataViewItemArray
*/
unsigned int CMP_TREE_MODEL_ADAPTER_BASE::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;
}
CMP_TREE_MODEL_ADAPTER_BASE::CMP_TREE_MODEL_ADAPTER_BASE()
:m_filter( CMP_FILTER_NONE ),
m_show_units( true ),
m_preselect_unit( 0 ),
m_col_part( nullptr ),
m_col_desc( nullptr ),
m_widget( nullptr )
{}
CMP_TREE_MODEL_ADAPTER_BASE::~CMP_TREE_MODEL_ADAPTER_BASE()
{}
void CMP_TREE_MODEL_ADAPTER_BASE::SetFilter( CMP_FILTER_TYPE aFilter )
{
m_filter = aFilter;
}
void CMP_TREE_MODEL_ADAPTER_BASE::ShowUnits( bool aShow )
{
m_show_units = aShow;
}
void CMP_TREE_MODEL_ADAPTER_BASE::SetPreselectNode( LIB_ID const& aLibId, int aUnit )
{
m_preselect_lib_id = aLibId;
m_preselect_unit = aUnit;
}
void CMP_TREE_MODEL_ADAPTER_BASE::AddLibrariesWithProgress(
const std::vector<wxString>& aNicknames, wxWindow* aParent )
{
auto* prg = new wxProgressDialog(
_( "Loading symbol libraries" ),
wxEmptyString,
aNicknames.size(),
aParent );
unsigned int ii = 0;
for( auto nickname : aNicknames )
{
prg->Update( ii++, wxString::Format( _( "Loading library '%s'" ), nickname ) );
AddLibrary( nickname );
}
prg->Destroy();
}
void CMP_TREE_MODEL_ADAPTER_BASE::AddAliasList(
wxString const& aNodeName,
std::vector<LIB_ALIAS*> const& aAliasList )
{
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_BASE::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_BASE::AttachTo( wxDataViewCtrl* aDataViewCtrl )
{
m_widget = aDataViewCtrl;
aDataViewCtrl->Freeze();
aDataViewCtrl->SetIndent( kDataViewIndent );
aDataViewCtrl->AssociateModel( this );
aDataViewCtrl->ClearColumns();
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 ) );
aDataViewCtrl->Thaw();
}
LIB_ID CMP_TREE_MODEL_ADAPTER_BASE::GetAliasFor( const wxDataViewItem& aSelection ) const
{
auto node = ToNode( aSelection );
LIB_ID emptyId;
if( !node )
return emptyId;
return node->LibId;
}
int CMP_TREE_MODEL_ADAPTER_BASE::GetUnitFor( const wxDataViewItem& aSelection ) const
{
auto node = ToNode( aSelection );
return node ? node->Unit : 0;
}
CMP_TREE_NODE::TYPE CMP_TREE_MODEL_ADAPTER_BASE::GetTypeFor( const wxDataViewItem& aSelection ) const
{
auto node = ToNode( aSelection );
return node ? node->Type : CMP_TREE_NODE::INVALID;
}
int CMP_TREE_MODEL_ADAPTER_BASE::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_BASE::HasContainerColumns( wxDataViewItem const& aItem ) const
{
return IsContainer( aItem );
}
bool CMP_TREE_MODEL_ADAPTER_BASE::IsContainer( wxDataViewItem const& aItem ) const
{
auto node = ToNode( aItem );
return node ? node->Children.size() : true;
}
wxDataViewItem CMP_TREE_MODEL_ADAPTER_BASE::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_BASE::GetChildren(
wxDataViewItem const& aItem,
wxDataViewItemArray& aChildren ) const
{
auto node = ( aItem.IsOk() ? ToNode( aItem ) : &m_tree );
if( node->Type != CMP_TREE_NODE::TYPE::LIBID || m_show_units )
return IntoArray( *node, aChildren );
else
return 0;
}
void CMP_TREE_MODEL_ADAPTER_BASE::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!" );
}
}
bool CMP_TREE_MODEL_ADAPTER_BASE::GetAttr(
wxDataViewItem const& aItem,
unsigned int aCol,
wxDataViewItemAttr& aAttr ) const
{
auto node = ToNode( aItem );
wxASSERT( node );
if( node->Type != CMP_TREE_NODE::LIBID )
{
// Currently only aliases are formatted at all
return false;
}
if( !node->IsRoot && aCol == 0 )
{
// Names of non-root aliases are italicized
aAttr.SetItalic( true );
return true;
}
else
{
return false;
}
}
int CMP_TREE_MODEL_ADAPTER_BASE::ColWidth( CMP_TREE_NODE& aTree, int aCol, wxString const& aHeading )
{
const int indent = aCol ? 0 : kDataViewIndent;
int min_width = WidthFor( aHeading, aCol );
int width = std::max( aTree.Score > 0 ? WidthFor( aTree, aCol ) : 0, min_width );
if( aTree.Score > 0 )
{
for( auto& node: aTree.Children )
{
width = std::max( width, ColWidth( *node, aCol, aHeading ) + indent );
}
}
return width;
}
int CMP_TREE_MODEL_ADAPTER_BASE::WidthFor( CMP_TREE_NODE& aNode, int aCol )
{
auto result = m_width_cache.find( aNode.Name );
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;
auto& val = m_width_cache[aNode.Name];
val.push_back( wname );
val.push_back( wdesc );
return val[aCol];
}
}
int CMP_TREE_MODEL_ADAPTER_BASE::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];
}
bool CMP_TREE_MODEL_ADAPTER_BASE::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;
}
bool CMP_TREE_MODEL_ADAPTER_BASE::ShowResults()
{
return FindAndExpand( m_tree,
[]( CMP_TREE_NODE const* n )
{
return n->Type == CMP_TREE_NODE::TYPE::LIBID && n->Score > 1;
} );
}
bool CMP_TREE_MODEL_ADAPTER_BASE::ShowPreselect()
{
if( !m_preselect_lib_id.IsValid() )
return false;
return FindAndExpand( m_tree,
[&]( CMP_TREE_NODE const* n )
{
if( n->Type == CMP_TREE_NODE::LIBID && ( n->Children.empty() || !m_preselect_unit ) )
return m_preselect_lib_id == n->LibId;
else if( n->Type == CMP_TREE_NODE::UNIT && m_preselect_unit )
return m_preselect_lib_id == n->Parent->LibId && m_preselect_unit == n->Unit;
else
return false;
} );
}
bool CMP_TREE_MODEL_ADAPTER_BASE::ShowSingleLibrary()
{
return FindAndExpand( m_tree,
[]( CMP_TREE_NODE const* n )
{
return n->Type == CMP_TREE_NODE::TYPE::LIBID &&
n->Parent->Parent->Children.size() == 1;
} );
}

View File

@ -0,0 +1,385 @@
/*
* 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/>.
*/
#ifndef _CMP_TREE_MODEL_ADAPTER_BASE_H
#define _CMP_TREE_MODEL_ADAPTER_BASE_H
#include <lib_id.h>
#include <cmp_tree_model.h>
#include <wx/hashmap.h>
#include <wx/dataview.h>
#include <vector>
#include <functional>
/**
* Adapter class in the component selector Model-View-Adapter (mediated MVC)
* architecture. The other pieces are in:
*
* - Model: CMP_TREE_NODE and descendants in eeschema/cmp_tree_model.h
* - View:
* - DIALOG_CHOOSE_COMPONENT in eeschema/dialogs/dialog_choose_component.h
* - wxDataViewCtrl
*
* This adapter presents the interface specified by wxDataViewModel to the
* wxDataViewCtrl:
*
* +---+ +------------------+
* +---+ Generates | A | | VIEW |
* | M | from libs | D | wxDataViewModel |------------------|
* | O | <---------- | A | <------------------> | wxDataViewCtrl |
* | D | | P | |------------------|
* | E | <---------> | T | <------------------- | wxTextCtrl |
* | L | UpdateScore | E | UpdateSearchString() |------------------|
* +---+ | R | | |
* +---+ +------------------+
*
* Because this adapter is a wxDataViewModel, it is reference-counted by
* wxObject. To ensure this interface is used correctly, the constructor
* is private; CMP_TREE_MODEL_ADAPTER should be created by the static
* factory method CMP_TREE_MODEL_ADAPTER::Create().
*
* Quick summary of methods used to drive this class:
*
* - `SetFilter()` - set whether the view is restricted to power parts
* - `ShowUnits()` - set whether units are displayed
* - `SetPreselectNode()` - set a node to highlight when not searching
* - `AddLibrary()` - populate the model with all aliases in a library
* - `AddAliasList()` - populate the model with a specific list of aliases
*
* Quick summary of methods used by the View:
*
* - `UpdateSearchString()` - pass in the user's search text
* - `AttachTo()` - pass in the wxDataViewCtrl
* - `GetAliasFor()` - get the LIB_ALIAS* for a selected item
* - `GetUnitFor()` - get the unit for a selected item
* - `GetComponentsCount()` - count the aliases loaded
*
* Methods implemented as part of wxDataViewModel:
*
* - `HasContainerColumns()` - whether a parent item has more than one column
* - `IsContainer()` - whether an item is a parent
* - `GetParent()` - return the parent of an item, or invalid if root
* - `GetChildren()` - get the children of an item
* - `GetColumnCount()` - get the number of columns in the view
* - `GetColumnType()` - get the data type shown in each column
* - `GetValue()` - get the data shown in a cell
* - `SetValue()` - edit the data in a cell (does nothing)
* - `GetAttr()` - get any per-item formatting
* - `Compare()` - compare two rows, for sorting
* - `HasDefaultCompare()` - whether sorted by default
*/
class CMP_TREE_MODEL_ADAPTER_BASE: public wxDataViewModel
{
public:
/**
* Reference-counting container for a pointer to CMP_TREE_MODEL_ADAPTER_BASE.
*/
typedef wxObjectDataPtr<CMP_TREE_MODEL_ADAPTER_BASE> PTR;
/**
* Destructor. Do NOT delete this class manually; it is reference-counted
* by wxObject.
*/
~CMP_TREE_MODEL_ADAPTER_BASE();
/**
* This enum allows a selective filtering of components to list
*/
enum CMP_FILTER_TYPE
{
CMP_FILTER_NONE, ///< no filtering
CMP_FILTER_POWER, ///< list components flagged PWR
};
/**
* Set the component filter type. Must be set before adding libraries
*
* @param aFilter if CMP_FILTER_POWER, only power parts are loaded
*/
void SetFilter( CMP_FILTER_TYPE aFilter );
/**
* Return the active filter.
*/
CMP_FILTER_TYPE GetFilter() const { return m_filter; }
/**
* Whether or not to show units. May be set at any time; updates at the next
* UpdateSearchString()
*
* @param aShow if true, units are displayed
*/
void ShowUnits( bool aShow );
/**
* Set the component name to be selected if there are no search results.
* May be set at any time; updates at the next UpdateSearchString().
*
* @param aLibId symbol #LIB_ID to be selected
* @param aUnit unit to be selected, if > 0 (0 selects the alias itself)
*/
void SetPreselectNode( LIB_ID const& aLibId, int aUnit );
/**
* Add all the components and their aliases in this library. To be called
* in the setup phase.
*
* @param aLibNickname reference to a symbol library nickname
*/
virtual void AddLibrary( wxString const& aLibNickname ) = 0;
/**
* Add all the libraries in a SYMBOL_LIB_TABLE to the model,
* displaying a progress dialog attached to the parent frame
*
* @param aNicknames is the list of library nicknames
* @param aParent is the parent window to display the progress dialog
*/
void AddLibrariesWithProgress( const std::vector<wxString>& aNicknames,
wxWindow* aParent );
/**
* Add the given list of components, by name. To be called in the setup
* phase.
*
* @param aNodeName the parent node the components will appear under
* @param aAliasNameList list of alias names
*/
virtual void AddAliasList(
wxString const& aNodeName,
wxArrayString const& aAliasNameList ) = 0;
/**
* Add the given list of components by alias. To be called in the setup
* phase.
*
* @param aNodeName the parent node the components will appear under
* @param aAliasList list of aliases
*/
void AddAliasList(
wxString const& aNodeName,
std::vector<LIB_ALIAS*> const& aAliasList );
/**
* Set the search string provided by the user.
*
* @param aSearch full, unprocessed search text
*/
void UpdateSearchString( wxString const& aSearch );
/**
* Attach to a wxDataViewCtrl and initialize it. This will set up columns
* and associate the model via the adapter.
*
* @param aDataViewCtrl the view component in the dialog
*/
void AttachTo( wxDataViewCtrl* aDataViewCtrl );
/**
* Return the alias for the given item.
*
* @param aSelection item from the wxDataViewCtrl
* (see wxDataViewCtrl::GetSelection())
*
* @return alias, or nullptr if none is selected
*/
LIB_ID GetAliasFor( const wxDataViewItem& aSelection ) const;
/**
* Return the unit for the given item.
*
* @param aSelection item from the wxDataViewCtrl
* (see wxDataViewCtrl::GetSelection())
*
* @return Unit, or zero if the alias itself is selected. Return valid is
* invalid if GetAliasFor() returns nullptr.
*/
int GetUnitFor( const wxDataViewItem& aSelection ) const;
/**
* Return node type for the given item.
*
* @param aSelection item from the wxDataViewCtrl
* (see wxDataViewCtrl::GetSelection())
*
* @return Type of the selected node, might be INVALID.
*/
CMP_TREE_NODE::TYPE GetTypeFor( const wxDataViewItem& aSelection ) const;
/**
* Return the number of components loaded in the tree.
*/
int GetComponentsCount() const;
protected:
static wxDataViewItem ToItem( CMP_TREE_NODE const* aNode );
static CMP_TREE_NODE const* ToNode( wxDataViewItem aItem );
static unsigned int IntoArray( CMP_TREE_NODE const& aNode, wxDataViewItemArray& aChildren );
CMP_TREE_NODE_ROOT m_tree;
/**
* Constructor
*/
CMP_TREE_MODEL_ADAPTER_BASE();
/**
* Check whether a container has columns too
*/
virtual bool HasContainerColumns( wxDataViewItem const& aItem ) const override;
/**
* Check whether an item can have children.
*/
virtual bool IsContainer( wxDataViewItem const& aItem ) const override;
/**
* Get the parent of an item.
*
* @param aItem item to get the parent of
* @return parent of aItem, or an invalid wxDataViewItem if parent is root
*/
virtual wxDataViewItem GetParent( wxDataViewItem const& aItem ) const override;
/**
* Populate a list of all the children of an item
*
* @return number of children
*/
virtual unsigned int GetChildren(
wxDataViewItem const& aItem,
wxDataViewItemArray& aChildren ) const override;
/**
* Return the number of columns in the model
*/
virtual unsigned int GetColumnCount() const override { return 2; }
/**
* Return the type of data stored in the column
*
* @return type of data as indicated by wxVariant::GetType()
*/
virtual wxString GetColumnType( unsigned int aCol ) const override { return "string"; }
/**
* Get the value of an item.
*
* @param aVariant wxVariant to receive the data
* @param aItem item whose data will be placed into aVariant
* @param aCol column number of the data
*/
virtual void GetValue(
wxVariant& aVariant,
wxDataViewItem const& aItem,
unsigned int aCol ) const override;
/**
* Set the value of an item. Does nothing - this model doesn't support
* editing.
*/
virtual bool SetValue(
wxVariant const& aVariant,
wxDataViewItem const& aItem,
unsigned int aCol ) override { return false; }
/**
* Get any formatting for an item.
*
* @param aItem item to get formatting for
* @param aCol column number of interest
* @param aAttr receiver for attributes
* @return true iff the item has non-default attributes
*/
virtual bool GetAttr(
wxDataViewItem const& aItem,
unsigned int aCol,
wxDataViewItemAttr& aAttr ) const override;
private:
CMP_FILTER_TYPE m_filter;
bool m_show_units;
LIB_ID m_preselect_lib_id;
int m_preselect_unit;
wxDataViewColumn* m_col_part;
wxDataViewColumn* m_col_desc;
wxDataViewCtrl* m_widget;
WX_DECLARE_STRING_HASH_MAP( std::vector<int>, WIDTH_CACHE );
static WIDTH_CACHE m_width_cache;
/**
* Compute the width required for the given column of a node and its
* children.
*
* @param aTree - root node of the tree
* @param aCol - column number
* @param aHeading - heading text, to set the minimum width
*/
int ColWidth( CMP_TREE_NODE& aTree, int aCol, wxString const& aHeading );
/**
* Return the width required to display a single row's aCol text.
* This is cached for efficiency as it's very slow on some platforms
* (*cough* macOS)
*/
int WidthFor( CMP_TREE_NODE& aNode, int aCol );
/**
* Return the width required to display a column's heading. This is
* cached by column number for the same reason as the width per cell.
*/
int WidthFor( wxString const& aHeading, int aCol );
/**
* Find any results worth highlighting and expand them, according to given
* criteria (f(CMP_TREE_NODE const*) -> bool)
*
* @return whether a node was expanded
*/
bool FindAndExpand(
CMP_TREE_NODE& aNode,
std::function<bool( CMP_TREE_NODE const* )> aFunc );
/**
* Find and expand successful search results
*/
bool ShowResults();
/**
* Find and expand preselected node
*/
bool ShowPreselect();
/**
* Find and expand a library if there is only one
*/
bool ShowSingleLibrary();
};
#endif // _CMP_TREE_MODEL_ADAPTER_BASE_H

View File

@ -36,7 +36,7 @@
COMPONENT_TREE::COMPONENT_TREE( wxWindow* aParent, SYMBOL_LIB_TABLE* aSymLibTable,
CMP_TREE_MODEL_ADAPTER::PTR& aAdapter, WIDGETS aWidgets )
CMP_TREE_MODEL_ADAPTER_BASE::PTR& aAdapter, WIDGETS aWidgets )
: wxPanel( aParent ),
m_sym_lib_table( aSymLibTable ),
m_adapter( aAdapter ),

View File

@ -47,7 +47,7 @@ public:
enum WIDGETS { NONE = 0x00, SEARCH = 0x01, DETAILS = 0x02, ALL = 0xFF };
COMPONENT_TREE( wxWindow* aParent, SYMBOL_LIB_TABLE* aSymLibTable,
CMP_TREE_MODEL_ADAPTER::PTR& aAdapter, WIDGETS aWidgets = ALL );
CMP_TREE_MODEL_ADAPTER_BASE::PTR& aAdapter, WIDGETS aWidgets = ALL );
/**
* For multi-unit components, if the user selects the component itself