KiCad PCB EDA Suite
Loading...
Searching...
No Matches
font_choice.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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include <widgets/font_choice.h>
21#include <kiplatform/ui.h>
22#include <font/fontconfig.h>
23#include <pgm_base.h>
24
25#include <wx/dc.h>
26#include <wx/event.h>
27#include <wx/fontenum.h>
28#include <wx/settings.h>
29#include <wx/textctrl.h>
30
31#include <thread>
32#include <mutex>
33#include <condition_variable>
34#include <atomic>
35#include <chrono>
36#include <algorithm>
37#include <cwctype>
38
39// The "official" name of the building Kicad stroke font (always existing)
41
42class FONT_LIST_MANAGER : public wxEvtHandler
43{
44public:
45 static FONT_LIST_MANAGER& Get();
46 wxArrayString GetFonts() const;
47 void Register( FONT_CHOICE* aCtrl );
48 void Unregister( FONT_CHOICE* aCtrl );
49
50private:
53 void Poll();
54 void UpdateFonts();
55
56 std::thread m_thread;
57 mutable std::mutex m_mutex;
58 std::condition_variable m_cv;
59 wxArrayString m_fonts;
60 std::vector<FONT_CHOICE*> m_controls;
61 std::atomic<bool> m_quit;
62};
63
65{
66 static FONT_LIST_MANAGER mgr;
67 return mgr;
68}
69
70wxArrayString FONT_LIST_MANAGER::GetFonts() const
71{
72 std::lock_guard<std::mutex> lock( m_mutex );
73 return m_fonts;
74}
75
77{
78 std::lock_guard<std::mutex> lock( m_mutex );
79 m_controls.push_back( aCtrl );
80}
81
83{
84 std::lock_guard<std::mutex> lock( m_mutex );
85 auto it = std::find( m_controls.begin(), m_controls.end(), aCtrl );
86
87 if( it != m_controls.end() )
88 m_controls.erase( it );
89}
90
92{
93 m_quit = false;
95
96// It appears that the polling mechanism does not work correctly
97// for mingw (hangs on exit)
98#ifndef __MINGW32__
99 m_thread = std::thread( &FONT_LIST_MANAGER::Poll, this );
100#endif
101}
102
104{
105 {
106 std::lock_guard<std::mutex> lock( m_mutex );
107 m_quit = true;
108 }
109
110#ifndef __MINGW32__
111 m_cv.notify_one();
112
113 if( m_thread.joinable() )
114 m_thread.join();
115#endif
116}
117
119{
120 std::unique_lock<std::mutex> lock( m_mutex );
121
122 while( !m_quit )
123 {
124 // N.B. wait_for will unlock the mutex while waiting but lock it before continuing
125 // so we need to relock before continuing in the loop
126 m_cv.wait_for( lock, std::chrono::seconds( 30 ), [&] { return m_quit.load(); } );
127
128 if( !m_quit )
129 {
130 lock.unlock();
131 UpdateFonts();
132 lock.lock();
133 }
134 }
135}
136
138{
139 std::vector<std::string> fontNames;
140 Fontconfig()->ListFonts( fontNames, std::string( Pgm().GetLanguageTag().utf8_str() ) );
141
142 wxArrayString menuList;
143
144 for( const std::string& name : fontNames )
145 menuList.Add( wxString( name ) );
146
147 menuList.Sort();
148
149 // Check if fonts changed and update controls
150 {
151 std::lock_guard<std::mutex> lock( m_mutex );
152
153 if( menuList == m_fonts )
154 return;
155
156 m_fonts = menuList;
157 }
158
159 CallAfter( [this]() {
160 std::vector<FONT_CHOICE*> controlsCopy;
161
162 // Copy controls list under lock protection
163 {
164 std::lock_guard<std::mutex> lock( m_mutex );
165 controlsCopy = m_controls;
166 }
167
168 // Update controls without holding lock
169 for( FONT_CHOICE* ctrl : controlsCopy )
170 {
171 if( ctrl && !ctrl->IsShownOnScreen() )
172 ctrl->RefreshFonts();
173 }
174 } );
175}
176
177
178FONT_CHOICE::FONT_CHOICE( wxWindow* aParent, int aId, wxPoint aPosition, wxSize aSize,
179 int nChoices, wxString* aChoices, int aStyle ) :
180 wxOwnerDrawnComboBox( aParent, aId, wxEmptyString, aPosition, aSize, 0, nullptr, aStyle )
181{
182 m_systemFontCount = nChoices;
183 m_notFound = wxS( " " ) + _( "<not found>" );
184 m_isFiltered = false;
185 m_lastText = wxEmptyString;
186 m_originalSelection = wxEmptyString;
187
189 RefreshFonts();
190
191 // Bind only essential events to restore functionality
192 Bind( wxEVT_KEY_DOWN, &FONT_CHOICE::OnKeyDown, this );
193 Bind( wxEVT_CHAR_HOOK, &FONT_CHOICE::OnCharHook, this );
194 Bind( wxEVT_COMMAND_TEXT_UPDATED, &FONT_CHOICE::OnTextCtrl, this );
195 Bind( wxEVT_COMBOBOX_DROPDOWN, &FONT_CHOICE::OnDropDown, this );
196 Bind( wxEVT_COMBOBOX_CLOSEUP, &FONT_CHOICE::OnCloseUp, this );
197 Bind( wxEVT_SET_FOCUS, &FONT_CHOICE::OnSetFocus, this );
198 Bind( wxEVT_KILL_FOCUS, &FONT_CHOICE::OnKillFocus, this );
199}
200
201
206
207
209{
210 // Do the same as wxOwnerDrawnComboBox::Clear().
211 // But, on MSW, Clear() has 2 issues:
212 // - it generate wxWidgets alerts
213 // - it generate a OnTextCtrl event, creating also recursions, not so easy to fix.
214 // We use wxOwnerDrawnComboBox::Delete that do not have these issues and can do the same job
215
216#if defined( __WXMSW__ )
217 while( GetCount() )
218 Delete( GetCount() - 1 );
219#else
220 Clear();
221#endif
222}
223
224
226{
227 wxArrayString menuList = FONT_LIST_MANAGER::Get().GetFonts();
228
229 wxString selection = GetValue();
230
231 // Store the full font list for filtering
232 m_fullFontList.Clear();
233
234 if( m_systemFontCount > 1 )
235 m_fullFontList.Add( _( "Default Font" ) );
236
238
239 for( const wxString& font : menuList )
240 m_fullFontList.Add( font );
241
242 Freeze();
243 clearList();
244
245 if( m_systemFontCount > 1 )
246 Append( _( "Default Font" ) );
247
248 Append( KICAD_FONT_NAME );
249 m_systemFontCount = GetCount();
250
251 Append( menuList );
252
253 if( !selection.IsEmpty() )
254 SetStringSelection( selection );
255
256 m_isFiltered = false;
257 Thaw();
258}
259
260
261void FONT_CHOICE::SetFontSelection( KIFONT::FONT* aFont, bool aSilentMode )
262{
263 if( !aFont )
264 {
265 SetSelection( 0 );
266 }
267 else
268 {
269 bool result = SetStringSelection( aFont->GetName() );
270
271 if( !result )
272 {
273 Append( aFont->GetName() + m_notFound );
274 SetSelection( GetCount() - 1 );
275 }
276 }
277}
278
279
281{
282 int sel = GetSelection();
283
284 if( sel < 0 )
285 return false;
286
287 if( GetString( sel ).EndsWith( m_notFound ) )
288 return false;
289
290 return true;
291}
292
293
294KIFONT::FONT* FONT_CHOICE::GetFontSelection( bool aBold, bool aItalic, bool aForDrawingSheet ) const
295{
296 if( GetSelection() <= 0 )
297 {
298 return nullptr;
299 }
300 else if( GetSelection() == 1 && m_systemFontCount == 2 )
301 {
302 return KIFONT::FONT::GetFont( KICAD_FONT_NAME, aBold, aItalic );
303 }
304 else
305 {
306 return KIFONT::FONT::GetFont( GetValue(), aBold, aItalic, nullptr,
307 aForDrawingSheet );
308 }
309}
310
311
312wxCoord FONT_CHOICE::OnMeasureItem( size_t aItem ) const
313{
314 wxString name = GetString( aItem );
315
316 // Get default font extent
317 int sysW = 0, sysH = 0;
318 GetTextExtent( name, &sysW, &sysH );
319
320 return sysH + FromDIP( 6 );
321}
322
323
324void FONT_CHOICE::OnDrawItem( wxDC& aDc, const wxRect& aRect, int aItem, int aFlags ) const
325{
326 static const wxString c_sampleString = wxS( "AaBbCcDd123456" );
327
328 if( aItem == wxNOT_FOUND )
329 return;
330
331 wxString name = GetString( aItem );
332
333 aDc.SetFont( wxSystemSettings::GetFont( wxSYS_DEFAULT_GUI_FONT ) );
334
335 // Get default font extent
336 int sysW = 0, sysH = 0, sysDescent = 0;
337 aDc.GetTextExtent( name, &sysW, &sysH, &sysDescent );
338
339 // Draw the font name vertically centered
340 wxRect nameRect = wxRect( aRect.x + 2, aRect.y, sysW, sysH ).CenterIn( aRect, wxVERTICAL );
341 aDc.DrawText( name, nameRect.GetTopLeft() );
342
343 if( aItem >= m_systemFontCount )
344 {
345 wxFont sampleFont( wxFontInfo( aDc.GetFont().GetPointSize() ).FaceName( name ) );
346 aDc.SetFont( sampleFont );
347
348 if( aFlags & wxODCB_PAINTING_SELECTED )
349 aDc.SetTextForeground( wxSystemSettings::GetColour( wxSYS_COLOUR_LISTBOXHIGHLIGHTTEXT ) );
350 else
351 aDc.SetTextForeground( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) );
352
353 // Get sample font extent
354 int sampleW = 0, sampleH = 0, sampleDescent = 0;
355 aDc.GetTextExtent( name, &sampleW, &sampleH, &sampleDescent );
356
357 // Align the baselines vertically
358 aDc.DrawText( c_sampleString, nameRect.GetRight() + 15,
359 nameRect.GetBottom() - sysDescent - sampleH + sampleDescent + 1 );
360 }
361}
362
363
365{
366 return GetValue();
367}
368
369
370void FONT_CHOICE::OnKeyDown( wxKeyEvent& aEvent )
371{
372 int keyCode = aEvent.GetKeyCode();
373
374 if( keyCode == WXK_RETURN || keyCode == WXK_NUMPAD_ENTER || keyCode == WXK_ESCAPE )
375 {
376 if( IsPopupShown() )
377 {
378 // Accept current text and close popup
379 Dismiss();
380 return;
381 }
382 }
383 else if( keyCode == WXK_BACK && !IsPopupShown() )
384 {
385 // Handle backspace when popup is not shown
386 // This allows normal character-by-character deletion instead of selecting all text
387
388 wxString currentText = GetValue();
389 long selStart, selEnd;
390 GetSelection( &selStart, &selEnd );
391
392 if( selStart != selEnd )
393 {
394 // There's a selection - delete the selected text
395 wxString newText = currentText.Left( selStart ) + currentText.Mid( selEnd );
396 m_lastText = newText; // Prevent recursive calls
397 ChangeValue( newText );
398 SetInsertionPoint( selStart );
399 return; // Don't skip this event
400 }
401 else if( selStart > 0 )
402 {
403 // No selection - delete character before cursor
404 wxString newText = currentText.Left( selStart - 1 ) + currentText.Mid( selStart );
405 m_lastText = newText; // Prevent recursive calls
406 ChangeValue( newText );
407 SetInsertionPoint( selStart - 1 );
408 return; // Don't skip this event
409 }
410 // If at beginning of text, let default behavior handle it
411 }
412
413 aEvent.Skip();
414}
415
416
417void FONT_CHOICE::OnCharHook( wxKeyEvent& aEvent )
418{
419 int keyCode = aEvent.GetUnicodeKey();
420 wchar_t wc = static_cast<wchar_t>( keyCode );
421
422 // When popup is not shown, let normal text processing handle printable characters
423 // The OnTextCtrl method will handle filtering and autocomplete
424 if( !IsPopupShown() )
425 {
426 aEvent.Skip();
427 return;
428 }
429
430 if( std::iswprint( wc ) && !std::iswcntrl( wc ) )
431 {
432 // Get current text and check if there's a selection
433 wxString currentText = GetValue();
434 long selStart, selEnd;
435 GetSelection( &selStart, &selEnd );
436
437 wxChar newChar = (wxChar)keyCode;
438 wxString newText;
439
440 if( selStart != selEnd )
441 {
442 // There's a selection - replace it with the new character
443 newText = currentText.Left( selStart ) + newChar + currentText.Mid( selEnd );
444 }
445 else
446 {
447 // No selection - append to current insertion point
448 long insertionPoint = GetInsertionPoint();
449 newText = currentText.Left( insertionPoint ) + newChar + currentText.Mid( insertionPoint );
450 }
451
452 // Update the text control
453 m_lastText = newText; // Prevent recursive calls
454 ChangeValue( newText );
455 SetInsertionPoint( selStart + 1 ); // Position cursor after new character
456
457 // Filter the font list based on new text (will handle trimming internally)
458 FilterFontList( newText );
459
460 // Try autocomplete
461 DoAutoComplete( newText );
462
463 return; // Don't skip this event
464 }
465
466 switch (keyCode)
467 {
468 case WXK_BACK:
469 {
470 wxString currentText = GetValue();
471 long selStart, selEnd;
472 GetSelection( &selStart, &selEnd );
473
474 wxString newText;
475 long newInsertionPoint;
476
477 if( selStart != selEnd )
478 {
479 // There's a selection - delete the selected text
480 newText = currentText.Left( selStart ) + currentText.Mid( selEnd );
481 newInsertionPoint = selStart;
482 }
483 else if( selStart > 0 )
484 {
485 // No selection - delete character before cursor
486 newText = currentText.Left( selStart - 1 ) + currentText.Mid( selStart );
487 newInsertionPoint = selStart - 1;
488 }
489 else
490 {
491 return; // At beginning, can't delete
492 }
493
494 m_lastText = newText; // Prevent recursive calls
495 ChangeValue( newText );
496 SetInsertionPoint( newInsertionPoint );
497
498 // Check if trimmed text is empty
499 wxString trimmedNewText = newText;
500 trimmedNewText.Trim().Trim( false );
501
502 if( trimmedNewText.IsEmpty() )
503 {
505 }
506 else
507 {
508 FilterFontList( newText );
509 // Don't call DoAutoComplete for backspace to avoid the loop
510 }
511
512 return; // Don't skip this event
513 }
514 case WXK_RETURN:
515 case WXK_NUMPAD_ENTER:
516 {
517 Dismiss();
518 return;
519 }
520 break;
521
522 case WXK_ESCAPE:
523 {
524 // Restore to original selection or default font if original doesn't exist
525 if( !m_originalSelection.IsEmpty() && FindBestMatch( m_originalSelection ) != wxNOT_FOUND )
526 {
527 SetStringSelection( m_originalSelection );
529 }
530 else
531 {
532 // Original font doesn't exist anymore, select default font
533 wxString defaultFont = GetDefaultFontName();
534 SetStringSelection( defaultFont );
535 m_lastText = defaultFont;
536 }
537
538 // Restore full font list if filtered
539 if( m_isFiltered )
540 {
542 }
543
544 // Only dismiss if popup is actually shown
545 if( IsPopupShown() )
546 {
547 Dismiss();
548 }
549 return;
550 }
551
552 default:
553 break;
554 }
555
556 aEvent.Skip();
557}
558
559
560void FONT_CHOICE::OnTextCtrl( wxCommandEvent& aEvent )
561{
562 wxString currentText = GetValue();
563
564 // Avoid recursive calls
565 if( currentText == m_lastText )
566 {
567 aEvent.Skip();
568 return;
569 }
570
571 m_lastText = currentText;
572
573 // If popup is shown, OnCharHook handles the text input, so just skip
574 if( IsPopupShown() )
575 {
576 aEvent.Skip();
577 return;
578 }
579
580 // Trim whitespace for processing
581 wxString trimmedText = currentText;
582 trimmedText.Trim().Trim(false);
583
584 // If text is empty or all whitespace, restore full list
585 if( trimmedText.IsEmpty() )
586 {
588 aEvent.Skip();
589 return;
590 }
591
592 // Filter the font list based on the text input
593 FilterFontList( currentText );
594
595 // Try to find a match for autocomplete (only when popup is not shown)
596 int bestMatch = FindBestMatch( trimmedText );
597
598 if( bestMatch != wxNOT_FOUND )
599 {
600 DoAutoComplete( trimmedText );
601 }
602
603 aEvent.Skip();
604}
605
606
607void FONT_CHOICE::OnDropDown( wxCommandEvent& aEvent )
608{
609 // Store the original selection when dropdown opens
610 m_originalSelection = GetValue();
611 aEvent.Skip();
612}
613
614
615void FONT_CHOICE::OnCloseUp( wxCommandEvent& aEvent )
616{
617 // When dropdown closes, we should only restore the full font list
618 // but NOT change the current text value unless explicitly selected
619 // The OnKillFocus handler will handle text validation when focus is lost
620
621 // Reset to full font list if filtered
622 if( m_isFiltered )
623 {
625 }
626
627 aEvent.Skip();
628}
629
630
631void FONT_CHOICE::OnSetFocus( wxFocusEvent& aEvent )
632{
633 // When the control gains focus, select all text so user can quickly replace it
634 // Only do this if we're not already showing the popup (which would indicate
635 // the user is actively interacting with the dropdown)
636 if( !GetValue().IsEmpty() && !IsPopupShown() )
637 {
638 // Use CallAfter to ensure the focus event is fully processed first
639 CallAfter( [this]() {
640 if( HasFocus() && !IsPopupShown() )
641 SelectAll();
642 } );
643 }
644
645 aEvent.Skip();
646}
647
648
649void FONT_CHOICE::OnKillFocus( wxFocusEvent& aEvent )
650{
651 // When losing focus, deselect text and validate/correct the font name
652
653 // First, deselect any selected text
654 if( GetInsertionPoint() != GetLastPosition() )
655 {
656 SetInsertionPointEnd();
657 }
658
659 // Get current text and trim whitespace
660 wxString currentText = GetValue();
661 currentText.Trim().Trim(false);
662
663 // If text is empty, set to default font
664 if( currentText.IsEmpty() )
665 {
666 wxString defaultFont = GetDefaultFontName();
667 SetStringSelection( defaultFont );
668 m_lastText = defaultFont;
669 aEvent.Skip();
670 return;
671 }
672
673 // Try to find exact match first
674 if( FindBestMatch( currentText ) != wxNOT_FOUND )
675 {
676 // Exact match found, keep the current text but ensure it's properly set
677 SetStringSelection( currentText );
678 m_lastText = currentText;
679 aEvent.Skip();
680 return;
681 }
682
683 // No exact match, try to find best partial match
684 wxString partialMatch = FindBestPartialMatch( currentText );
685
686 if( !partialMatch.IsEmpty() )
687 {
688 SetStringSelection( partialMatch );
689 m_lastText = partialMatch;
690 }
691 else
692 {
693 // No decent partial match, fall back to default font
694 wxString defaultFont = GetDefaultFontName();
695 SetStringSelection( defaultFont );
696 m_lastText = defaultFont;
697 }
698
699 // Ensure we restore full font list if it was filtered
700 if( m_isFiltered )
701 {
703 }
704
705 aEvent.Skip();
706}
707
708
709void FONT_CHOICE::DoAutoComplete( const wxString& aText )
710{
711 if( aText.IsEmpty() )
712 return;
713
714 // Find the best matching font
715 int bestMatch = FindBestMatch( aText );
716
717 if( bestMatch == wxNOT_FOUND )
718 return;
719
720
721 wxString matchText = GetString( bestMatch );
722
723 // Only do autocomplete if the match is longer than what we typed
724 if( matchText.Length() > aText.Length() && matchText.Lower().StartsWith( aText.Lower() ) )
725 {
726 // Set the text with the autocompleted portion selected
727 m_lastText = matchText; // Update to prevent recursive calls
728 ChangeValue( matchText );
729 SetInsertionPoint( aText.Length() );
730 SetSelection( aText.Length(), matchText.Length() );
731
732 if( IsPopupShown() )
733 {
734 SetSelection( bestMatch );
735 }
736 }
737}
738
739
740void FONT_CHOICE::FilterFontList( const wxString& aFilter )
741{
742 // Trim whitespace from filter
743 wxString trimmedFilter = aFilter;
744 trimmedFilter.Trim().Trim(false);
745
746 if( trimmedFilter.IsEmpty() )
747 {
749 return;
750 }
751
752 wxArrayString filteredList;
753
754 // Add system fonts first
755 for( int i = 0; i < m_systemFontCount; i++ )
756 {
757 wxString fontName = m_fullFontList[i];
758 filteredList.Add( fontName );
759 }
760
761 // Add matching fonts from the full list
762 for( size_t i = m_systemFontCount; i < m_fullFontList.GetCount(); i++ )
763 {
764 wxString fontName = m_fullFontList[i];
765
766 if( fontName.Lower().StartsWith( trimmedFilter.Lower() ) )
767 filteredList.Add( fontName );
768 }
769
770 // Preserve the current text value
771 wxString currentText = GetValue();
772
773 // Check if we had items before and now have none - this indicates we need to force refresh
774 bool hadItemsBefore = GetCount() > 0;
775 bool haveItemsNow = filteredList.GetCount() > 0;
776 bool needsPopupRefresh = hadItemsBefore && !haveItemsNow && IsPopupShown();
777
778 // Update the combo box with filtered list (even if empty)
779 Freeze();
780 clearList();
781
782 if( haveItemsNow )
783 {
784 Append( filteredList );
785 }
786 // If no matches, leave the dropdown empty
787
788 m_isFiltered = true;
789
790 // Restore the text value after filtering
791 if( !currentText.IsEmpty() )
792 {
793 ChangeValue( currentText );
794 SetInsertionPointEnd();
795 }
796
797 Thaw();
798
799 // Handle popup display
800 if( needsPopupRefresh )
801 {
802 // We had items before but now have none - dismiss the popup
803 Dismiss();
804 }
805 else if( !IsPopupShown() && haveItemsNow )
806 {
807 // Only show popup if we have items to display and control has focus
808 // This prevents popup from showing during programmatic text changes
809 if( HasFocus() )
810 {
811 Popup();
812 }
813 }
814 else if( IsPopupShown() && !haveItemsNow )
815 {
816 // If popup is shown but we have no items, dismiss it
817 Dismiss();
818 }
819
820 // Force a refresh to ensure the popup displays correctly only if it's shown and has items
821 if( IsPopupShown() && haveItemsNow )
822 {
823 Update();
824 Refresh();
825 }
826}
827
828
830{
831 if( !m_isFiltered )
832 return;
833
834 wxString selection = GetValue();
835
836 Freeze();
837 clearList();
838 Append( m_fullFontList );
839 m_isFiltered = false;
840
841 if( !selection.IsEmpty() )
842 {
843 ChangeValue( selection );
844 SetInsertionPointEnd();
845 }
846
847 Thaw();
848}
849
850
851int FONT_CHOICE::FindBestMatch( const wxString& aText )
852{
853 if( aText.IsEmpty() )
854 return wxNOT_FOUND;
855
856 // Trim whitespace from search text
857 wxString trimmedText = aText;
858 trimmedText.Trim().Trim(false);
859
860 if( trimmedText.IsEmpty() )
861 return wxNOT_FOUND;
862
863 wxString lowerText = trimmedText.Lower();
864
865 // Search in the full font list to find matches, then map to current list
866 for( size_t i = 0; i < m_fullFontList.GetCount(); i++ )
867 {
868 wxString itemText = m_fullFontList[i].Lower();
869
870 if( itemText.StartsWith( lowerText ) )
871 {
872 // Find this font in the current displayed list
873 wxString fullFontName = m_fullFontList[i];
874
875 for( unsigned int j = 0; j < GetCount(); j++ )
876 {
877 if( GetString( j ) == fullFontName )
878 return j;
879 }
880 }
881 }
882
883 return wxNOT_FOUND;
884}
885
886
887wxString FONT_CHOICE::FindBestPartialMatch( const wxString& aText )
888{
889 if( aText.IsEmpty() )
890 return wxEmptyString;
891
892 // Trim whitespace from search text
893 wxString trimmedText = aText;
894 trimmedText.Trim().Trim(false);
895
896 if( trimmedText.IsEmpty() )
897 return wxEmptyString;
898
899 wxString testText = trimmedText;
900
901 // Try progressively shorter versions of the text by removing characters from the end
902 // Don't go below a minimum length of 2 characters for meaningful partial matches
903 while( testText.Length() >= 2 )
904 {
905 wxString lowerTestText = testText.Lower();
906
907 // Search in the full font list for a match
908 for( size_t i = 0; i < m_fullFontList.GetCount(); i++ )
909 {
910 wxString itemText = m_fullFontList[i].Lower();
911
912 if( itemText.StartsWith( lowerTestText ) )
913 {
914 // Found a match, return the full font name
915 return m_fullFontList[i];
916 }
917 }
918
919 // Remove the last character and try again
920 testText = testText.Left( testText.Length() - 1 );
921 }
922
923 // No decent partial match found (need at least 2 characters for a meaningful match)
924 return wxEmptyString;
925}
926
927
929{
930 // Return KiCad font name as the default
931 return KICAD_FONT_NAME;
932}
const char * name
void SetFontSelection(KIFONT::FONT *aFont, bool aSilentMode=false)
Set the selection in wxChoice widget.
void FilterFontList(const wxString &aFilter)
void OnKeyDown(wxKeyEvent &aEvent)
void RestoreFullFontList()
void OnDropDown(wxCommandEvent &aEvent)
KIFONT::FONT * GetFontSelection(bool aBold, bool aItalic, bool aForDrawingSheet=false) const
wxCoord OnMeasureItem(size_t aItem) const override
bool HaveFontSelection() const
wxString m_originalSelection
Definition font_choice.h:80
FONT_CHOICE(wxWindow *aParent, int aId, wxPoint aPosition, wxSize aSize, int nChoices, wxString *aChoices, int aStyle)
void OnKillFocus(wxFocusEvent &aEvent)
void OnDrawItem(wxDC &aDc, const wxRect &aRect, int aItem, int aFlags) const override
void RefreshFonts()
wxArrayString m_fullFontList
Definition font_choice.h:77
int FindBestMatch(const wxString &aText)
wxString GetDefaultFontName() const
void DoAutoComplete(const wxString &aText)
void OnCloseUp(wxCommandEvent &aEvent)
void OnSetFocus(wxFocusEvent &aEvent)
wxString FindBestPartialMatch(const wxString &aText)
int m_systemFontCount
Definition font_choice.h:75
void OnTextCtrl(wxCommandEvent &aEvent)
virtual ~FONT_CHOICE()
wxString m_notFound
Definition font_choice.h:76
void OnCharHook(wxKeyEvent &aEvent)
wxString GetStringSelection() const override
bool m_isFiltered
Definition font_choice.h:78
wxString m_lastText
Definition font_choice.h:79
std::thread m_thread
std::vector< FONT_CHOICE * > m_controls
void Register(FONT_CHOICE *aCtrl)
wxArrayString GetFonts() const
wxArrayString m_fonts
static FONT_LIST_MANAGER & Get()
void Unregister(FONT_CHOICE *aCtrl)
std::condition_variable m_cv
std::atomic< bool > m_quit
FONT is an abstract base class for both outline and stroke fonts.
Definition font.h:131
static FONT * GetFont(const wxString &aFontName=wxEmptyString, bool aBold=false, bool aItalic=false, const std::vector< wxString > *aEmbeddedFiles=nullptr, bool aForDrawingSheet=false)
Definition font.cpp:147
const wxString & GetName() const
Definition font.h:149
#define _(s)
FONTCONFIG * Fontconfig()
#define KICAD_FONT_NAME
void Refresh()
Update the board display after modifying it by a python script (note: it is automatically called by a...
PGM_BASE & Pgm()
The global program "get" accessor.
Definition pgm_base.cpp:946
see class PGM_BASE
wxString result
Test unit parsing edge cases and error handling.