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