KiCad PCB EDA Suite
Loading...
Searching...
No Matches
panel_footprint_filters.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
21
22#include <algorithm>
23
24#include <wx/clipbrd.h>
25#include <wx/menu.h>
26#include <wx/wupdlock.h>
27
28#include <bitmaps.h>
31#include <kiway.h>
32#include <kiway_mail.h>
33#include <lib_id.h>
34#include <string_utils.h>
37
38
44static wxString SanitizeFpFilter( const wxString& aFilter )
45{
46 wxString filter = aFilter;
47 filter.Replace( wxT( " " ), wxT( "_" ) );
48 return filter;
49}
50
51
60{
61public:
62 explicit FP_FILTER_MATCHER( KIWAY& aKiway ) :
63 m_kiway( aKiway )
64 {
65 }
66
71 FOOTPRINT_MATCH_RESULT GetMatches( const std::vector<wxString>& aPatterns )
72 {
74 result.m_Success = true;
75
76 for( const wxString& pattern : aPatterns )
77 {
78 FOOTPRINT_MATCH_RESULT patternResult = fetchMatches( pattern );
79
80 result.m_Success = result.m_Success && patternResult.m_Success;
81 result.m_IsLimited = result.m_IsLimited || patternResult.m_IsLimited;
82
83 result.m_MatchingNames.insert( result.m_MatchingNames.end(), patternResult.m_MatchingNames.begin(),
84 patternResult.m_MatchingNames.end() );
85 }
86
87 std::sort( result.m_MatchingNames.begin(), result.m_MatchingNames.end(),
88 []( const wxString& aLeft, const wxString& aRight )
89 {
90 return StrNumCmp( aLeft, aRight, true ) < 0;
91 } );
92
93 auto sameName = []( const wxString& aLeft, const wxString& aRight )
94 {
95 return aLeft.IsSameAs( aRight, false );
96 };
97
98 result.m_MatchingNames.erase(
99 std::unique( result.m_MatchingNames.begin(), result.m_MatchingNames.end(), sameName ),
100 result.m_MatchingNames.end() );
101
102 return result;
103 }
104
112 void PruneCache( const std::vector<wxString>& aPatterns )
113 {
114 std::set<wxString> wanted;
115
116 for( const wxString& pattern : aPatterns )
117 wanted.insert( pattern.Lower() );
118
119 std::erase_if( m_cache,
120 [&wanted]( const auto& aEntry )
121 {
122 return !wanted.contains( aEntry.first );
123 } );
124 }
125
126private:
131 FOOTPRINT_MATCH_RESULT fetchMatches( const wxString& aPattern )
132 {
133 wxString key = aPattern.Lower();
134 auto it = m_cache.find( key );
135
136 if( it != m_cache.end() )
137 return it->second;
138
140 query.m_Patterns.push_back( aPattern );
142
144
145 // A failed query is not cached: the libraries may be reachable next time.
146 if( result.m_Success )
147 m_cache.emplace( key, result );
148
149 return result;
150 }
151
152 // Symbol filters normally select at most a few hundred footprints, which is all the
153 // matching-footprints list needs to show.
154 static constexpr int MAX_MATCH_RESULTS = 500;
155
157 std::map<wxString, FOOTPRINT_MATCH_RESULT> m_cache;
158};
159
160
163 m_kiway( aKiway ),
165 m_fpMatcher( std::make_unique<FP_FILTER_MATCHER>( aKiway ) ),
166 m_matchLoadTimer( this ),
168 [this]()
169 {
171 },
173{
174 // Configure button logos
175 m_addFilterButton->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) );
176 m_editFilterButton->SetBitmap( KiBitmapBundle( BITMAPS::small_edit ) );
177 m_deleteFilterButton->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) );
178
179 // Forward the delete button to the tricks
180 m_deleteFilterButton->Bind( wxEVT_BUTTON,
181 [&]( wxCommandEvent& aEvent )
182 {
183 wxCommandEvent cmdEvent( EDA_EVT_LISTBOX_DELETE );
184 m_fpFilterTricks->ProcessEvent( cmdEvent );
185 } );
186
187 // When the filter tricks modifies something, update ourselves
188 m_FootprintFilterListBox->Bind( EDA_EVT_LISTBOX_CHANGED,
189 [&]( wxCommandEvent& aEvent )
190 {
191 // Filters pasted or duplicated into the list need the
192 // same space handling as ones typed into the dialogs.
193 for( unsigned ii = 0; ii < m_FootprintFilterListBox->GetCount(); ++ii )
194 {
195 wxString filter = m_FootprintFilterListBox->GetString( ii );
196 m_FootprintFilterListBox->SetString( ii, SanitizeFpFilter( filter ) );
197 }
198
199 onModify();
200 updateMatchingFootprints();
201 } );
202
203 // The selected event is sent whenever the selection changes, including when it is
204 // cleared; the handler always reads the current state.
205 m_FootprintFilterListBox->Bind( wxEVT_COMMAND_LISTBOX_SELECTED,
206 [this]( wxCommandEvent& aEvent )
207 {
208 updateMatchingFootprints();
209 } );
210
211 // Loading the footprint libraries can take a while, so only query them once the page
212 // is actually selected.
213 aParent->Bind( wxEVT_NOTEBOOK_PAGE_CHANGED, &PANEL_FOOTPRINT_FILTERS::onNotebookPageChanged, this );
214
215 // wxLC_REPORT needs a column and wxLC_NO_HEADER hides the header. Keep the single
216 // column filling the visible width.
217 m_matchingFootprints->AppendColumn( wxEmptyString, wxLIST_FORMAT_LEFT, 200 );
218
219 m_matchingFootprints->Bind( wxEVT_SIZE,
220 [this]( wxSizeEvent& aEvent )
221 {
222 int width = m_matchingFootprints->GetClientSize().GetWidth() - 2;
223 m_matchingFootprints->SetColumnWidth( 0, std::max( width, 20 ) );
224 aEvent.Skip();
225 } );
226
227 // Double-clicking a match opens that footprint in the footprint editor.
228 m_matchingFootprints->Bind( wxEVT_LIST_ITEM_ACTIVATED, &PANEL_FOOTPRINT_FILTERS::onMatchingFootprintDoubleClick,
229 this );
230
231 m_matchingFootprints->Bind( wxEVT_LIST_ITEM_RIGHT_CLICK, &PANEL_FOOTPRINT_FILTERS::onMatchingFootprintContextMenu,
232 this );
233
234 m_matchingFootprints->Bind( wxEVT_KEY_DOWN, &PANEL_FOOTPRINT_FILTERS::onMatchingFootprintKeyDown, this );
235
236 // The libraries the matches come from are loaded on first use. Rather than blocking the
237 // dialog, the matches panel shows a loading notice and is greyed out until they arrive.
238 Bind( wxEVT_TIMER, &PANEL_FOOTPRINT_FILTERS::onFpFilterMatchLoadTimer, this, m_matchLoadTimer.GetId() );
239
240 // Most likely the footprint libraries are already loaded/ing (the parent dialog has already triggered the load)
241 // but this is basically free in that case.
243}
244
245
247{
248 m_matchLoadTimer.Stop();
249 Unbind( wxEVT_TIMER, &PANEL_FOOTPRINT_FILTERS::onFpFilterMatchLoadTimer, this, m_matchLoadTimer.GetId() );
250
251 GetParent()->Unbind( wxEVT_NOTEBOOK_PAGE_CHANGED, &PANEL_FOOTPRINT_FILTERS::onNotebookPageChanged, this );
252}
253
254
255void PANEL_FOOTPRINT_FILTERS::SetFilters( const wxArrayString& aFilters )
256{
257 m_FootprintFilterListBox->Set( aFilters );
258}
259
260
262{
263 return m_FootprintFilterListBox->GetStrings();
264}
265
266
268 FOOTPRINT_ASSIGNER aAssign )
269{
270 m_canAssignFootprint = std::move( aCanAssign );
271 m_assignFootprint = std::move( aAssign );
272}
273
274
276{
277 if( DIALOG_SHIM* dlg = dynamic_cast<DIALOG_SHIM*>( wxGetTopLevelParent( this ) ) )
278 dlg->OnModify();
279}
280
281
283{
284 wxNotebook* notebook = dynamic_cast<wxNotebook*>( aEvent.GetEventObject() );
285
286 if( notebook && notebook == GetParent() && aEvent.GetSelection() >= 0
287 && notebook->GetPage( aEvent.GetSelection() ) == this )
288 {
290 }
291
292 aEvent.Skip();
293}
294
295
297{
298 int idx = m_FootprintFilterListBox->HitTest( aEvent.GetPosition() );
299 wxCommandEvent dummy;
300
301 if( idx >= 0 )
303 else
305}
306
307
309{
310 std::vector<wxString> filters;
311
312 for( unsigned ii = 0; ii < m_FootprintFilterListBox->GetCount(); ++ii )
313 filters.push_back( m_FootprintFilterListBox->GetString( ii ) );
314
315 std::vector<wxString> patterns;
316
317 if( !m_fpFilterPreview.IsEmpty() )
318 {
319 // A filter is being typed: preview it rather than the filters in the list. Its
320 // patterns have nowhere in the list to live yet, so leave the cache alone until
321 // the edit ends.
322 patterns.push_back( m_fpFilterPreview );
323 }
324 else
325 {
326 // Filters that were edited away or deleted are gone for good; the other filters
327 // keep their cached results.
328 m_fpMatcher->PruneCache( filters );
329
330 wxArrayInt selections;
331 m_FootprintFilterListBox->GetSelections( selections );
332
333 // With nothing selected, show the union of every filter; otherwise just the
334 // selected filters.
335 if( selections.IsEmpty() )
336 {
337 patterns = filters;
338 }
339 else
340 {
341 for( int ii : selections )
342 patterns.push_back( filters[ii] );
343 }
344 }
345
346 showFootprintMatches( patterns );
347}
348
349
350void PANEL_FOOTPRINT_FILTERS::showFootprintMatches( const std::vector<wxString>& aPatterns )
351{
352 // If the footprint libraries are not ready yet, keep the loading indication live.
353 if( !aPatterns.empty() && !matchLibrariesReady() )
354 return;
355
356 FOOTPRINT_MATCH_RESULT result = m_fpMatcher->GetMatches( aPatterns );
357
358 wxWindowUpdateLocker lock( m_matchingFootprints );
359 m_matchingFootprints->DeleteAllItems();
360
361 for( const wxString& footprint : result.m_MatchingNames )
362 m_matchingFootprints->InsertItem( m_matchingFootprints->GetItemCount(), footprint );
363
364 wxString labelText;
365 if( !result.m_Success )
366 {
367 labelText = _( "Failed to query matching footprints." );
368 }
369 else
370 {
371 wxString countFormat =
372 result.m_IsLimited ? _( "Matching footprints (%zu+):" ) : _( "Matching footprints (%zu):" );
373 labelText = wxString::Format( countFormat, result.m_MatchingNames.size() );
374 }
375
376 m_staticTextFootprints1->SetLabel( labelText );
377
378 Layout();
379}
380
381
383{
385 return true;
386
387 float progress = GetFootprintLibrariesLoadProgress( m_kiway );
388
389 if( progress >= 1.0f )
390 {
393 return true;
394 }
395
396 showFpFilterMatchLoadIndication( true, progress );
397 // Go and poll in #onMatchLoadTimer
398 m_matchLoadTimer.StartOnce( 250 );
399 return false;
400}
401
402
404{
405 m_matchingFootprints->Enable( !aShow );
406
407 if( aShow )
408 {
409 // The result count is not known yet
410 m_staticTextFootprints1->SetLabel( _( "Matching footprints:" ) );
411
412 // The load reports less than 1.0 until it has finished, so this never reads 100%.
413 wxString notice = wxString::Format( _( "Loading footprint libraries (%d%%)..." ),
414 static_cast<int>( aProgress * 100.0f ) );
415
416 if( m_matchingFootprints->GetItemCount() == 0 )
417 m_matchingFootprints->InsertItem( 0, notice );
418 else
419 m_matchingFootprints->SetItemText( 0, notice );
420 }
421 else
422 {
423 m_matchingFootprints->DeleteAllItems();
424 }
425}
426
427
429{
430 if( !matchLibrariesReady() )
431 return;
432
433 // The filters may have changed while the libraries were loading; update the matches.
435}
436
437
439{
440 m_fpFilterPreview = SanitizeFpFilter( aEvent.GetString() );
441 m_filterPreviewDebounce.Restart();
442}
443
444
446{
447 // Events can have, wxNOT_FOUND as the index.
448 if( aItem < 0 || aItem >= m_matchingFootprints->GetItemCount() )
449 return wxEmptyString;
450
451 LIB_ID fpId;
452
453 // The list holds "library:footprint" names.
454 if( fpId.Parse( m_matchingFootprints->GetItemText( aItem ) ) != -1 || !fpId.IsValid() )
455 return wxEmptyString;
456
457 return fpId.Format();
458}
459
460
461void PANEL_FOOTPRINT_FILTERS::openFootprintInEditor( const wxString& aFootprintName )
462{
463 m_kiway.Player( FRAME_FOOTPRINT_EDITOR, true );
464
465 std::string packet = aFootprintName.utf8_string();
466 m_kiway.ExpressMail( FRAME_FOOTPRINT_EDITOR, MAIL_FP_EDIT_LIBID, packet, this );
467}
468
469
470void PANEL_FOOTPRINT_FILTERS::copyFootprintName( const wxString& aFootprintName )
471{
472 if( wxTheClipboard->Open() )
473 {
474 wxTheClipboard->SetData( new wxTextDataObject( aFootprintName ) );
475 wxTheClipboard->Close();
476 }
477}
478
479
481{
482 wxString fpName = matchingFootprintName( aEvent.GetIndex() );
483
484 if( !fpName.IsEmpty() )
485 openFootprintInEditor( fpName );
486}
487
488
490{
491 wxString fpName = matchingFootprintName( aEvent.GetIndex() );
492
493 if( fpName.IsEmpty() )
494 return;
495
496 enum
497 {
498 OPEN_IN_FOOTPRINT_EDITOR = 1,
499 ASSIGN_TO_FOOTPRINT_FIELD,
500 COPY_FOOTPRINT_NAME
501 };
502
503 wxMenu menu;
504
505 menu.Append( OPEN_IN_FOOTPRINT_EDITOR, _( "Open in Footprint Editor" ) );
506
507 // The host dialog's Footprint field cannot always be written (it is read-only when the
508 // symbol has no footprint).
510 menu.Append( ASSIGN_TO_FOOTPRINT_FIELD, _( "Assign to Footprint Field" ) );
511
512 menu.Append( COPY_FOOTPRINT_NAME, _( "Copy Footprint Name" ) + "\tCtrl+C" );
513
514 switch( GetPopupMenuSelectionFromUser( menu ) )
515 {
516 case OPEN_IN_FOOTPRINT_EDITOR:
517 openFootprintInEditor( fpName );
518 break;
519 case ASSIGN_TO_FOOTPRINT_FIELD:
521 m_assignFootprint( fpName );
522
523 break;
524 case COPY_FOOTPRINT_NAME:
525 copyFootprintName( fpName );
526 break;
527 default:
528 break;
529 }
530}
531
532
534{
535 if( aEvent.ControlDown() && aEvent.GetKeyCode() == 'C' )
536 {
537 long item = m_matchingFootprints->GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
538 wxString fpName = matchingFootprintName( item );
539
540 if( !fpName.IsEmpty() )
541 {
542 copyFootprintName( fpName );
543 return;
544 }
545 }
546
547 aEvent.Skip();
548}
549
550
552{
553 wxString filterLine;
554 WX_TEXT_ENTRY_DIALOG dlg( this, _( "Filter:" ), _( "Add Footprint Filter" ), filterLine );
555
556 // Let the matches list preview what is being typed.
557 dlg.Bind( wxEVT_TEXT, &PANEL_FOOTPRINT_FILTERS::onFilterPreviewText, this );
558
559 int modalResult = dlg.ShowModal();
560
561 // The preview is over, so a pending debounce must not fire after it.
563 m_fpFilterPreview.clear();
564
565 if( modalResult == wxID_CANCEL || dlg.GetValue().IsEmpty() )
566 {
567 updateMatchingFootprints(); // Discard the live preview
568 return;
569 }
570
571 filterLine = SanitizeFpFilter( dlg.GetValue() );
572
573 // duplicate filters do no harm, so don't be a nanny.
574 m_FootprintFilterListBox->Append( filterLine );
575 m_FootprintFilterListBox->SetSelection( (int) m_FootprintFilterListBox->GetCount() - 1 );
576
577 onModify();
579}
580
581
583{
584 wxArrayInt selections;
585 const int n = m_FootprintFilterListBox->GetSelections( selections );
586
587 if( n > 0 )
588 {
589 // Just edit the first one
590 int idx = selections[0];
591 wxString filter = m_FootprintFilterListBox->GetString( idx );
592
593 WX_TEXT_ENTRY_DIALOG dlg( this, _( "Filter:" ), _( "Edit Footprint Filter" ), filter );
594
595 // Let the matches list preview what is being typed.
596 dlg.Bind( wxEVT_TEXT, &PANEL_FOOTPRINT_FILTERS::onFilterPreviewText, this );
597
598 int modalResult = dlg.ShowModal();
599
600 // The preview is over, so a pending debounce must not fire after it.
602 m_fpFilterPreview.clear();
603
604 if( modalResult == wxID_OK && !dlg.GetValue().IsEmpty() )
605 {
606 m_FootprintFilterListBox->SetString( (unsigned) idx, SanitizeFpFilter( dlg.GetValue() ) );
607 onModify();
608 }
609
610 updateMatchingFootprints(); // Refresh, also discarding a cancelled preview
611 }
612}
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
Dialog helper object to sit in the inheritance tree between wxDialog and any class written by wxFormB...
Definition dialog_shim.h:80
int ShowModal() override
Supplies the footprints matching a symbol's footprint filter patterns.
std::map< wxString, FOOTPRINT_MATCH_RESULT > m_cache
void PruneCache(const std::vector< wxString > &aPatterns)
Drop the cached results for the patterns that are not in aPatterns.
FOOTPRINT_MATCH_RESULT GetMatches(const std::vector< wxString > &aPatterns)
Return the case-insensitively sorted, de-duplicated union of the footprints matching any of the given...
FOOTPRINT_MATCH_RESULT fetchMatches(const wxString &aPattern)
Return the matches for a single pattern, querying the libraries when it is not cached yet.
static constexpr int MAX_MATCH_RESULTS
A minimalistic software bus for communications between various DLLs/DSOs (DSOs) within the same KiCad...
Definition kiway.h:348
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
int Parse(const UTF8 &aId, bool aFix=false)
Parse LIB_ID with the information from aId.
Definition lib_id.cpp:65
bool IsValid() const
Check if this LID_ID is valid.
Definition lib_id.h:168
UTF8 Format() const
Definition lib_id.cpp:132
PANEL_FOOTPRINT_FILTERS_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(500, 300), long style=wxTAB_TRAVERSAL, const wxString &name=wxEmptyString)
std::unique_ptr< LISTBOX_TRICKS > m_fpFilterTricks
wxString matchingFootprintName(long aItem) const
Return the "library:footprint" name of the given item in the matches list, or an empty string when th...
std::function< void(const wxString &)> FOOTPRINT_ASSIGNER
FOOTPRINT_ASSIGN_QUERY m_canAssignFootprint
Access to panel-external interfaces.
void OnAddFootprintFilter(wxCommandEvent &aEvent) override
void onMatchingFootprintKeyDown(wxKeyEvent &aEvent)
PANEL_FOOTPRINT_FILTERS(wxWindow *aParent, KIWAY &aKiway)
void openFootprintInEditor(const wxString &aFootprintName)
void onModify()
Mark the host dialog (the top level window) as modified.
wxString m_fpFilterPreview
The sanitised filter text being typed in the Add/Edit filter dialog.
void copyFootprintName(const wxString &aFootprintName)
void onFpFilterMatchLoadTimer(wxTimerEvent &aEvent)
void showFpFilterMatchLoadIndication(bool aShow, float aProgress=0.0f)
Show or hide the footprint filter match list's library load indicator.
WX_DEBOUNCED_ACTION m_filterPreviewDebounce
Debounces the preview of the filter being type to avoid spamming queries.
void onNotebookPageChanged(wxBookCtrlEvent &aEvent)
Query the matches when the page is selected; the libraries are not loaded before that,...
void updateMatchingFootprints()
Refresh the matches list for the current filters, or for the filter being typed in the Add/Edit dialo...
void SetFilters(const wxArrayString &aFilters)
Replace the filters in the list with aFilters.
void SetFootprintFieldAccessors(FOOTPRINT_ASSIGN_QUERY aCanAssign, FOOTPRINT_ASSIGNER aAssign)
Injection for interfaces that reach outside the panel to interact with host dialog.
void OnEditFootprintFilter(wxCommandEvent &aEvent) override
std::function< bool()> FOOTPRINT_ASSIGN_QUERY
void onMatchingFootprintContextMenu(wxListEvent &aEvent)
void showFootprintMatches(const std::vector< wxString > &aPatterns)
void onMatchingFootprintDoubleClick(wxListEvent &aEvent)
void onFilterPreviewText(wxCommandEvent &aEvent)
Preview the filter text typed in the Add/Edit dialog, once typing pauses.
bool matchLibrariesReady()
Return true once the footprint libraries can be queried.
void OnFpFilterDClick(wxMouseEvent &aEvent) override
std::unique_ptr< FP_FILTER_MATCHER > m_fpMatcher
Supplies the matches for the footprint filter patterns.
static constexpr int TEXT_INPUT_DEBOUNCE_MS
A typical debounce period for text input events, in milliseconds.
A KICAD version of wxTextEntryDialog which supports the various improvements/work-arounds from DIALOG...
wxString GetValue() const
#define _(s)
KIWAY * m_kiway
Definition eeschema.cpp:295
float GetFootprintLibrariesLoadProgress(KIWAY &aKiway)
Return how far the background load of the footprint libraries has got.
bool StartFootprintLibrariesLoad(KIWAY &aKiway)
Start loading the footprint libraries in the background.
FOOTPRINT_MATCH_RESULT QueryMatchingFootprints(KIWAY &aKiway, const FOOTPRINT_MATCH_QUERY &aQuery)
Ask the footprint libraries, for the footprints matching a query (over the Pcbnew kiface).
@ FRAME_FOOTPRINT_EDITOR
Definition frame_type.h:39
@ MAIL_FP_EDIT_LIBID
Definition mail_type.h:53
STL namespace.
static wxString SanitizeFpFilter(const wxString &aFilter)
Footprint filters are saved as a space-separated list, so a space inside a filter would be read as a ...
static const std::vector< std::string > filters
Definition plugin.cpp:64
std::vector< FAB_LAYER_COLOR > dummy
All the information needed to query the footprint libraries for matching footprints.
std::vector< wxString > m_Patterns
Wildcard patterns matched against the footprint name, or against "library:footprint" when a pattern c...
int m_MaxResults
Limits the number of returned footprints; zero means no limit.
The outcome of a footprint match query, and the JSON encoding used to move it across the Pcbnew kifac...
std::vector< wxString > m_MatchingNames
The matching "library:footprint" names.
bool m_Success
False if the query could not be performed, in which case MatchingNames is empty.
bool m_IsLimited
True if the result set is limited by the maximum number of results.
wxString result
Test unit parsing edge cases and error handling.