Switch component chooser to wxDataViewCtrl, refactor

This refactors COMPONENT_TREE_SEARCH_CONTAINER into a Model-View-Adapter
architecture comprising:

- eeschema/cmp_tree_model.h
    - CMP_TREE_NODE: Base class representing a searchable library
        set with scoring and sorting

        - CMP_TREE_NODE_UNIT
        - CMP_TREE_NODE_ALIAS
        - CMP_TREE_NODE_LIB
        - CMP_TREE_NODE_ROOT

- eeschema/cmp_tree_model_adapter.h
    - CMP_TREE_MODEL_ADAPTER: mediator between wxDataViewCtrl (via
        wxDataViewModel) and CMP_TREE_NODE*

                   +---+                      +------------------+
 +---+  Generates  | A |                      |       VIEW       |
 | M |  from libs  | D |   wxDataViewModel    |------------------|
 | O | <---------- | A | <------------------> |  wxDataViewCtrl  |
 | D |             | P |                      |------------------|
 | E | <---------> | T | <------------------- |    wxTextCtrl    |
 | L | UpdateScore | E | UpdateSearchString() |------------------|
 +---+             | R |                      |                  |
                   +---+                      +------------------+

Representing the data with a proper model allows the wxDataViewCtrl to
be updated in bulk, which is significantly faster than the old method of
populating it one item at a time. This also adds flexibility if more
data is to be added in the future (which may come in handy with the
upcoming .sweet format, as that adds more ways components can be related
to each other).

Replacing the wxTreeListCtrl with a wxDataViewCtrl also significantly
reduces the general GUI quirkiness, as wxDataViewCtrl is much more well
behaved.
This commit is contained in:
Chris Pavlina 2017-03-06 09:50:48 -05:00
parent fc4240886b
commit f8415633bd
17 changed files with 1584 additions and 917 deletions

View File

@ -1,8 +1,8 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2015 Chris Pavlina <pavlina.chris@gmail.com>
* Copyright (C) 2015 KiCad Developers, see change_log.txt for contributors.
* Copyright (C) 2015-2017 Chris Pavlina <pavlina.chris@gmail.com>
* Copyright (C) 2015-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
@ -25,6 +25,7 @@
#include <eda_pattern_match.h>
#include <wx/log.h>
#include <climits>
#include <make_unique.h>
bool EDA_PATTERN_MATCH_SUBSTR::SetPattern( const wxString& aPattern )
{
@ -135,3 +136,56 @@ int EDA_PATTERN_MATCH_WILDCARD::Find( const wxString& aCandidate ) const
{
return EDA_PATTERN_MATCH_REGEX::Find( aCandidate );
}
EDA_COMBINED_MATCHER::EDA_COMBINED_MATCHER( const wxString& aPattern )
: m_pattern( aPattern )
{
// Whatever syntax users prefer, it shall be matched.
AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_REGEX>() );
AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_WILDCARD>() );
// If any of the above matchers couldn't be created because the pattern
// syntax does not match, the substring will try its best.
AddMatcher( aPattern, std::make_unique<EDA_PATTERN_MATCH_SUBSTR>() );
}
bool EDA_COMBINED_MATCHER::Find( const wxString& aTerm, int& aMatchersTriggered, int& aPosition )
{
aPosition = EDA_PATTERN_NOT_FOUND;
aMatchersTriggered = 0;
for( auto const& matcher : m_matchers )
{
int local_find = matcher->Find( aTerm );
if ( local_find != EDA_PATTERN_NOT_FOUND )
{
aMatchersTriggered += 1;
if ( local_find < aPosition || aPosition == EDA_PATTERN_NOT_FOUND )
{
aPosition = local_find;
}
}
}
return aPosition != EDA_PATTERN_NOT_FOUND;
}
wxString const& EDA_COMBINED_MATCHER::GetPattern() const
{
return m_pattern;
}
void EDA_COMBINED_MATCHER::AddMatcher(
const wxString &aPattern,
std::unique_ptr<EDA_PATTERN_MATCH> aMatcher )
{
if ( aMatcher->SetPattern( aPattern ) )
{
m_matchers.push_back( std::move( aMatcher ) );
}
}

View File

