File: cmp_tree_model.cpp

package info (click to toggle)
kicad 5.0.2%2Bdfsg1-1
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 234,592 kB
  • sloc: cpp: 505,330; ansic: 57,038; python: 4,886; sh: 879; awk: 294; makefile: 253; xml: 103; perl: 5
file content (313 lines) | stat: -rw-r--r-- 8,812 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
/*
 * 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-2018 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>
#include <pgm_base.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;


// 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 ),
      Type( INVALID ),
      IntrinsicRank( 0 ),
      Score( kLowestDefaultScore ),
      SearchTextNormalized( false ),
      Unit( 0 ),
      IsRoot( false )
{}


CMP_TREE_NODE_UNIT::CMP_TREE_NODE_UNIT( CMP_TREE_NODE* aParent, int aUnit )
{
    static void* locale = nullptr;
    static wxString namePrefix;

    // Fetching translations can take a surprising amount of time when loading libraries,
    // so only do it when necessary.
    if( Pgm().GetLocale() != locale )
    {
        namePrefix = _( "Unit" );
        locale = Pgm().GetLocale();
    }

    Parent = aParent;
    Type = UNIT;

    Unit = aUnit;
    LibId = aParent->LibId;

    Name = namePrefix + " " + LIB_PART::SubReference( aUnit, false );
    Desc = wxEmptyString;
    MatchName = wxEmptyString;

    IntrinsicRank = -aUnit;
}


CMP_TREE_NODE_LIB_ID::CMP_TREE_NODE_LIB_ID( CMP_TREE_NODE* aParent, LIB_ALIAS* aAlias )
{
    wxASSERT( aParent && aAlias );

    Type = LIBID;
    Parent = aParent;
    Update( aAlias );
}


CMP_TREE_NODE_UNIT& CMP_TREE_NODE_LIB_ID::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_LIB_ID::Update( LIB_ALIAS* aAlias )
{
    Name        = aAlias->GetName();
    Desc        = aAlias->GetDescription();

    // Parent node is the library nickname so set the LIB_ID library nickname.
    IsRoot = aAlias->IsRoot();

    // Pre-normalized strings for fast case-insensitive matching
    // Search text spaces out keywords and description to penalize description
    // matches - earlier matches are worth more.
    MatchName   = aAlias->GetName().Lower();
    SearchText  = (aAlias->GetKeyWords() + "        " + Desc);

    // Extract default footprint text
    LIB_PART* part = aAlias->GetPart();

    wxString footprint;

    if( part )
    {
        LibId = part->GetLibId();
        LibId.SetLibItemName( Name );
        footprint = part->GetFootprintField().GetText();
    }

    // If a footprint is defined for the part,
    // add it to the serach string
    if( !footprint.IsEmpty() )
    {
        SearchText += "        ";
        SearchText += footprint;
    }

    Children.clear();

    if( part && part->IsMulti() )
    {
        for( int u = 1; u <= part->GetUnitCount(); ++u )
            AddUnit( u );
    }

    SearchTextNormalized = false;
}


void CMP_TREE_NODE_LIB_ID::UpdateScore( EDA_COMBINED_MATCHER& aMatcher )
{
    if( Score <= 0 )
        return; // Leaf nodes without scores are out of the game.

    if( !SearchTextNormalized )
    {
        SearchText = SearchText.Lower();
        SearchTextNormalized = true;
    }

    // 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, wxString const& aDesc )
{
    Type = LIB;
    Name = aName;
    MatchName = aName.Lower();
    Desc = aDesc;
    Parent = aParent;
    LibId.SetLibNickname( aName );
}


CMP_TREE_NODE_LIB_ID& CMP_TREE_NODE_LIB::AddAlias( LIB_ALIAS* aAlias )
{
    CMP_TREE_NODE_LIB_ID* alias = new CMP_TREE_NODE_LIB_ID( 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, wxString const& aDesc )
{
    CMP_TREE_NODE_LIB* lib = new CMP_TREE_NODE_LIB( this, aName, aDesc );
    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 );
}