@ -108,7 +108,8 @@ set( EESCHEMA_SRCS
files-io.cpp
find.cpp
getpart.cpp
component_tree_search_container.cpp
cmp_tree_model.cpp
cmp_tree_model_adapter.cpp
generate_alias_info.cpp
hierarch.cpp
highlight_connection.cpp

250
eeschema/cmp_tree_model.cpp Normal file
View File

@ -0,0 +1,250 @@
/*
* 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.h>
#include <class_library.h>
#include <eda_pattern_match.h>
#include <make_unique.h>
#include <utility>
// Each node gets this lowest score initially, without any matches applied.
// Matches will then increase this score depending on match quality. This way,
// an empty search string will result in all components being displayed as they
// have the minimum score. However, in that case, we avoid expanding all the
// nodes asd the result is very unspecific.
static const unsigned kLowestDefaultScore = 1;
// Creates a score depending on the position of a string match. If the position
// is 0 (= prefix match), this returns the maximum score. This degrades until
// pos == max, which returns a score of 0; Evertyhing else beyond that is just
// 0. Only values >= 0 allowed for position and max.
//
// @param aPosition is the position a string has been found in a substring.
// @param aMaximum is the maximum score this function returns.
// @return position dependent score.
static int matchPosScore(int aPosition, int aMaximum)
{
return ( aPosition < aMaximum ) ? aMaximum - aPosition : 0;
}
void CMP_TREE_NODE::ResetScore()
{
for( auto& child: Children )
child->ResetScore();
Score = kLowestDefaultScore;
}
void CMP_TREE_NODE::AssignIntrinsicRanks()
{
std::vector<CMP_TREE_NODE*> sort_buf;
for( auto const& node: Children )
sort_buf.push_back( &*node );
std::sort( sort_buf.begin(), sort_buf.end(),
[]( CMP_TREE_NODE* a, CMP_TREE_NODE* b ) -> bool
{ return a->MatchName > b->MatchName; } );
for( int i = 0; i < (int) sort_buf.size(); ++i )
sort_buf[i]->IntrinsicRank = i;
}
void CMP_TREE_NODE::SortNodes()
{
std::sort( Children.begin(), Children.end(),
[]( std::unique_ptr<CMP_TREE_NODE> const& a, std::unique_ptr<CMP_TREE_NODE> const& b )
{ return Compare( *a, *b ) > 0; } );
for( auto& node: Children )
{
node->SortNodes();
}
}
int CMP_TREE_NODE::Compare( CMP_TREE_NODE const& aNode1, CMP_TREE_NODE const& aNode2 )
{
if( aNode1.Type != aNode2.Type )
return 0;
if( aNode1.Score != aNode2.Score )
return aNode1.Score - aNode2.Score;
if( aNode1.Parent != aNode2.Parent )
return 0;
return aNode1.IntrinsicRank - aNode2.IntrinsicRank;
}
CMP_TREE_NODE::CMP_TREE_NODE()
: Parent( nullptr ),
IntrinsicRank( 0 ),
Score( kLowestDefaultScore ),
Alias( nullptr ),
Unit( 0 )
{}
CMP_TREE_NODE_UNIT::CMP_TREE_NODE_UNIT( CMP_TREE_NODE* aParent, int aUnit )
{
Parent = aParent;
Type = UNIT;
Unit = aUnit;
Alias = aParent->Alias;
Name = _( "Unit" ) + " " + LIB_PART::SubReference( aUnit, false );
Desc = wxEmptyString;
MatchName = wxEmptyString;
IntrinsicRank = -aUnit;
}
CMP_TREE_NODE_ALIAS::CMP_TREE_NODE_ALIAS( CMP_TREE_NODE* aParent, LIB_ALIAS* aAlias )
{
Parent = aParent;
Type = ALIAS;
Name = aAlias->GetName();
Desc = aAlias->GetDescription();
MatchName = aAlias->GetName().Lower();
SearchText = aAlias->GetKeyWords() + " " + Desc;
Alias = aAlias;
if( aAlias->GetPart()->IsMulti() )
{
for( int u = 1; u < aAlias->GetPart()->GetUnitCount(); ++u )
{
AddUnit( u );
}
}
}
CMP_TREE_NODE_UNIT& CMP_TREE_NODE_ALIAS::AddUnit( int aUnit )
{
CMP_TREE_NODE_UNIT* unit = new CMP_TREE_NODE_UNIT( this, aUnit );
Children.push_back( std::unique_ptr<CMP_TREE_NODE>( unit ) );
return *unit;
}
void CMP_TREE_NODE_ALIAS::UpdateScore( EDA_COMBINED_MATCHER& aMatcher )
{
if( Score <= 0 )
return; // Leaf nodes without scores are out of the game.
// Keywords and description we only count if the match string is at
// least two characters long. That avoids spurious, low quality
// matches. Most abbreviations are at three characters long.
int found_pos = EDA_PATTERN_NOT_FOUND;
int matchers_fired = 0;
if( aMatcher.GetPattern() == MatchName )
Score += 1000; // exact match. High score :)
else if( aMatcher.Find( MatchName, matchers_fired, found_pos ) )
{
// Substring match. The earlier in the string the better.
Score += matchPosScore( found_pos, 20 ) + 20;
}
else if( aMatcher.Find( Parent->MatchName, matchers_fired, found_pos ) )
Score += 19; // parent name matches. score += 19
else if( aMatcher.Find( SearchText, matchers_fired, found_pos ) )
{
// If we have a very short search term (like one or two letters),
// we don't want to accumulate scores if they just happen to be in
// keywords or description as almost any one or two-letter
// combination shows up in there.
if( aMatcher.GetPattern().length() >= 2 )
{
// For longer terms, we add scores 1..18 for positional match
// (higher in the front, where the keywords are).
Score += matchPosScore( found_pos, 17 ) + 1;
}
}
else
{
// No match. That's it for this item.
Score = 0;
}
// More matchers = better match
Score += 2 * matchers_fired;
}
CMP_TREE_NODE_LIB::CMP_TREE_NODE_LIB( CMP_TREE_NODE* aParent, wxString const& aName )
{
Type = LIB;
Name = aName;
MatchName = aName.Lower();
Parent = aParent;
}
CMP_TREE_NODE_ALIAS& CMP_TREE_NODE_LIB::AddAlias( LIB_ALIAS* aAlias )
{
CMP_TREE_NODE_ALIAS* alias = new CMP_TREE_NODE_ALIAS( this, aAlias );
Children.push_back( std::unique_ptr<CMP_TREE_NODE>( alias ) );
return *alias;
}
void CMP_TREE_NODE_LIB::UpdateScore( EDA_COMBINED_MATCHER& aMatcher )
{
Score = 0;
for( auto& child: Children )
{
child->UpdateScore( aMatcher );
Score = std::max( Score, child->Score );
}
}
CMP_TREE_NODE_ROOT::CMP_TREE_NODE_ROOT()
{
Type = ROOT;
}
CMP_TREE_NODE_LIB& CMP_TREE_NODE_ROOT::AddLib( wxString const& aName )
{
CMP_TREE_NODE_LIB* lib = new CMP_TREE_NODE_LIB( this, aName );
Children.push_back( std::unique_ptr<CMP_TREE_NODE>( lib ) );
return *lib;
}
void CMP_TREE_NODE_ROOT::UpdateScore( EDA_COMBINED_MATCHER& aMatcher )
{
for( auto& child: Children )
child->UpdateScore( aMatcher );
}

276
eeschema/cmp_tree_model.h Normal file
View File

@ -0,0 +1,276 @@
/*
* 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_H
#define _CMP_TREE_MODEL_H
#include <vector>
#include <memory>
#include <wx/string.h>
class EDA_COMBINED_MATCHER;
class TREE_NODE;
class LIB_ALIAS;
/**
* Model class in the component selector Model-View-Adapter (mediated MVC)
* architecture. The other pieces are in:
*
* - Adapter: CMP_TREE_MODEL_ADAPTER in eeschema/cmp_tree_model_adapter.h
* - View:
* - DIALOG_CHOOSE_COMPONENT in eeschema/dialogs/dialog_choose_component.h
* - wxDataViewCtrl
*
* This model is populated from LIB_ALIASes; helper methods in the adapter
* can accept entire libraries.
*
* Quick summary of methods used to populate this class:
* - `CMP_TREE_NODE_ROOT::AddLib()` - add a new, empty library to the root
* - `CMP_TREE_NODE_LIB::AddAlias()` - add an alias and its units from a
* given LIB_ALIAS*
*
* Quick summary of methods used to drive this class:
*
* - `UpdateScore()` - accumulate scores recursively given a new search token
* - `ResetScore()` - reset scores recursively for a new search string
* - `AssignIntrinsicRanks()` - calculate and cache the initial sort order
* - `SortNodes()` - recursively sort the tree by score
* - `Compare()` - compare two nodes; used by `SortNodes()`
*
* The data in the model is meant to be accessed directly. Quick summary of
* data members:
*
* - `Parent` - parent node, or nullptr if root
* - `Children` - vector of unique_ptrs to children
* - `Type` - ROOT, LIB, ALIAS, or UNIT
* - `IntrinsicRank` - cached initial sort order
* - `Score` - score taking into account search terms. Zero means irrelevant and
* should be hidden.
* - `Name` - name of the library/alias/unit, to be displayed
* - `Desc` - description of the alias, to be displayed
* - `MatchName` - Name, normalized to lowercase for matching
* - `SearchText` - normalized composite of keywords and description
* - `Alias` - the LIB_ALIAS* this alias or unit is from, or nullptr
* - `Unit` - the unit number, or zero for non-units
*/
class CMP_TREE_NODE {
public:
enum TYPE {
ROOT, LIB, ALIAS, UNIT
};
CMP_TREE_NODE* Parent; ///< Parent node or null
std::vector<std::unique_ptr<CMP_TREE_NODE>> Children; ///< List of child nodes
enum TYPE Type; ///< Node type
/**
* The rank of the item before any search terms are applied. This is
* a fairly expensive sort (involving string compares) so it helps to
* store the result of that sort.
*/
int IntrinsicRank;
/// The score of an item resulting from the search algorithm.
int Score;
wxString Name; ///< Actual name of the part
wxString Desc; ///< Description to be displayed
wxString MatchName; ///< Normalized name for matching
wxString SearchText; ///< Descriptive text to search
LIB_ALIAS* Alias; ///< Actual LIB_ALIAS*, or null
int Unit; ///< Actual unit, or zero
/**
* Update the score for this part. This is accumulative - it will be
* called once per search term.
*
* @param aMatcher an EDA_COMBINED_MATCHER initialized with the search
* term
*/
virtual void UpdateScore( EDA_COMBINED_MATCHER& aMatcher ) = 0;
/**
* Initialize score to kLowestDefaultScore, recursively.
*/
void ResetScore();
/**
* Store intrinsic ranks on all children of this node. See IntrinsicRank
* member doc for more information.
*/
void AssignIntrinsicRanks();
/**
* Sort child nodes quickly and recursively (IntrinsicRanks must have been set).
*/
void SortNodes();
/**
* Compare two nodes. Returns negative if aNode1 < aNode2, zero if aNode1 ==
* aNode2, or positive if aNode1 > aNode2.
*/
static int Compare( CMP_TREE_NODE const& aNode1, CMP_TREE_NODE const& aNode2 );
CMP_TREE_NODE();
virtual ~CMP_TREE_NODE() {}
};
/**
* Node type: unit of component.
*/
class CMP_TREE_NODE_UNIT: public CMP_TREE_NODE
{
public:
/**
* The addresses of CMP_TREE_NODEs are used as unique IDs for the
* wxDataViewModel, so don't let them be copied around.
*/
CMP_TREE_NODE_UNIT( CMP_TREE_NODE_UNIT const& _ ) = delete;
void operator=( CMP_TREE_NODE_UNIT const& _ ) = delete;
/**
* Construct a unit node.
*
* The name of the unit will be "Unit %s", where %s is aUnit formatted
* by LIB_PART::SubReference.
*
* @param aParent parent node, should be a CMP_TREE_NODE_ALIAS
* @param aUnit unit number
*/
CMP_TREE_NODE_UNIT( CMP_TREE_NODE* aParent, int aUnit );
/**
* Do nothing, units just take the parent's score
*/
virtual void UpdateScore( EDA_COMBINED_MATCHER& aMatcher ) override {}
};
/**
* Node type: alias.
*/
class CMP_TREE_NODE_ALIAS: public CMP_TREE_NODE
{
public:
/**
* The addresses of CMP_TREE_NODEs are used as unique IDs for the
* wxDataViewModel, so don't let them be copied around.
*/
CMP_TREE_NODE_ALIAS( CMP_TREE_NODE_ALIAS const& _ ) = delete;
void operator=( CMP_TREE_NODE_ALIAS const& _ ) = delete;
/**
* Construct an alias node.
*
* All fields will be populated from the LIB_ALIAS, including children
* (unit nodes will be generated automatically).
*
* @param aParent parent node, should be a CMP_TREE_NODE_LIB
* @param aAlias LIB_ALIAS to populate the node
*/
CMP_TREE_NODE_ALIAS( CMP_TREE_NODE* aParent, LIB_ALIAS* aAlias );
/**
* Perform the actual search.
*/
virtual void UpdateScore( EDA_COMBINED_MATCHER& aMatcher ) override;
protected:
/**
* Add a new unit to the component and return it.
*
* This should not be used directly, as the constructor adds all units.
*/
CMP_TREE_NODE_UNIT& AddUnit( int aUnit );
};
/**
* Node type: library
*/
class CMP_TREE_NODE_LIB: public CMP_TREE_NODE
{
public:
/**
* The addresses of CMP_TREE_NODEs are used as unique IDs for the
* wxDataViewModel, so don't let them be copied around.
*/
CMP_TREE_NODE_LIB( CMP_TREE_NODE_LIB const& _ ) = delete;
void operator=( CMP_TREE_NODE_LIB const& _ ) = delete;
/**
* Construct an empty library node.
*
* @param aParent parent node, should be a CMP_TREE_NODE_ROOT
* @param aName display name of the library
*/
CMP_TREE_NODE_LIB( CMP_TREE_NODE* aParent, wxString const& aName );
/**
* Construct a new alias node, add it to this library, and return it.
*
* @param aAlias LIB_ALIAS to provide data
*/
CMP_TREE_NODE_ALIAS& AddAlias( LIB_ALIAS* aAlias );
virtual void UpdateScore( EDA_COMBINED_MATCHER& aMatcher ) override;
};
/**
* Node type: root
*/
class CMP_TREE_NODE_ROOT: public CMP_TREE_NODE
{
public:
/**
* The addresses of CMP_TREE_NODEs are used as unique IDs for the
* wxDataViewModel, so don't let them be copied around.
*/
CMP_TREE_NODE_ROOT( CMP_TREE_NODE_ROOT const& _ ) = delete;
void operator=( CMP_TREE_NODE_ROOT const& _ ) = delete;
/**
* Construct the root node. Root nodes have no properties.
*/
CMP_TREE_NODE_ROOT();
/**
* Construct an empty library node, add it to the root, and return it.
*/
CMP_TREE_NODE_LIB& AddLib( wxString const& aName );
virtual void UpdateScore( EDA_COMBINED_MATCHER& aMatcher ) override;
};
#endif // _CMP_TREE_MODEL_H

View File

@ -0,0 +1,386 @@
/*
* 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 <class_library.h>
#include <eda_pattern_match.h>
#include <wx/tokenzr.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( PART_LIBS* aLibs )
{
auto adapter = new CMP_TREE_MODEL_ADAPTER( aLibs );
auto container = CMP_TREE_MODEL_ADAPTER::PTR( adapter );
return container;
}
CMP_TREE_MODEL_ADAPTER::CMP_TREE_MODEL_ADAPTER( PART_LIBS* aLibs )
:m_filter( CMP_FILTER_NONE ),
m_show_units( true ),
m_libs( aLibs ),
m_preselect_unit( 0 )
{}
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( wxString const& aName, int aUnit )
{
m_preselect_name = aName;
m_preselect_unit = aUnit;
}
void CMP_TREE_MODEL_ADAPTER::AddLibrary( PART_LIB& aLib )
{
if( m_filter == CMP_FILTER_POWER )
{
wxArrayString all_aliases;
aLib.GetEntryTypePowerNames( all_aliases );
AddAliasList( aLib.GetName(), all_aliases, &aLib );
}
else
{
std::vector<LIB_ALIAS*> all_aliases;
aLib.GetAliases( all_aliases );
AddAliasList( aLib.GetName(), all_aliases, &aLib );
}
m_tree.AssignIntrinsicRanks();
}
void CMP_TREE_MODEL_ADAPTER::AddAliasList(
wxString const& aNodeName,
wxArrayString const& aAliasNameList,
PART_LIB* aOptionalLib )
{
std::vector<LIB_ALIAS*> alias_list;
for( const wxString& name: aAliasNameList )
{
LIB_ALIAS* a;
if( aOptionalLib )
a = aOptionalLib->FindAlias( name );
else
a = m_libs->FindLibraryAlias( LIB_ID( wxEmptyString, name ), wxEmptyString );
if( a )
alias_list.push_back( a );
}
AddAliasList( aNodeName, alias_list, aOptionalLib );
}
void CMP_TREE_MODEL_ADAPTER::AddAliasList(
wxString const& aNodeName,
std::vector<LIB_ALIAS*> const& aAliasList,
PART_LIB* aOptionalLib )
{
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();
m_col_part = aDataViewCtrl->AppendTextColumn( _( "Part" ), 0, wxDATAVIEW_CELL_INERT,
ColWidth( m_tree, 0 ) );
m_col_desc = aDataViewCtrl->AppendTextColumn( _( "Description" ), 1, wxDATAVIEW_CELL_INERT,
ColWidth( m_tree, 1 ) );
aDataViewCtrl->Thaw();
}
LIB_ALIAS* CMP_TREE_MODEL_ADAPTER::GetAliasFor( wxDataViewItem aSelection ) const
{
auto node = ToNode( aSelection );
return node ? node->Alias : nullptr;
}
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!" );
}
}
int CMP_TREE_MODEL_ADAPTER::Compare(
wxDataViewItem const& aFirst, wxDataViewItem const& aSecond,
unsigned int aCol, bool aAscending ) const
{
auto node1 = ToNode( aFirst );
auto node2 = ToNode( aSecond );
if( aAscending )
return -CMP_TREE_NODE::Compare(*node1, *node2);
else
return CMP_TREE_NODE::Compare(*node1, *node2);
}
int CMP_TREE_MODEL_ADAPTER::ColWidth( CMP_TREE_NODE& aNode, int aCol )
{
const int indent = aCol ? 0 : kDataViewIndent;
int max_width = aNode.Score > 0 ? WidthFor( aNode, aCol ) : 0;
for( auto& node: aNode.Children )
{
if( aNode.Score > 0 )
max_width = std::max( max_width, ColWidth( *node, aCol ) + indent );
}
return max_width;
}
int CMP_TREE_MODEL_ADAPTER::WidthFor( CMP_TREE_NODE& aNode, int aCol )
{
auto result = m_width_cache.find( &aNode );
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;
m_width_cache[&aNode][0] = wname;
m_width_cache[&aNode][1] = wdesc;
return m_width_cache[&aNode][aCol];
}
}
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()
{
if( m_preselect_name == wxEmptyString )
return false;
return FindAndExpand( m_tree,
[&]( CMP_TREE_NODE const* n )
{
return m_preselect_name == n->Name && m_preselect_unit == n->Unit;
} );
}
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;
} );
}

View File

@ -0,0 +1,380 @@
/*
* 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_H
#define _CMP_TREE_MODEL_ADAPTER_H
#include <cmp_tree_model.h>
#include <wx/dataview.h>
#include <vector>
#include <unordered_map>
#include <memory>
class LIB_ALIAS;
class PART_LIB;
class PART_LIBS;
/**
* 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)
* - `Compare()` - compare two rows, for sorting
* - `HasDefaultCompare()` - whether sorted by default
*/
class CMP_TREE_MODEL_ADAPTER: public wxDataViewModel
{
public:
/**
* Reference-counting container for a pointer to CMP_TREE_MODEL_ADAPTER.
*/
typedef wxObjectDataPtr<CMP_TREE_MODEL_ADAPTER> PTR;
/**
* Destructor. Do NOT delete this class manually; it is reference-counted
* by wxObject.
*/
~CMP_TREE_MODEL_ADAPTER();
/**
* Factory function: create a model adapter in a reference-counting
* container.
*
* @param aLibs library set from which parts will be loaded
*/
static PTR Create( PART_LIBS* 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 aName component name to be selected
* @param aUnit unit to be selected, if > 0 (0 selects the alias itself)
*/
void SetPreselectNode( wxString const& aName, int aUnit );
/**
* Add all the components and their aliases in this library. To be called
* in the setup phase.
*
* @param aLib reference to a library
*/
void AddLibrary( PART_LIB& aLib );
/**
* 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
* @param aOptionalLib library to look up names in (null = global)
*/
void AddAliasList(
wxString const& aNodeName,
wxArrayString const& aAliasNameList,
PART_LIB* aOptionalLib = nullptr );
/**
* 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 aAliasNameList list of aliases
* @param aOptionalLib library to look up names in (null = global)
*/
void AddAliasList(
wxString const& aNodeName,
std::vector<LIB_ALIAS*> const& aAliasList,
PART_LIB* aOptionalLib = nullptr );
/**
* 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_ALIAS* GetAliasFor( 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( wxDataViewItem aSelection ) const;
/**
* Return the number of components loaded in the tree.
*/
int GetComponentsCount() const;
protected:
/**
* Constructor; takes a set of libraries to be included in the search.
*/
CMP_TREE_MODEL_ADAPTER( PART_LIBS* 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;
/**
* Compare two data items, for sorting.
*/
virtual int Compare(
wxDataViewItem const& aFirst,
wxDataViewItem const& aSecond,
unsigned int aCol,
bool aAscending ) const override;
/**
* Whether list is sorted even if the user hasn't selected a sort column
*/
virtual bool HasDefaultCompare() const override { return true; }
/**
* 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; }
private:
CMP_FILTER_TYPE m_filter;
bool m_show_units;
PART_LIBS* m_libs;
wxString m_preselect_name;
int m_preselect_unit;
CMP_TREE_NODE_ROOT m_tree;
wxDataViewColumn* m_col_part;
wxDataViewColumn* m_col_desc;
wxDataViewCtrl* m_widget;
typedef std::unordered_map<CMP_TREE_NODE*, int[2]> WIDTH_CACHE;
static WIDTH_CACHE m_width_cache;
/**
* Compute the width required for the given column of a node and its
* children.
*/
int ColWidth( CMP_TREE_NODE& aNode, int aCol );
/**
* 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 );
/**
* Find any results worth highlighting and expand them, according to given
* criteria (f(CMP_TREE_NODE const*) -> bool)
*
* @return whether a node was expanded
*/
template<typename Func>
bool FindAndExpand( CMP_TREE_NODE& aNode, Func f )
{
for( auto& node: aNode.Children )
{
if( f( &*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, f ) )
{
return true;
}
}
return false;
}
/**
* 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

@ -1,547 +0,0 @@
/* -*- c++ -*-
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2014 Henner Zeller <h.zeller@acm.org>
* Copyright (C) 2015-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 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 <component_tree_search_container.h>
#include <algorithm>
#include <set>
#include <wx/string.h>
#include <wx/tokenzr.h>
#include <wx/arrstr.h>
#include <wx/dataview.h>
#include <widgets/two_column_tree_list.h>
#include <class_library.h>
#include <macros.h>
#include <profile.h>
#include <eda_pattern_match.h>
// Each node gets this lowest score initially, without any matches applied. Matches
// will then increase this score depending on match quality.
// This way, an empty search string will result in all components being displayed as they
// have the minimum score. However, in that case, we avoid expanding all the nodes asd the
// result is very unspecific.
static const unsigned kLowestDefaultScore = 1;
struct COMPONENT_TREE_SEARCH_CONTAINER::TREE_NODE
{
// Levels of nodes.
enum NODE_TYPE {
TYPE_LIB,
TYPE_ALIAS,
TYPE_UNIT
};
TREE_NODE(NODE_TYPE aType, TREE_NODE* aParent, LIB_ALIAS* aAlias,
const wxString& aName, const wxString& aDisplayInfo,
const wxString& aSearchText )
: Type( aType ),
Parent( aParent ), Alias( aAlias ), Unit( 0 ),
DisplayName( aName ),
DisplayInfo( aDisplayInfo ),
MatchName( aName.Lower() ),
SearchText( aSearchText.Lower() ),
MatchScore( 0 ), PreviousScore( 0 )
{
}
const NODE_TYPE Type; ///< Type of node in the hierarchy.
TREE_NODE* const Parent; ///< NULL if library, pointer to parent when component/alias.
LIB_ALIAS* const Alias; ///< Component alias associated with this entry.
int Unit; ///< Part number; Assigned: >= 1; default = 0
const wxString DisplayName; ///< Exact name as displayed to the user.
const wxString DisplayInfo; ///< Additional info displayed in the tree (description..)
const wxString MatchName; ///< Preprocessed: lowercased display name.
const wxString SearchText; ///< Other text (keywords, description..) to search in.
unsigned MatchScore; ///< Result-Score after UpdateSearchTerm()
unsigned PreviousScore; ///< Optimization: used to see if we need any tree update.
wxTreeListItem TreeId; ///< Tree-ID if stored in the tree (if MatchScore > 0).
};
// Sort tree nodes by reverse match-score (bigger is first), then alphabetically.
// Library (i.e. the ones that don't have a parent) are always sorted before any
// leaf nodes. Component
bool COMPONENT_TREE_SEARCH_CONTAINER::scoreComparator( const TREE_NODE* a1, const TREE_NODE* a2 )
{
if( a1->Type != a2->Type )
return a1->Type < a2->Type;
if( a1->MatchScore != a2->MatchScore )
return a1->MatchScore > a2->MatchScore; // biggest first.
if( a1->Parent != a2->Parent )
return scoreComparator( a1->Parent, a2->Parent );
return a1->MatchName.Cmp( a2->MatchName ) < 0;
}
COMPONENT_TREE_SEARCH_CONTAINER::COMPONENT_TREE_SEARCH_CONTAINER( PART_LIBS* aLibs )
: m_tree( NULL ),
m_libraries_added( 0 ),
m_components_added( 0 ),
m_preselect_unit_number( -1 ),
m_show_units( true ),
m_libs( aLibs ),
m_filter( CMP_FILTER_NONE )
{
}
COMPONENT_TREE_SEARCH_CONTAINER::~COMPONENT_TREE_SEARCH_CONTAINER()
{
for( TREE_NODE* node : m_nodes )
delete node;
m_nodes.clear();
}
void COMPONENT_TREE_SEARCH_CONTAINER::SetPreselectNode( const wxString& aComponentName,
int aUnit )
{
m_preselect_node_name = aComponentName.Lower();
m_preselect_unit_number = aUnit;
}
void COMPONENT_TREE_SEARCH_CONTAINER::ShowUnits( bool aShowUnits )
{
m_show_units = aShowUnits;
}
void COMPONENT_TREE_SEARCH_CONTAINER::SetTree( TWO_COLUMN_TREE_LIST* aTree )
{
m_tree = aTree;
if( m_tree )
{
m_tree->AppendColumn( _( "Part" ), 100, wxALIGN_LEFT, wxCOL_RESIZABLE );
m_tree->AppendColumn( _( "Description" ), 100, wxALIGN_LEFT, wxCOL_RESIZABLE );
m_tree->SetRubberBandColumn( 1 );
}
}
void COMPONENT_TREE_SEARCH_CONTAINER::AddLibrary( PART_LIB& aLib )
{
if( m_filter == CMP_FILTER_POWER )
{
wxArrayString all_aliases;
aLib.GetEntryTypePowerNames( all_aliases );
AddAliasList( aLib.GetName(), all_aliases, &aLib );
}
else
{
std::vector<LIB_ALIAS*> all_aliases;
aLib.GetAliases( all_aliases );
AddAliasList( aLib.GetName(), all_aliases, &aLib );
}
++m_libraries_added;
}
void COMPONENT_TREE_SEARCH_CONTAINER::AddAliasList( const wxString& aNodeName,
const wxArrayString& aAliasNameList,
PART_LIB* aOptionalLib )
{
std::vector<LIB_ALIAS*> alias_list;
for( const wxString& aName: aAliasNameList )
{
LIB_ALIAS* a;
if( aOptionalLib )
a = aOptionalLib->FindAlias( aName );
else
a = m_libs->FindLibraryAlias( LIB_ID( wxEmptyString, aName ), wxEmptyString );
if( a )
{
alias_list.push_back( a );
}
}
AddAliasList( aNodeName, alias_list, aOptionalLib );
}
void COMPONENT_TREE_SEARCH_CONTAINER::AddAliasList( const wxString& aNodeName,
const std::vector<LIB_ALIAS*>& aAliasList,
PART_LIB* aOptionalLib )
{
TREE_NODE* const lib_node = new TREE_NODE( TREE_NODE::TYPE_LIB, NULL, NULL,
aNodeName, wxEmptyString, wxEmptyString );
m_nodes.push_back( lib_node );
for( auto a: aAliasList )
{
wxString search_text;
search_text = ( a->GetKeyWords().empty() ) ? wxT(" ") : a->GetKeyWords();
search_text += a->GetDescription();
wxString display_info;
if( !a->GetDescription().empty() )
{
display_info = a->GetDescription();
}
TREE_NODE* alias_node = new TREE_NODE( TREE_NODE::TYPE_ALIAS, lib_node,
a, a->GetName(), display_info, search_text );
m_nodes.push_back( alias_node );
if( m_show_units && a->GetPart()->IsMulti() ) // Add all units as sub-nodes.
{
for( int u = 1; u <= a->GetPart()->GetUnitCount(); ++u )
{
wxString unitName = _( "Unit" );
unitName += wxT( " " ) + LIB_PART::SubReference( u, false );
TREE_NODE* unit_node = new TREE_NODE( TREE_NODE::TYPE_UNIT,
alias_node, a,
unitName,
wxEmptyString, wxEmptyString );
unit_node->Unit = u;
m_nodes.push_back( unit_node );
}
}
++m_components_added;
}
}
LIB_ALIAS* COMPONENT_TREE_SEARCH_CONTAINER::GetSelectedAlias( int* aUnit )
{
if( m_tree == NULL )
return NULL;
const wxTreeListItem& select_id = m_tree->GetSelection();
for( TREE_NODE* node : m_nodes )
{
if( node->MatchScore > 0 && node->TreeId == select_id )
{
if( aUnit && node->Unit > 0 )
*aUnit = node->Unit;
return node->Alias;
}
}
return NULL;
}
// Creates a score depending on the position of a string match. If the position
// is 0 (= prefix match), this returns the maximum score. This degrades until
// pos == max, which returns a score of 0;
// Evertyhing else beyond that is just 0. Only values >= 0 allowed for position and max.
//
// @param aPosition is the position a string has been found in a substring.
// @param aMaximum is the maximum score this function returns.
// @return position dependent score.
static int matchPosScore(int aPosition, int aMaximum)
{
return ( aPosition < aMaximum ) ? aMaximum - aPosition : 0;
}
namespace
{
class EDA_COMBINED_MATCHER
{
public:
EDA_COMBINED_MATCHER( const wxString &aPattern )
{
// Whatever syntax users prefer, it shall be matched.
AddMatcher( aPattern, new EDA_PATTERN_MATCH_REGEX() );
AddMatcher( aPattern, new EDA_PATTERN_MATCH_WILDCARD() );
// If any of the above matchers couldn't be created because the pattern
// syntax does not match, the substring will try its best.
AddMatcher( aPattern, new EDA_PATTERN_MATCH_SUBSTR() );
}
~EDA_COMBINED_MATCHER()
{
for( const EDA_PATTERN_MATCH* matcher : m_matchers )
delete matcher;
}
/*
* Look in all existing matchers, return the earliest match of any of
* the existing. Returns EDA_PATTERN_NOT_FOUND if no luck.
*/
int Find( const wxString &aTerm, int *aMatchersTriggered )
{
int result = EDA_PATTERN_NOT_FOUND;
for( const EDA_PATTERN_MATCH* matcher : m_matchers )
{
int local_find = matcher->Find( aTerm );
if ( local_find != EDA_PATTERN_NOT_FOUND )
{
*aMatchersTriggered += 1;
if ( local_find < result || result == EDA_PATTERN_NOT_FOUND )
{
result = local_find;
}
}
}
return result;
}
private:
// Add matcher if it can compile the pattern.
void AddMatcher( const wxString &aPattern, EDA_PATTERN_MATCH *aMatcher )
{
if ( aMatcher->SetPattern( aPattern ) )
{
m_matchers.push_back( aMatcher );
}
else
{
delete aMatcher;
}
}
std::vector<const EDA_PATTERN_MATCH*> m_matchers;
};
}
void COMPONENT_TREE_SEARCH_CONTAINER::UpdateSearchTerm( const wxString& aSearch )
{
if( m_tree == NULL )
return;
//#define SHOW_CALC_TIME // uncomment this to show calculation time
#ifdef SHOW_CALC_TIME
unsigned starttime = GetRunningMicroSecs();
#endif
// We score the list by going through it several time, essentially with a complexity
// of O(n). For the default library of 2000+ items, this typically takes less than 5ms
// on an i5. Good enough, no index needed.
// Initial AND condition: Leaf nodes are considered to match initially.
for( TREE_NODE* node : m_nodes )
{
node->PreviousScore = node->MatchScore;
node->MatchScore = ( node->Type == TREE_NODE::TYPE_LIB ) ? 0 : kLowestDefaultScore;
}
// Create match scores for each node for all the terms, that come space-separated.
// Scoring adds up values for each term according to importance of the match. If a term does
// not match at all, the result is thrown out of the results (AND semantics).
// From high to low
// - Exact match for a ccmponent name gives the highest score, trumping all.
// - A positional score depending of where a term is found as substring; prefix-match: high.
// - substring-match in library name.
// - substring match in keywords and descriptions with positional score. Keywords come
// first so contribute more to the score.
//
// This is of course subject to tweaking.
wxStringTokenizer tokenizer( aSearch );
while ( tokenizer.HasMoreTokens() )
{
const wxString term = tokenizer.GetNextToken().Lower();
EDA_COMBINED_MATCHER matcher( term );
for( TREE_NODE* node : m_nodes )
{
if( node->Type != TREE_NODE::TYPE_ALIAS )
continue; // Only aliases are actually scored here.
if( node->MatchScore == 0)
continue; // Leaf node without score are out of the game.
// Keywords and description we only count if the match string is at
// least two characters long. That avoids spurious, low quality
// matches. Most abbreviations are at three characters long.
int found_pos;
int matcher_fired = 0;
if( term == node->MatchName )
node->MatchScore += 1000; // exact match. High score :)
else if( (found_pos = matcher.Find( node->MatchName, &matcher_fired ) ) != EDA_PATTERN_NOT_FOUND )
{
// Substring match. The earlier in the string the better. score += 20..40
node->MatchScore += matchPosScore( found_pos, 20 ) + 20;
}
else if( matcher.Find( node->Parent->MatchName, &matcher_fired ) != EDA_PATTERN_NOT_FOUND )
node->MatchScore += 19; // parent name matches. score += 19
else if( ( found_pos = matcher.Find( node->SearchText, &matcher_fired ) ) != EDA_PATTERN_NOT_FOUND )
{
// If we have a very short search term (like one or two letters), we don't want
// to accumulate scores if they just happen to be in keywords or description as
// almost any one or two-letter combination shows up in there.
// For longer terms, we add scores 1..18 for positional match (higher in the
// front, where the keywords are). score += 0..18
node->MatchScore += ( ( term.length() >= 2 )
? matchPosScore( found_pos, 17 ) + 1
: 0 );
}
else
node->MatchScore = 0; // No match. That's it for this item.
node->MatchScore += 2 * matcher_fired;
}
}
// Library nodes have the maximum score seen in any of their children.
// Alias nodes have the score of their parents.
unsigned highest_score_seen = 0;
bool any_change = false;
for( TREE_NODE* node : m_nodes )
{
switch( node->Type )
{
case TREE_NODE::TYPE_ALIAS:
{
any_change |= (node->PreviousScore != node->MatchScore);
// Update library score.
node->Parent->MatchScore = std::max( node->Parent->MatchScore, node->MatchScore );
highest_score_seen = std::max( highest_score_seen, node->MatchScore );
}
break;
case TREE_NODE::TYPE_UNIT:
node->MatchScore = node->Parent->MatchScore;
break;
default:
break;
}
}
// The tree update might be slow, so we want to bail out if there is no change.
if( !any_change )
return;
// Now: sort all items according to match score, libraries first.
std::sort( m_nodes.begin(), m_nodes.end(), scoreComparator );
#ifdef SHOW_CALC_TIME
unsigned sorttime = GetRunningMicroSecs();
#endif
// Fill the tree with all items that have a match. Re-arranging, adding and removing changed
// items is pretty complex, so we just re-build the whole tree.
m_tree->Freeze();
m_tree->DeleteAllItems();
const wxTreeListItem root_id = m_tree->GetRootItem();
const TREE_NODE* first_match = NULL;
const TREE_NODE* preselected_node = NULL;
bool override_preselect = false;
for( TREE_NODE* node : m_nodes )
{
if( node->MatchScore == 0 )
continue;
// If we have nodes that go beyond the default score, suppress nodes that
// have the default score. That can happen if they have an honary += 0 score due to
// some one-letter match in the keyword or description. In this case, we prefer matches
// that just have higher scores. Improves relevancy and performance as the tree has to
// display less items.
if( highest_score_seen > kLowestDefaultScore && node->MatchScore == kLowestDefaultScore )
continue;
node->TreeId = m_tree->AppendItem( node->Parent ? node->Parent->TreeId : root_id,
node->DisplayName );
m_tree->SetItemText( node->TreeId, 1, node->DisplayInfo );
// If there is only a single library in this container, we want to have it
// unfolded (example: power library, libedit)
if( node->Type == TREE_NODE::TYPE_ALIAS && m_libraries_added == 1 )
{
m_tree->Expand( node->TreeId );
if( first_match == NULL )
first_match = node;
}
// If we are a nicely scored alias, we want to have it visible.
if( node->Type == TREE_NODE::TYPE_ALIAS && ( node->MatchScore > kLowestDefaultScore ) )
{
m_tree->Expand( node->TreeId );
if( first_match == NULL )
first_match = node; // First, highest scoring: the "I am feeling lucky" element.
// The user is searching, don't preselect!
override_preselect = true;
}
// The first node that matches our pre-select criteria is choosen. 'First node'
// means, it shows up in the history, as the history node is displayed very first
// (by virtue of alphabetical ordering)
if( preselected_node == NULL
&& node->Type == TREE_NODE::TYPE_ALIAS
&& node->MatchName == m_preselect_node_name )
preselected_node = node;
// Refinement in case we come accross a matching unit node.
if( preselected_node != NULL && preselected_node->Type == TREE_NODE::TYPE_ALIAS
&& node->Parent == preselected_node
&& m_preselect_unit_number >= 1 && node->Unit == m_preselect_unit_number )
preselected_node = node;
}
if( first_match && ( !preselected_node || override_preselect ) )
{
// This is the wx call that pumps the event loop on some ports (namely
// macOS). TODO: find a way to avoid that.
m_tree->Select( first_match->TreeId );
//m_tree->EnsureVisible( first_match->TreeId );
}
else if( preselected_node )
{
// This is the wx call that pumps the event loop on some ports (namely
// macOS). TODO: find a way to avoid that.
m_tree->Select( preselected_node->TreeId );
//m_tree->EnsureVisible( preselected_node->TreeId );
}
m_tree->AutosizeColumns();
m_tree->Thaw();
#ifdef SHOW_CALC_TIME
unsigned endtime = GetRunningMicroSecs();
wxLogMessage( wxT("sort components %.1f ms, rebuild tree %.1f ms"),
double(sorttime-starttime)/1000.0, double(endtime-sorttime)/1000.0 );
#endif
}

View File

@ -1,175 +0,0 @@
/* -*- c++ -*-
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2014 Henner Zeller <h.zeller@acm.org>
* Copyright (C) 2015 KiCad Developers, see change_log.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
*/
#ifndef COMPONENT_TREE_SEARCH_CONTAINER_H
#define COMPONENT_TREE_SEARCH_CONTAINER_H
#include <vector>
#include <wx/string.h>
class LIB_ALIAS;
class PART_LIB;
class PART_LIBS;
class TWO_COLUMN_TREE_LIST;
class wxArrayString;
// class COMPONENT_TREE_SEARCH_CONTAINER
// A container for components that allows to search them matching their name, keywords
// and descriptions, updating a wxTreeCtrl with the results (toplevel nodes:
// libraries, leafs: components), scored by relevance.
//
// The scored result list is adpated on each update on the search-term: this allows
// to have a search-as-you-type experience.
class COMPONENT_TREE_SEARCH_CONTAINER
{
public:
/** This enum allows a selective filtering of component to list
* currently: no filtering
* list power components only
*/
enum CMP_FILTER_TYPE
{
CMP_FILTER_NONE, ///< no filtering
CMP_FILTER_POWER ///< list components flagged PWR
};
public:
COMPONENT_TREE_SEARCH_CONTAINER( PART_LIBS* aLibs );
~COMPONENT_TREE_SEARCH_CONTAINER();
void SetFilter( CMP_FILTER_TYPE aFilter )
{
m_filter = aFilter;
}
/** Function AddLibrary
* Add all the components and their aliases of this library to be searched.
* To be called in the setup phase to fill this container.
*
* @param aLib containting all the components to be added.
*/
void AddLibrary( PART_LIB& aLib );
/**
* Add the given list of components, given by name, to be searched.
* To be called in the setup phase to fill this container.
*
* @param aNodeName The parent node name the components will show up as leaf.
* @param aAliasNameList List of alias names.
* @param aOptionalLib Library to look up the component names (if NULL: global lookup)
*/
void AddAliasList( const wxString& aNodeName, const wxArrayString& aAliasNameList,
PART_LIB* aOptionalLib );
/**
* Add the given list of components, given by alias, to be searched.
* To be called in the setup phase to fill this container.
*
* @param aNodeName The parent node name the components will show up as leaf.
* @param aAliasList List of aliases.
* @param aOptionalLib Library to look up the component names (if NULL: global lookup)
*/
void AddAliasList( const wxString& aNodeName, const std::vector<LIB_ALIAS*>& aAliasList,
PART_LIB* aOptionalLib );
/** Function SetPreselectNode
* Set the component name to be selected in absence of any search-result.
*
* @param aComponentName the component name to be selected.
* @param aUnit the component unit to be selected (if > 0).
*/
void SetPreselectNode( const wxString& aComponentName, int aUnit );
/**
* Set whether units should be shown under parts. Default: true
*
* Must be called before AddLibrary, as this is when the list is populated!
*/
void ShowUnits( bool aShowUnits );
/** Function SetTree
* Set the tree to be manipulated.
* Each update of the search term will update the tree, with the most
* scoring component at the top and selected. If a preselect node is set, this
* is displayed. Does not take ownership of the tree.
*
* After calling SetTree(), UpdateSearchTerm( wxEmptyString ) should be
* called. Note the warning with respsect to calling UpdateSearchTerm() in
* dialog constructors.
*
* @param aTree that is to be modified on search updates.
*/
void SetTree( TWO_COLUMN_TREE_LIST* aTree );
/** Function UpdateSearchTerm
* Update the search string provided by the user and narrow down the result list.
*
* XXX WARNING: Do not call this method in a dialog constructor until the
* dialog has been otherwise fully initialized (i.e. this should be the last
* thing called in the constructor). Some wx ports pump the event loop inside
* this method, which can result in event handlers being called before things
* they access are initialized.
*
* This string is a space-separated list of terms, each of which
* is applied to the components list to narrow it down. Results are scored by
* relevancy (e.g. exact match scores higher than prefix-match which in turn scores
* higher than substring match). This updates the search and tree on each call.
*
* @param aSearch is the user-provided search string.
*/
void UpdateSearchTerm( const wxString& aSearch );
/**
* Get the currently selected alias.
*
* @param aUnit : if not null, the selected sub-unit is set here.
* @return the selected alias or nullptr if there is none, or there is no tree.
*/
LIB_ALIAS* GetSelectedAlias( int* aUnit = nullptr );
/**
* Function GetComponentsCount
* @return the number of components loaded in the tree
*/
int GetComponentsCount() { return m_components_added; }
private:
struct TREE_NODE;
static bool scoreComparator( const TREE_NODE* a1, const TREE_NODE* a2 );
std::vector<TREE_NODE*> m_nodes;
TWO_COLUMN_TREE_LIST* m_tree;
int m_libraries_added;
int m_components_added;
wxString m_preselect_node_name;
int m_preselect_unit_number;
bool m_show_units;
PART_LIBS* m_libs; // no ownership
enum CMP_FILTER_TYPE m_filter; // the current filter
};
#endif /* COMPONENT_TREE_SEARCH_CONTAINER_H */

View File

@ -29,7 +29,6 @@
#include <wx/utils.h>
#include <class_library.h>
#include <component_tree_search_container.h>
#include <sch_base_frame.h>
#include <widgets/footprint_preview_panel.h>
#include <widgets/two_column_tree_list.h>
@ -38,20 +37,21 @@
#include <make_unique.h>
// Tree navigation helpers.
static wxTreeListItem GetPrevItem( const wxTreeListCtrl& tree, const wxTreeListItem& item );
static wxTreeListItem GetNextItem( const wxTreeListCtrl& tree, const wxTreeListItem& item );
static wxTreeListItem GetPrevSibling( const wxTreeListCtrl& tree, const wxTreeListItem& item );
static wxDataViewItem GetPrevItem( const wxDataViewCtrl& ctrl, const wxDataViewItem& item );
static wxDataViewItem GetNextItem( const wxDataViewCtrl& ctrl, const wxDataViewItem& item );
static wxDataViewItem GetPrevSibling( const wxDataViewCtrl& ctrl, const wxDataViewItem& item );
static wxDataViewItem GetNextSibling( const wxDataViewCtrl& ctrl, const wxDataViewItem& item );
DIALOG_CHOOSE_COMPONENT::DIALOG_CHOOSE_COMPONENT( SCH_BASE_FRAME* aParent, const wxString& aTitle,
COMPONENT_TREE_SEARCH_CONTAINER* const aContainer,
CMP_TREE_MODEL_ADAPTER::PTR& aAdapter,
int aDeMorganConvert )
: DIALOG_CHOOSE_COMPONENT_BASE( aParent, wxID_ANY, aTitle ), m_search_container( aContainer ),
: DIALOG_CHOOSE_COMPONENT_BASE( aParent, wxID_ANY, aTitle ), m_adapter( aAdapter ),
m_footprintPreviewPanel( nullptr )
{
m_parent = aParent;
m_deMorganConvert = aDeMorganConvert >= 0 ? aDeMorganConvert : 0;
m_external_browser_requested = false;
m_search_container->SetTree( m_libraryComponentTree );
m_adapter->AttachTo( m_libraryComponentTree );
m_componentView->SetLayoutDirection( wxLayout_LeftToRight );
m_dbl_click_timer = std::make_unique<wxTimer>( this );
@ -89,19 +89,20 @@ DIALOG_CHOOSE_COMPONENT::DIALOG_CHOOSE_COMPONENT( SCH_BASE_FRAME* aParent, const
#endif
Bind( wxEVT_TIMER, &DIALOG_CHOOSE_COMPONENT::OnCloseTimer, this );
Bind( wxEVT_CHAR_HOOK, &DIALOG_CHOOSE_COMPONENT::OnSearchBoxCharHook, this );
Layout();
Centre();
// Per warning in component_tree_search_container.h, this must be called
// near the end of the constructor.
m_search_container->UpdateSearchTerm( wxEmptyString );
m_adapter->UpdateSearchString( wxEmptyString );
updateSelection();
}
DIALOG_CHOOSE_COMPONENT::~DIALOG_CHOOSE_COMPONENT()
{
m_search_container->SetTree( NULL );
//m_search_container->SetTree( NULL );
}
void DIALOG_CHOOSE_COMPONENT::OnInitDialog( wxInitDialogEvent& event )
@ -112,13 +113,18 @@ void DIALOG_CHOOSE_COMPONENT::OnInitDialog( wxInitDialogEvent& event )
LIB_ALIAS* DIALOG_CHOOSE_COMPONENT::GetSelectedAlias( int* aUnit ) const
{
return m_search_container->GetSelectedAlias( aUnit );
auto sel = m_libraryComponentTree->GetSelection();
if( aUnit && m_adapter->GetUnitFor( sel ) )
*aUnit = m_adapter->GetUnitFor( sel );
return m_adapter->GetAliasFor( sel );
}
void DIALOG_CHOOSE_COMPONENT::OnSearchBoxChange( wxCommandEvent& aEvent )
{
m_search_container->UpdateSearchTerm( m_searchBox->GetLineText( 0 ) );
m_adapter->UpdateSearchString( m_searchBox->GetLineText( 0 ) );
updateSelection();
}
@ -129,16 +135,19 @@ void DIALOG_CHOOSE_COMPONENT::OnSearchBoxEnter( wxCommandEvent& aEvent )
}
void DIALOG_CHOOSE_COMPONENT::selectIfValid( const wxTreeListItem& aTreeId )
void DIALOG_CHOOSE_COMPONENT::selectIfValid( const wxDataViewItem& aTreeId )
{
if( aTreeId.IsOk() && aTreeId != m_libraryComponentTree->GetRootItem() )
if( aTreeId.IsOk() )
{
m_libraryComponentTree->Select( aTreeId );
m_libraryComponentTree->EnsureVisible( aTreeId );
}
updateSelection();
}
void DIALOG_CHOOSE_COMPONENT::OnSearchBoxKey( wxKeyEvent& aKeyStroke )
void DIALOG_CHOOSE_COMPONENT::OnSearchBoxCharHook( wxKeyEvent& aKeyStroke )
{
auto const sel = m_libraryComponentTree->GetSelection();
@ -159,33 +168,19 @@ void DIALOG_CHOOSE_COMPONENT::OnSearchBoxKey( wxKeyEvent& aKeyStroke )
}
void DIALOG_CHOOSE_COMPONENT::OnTreeSelect( wxTreeListEvent& aEvent )
void DIALOG_CHOOSE_COMPONENT::OnTreeSelect( wxDataViewEvent& aEvent )
{
updateSelection();
}
void DIALOG_CHOOSE_COMPONENT::OnTreeActivate( wxTreeListEvent& aEvent )
void DIALOG_CHOOSE_COMPONENT::OnTreeActivate( wxDataViewEvent& aEvent )
{
updateSelection();
HandleItemSelection();
}
void DIALOG_CHOOSE_COMPONENT::OnTreeKeyUp( wxKeyEvent& aEvent )
{
if( aEvent.GetKeyCode() == WXK_RETURN )
{
updateSelection();
HandleItemSelection();
}
else
{
aEvent.Skip();
}
}
void DIALOG_CHOOSE_COMPONENT::OnCloseTimer( wxTimerEvent& aEvent )
{
// Hack handler because of eaten MouseUp event. See
@ -216,12 +211,13 @@ void DIALOG_CHOOSE_COMPONENT::OnStartComponentBrowser( wxMouseEvent& aEvent )
bool DIALOG_CHOOSE_COMPONENT::updateSelection()
{
int unit = 0;
LIB_ALIAS* selection = m_search_container->GetSelectedAlias( &unit );
auto sel = m_libraryComponentTree->GetSelection();
int unit = m_adapter->GetUnitFor( sel );
LIB_ALIAS* alias = m_adapter->GetAliasFor( sel );
m_componentView->Refresh();
if( selection == NULL )
if( alias == NULL )
{
if( m_footprintPreviewPanel )
{
@ -233,7 +229,7 @@ bool DIALOG_CHOOSE_COMPONENT::updateSelection()
return false;
}
m_componentDetails->SetPage( GenerateAliasInfo( selection, unit ) );
m_componentDetails->SetPage( GenerateAliasInfo( alias, unit ) );
updateFootprint();
@ -246,14 +242,14 @@ void DIALOG_CHOOSE_COMPONENT::updateFootprint()
if( !m_footprintPreviewPanel )
return;
int dummy_unit = 0;
LIB_ALIAS* selection = m_search_container->GetSelectedAlias( &dummy_unit );
auto sel = m_libraryComponentTree->GetSelection();
LIB_ALIAS* alias = m_adapter->GetAliasFor( sel );
if( !selection )
if( !alias )
return;
LIB_FIELDS fields;
selection->GetPart()->GetFields( fields );
alias->GetPart()->GetFields( fields );
for( auto const & field: fields )
{
@ -284,16 +280,18 @@ void DIALOG_CHOOSE_COMPONENT::OnDatasheetClick( wxHtmlLinkEvent& aEvent )
void DIALOG_CHOOSE_COMPONENT::OnHandlePreviewRepaint( wxPaintEvent& aRepaintEvent )
{
int unit = 0;
LIB_ALIAS* selection = m_search_container->GetSelectedAlias( &unit );
LIB_PART* part = selection ? selection->GetPart() : NULL;
auto sel = m_libraryComponentTree->GetSelection();
int unit = m_adapter->GetUnitFor( sel );
LIB_ALIAS* alias = m_adapter->GetAliasFor( sel );
LIB_PART* part = alias ? alias->GetPart() : NULL;
// Don't draw anything (not even the background) if we don't have
// a part to show
if( !part )
return;
if( selection->IsRoot() )
if( alias->IsRoot() )
{
// just show the part directly
renderPreview( part, unit );
@ -302,7 +300,7 @@ void DIALOG_CHOOSE_COMPONENT::OnHandlePreviewRepaint( wxPaintEvent& aRepaintEven
{
// switch out the name temporarily for the alias name
wxString tmp( part->GetName() );
part->SetName( selection->GetName() );
part->SetName( alias->GetName() );
renderPreview( part, unit );
@ -356,7 +354,7 @@ void DIALOG_CHOOSE_COMPONENT::renderPreview( LIB_PART* aComponent, int aUnit )
void DIALOG_CHOOSE_COMPONENT::HandleItemSelection()
{
if( m_search_container->GetSelectedAlias() )
if( m_adapter->GetAliasFor( m_libraryComponentTree->GetSelection() ) )
{
// Got a selection. We can't just end the modal dialog here, because
// wx leaks some events back to the parent window (in particular, the
@ -384,55 +382,49 @@ void DIALOG_CHOOSE_COMPONENT::HandleItemSelection()
}
static wxTreeListItem GetPrevItem( const wxTreeListCtrl& tree, const wxTreeListItem& item )
static wxDataViewItem GetPrevItem( const wxDataViewCtrl& tree, const wxDataViewItem& item )
{
wxTreeListItem prevItem = GetPrevSibling( tree, item );
auto prevItem = GetPrevSibling( tree, item );
if( !prevItem.IsOk() )
{
prevItem = tree.GetItemParent( item );
prevItem = tree.GetModel()->GetParent( item );
}
else if( tree.IsExpanded( prevItem ) )
{
// wxTreeListCtrl has no .GetLastChild. Simulate it
prevItem = tree.GetFirstChild( prevItem );
wxTreeListItem next;
do
{
next = tree.GetNextSibling( prevItem );
if( next.IsOk() )
{
prevItem = next;
}
} while( next.IsOk() );
wxDataViewItemArray children;
tree.GetModel()->GetChildren( prevItem, children );
prevItem = children[children.size() - 1];
}
return prevItem;
}
static wxTreeListItem GetNextItem( const wxTreeListCtrl& tree, const wxTreeListItem& item )
static wxDataViewItem GetNextItem( const wxDataViewCtrl& tree, const wxDataViewItem& item )
{
wxTreeListItem nextItem;
wxDataViewItem nextItem;
if( !item.IsOk() )
return nextItem; // item is not valid: return a not valid wxTreeListItem
{
// No selection. Select the first.
wxDataViewItemArray children;
tree.GetModel()->GetChildren( item, children );
return children[0];
}
if( tree.IsExpanded( item ) )
{
nextItem = tree.GetFirstChild( item );
wxDataViewItemArray children;
tree.GetModel()->GetChildren( item, children );
nextItem = children[0];
}
else
{
wxTreeListItem root_cell= tree.GetRootItem();
// Walk up levels until we find one that has a next sibling.
for ( wxTreeListItem walk = item; walk.IsOk(); walk = tree.GetItemParent( walk ) )
for ( wxDataViewItem walk = item; walk.IsOk(); walk = tree.GetModel()->GetParent( walk ) )
{
if( walk == root_cell ) // the root cell (not displayed) is reached
break; // Exit (calling GetNextSibling( root_cell ) crashes.
nextItem = tree.GetNextSibling( walk );
nextItem = GetNextSibling( tree, walk );
if( nextItem.IsOk() )
break;
@ -443,30 +435,47 @@ static wxTreeListItem GetNextItem( const wxTreeListCtrl& tree, const wxTreeListI
}
static wxTreeListItem GetPrevSibling( const wxTreeListCtrl& tree, const wxTreeListItem& item )
static wxDataViewItem GetPrevSibling( const wxDataViewCtrl& tree, const wxDataViewItem& item )
{
// Why wxTreeListCtrl has no GetPrevSibling when it does have GetNextSibling
// is beyond me. wxTreeCtrl has this.
wxDataViewItemArray siblings;
wxDataViewItem invalid;
wxDataViewItem parent = tree.GetModel()->GetParent( item );
wxTreeListItem last_item;
wxTreeListItem parent = tree.GetItemParent( item );
tree.GetModel()->GetChildren( parent, siblings );
if( !parent.IsOk() )
return last_item; // invalid signifies not found
last_item = tree.GetFirstChild( parent );
while( last_item.IsOk() )
for( size_t i = 0; i < siblings.size(); ++i )
{
wxTreeListItem next_item = tree.GetNextSibling( last_item );
if( next_item == item )
if( siblings[i] == item )
{
return last_item;
}
else
{
last_item = next_item;
if( i == 0 )
return invalid;
else
return siblings[i - 1];
}
}
return last_item;
return invalid;
}
static wxDataViewItem GetNextSibling( const wxDataViewCtrl& tree, const wxDataViewItem& item )
{
wxDataViewItemArray siblings;
wxDataViewItem invalid;
wxDataViewItem parent = tree.GetModel()->GetParent( item );
tree.GetModel()->GetChildren( parent, siblings );
for( size_t i = 0; i < siblings.size(); ++i )
{
if( siblings[i] == item )
{
if( i == siblings.size() - 1 )
return invalid;
else
return siblings[i + 1];
}
}
return invalid;
}

View File

@ -25,35 +25,71 @@
#define DIALOG_CHOOSE_COMPONENT_H
#include <dialog_choose_component_base.h>
#include <cmp_tree_model_adapter.h>
#include <wx/timer.h>
#include <memory>
class FOOTPRINT_PREVIEW_PANEL;
class COMPONENT_TREE_SEARCH_CONTAINER;
class LIB_ALIAS;
class LIB_PART;
class wxTreeListItem;
class SCH_BASE_FRAME;
/**
* Dialog class to select a component from the libraries. This is the master
* View class in a Model-View-Adapter (mediated MVC) architecture. The other
* pieces are in:
*
* - Adapter: CMP_TREE_MODEL_ADAPTER in eeschema/cmp_tree_model_adapter.h
* - Model: CMP_TREE_NODE and descendants in eeschema/cmp_tree_model.h
*
* Because everything is tied together in the adapter class, see that file
* for thorough documentation. A simple example usage follows:
*
* // Create the adapter class
* auto adapter( Prj().SchLibs() );
*
* // Perform any configuration of adapter properties here
* adapter->SetPreselectNode( "TL072", 2 );
*
* // Initialize model from PART_LIBs
* for( PART_LIB& lib: *libs )
* adapter->AddLibrary( lib );
*
* // Create and display dialog
* DIALOG_CHOOSE_COMPONENT dlg( this, title, adapter, 1 );
* bool selected = ( dlg.ShowModal() != wxID_CANCEL );
*
* // Receive part
* if( selected )
* {
* int unit;
* LIB_ALIAS* alias = dlg.GetSelectedAlias( &unit );
* do_something( alias, unit );
* }
*
*/
class DIALOG_CHOOSE_COMPONENT : public DIALOG_CHOOSE_COMPONENT_BASE
{
SCH_BASE_FRAME* m_parent;
COMPONENT_TREE_SEARCH_CONTAINER* const m_search_container;
SCH_BASE_FRAME* m_parent;
CMP_TREE_MODEL_ADAPTER::PTR m_adapter;
int m_deMorganConvert;
bool m_external_browser_requested;
public:
/**
* Create dialog to choose component.
*
* @param aParent a SCH_BASE_FRAME parent window.
* @param aTitle Dialog title.
* @param aSearchContainer The tree selection search container. Needs to be pre-populated
* This dialog does not take over ownership of this object.
* @param aDeMorganConvert preferred deMorgan conversion (TODO: should happen in dialog)
* @param aParent a SCH_BASE_FRAME parent window.
* @param aTitle Dialog title.
* @param aAdapter CMP_TREE_MODEL_ADAPTER::PTR. See CMP_TREE_MODEL_ADAPTER
* for documentation.
* @param aDeMorganConvert preferred deMorgan conversion
* (TODO: should happen in dialog)
*/
DIALOG_CHOOSE_COMPONENT( SCH_BASE_FRAME* aParent, const wxString& aTitle,
COMPONENT_TREE_SEARCH_CONTAINER* const aSearchContainer,
CMP_TREE_MODEL_ADAPTER::PTR& aAdapter,
int aDeMorganConvert );
virtual ~DIALOG_CHOOSE_COMPONENT();
void OnInitDialog( wxInitDialogEvent& event ) override;
@ -76,11 +112,10 @@ public:
protected:
virtual void OnSearchBoxChange( wxCommandEvent& aEvent ) override;
virtual void OnSearchBoxEnter( wxCommandEvent& aEvent ) override;
virtual void OnSearchBoxKey( wxKeyEvent& aEvent ) override;
void OnSearchBoxCharHook( wxKeyEvent& aEvent );
virtual void OnTreeSelect( wxTreeListEvent& aEvent ) override;
virtual void OnTreeActivate( wxTreeListEvent& aEvent ) override;
virtual void OnTreeKeyUp( wxKeyEvent& aEvent ) override;
virtual void OnTreeSelect( wxDataViewEvent& aEvent ) override;
virtual void OnTreeActivate( wxDataViewEvent& aEvent ) override;
virtual void OnStartComponentBrowser( wxMouseEvent& aEvent ) override;
virtual void OnHandlePreviewRepaint( wxPaintEvent& aRepaintEvent ) override;
@ -91,7 +126,7 @@ protected:
private:
bool updateSelection();
void updateFootprint();
void selectIfValid( const wxTreeListItem& aTreeId );
void selectIfValid( const wxDataViewItem& aTreeId );
void renderPreview( LIB_PART* aComponent, int aUnit );
/**

View File

@ -5,8 +5,6 @@
// PLEASE DO "NOT" EDIT THIS FILE!
///////////////////////////////////////////////////////////////////////////
#include "widgets/two_column_tree_list.h"
#include "dialog_choose_component_base.h"
///////////////////////////////////////////////////////////////////////////
@ -39,9 +37,8 @@ DIALOG_CHOOSE_COMPONENT_BASE::DIALOG_CHOOSE_COMPONENT_BASE( wxWindow* parent, wx
bSizer10->Add( m_searchBoxSizer, 0, wxEXPAND, 5 );
m_libraryComponentTree = new TWO_COLUMN_TREE_LIST( m_panel3, wxID_ANY, wxDefaultPosition, wxSize( 320,240 ), wxTL_DEFAULT_STYLE );
bSizer10->Add( m_libraryComponentTree, 1, wxEXPAND | wxALL, 5 );
m_libraryComponentTree = new wxDataViewCtrl( m_panel3, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_SINGLE );
bSizer10->Add( m_libraryComponentTree, 1, wxALL|wxEXPAND, 5 );
m_componentDetails = new wxHtmlWindow( m_panel3, wxID_ANY, wxDefaultPosition, wxSize( 320,240 ), wxHW_SCROLLBAR_AUTO|wxSUNKEN_BORDER );
bSizer10->Add( m_componentDetails, 1, wxALL|wxEXPAND, 5 );
@ -108,12 +105,10 @@ DIALOG_CHOOSE_COMPONENT_BASE::DIALOG_CHOOSE_COMPONENT_BASE( wxWindow* parent, wx
// Connect Events
this->Connect( wxEVT_IDLE, wxIdleEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnIdle ) );
this->Connect( wxEVT_INIT_DIALOG, wxInitDialogEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnInitDialog ) );
m_searchBox->Connect( wxEVT_KEY_UP, wxKeyEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnSearchBoxKey ), NULL, this );
m_searchBox->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnSearchBoxChange ), NULL, this );
m_searchBox->Connect( wxEVT_COMMAND_TEXT_ENTER, wxCommandEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnSearchBoxEnter ), NULL, this );
m_libraryComponentTree->Connect( wxEVT_KEY_UP, wxKeyEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnTreeKeyUp ), NULL, this );
m_libraryComponentTree->Connect( wxEVT_TREELIST_ITEM_ACTIVATED, wxTreeListEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnTreeActivate ), NULL, this );
m_libraryComponentTree->Connect( wxEVT_TREELIST_SELECTION_CHANGED, wxTreeListEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnTreeSelect ), NULL, this );
m_libraryComponentTree->Connect( wxEVT_COMMAND_DATAVIEW_ITEM_ACTIVATED, wxDataViewEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnTreeActivate ), NULL, this );
m_libraryComponentTree->Connect( wxEVT_COMMAND_DATAVIEW_SELECTION_CHANGED, wxDataViewEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnTreeSelect ), NULL, this );
m_componentDetails->Connect( wxEVT_COMMAND_HTML_LINK_CLICKED, wxHtmlLinkEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnDatasheetClick ), NULL, this );
m_componentView->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnStartComponentBrowser ), NULL, this );
m_componentView->Connect( wxEVT_PAINT, wxPaintEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnHandlePreviewRepaint ), NULL, this );
@ -124,12 +119,10 @@ DIALOG_CHOOSE_COMPONENT_BASE::~DIALOG_CHOOSE_COMPONENT_BASE()
// Disconnect Events
this->Disconnect( wxEVT_IDLE, wxIdleEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnIdle ) );
this->Disconnect( wxEVT_INIT_DIALOG, wxInitDialogEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnInitDialog ) );
m_searchBox->Disconnect( wxEVT_KEY_UP, wxKeyEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnSearchBoxKey ), NULL, this );
m_searchBox->Disconnect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnSearchBoxChange ), NULL, this );
m_searchBox->Disconnect( wxEVT_COMMAND_TEXT_ENTER, wxCommandEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnSearchBoxEnter ), NULL, this );
m_libraryComponentTree->Disconnect( wxEVT_KEY_UP, wxKeyEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnTreeKeyUp ), NULL, this );
m_libraryComponentTree->Disconnect( wxEVT_TREELIST_ITEM_ACTIVATED, wxTreeListEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnTreeActivate ), NULL, this );
m_libraryComponentTree->Disconnect( wxEVT_TREELIST_SELECTION_CHANGED, wxTreeListEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnTreeSelect ), NULL, this );
m_libraryComponentTree->Disconnect( wxEVT_COMMAND_DATAVIEW_ITEM_ACTIVATED, wxDataViewEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnTreeActivate ), NULL, this );
m_libraryComponentTree->Disconnect( wxEVT_COMMAND_DATAVIEW_SELECTION_CHANGED, wxDataViewEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnTreeSelect ), NULL, this );
m_componentDetails->Disconnect( wxEVT_COMMAND_HTML_LINK_CLICKED, wxHtmlLinkEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnDatasheetClick ), NULL, this );
m_componentView->Disconnect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnStartComponentBrowser ), NULL, this );
m_componentView->Disconnect( wxEVT_PAINT, wxPaintEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnHandlePreviewRepaint ), NULL, this );

View File

@ -417,7 +417,7 @@
<event name="OnEnterWindow"></event>
<event name="OnEraseBackground"></event>
<event name="OnKeyDown"></event>
<event name="OnKeyUp">OnSearchBoxKey</event>
<event name="OnKeyUp"></event>
<event name="OnKillFocus"></event>
<event name="OnLeaveWindow"></event>
<event name="OnLeftDClick"></event>
@ -446,65 +446,52 @@
</object>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxEXPAND | wxALL</property>
<property name="flag">wxALL|wxEXPAND</property>
<property name="proportion">1</property>
<object class="wxTreeListCtrl" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<object class="wxDataViewCtrl" expanded="1">
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size">-1,-1</property>
<property name="moveable">1</property>
<property name="minimum_size"></property>
<property name="name">m_libraryComponentTree</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size">320,240</property>
<property name="style">wxTL_DEFAULT_STYLE</property>
<property name="subclass">TWO_COLUMN_TREE_LIST; widgets/two_column_tree_list.h</property>
<property name="toolbar_pane">0</property>
<property name="size"></property>
<property name="style">wxDV_SINGLE</property>
<property name="subclass"></property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<event name="OnChar"></event>
<event name="OnDataViewCtrlColumnHeaderClick"></event>
<event name="OnDataViewCtrlColumnHeaderRightClick"></event>
<event name="OnDataViewCtrlColumnReordered"></event>
<event name="OnDataViewCtrlColumnSorted"></event>
<event name="OnDataViewCtrlItemActivated">OnTreeActivate</event>
<event name="OnDataViewCtrlItemBeginDrag"></event>
<event name="OnDataViewCtrlItemCollapsed"></event>
<event name="OnDataViewCtrlItemCollapsing"></event>
<event name="OnDataViewCtrlItemContextMenu"></event>
<event name="OnDataViewCtrlItemDrop"></event>
<event name="OnDataViewCtrlItemDropPossible"></event>
<event name="OnDataViewCtrlItemEditingDone"></event>
<event name="OnDataViewCtrlItemEditingStarted"></event>
<event name="OnDataViewCtrlItemExpanded"></event>
<event name="OnDataViewCtrlItemExpanding"></event>
<event name="OnDataViewCtrlItemStartEditing"></event>
<event name="OnDataViewCtrlItemValueChanged"></event>
<event name="OnDataViewCtrlSelectionChanged">OnTreeSelect</event>
<event name="OnEnterWindow"></event>
<event name="OnEraseBackground"></event>
<event name="OnKeyDown"></event>
<event name="OnKeyUp">OnTreeKeyUp</event>
<event name="OnKeyUp"></event>
<event name="OnKillFocus"></event>
<event name="OnLeaveWindow"></event>
<event name="OnLeftDClick"></event>
@ -522,13 +509,6 @@
<event name="OnRightUp"></event>
<event name="OnSetFocus"></event>
<event name="OnSize"></event>
<event name="OnTreelistColumnSorted"></event>
<event name="OnTreelistItemActivated">OnTreeActivate</event>
<event name="OnTreelistItemChecked"></event>
<event name="OnTreelistItemContextMenu"></event>
<event name="OnTreelistItemExpanded"></event>
<event name="OnTreelistItemExpanding"></event>
<event name="OnTreelistSelectionChanged">OnTreeSelect</event>
<event name="OnUpdateUI"></event>
</object>
</object>

View File

@ -12,7 +12,6 @@
#include <wx/xrc/xmlres.h>
#include <wx/intl.h>
class DIALOG_SHIM;
class TWO_COLUMN_TREE_LIST;
#include "dialog_shim.h"
#include <wx/bitmap.h>
@ -26,7 +25,7 @@ class TWO_COLUMN_TREE_LIST;
#include <wx/string.h>
#include <wx/textctrl.h>
#include <wx/sizer.h>
#include <wx/treelist.h>
#include <wx/dataview.h>
#include <wx/html/htmlwin.h>
#include <wx/panel.h>
#include <wx/choice.h>
@ -49,7 +48,7 @@ class DIALOG_CHOOSE_COMPONENT_BASE : public DIALOG_SHIM
wxPanel* m_panel3;
wxStaticBitmap* m_searchBoxIcon;
wxTextCtrl* m_searchBox;
TWO_COLUMN_TREE_LIST* m_libraryComponentTree;
wxDataViewCtrl* m_libraryComponentTree;
wxHtmlWindow* m_componentDetails;
wxPanel* m_panel4;
wxPanel* m_componentView;
@ -62,12 +61,10 @@ class DIALOG_CHOOSE_COMPONENT_BASE : public DIALOG_SHIM
// Virtual event handlers, overide them in your derived class
virtual void OnIdle( wxIdleEvent& event ) { event.Skip(); }
virtual void OnInitDialog( wxInitDialogEvent& event ) { event.Skip(); }
virtual void OnSearchBoxKey( wxKeyEvent& event ) { event.Skip(); }
virtual void OnSearchBoxChange( wxCommandEvent& event ) { event.Skip(); }
virtual void OnSearchBoxEnter( wxCommandEvent& event ) { event.Skip(); }
virtual void OnTreeKeyUp( wxKeyEvent& event ) { event.Skip(); }
virtual void OnTreeActivate( wxTreeListEvent& event ) { event.Skip(); }
virtual void OnTreeSelect( wxTreeListEvent& event ) { event.Skip(); }
virtual void OnTreeActivate( wxDataViewEvent& event ) { event.Skip(); }
virtual void OnTreeSelect( wxDataViewEvent& event ) { event.Skip(); }
virtual void OnDatasheetClick( wxHtmlLinkEvent& event ) { event.Skip(); }
virtual void OnStartComponentBrowser( wxMouseEvent& event ) { event.Skip(); }
virtual void OnHandlePreviewRepaint( wxPaintEvent& event ) { event.Skip(); }

View File

@ -3,7 +3,7 @@
*
* Copyright (C) 2015 Jean-Pierre Charras, jp.charras at wanadoo.fr
* Copyright (C) 2008-2017 Wayne Stambaugh <stambaughw@verizon.net>
* Copyright (C) 2004-2017 KiCad Developers, see change_log.txt for contributors.
* Copyright (C) 2004-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
@ -46,7 +46,7 @@
#include <eeschema_id.h>
#include <dialog_choose_component.h>
#include <component_tree_search_container.h>
#include <cmp_tree_model_adapter.h>
#include <dialog_get_component.h>
@ -107,7 +107,7 @@ wxString SCH_BASE_FRAME::SelectComponentFromLibrary( const SCHLIB_FILTER* aFilte
wxString dialogTitle;
PART_LIBS* libs = Prj().SchLibs();
COMPONENT_TREE_SEARCH_CONTAINER search_container( libs ); // Container doing search-as-you-type
auto adapter( CMP_TREE_MODEL_ADAPTER::Create( libs ) );
bool loaded = false;
if( aFilter )
@ -121,12 +121,12 @@ wxString SCH_BASE_FRAME::SelectComponentFromLibrary( const SCHLIB_FILTER* aFilte
if( currLibrary )
{
loaded = true;
search_container.AddLibrary( *currLibrary );
adapter->AddLibrary( *currLibrary );
}
}
if( aFilter->GetFilterPowerParts() )
search_container.SetFilter( COMPONENT_TREE_SEARCH_CONTAINER::CMP_FILTER_POWER );
adapter->SetFilter( CMP_TREE_MODEL_ADAPTER::CMP_FILTER_POWER );
}
@ -134,7 +134,7 @@ wxString SCH_BASE_FRAME::SelectComponentFromLibrary( const SCHLIB_FILTER* aFilte
{
for( PART_LIB& lib : *libs )
{
search_container.AddLibrary( lib );
adapter->AddLibrary( lib );
}
}
@ -149,17 +149,17 @@ wxString SCH_BASE_FRAME::SelectComponentFromLibrary( const SCHLIB_FILTER* aFilte
// we build it with only with "History" string translatable
wxString nodename;
nodename << wxT("-- ") << _("History") << wxT(" --");
search_container.AddAliasList( nodename, aHistoryList, NULL );
search_container.SetPreselectNode( aHistoryList[0], aHistoryLastUnit );
adapter->AddAliasList( nodename, aHistoryList, NULL );
adapter->SetPreselectNode( aHistoryList[0], aHistoryLastUnit );
}
if( !aHighlight.IsEmpty() )
search_container.SetPreselectNode( aHighlight, /* aUnit */ 0 );
adapter->SetPreselectNode( aHighlight, /* aUnit */ 0 );
const int deMorgan = aConvert ? *aConvert : 1;
dialogTitle.Printf( _( "Choose Component (%d items loaded)" ),
search_container.GetComponentsCount() );
DIALOG_CHOOSE_COMPONENT dlg( this, dialogTitle, &search_container, deMorgan );
adapter->GetComponentsCount() );
DIALOG_CHOOSE_COMPONENT dlg( this, dialogTitle, adapter, deMorgan );
if( dlg.ShowModal() == wxID_CANCEL )
return wxEmptyString;

View File

@ -47,7 +47,7 @@
#include <schframe.h>
#include <dialog_choose_component.h>
#include <component_tree_search_container.h>
#include <cmp_tree_model_adapter.h>
#include <dialogs/dialog_lib_new_component.h>
@ -542,18 +542,17 @@ void LIB_EDIT_FRAME::DeleteOnePart( wxCommandEvent& event )
}
}
COMPONENT_TREE_SEARCH_CONTAINER search_container( Prj().SchLibs() );
auto adapter( CMP_TREE_MODEL_ADAPTER::Create( Prj().SchLibs() ) );
wxString name = part ? part->GetName() : wxString( wxEmptyString );
search_container.SetPreselectNode( name, /* aUnit */ 0 );
search_container.ShowUnits( false );
search_container.AddLibrary( *lib );
adapter->SetPreselectNode( name, /* aUnit */ 0 );
adapter->ShowUnits( false );
adapter->AddLibrary( *lib );
wxString dialogTitle;
dialogTitle.Printf( _( "Delete Component (%u items loaded)" ),
search_container.GetComponentsCount() );
dialogTitle.Printf( _( "Delete Component (%u items loaded)" ), adapter->GetComponentsCount() );
DIALOG_CHOOSE_COMPONENT dlg( this, dialogTitle, &search_container, m_convert );
DIALOG_CHOOSE_COMPONENT dlg( this, dialogTitle, adapter, m_convert );
if( dlg.ShowModal() == wxID_CANCEL )
{

View File

@ -41,7 +41,7 @@
#include <class_library.h>
#include <dialog_helpers.h>
#include <dialog_choose_component.h>
#include <component_tree_search_container.h>
#include <cmp_tree_model_adapter.h>
void LIB_VIEW_FRAME::OnSelectSymbol( wxCommandEvent& aEvent )
@ -50,16 +50,16 @@ void LIB_VIEW_FRAME::OnSelectSymbol( wxCommandEvent& aEvent )
PART_LIBS* libs = Prj().SchLibs();
// Container doing search-as-you-type.
COMPONENT_TREE_SEARCH_CONTAINER search_container( libs );
auto adapter( CMP_TREE_MODEL_ADAPTER::Create( libs ) );
for( PART_LIB& lib : *libs )
{
search_container.AddLibrary( lib );
adapter->AddLibrary( lib );
}
dialogTitle.Printf( _( "Choose Component (%d items loaded)" ),
search_container.GetComponentsCount() );
DIALOG_CHOOSE_COMPONENT dlg( this, dialogTitle, &search_container, m_convert );
adapter->GetComponentsCount() );
DIALOG_CHOOSE_COMPONENT dlg( this, dialogTitle, adapter, m_convert );
if( dlg.ShowModal() == wxID_CANCEL )
return;

View File

@ -31,6 +31,7 @@
#define EDA_PATTERN_MATCH_H
#include <vector>
#include <memory>
#include <wx/wx.h>
#include <wx/string.h>
#include <wx/regex.h>
@ -95,4 +96,32 @@ public:
virtual int Find( const wxString& aCandidate ) const override;
};
class EDA_COMBINED_MATCHER
{
public:
EDA_COMBINED_MATCHER( const wxString &aPattern );
/*
* Look in all existing matchers, return the earliest match of any of
* the existing.
*
* @param aTerm term to look for
* @param aMatchersTriggered out: number of matcher that found the term
* @param aPostion out: where the term was found, or EDA_PATTERN_NOT_FOUND
*
* @return true if any matchers found the term
*/
bool Find( const wxString &aTerm, int& aMatchersTriggered, int& aPosition );
wxString const& GetPattern() const;
private:
// Add matcher if it can compile the pattern.
void AddMatcher( const wxString &aPattern, std::unique_ptr<EDA_PATTERN_MATCH> aMatcher );
std::vector<std::unique_ptr<EDA_PATTERN_MATCH>> m_matchers;
wxString m_pattern;
};
#endif // EDA_PATTERN_MATCH_H