mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 10:13:19 +02:00
887 lines
23 KiB
C++
887 lines
23 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright The 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 <widgets/font_choice.h>
|
|
#include <kiplatform/ui.h>
|
|
#include <font/fontconfig.h>
|
|
#include <pgm_base.h>
|
|
|
|
#include <wx/dc.h>
|
|
#include <wx/event.h>
|
|
#include <wx/fontenum.h>
|
|
#include <wx/settings.h>
|
|
#include <wx/textctrl.h>
|
|
|
|
#include <thread>
|
|
#include <mutex>
|
|
#include <condition_variable>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <algorithm>
|
|
#include <cwctype>
|
|
|
|
// The "official" name of the building Kicad stroke font (always existing)
|
|
#include <font/kicad_font_name.h>
|
|
|
|
class FONT_LIST_MANAGER : public wxEvtHandler
|
|
{
|
|
public:
|
|
static FONT_LIST_MANAGER& Get();
|
|
wxArrayString GetFonts() const;
|
|
void Register( FONT_CHOICE* aCtrl );
|
|
void Unregister( FONT_CHOICE* aCtrl );
|
|
|
|
private:
|
|
FONT_LIST_MANAGER();
|
|
~FONT_LIST_MANAGER();
|
|
void Poll();
|
|
void UpdateFonts();
|
|
|
|
std::thread m_thread;
|
|
mutable std::mutex m_mutex;
|
|
std::condition_variable m_cv;
|
|
wxArrayString m_fonts;
|
|
std::vector<FONT_CHOICE*> m_controls;
|
|
std::atomic<bool> m_quit;
|
|
};
|
|
|
|
FONT_LIST_MANAGER& FONT_LIST_MANAGER::Get()
|
|
{
|
|
static FONT_LIST_MANAGER mgr;
|
|
return mgr;
|
|
}
|
|
|
|
wxArrayString FONT_LIST_MANAGER::GetFonts() const
|
|
{
|
|
std::lock_guard<std::mutex> lock( m_mutex );
|
|
return m_fonts;
|
|
}
|
|
|
|
void FONT_LIST_MANAGER::Register( FONT_CHOICE* aCtrl )
|
|
{
|
|
std::lock_guard<std::mutex> lock( m_mutex );
|
|
m_controls.push_back( aCtrl );
|
|
}
|
|
|
|
void FONT_LIST_MANAGER::Unregister( FONT_CHOICE* aCtrl )
|
|
{
|
|
std::lock_guard<std::mutex> lock( m_mutex );
|
|
auto it = std::find( m_controls.begin(), m_controls.end(), aCtrl );
|
|
|
|
if( it != m_controls.end() )
|
|
m_controls.erase( it );
|
|
}
|
|
|
|
FONT_LIST_MANAGER::FONT_LIST_MANAGER()
|
|
{
|
|
m_quit = false;
|
|
UpdateFonts();
|
|
|
|
// It appears that the polling mechanism does not work correctly
|
|
// for mingw (hangs on exit)
|
|
#ifndef __MINGW32__
|
|
m_thread = std::thread( &FONT_LIST_MANAGER::Poll, this );
|
|
#endif
|
|
}
|
|
|
|
FONT_LIST_MANAGER::~FONT_LIST_MANAGER()
|
|
{
|
|
{
|
|
std::lock_guard<std::mutex> lock( m_mutex );
|
|
m_quit = true;
|
|
}
|
|
|
|
#ifndef __MINGW32__
|
|
m_cv.notify_one();
|
|
|
|
if( m_thread.joinable() )
|
|
m_thread.join();
|
|
#endif
|
|
}
|
|
|
|
void FONT_LIST_MANAGER::Poll()
|
|
{
|
|
std::unique_lock<std::mutex> lock( m_mutex );
|
|
|
|
while( !m_quit )
|
|
{
|
|
// N.B. wait_for will unlock the mutex while waiting but lock it before continuing
|
|
// so we need to relock before continuing in the loop
|
|
m_cv.wait_for( lock, std::chrono::seconds( 30 ), [&] { return m_quit.load(); } );
|
|
|
|
if( !m_quit )
|
|
{
|
|
lock.unlock();
|
|
UpdateFonts();
|
|
lock.lock();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FONT_LIST_MANAGER::UpdateFonts()
|
|
{
|
|
std::vector<std::string> fontNames;
|
|
Fontconfig()->ListFonts( fontNames, std::string( Pgm().GetLanguageTag().utf8_str() ) );
|
|
|
|
wxArrayString menuList;
|
|
|
|
for( const std::string& name : fontNames )
|
|
menuList.Add( wxString( name ) );
|
|
|
|
menuList.Sort();
|
|
|
|
// Check if fonts changed and update controls
|
|
{
|
|
std::lock_guard<std::mutex> lock( m_mutex );
|
|
|
|
if( menuList == m_fonts )
|
|
return;
|
|
|
|
m_fonts = menuList;
|
|
}
|
|
|
|
CallAfter( [this]() {
|
|
std::vector<FONT_CHOICE*> controlsCopy;
|
|
|
|
// Copy controls list under lock protection
|
|
{
|
|
std::lock_guard<std::mutex> lock( m_mutex );
|
|
controlsCopy = m_controls;
|
|
}
|
|
|
|
// Update controls without holding lock
|
|
for( FONT_CHOICE* ctrl : controlsCopy )
|
|
{
|
|
if( ctrl && !ctrl->IsShownOnScreen() )
|
|
ctrl->RefreshFonts();
|
|
}
|
|
} );
|
|
}
|
|
|
|
|
|
FONT_CHOICE::FONT_CHOICE( wxWindow* aParent, int aId, wxPoint aPosition, wxSize aSize,
|
|
int nChoices, wxString* aChoices, int aStyle ) :
|
|
wxOwnerDrawnComboBox( aParent, aId, wxEmptyString, aPosition, aSize, 0, nullptr, aStyle )
|
|
{
|
|
m_systemFontCount = nChoices;
|
|
m_notFound = wxS( " " ) + _( "<not found>" );
|
|
m_isFiltered = false;
|
|
m_lastText = wxEmptyString;
|
|
m_originalSelection = wxEmptyString;
|
|
|
|
FONT_LIST_MANAGER::Get().Register( this );
|
|
RefreshFonts();
|
|
|
|
// Bind only essential events to restore functionality
|
|
Bind( wxEVT_KEY_DOWN, &FONT_CHOICE::OnKeyDown, this );
|
|
Bind( wxEVT_CHAR_HOOK, &FONT_CHOICE::OnCharHook, this );
|
|
Bind( wxEVT_COMMAND_TEXT_UPDATED, &FONT_CHOICE::OnTextCtrl, this );
|
|
Bind( wxEVT_COMBOBOX_DROPDOWN, &FONT_CHOICE::OnDropDown, this );
|
|
Bind( wxEVT_COMBOBOX_CLOSEUP, &FONT_CHOICE::OnCloseUp, this );
|
|
Bind( wxEVT_SET_FOCUS, &FONT_CHOICE::OnSetFocus, this );
|
|
Bind( wxEVT_KILL_FOCUS, &FONT_CHOICE::OnKillFocus, this );
|
|
}
|
|
|
|
|
|
FONT_CHOICE::~FONT_CHOICE()
|
|
{
|
|
FONT_LIST_MANAGER::Get().Unregister( this );
|
|
}
|
|
|
|
|
|
void FONT_CHOICE::RefreshFonts()
|
|
{
|
|
wxArrayString menuList = FONT_LIST_MANAGER::Get().GetFonts();
|
|
|
|
wxString selection = GetValue();
|
|
|
|
// Store the full font list for filtering
|
|
m_fullFontList.Clear();
|
|
|
|
if( m_systemFontCount > 1 )
|
|
m_fullFontList.Add( _( "Default Font" ) );
|
|
|
|
m_fullFontList.Add( KICAD_FONT_NAME );
|
|
|
|
for( const wxString& font : menuList )
|
|
m_fullFontList.Add( font );
|
|
|
|
Freeze();
|
|
Clear();
|
|
|
|
if( m_systemFontCount > 1 )
|
|
Append( _( "Default Font" ) );
|
|
|
|
Append( KICAD_FONT_NAME );
|
|
m_systemFontCount = GetCount();
|
|
|
|
Append( menuList );
|
|
|
|
if( !selection.IsEmpty() )
|
|
SetStringSelection( selection );
|
|
|
|
m_isFiltered = false;
|
|
Thaw();
|
|
}
|
|
|
|
|
|
void FONT_CHOICE::SetFontSelection( KIFONT::FONT* aFont, bool aSilentMode )
|
|
{
|
|
if( !aFont )
|
|
{
|
|
SetSelection( 0 );
|
|
}
|
|
else
|
|
{
|
|
bool result = SetStringSelection( aFont->GetName() );
|
|
|
|
if( !result )
|
|
{
|
|
Append( aFont->GetName() + m_notFound );
|
|
SetSelection( GetCount() - 1 );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool FONT_CHOICE::HaveFontSelection() const
|
|
{
|
|
int sel = GetSelection();
|
|
|
|
if( sel < 0 )
|
|
return false;
|
|
|
|
if( GetString( sel ).EndsWith( m_notFound ) )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
KIFONT::FONT* FONT_CHOICE::GetFontSelection( bool aBold, bool aItalic, bool aForDrawingSheet ) const
|
|
{
|
|
if( GetSelection() <= 0 )
|
|
{
|
|
return nullptr;
|
|
}
|
|
else if( GetSelection() == 1 && m_systemFontCount == 2 )
|
|
{
|
|
return KIFONT::FONT::GetFont( KICAD_FONT_NAME, aBold, aItalic );
|
|
}
|
|
else
|
|
{
|
|
return KIFONT::FONT::GetFont( GetValue(), aBold, aItalic, nullptr,
|
|
aForDrawingSheet );
|
|
}
|
|
}
|
|
|
|
|
|
void FONT_CHOICE::OnDrawItem( wxDC& dc, const wxRect& rect, int item, int flags ) const
|
|
{
|
|
if( item == wxNOT_FOUND )
|
|
return;
|
|
|
|
wxString name = GetString( item );
|
|
|
|
dc.SetFont( wxSystemSettings::GetFont( wxSYS_DEFAULT_GUI_FONT ) );
|
|
dc.DrawText( name, rect.x + 2, rect.y + 2 );
|
|
|
|
if( item >= m_systemFontCount )
|
|
{
|
|
wxCoord w, h;
|
|
dc.GetTextExtent( name, &w, &h );
|
|
wxFont sampleFont( wxFontInfo( dc.GetFont().GetPointSize() ).FaceName( name ) );
|
|
dc.SetFont( sampleFont );
|
|
dc.SetTextForeground( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) );
|
|
dc.DrawText( wxS( "AaBbCcDd123456" ), rect.x + w + 15, rect.y + 2 );
|
|
}
|
|
}
|
|
|
|
|
|
wxString FONT_CHOICE::GetStringSelection() const
|
|
{
|
|
return GetValue();
|
|
}
|
|
|
|
|
|
void FONT_CHOICE::OnKeyDown( wxKeyEvent& aEvent )
|
|
{
|
|
int keyCode = aEvent.GetKeyCode();
|
|
|
|
if( keyCode == WXK_RETURN || keyCode == WXK_NUMPAD_ENTER || keyCode == WXK_ESCAPE )
|
|
{
|
|
if( IsPopupShown() )
|
|
{
|
|
// Accept current text and close popup
|
|
Dismiss();
|
|
return;
|
|
}
|
|
}
|
|
else if( keyCode == WXK_BACK && !IsPopupShown() )
|
|
{
|
|
// Handle backspace when popup is not shown
|
|
// This allows normal character-by-character deletion instead of selecting all text
|
|
|
|
wxString currentText = GetValue();
|
|
long selStart, selEnd;
|
|
GetSelection( &selStart, &selEnd );
|
|
|
|
if( selStart != selEnd )
|
|
{
|
|
// There's a selection - delete the selected text
|
|
wxString newText = currentText.Left( selStart ) + currentText.Mid( selEnd );
|
|
m_lastText = newText; // Prevent recursive calls
|
|
ChangeValue( newText );
|
|
SetInsertionPoint( selStart );
|
|
return; // Don't skip this event
|
|
}
|
|
else if( selStart > 0 )
|
|
{
|
|
// No selection - delete character before cursor
|
|
wxString newText = currentText.Left( selStart - 1 ) + currentText.Mid( selStart );
|
|
m_lastText = newText; // Prevent recursive calls
|
|
ChangeValue( newText );
|
|
SetInsertionPoint( selStart - 1 );
|
|
return; // Don't skip this event
|
|
}
|
|
// If at beginning of text, let default behavior handle it
|
|
}
|
|
|
|
aEvent.Skip();
|
|
}
|
|
|
|
|
|
void FONT_CHOICE::OnCharHook( wxKeyEvent& aEvent )
|
|
{
|
|
int keyCode = aEvent.GetUnicodeKey();
|
|
wchar_t wc = static_cast<wchar_t>( keyCode );
|
|
|
|
// When popup is not shown, let normal text processing handle printable characters
|
|
// The OnTextCtrl method will handle filtering and autocomplete
|
|
if( !IsPopupShown() )
|
|
{
|
|
aEvent.Skip();
|
|
return;
|
|
}
|
|
|
|
if( std::iswprint( wc ) && !std::iswcntrl( wc ) )
|
|
{
|
|
// Get current text and check if there's a selection
|
|
wxString currentText = GetValue();
|
|
long selStart, selEnd;
|
|
GetSelection( &selStart, &selEnd );
|
|
|
|
wxChar newChar = (wxChar)keyCode;
|
|
wxString newText;
|
|
|
|
if( selStart != selEnd )
|
|
{
|
|
// There's a selection - replace it with the new character
|
|
newText = currentText.Left( selStart ) + newChar + currentText.Mid( selEnd );
|
|
}
|
|
else
|
|
{
|
|
// No selection - append to current insertion point
|
|
long insertionPoint = GetInsertionPoint();
|
|
newText = currentText.Left( insertionPoint ) + newChar + currentText.Mid( insertionPoint );
|
|
}
|
|
|
|
// Update the text control
|
|
m_lastText = newText; // Prevent recursive calls
|
|
ChangeValue( newText );
|
|
SetInsertionPoint( selStart + 1 ); // Position cursor after new character
|
|
|
|
// Filter the font list based on new text (will handle trimming internally)
|
|
FilterFontList( newText );
|
|
|
|
// Try autocomplete
|
|
DoAutoComplete( newText );
|
|
|
|
return; // Don't skip this event
|
|
}
|
|
|
|
switch (keyCode)
|
|
{
|
|
case WXK_BACK:
|
|
{
|
|
wxString currentText = GetValue();
|
|
long selStart, selEnd;
|
|
GetSelection( &selStart, &selEnd );
|
|
|
|
wxString newText;
|
|
long newInsertionPoint;
|
|
|
|
if( selStart != selEnd )
|
|
{
|
|
// There's a selection - delete the selected text
|
|
newText = currentText.Left( selStart ) + currentText.Mid( selEnd );
|
|
newInsertionPoint = selStart;
|
|
}
|
|
else if( selStart > 0 )
|
|
{
|
|
// No selection - delete character before cursor
|
|
newText = currentText.Left( selStart - 1 ) + currentText.Mid( selStart );
|
|
newInsertionPoint = selStart - 1;
|
|
}
|
|
else
|
|
{
|
|
return; // At beginning, can't delete
|
|
}
|
|
|
|
m_lastText = newText; // Prevent recursive calls
|
|
ChangeValue( newText );
|
|
SetInsertionPoint( newInsertionPoint );
|
|
|
|
// Check if trimmed text is empty
|
|
wxString trimmedNewText = newText;
|
|
trimmedNewText.Trim().Trim( false );
|
|
|
|
if( trimmedNewText.IsEmpty() )
|
|
{
|
|
RestoreFullFontList();
|
|
}
|
|
else
|
|
{
|
|
FilterFontList( newText );
|
|
// Don't call DoAutoComplete for backspace to avoid the loop
|
|
}
|
|
|
|
return; // Don't skip this event
|
|
}
|
|
case WXK_RETURN:
|
|
case WXK_NUMPAD_ENTER:
|
|
{
|
|
Dismiss();
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case WXK_ESCAPE:
|
|
{
|
|
// Restore to original selection or default font if original doesn't exist
|
|
if( !m_originalSelection.IsEmpty() && FindBestMatch( m_originalSelection ) != wxNOT_FOUND )
|
|
{
|
|
SetStringSelection( m_originalSelection );
|
|
m_lastText = m_originalSelection;
|
|
}
|
|
else
|
|
{
|
|
// Original font doesn't exist anymore, select default font
|
|
wxString defaultFont = GetDefaultFontName();
|
|
SetStringSelection( defaultFont );
|
|
m_lastText = defaultFont;
|
|
}
|
|
|
|
// Restore full font list if filtered
|
|
if( m_isFiltered )
|
|
{
|
|
RestoreFullFontList();
|
|
}
|
|
|
|
// Only dismiss if popup is actually shown
|
|
if( IsPopupShown() )
|
|
{
|
|
Dismiss();
|
|
}
|
|
return;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
aEvent.Skip();
|
|
}
|
|
|
|
|
|
void FONT_CHOICE::OnTextCtrl( wxCommandEvent& aEvent )
|
|
{
|
|
wxString currentText = GetValue();
|
|
|
|
// Avoid recursive calls
|
|
if( currentText == m_lastText )
|
|
{
|
|
aEvent.Skip();
|
|
return;
|
|
}
|
|
|
|
m_lastText = currentText;
|
|
|
|
// If popup is shown, OnCharHook handles the text input, so just skip
|
|
if( IsPopupShown() )
|
|
{
|
|
aEvent.Skip();
|
|
return;
|
|
}
|
|
|
|
// Trim whitespace for processing
|
|
wxString trimmedText = currentText;
|
|
trimmedText.Trim().Trim(false);
|
|
|
|
// If text is empty or all whitespace, restore full list
|
|
if( trimmedText.IsEmpty() )
|
|
{
|
|
RestoreFullFontList();
|
|
aEvent.Skip();
|
|
return;
|
|
}
|
|
|
|
// Filter the font list based on the text input
|
|
FilterFontList( currentText );
|
|
|
|
// Try to find a match for autocomplete (only when popup is not shown)
|
|
int bestMatch = FindBestMatch( trimmedText );
|
|
|
|
if( bestMatch != wxNOT_FOUND )
|
|
{
|
|
DoAutoComplete( trimmedText );
|
|
}
|
|
|
|
aEvent.Skip();
|
|
}
|
|
|
|
|
|
void FONT_CHOICE::OnDropDown( wxCommandEvent& aEvent )
|
|
{
|
|
// Store the original selection when dropdown opens
|
|
m_originalSelection = GetValue();
|
|
aEvent.Skip();
|
|
}
|
|
|
|
|
|
void FONT_CHOICE::OnCloseUp( wxCommandEvent& aEvent )
|
|
{
|
|
// When dropdown closes, we should only restore the full font list
|
|
// but NOT change the current text value unless explicitly selected
|
|
// The OnKillFocus handler will handle text validation when focus is lost
|
|
|
|
// Reset to full font list if filtered
|
|
if( m_isFiltered )
|
|
{
|
|
RestoreFullFontList();
|
|
}
|
|
|
|
aEvent.Skip();
|
|
}
|
|
|
|
|
|
void FONT_CHOICE::OnSetFocus( wxFocusEvent& aEvent )
|
|
{
|
|
// When the control gains focus, select all text so user can quickly replace it
|
|
// Only do this if we're not already showing the popup (which would indicate
|
|
// the user is actively interacting with the dropdown)
|
|
if( !GetValue().IsEmpty() && !IsPopupShown() )
|
|
{
|
|
// Use CallAfter to ensure the focus event is fully processed first
|
|
CallAfter( [this]() {
|
|
if( HasFocus() && !IsPopupShown() )
|
|
SelectAll();
|
|
} );
|
|
}
|
|
|
|
aEvent.Skip();
|
|
}
|
|
|
|
|
|
void FONT_CHOICE::OnKillFocus( wxFocusEvent& aEvent )
|
|
{
|
|
// When losing focus, deselect text and validate/correct the font name
|
|
|
|
// First, deselect any selected text
|
|
if( GetInsertionPoint() != GetLastPosition() )
|
|
{
|
|
SetInsertionPointEnd();
|
|
}
|
|
|
|
// Get current text and trim whitespace
|
|
wxString currentText = GetValue();
|
|
currentText.Trim().Trim(false);
|
|
|
|
// If text is empty, set to default font
|
|
if( currentText.IsEmpty() )
|
|
{
|
|
wxString defaultFont = GetDefaultFontName();
|
|
SetStringSelection( defaultFont );
|
|
m_lastText = defaultFont;
|
|
aEvent.Skip();
|
|
return;
|
|
}
|
|
|
|
// Try to find exact match first
|
|
if( FindBestMatch( currentText ) != wxNOT_FOUND )
|
|
{
|
|
// Exact match found, keep the current text but ensure it's properly set
|
|
SetStringSelection( currentText );
|
|
m_lastText = currentText;
|
|
aEvent.Skip();
|
|
return;
|
|
}
|
|
|
|
// No exact match, try to find best partial match
|
|
wxString partialMatch = FindBestPartialMatch( currentText );
|
|
|
|
if( !partialMatch.IsEmpty() )
|
|
{
|
|
SetStringSelection( partialMatch );
|
|
m_lastText = partialMatch;
|
|
}
|
|
else
|
|
{
|
|
// No decent partial match, fall back to default font
|
|
wxString defaultFont = GetDefaultFontName();
|
|
SetStringSelection( defaultFont );
|
|
m_lastText = defaultFont;
|
|
}
|
|
|
|
// Ensure we restore full font list if it was filtered
|
|
if( m_isFiltered )
|
|
{
|
|
RestoreFullFontList();
|
|
}
|
|
|
|
aEvent.Skip();
|
|
}
|
|
|
|
|
|
void FONT_CHOICE::DoAutoComplete( const wxString& aText )
|
|
{
|
|
if( aText.IsEmpty() )
|
|
return;
|
|
|
|
// Find the best matching font
|
|
int bestMatch = FindBestMatch( aText );
|
|
|
|
if( bestMatch == wxNOT_FOUND )
|
|
return;
|
|
|
|
|
|
wxString matchText = GetString( bestMatch );
|
|
|
|
// Only do autocomplete if the match is longer than what we typed
|
|
if( matchText.Length() > aText.Length() && matchText.Lower().StartsWith( aText.Lower() ) )
|
|
{
|
|
// Set the text with the autocompleted portion selected
|
|
m_lastText = matchText; // Update to prevent recursive calls
|
|
ChangeValue( matchText );
|
|
SetInsertionPoint( aText.Length() );
|
|
SetSelection( aText.Length(), matchText.Length() );
|
|
|
|
if( IsPopupShown() )
|
|
{
|
|
SetSelection( bestMatch );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FONT_CHOICE::FilterFontList( const wxString& aFilter )
|
|
{
|
|
// Trim whitespace from filter
|
|
wxString trimmedFilter = aFilter;
|
|
trimmedFilter.Trim().Trim(false);
|
|
|
|
if( trimmedFilter.IsEmpty() )
|
|
{
|
|
RestoreFullFontList();
|
|
return;
|
|
}
|
|
|
|
wxArrayString filteredList;
|
|
|
|
// Add system fonts first
|
|
for( int i = 0; i < m_systemFontCount; i++ )
|
|
{
|
|
wxString fontName = m_fullFontList[i];
|
|
|
|
if( fontName.Lower().StartsWith( trimmedFilter.Lower() ) )
|
|
filteredList.Add( fontName );
|
|
}
|
|
|
|
// Add matching fonts from the full list
|
|
for( size_t i = m_systemFontCount; i < m_fullFontList.GetCount(); i++ )
|
|
{
|
|
wxString fontName = m_fullFontList[i];
|
|
|
|
if( fontName.Lower().StartsWith( trimmedFilter.Lower() ) )
|
|
filteredList.Add( fontName );
|
|
}
|
|
|
|
// Preserve the current text value
|
|
wxString currentText = GetValue();
|
|
|
|
// Check if we had items before and now have none - this indicates we need to force refresh
|
|
bool hadItemsBefore = GetCount() > 0;
|
|
bool haveItemsNow = filteredList.GetCount() > 0;
|
|
bool needsPopupRefresh = hadItemsBefore && !haveItemsNow && IsPopupShown();
|
|
|
|
// Update the combo box with filtered list (even if empty)
|
|
Freeze();
|
|
Clear();
|
|
|
|
if( haveItemsNow )
|
|
{
|
|
Append( filteredList );
|
|
}
|
|
// If no matches, leave the dropdown empty
|
|
|
|
m_isFiltered = true;
|
|
|
|
// Restore the text value after filtering
|
|
if( !currentText.IsEmpty() )
|
|
{
|
|
ChangeValue( currentText );
|
|
SetInsertionPointEnd();
|
|
}
|
|
|
|
Thaw();
|
|
|
|
// Handle popup display
|
|
if( needsPopupRefresh )
|
|
{
|
|
// We had items before but now have none - dismiss the popup
|
|
Dismiss();
|
|
}
|
|
else if( !IsPopupShown() && haveItemsNow )
|
|
{
|
|
// Only show popup if we have items to display and control has focus
|
|
// This prevents popup from showing during programmatic text changes
|
|
if( HasFocus() )
|
|
{
|
|
Popup();
|
|
}
|
|
}
|
|
else if( IsPopupShown() && !haveItemsNow )
|
|
{
|
|
// If popup is shown but we have no items, dismiss it
|
|
Dismiss();
|
|
}
|
|
|
|
// Force a refresh to ensure the popup displays correctly only if it's shown and has items
|
|
if( IsPopupShown() && haveItemsNow )
|
|
{
|
|
Update();
|
|
Refresh();
|
|
}
|
|
}
|
|
|
|
|
|
void FONT_CHOICE::RestoreFullFontList()
|
|
{
|
|
if( !m_isFiltered )
|
|
return;
|
|
|
|
wxString selection = GetValue();
|
|
|
|
Freeze();
|
|
Clear();
|
|
Append( m_fullFontList );
|
|
m_isFiltered = false;
|
|
|
|
if( !selection.IsEmpty() )
|
|
{
|
|
ChangeValue( selection );
|
|
SetInsertionPointEnd();
|
|
}
|
|
|
|
Thaw();
|
|
}
|
|
|
|
|
|
int FONT_CHOICE::FindBestMatch( const wxString& aText )
|
|
{
|
|
if( aText.IsEmpty() )
|
|
return wxNOT_FOUND;
|
|
|
|
// Trim whitespace from search text
|
|
wxString trimmedText = aText;
|
|
trimmedText.Trim().Trim(false);
|
|
|
|
if( trimmedText.IsEmpty() )
|
|
return wxNOT_FOUND;
|
|
|
|
wxString lowerText = trimmedText.Lower();
|
|
|
|
// Search in the full font list to find matches, then map to current list
|
|
for( size_t i = 0; i < m_fullFontList.GetCount(); i++ )
|
|
{
|
|
wxString itemText = m_fullFontList[i].Lower();
|
|
|
|
if( itemText.StartsWith( lowerText ) )
|
|
{
|
|
// Find this font in the current displayed list
|
|
wxString fullFontName = m_fullFontList[i];
|
|
|
|
for( unsigned int j = 0; j < GetCount(); j++ )
|
|
{
|
|
if( GetString( j ) == fullFontName )
|
|
return j;
|
|
}
|
|
}
|
|
}
|
|
|
|
return wxNOT_FOUND;
|
|
}
|
|
|
|
|
|
wxString FONT_CHOICE::FindBestPartialMatch( const wxString& aText )
|
|
{
|
|
if( aText.IsEmpty() )
|
|
return wxEmptyString;
|
|
|
|
// Trim whitespace from search text
|
|
wxString trimmedText = aText;
|
|
trimmedText.Trim().Trim(false);
|
|
|
|
if( trimmedText.IsEmpty() )
|
|
return wxEmptyString;
|
|
|
|
wxString testText = trimmedText;
|
|
|
|
// Try progressively shorter versions of the text by removing characters from the end
|
|
// Don't go below a minimum length of 2 characters for meaningful partial matches
|
|
while( testText.Length() >= 2 )
|
|
{
|
|
wxString lowerTestText = testText.Lower();
|
|
|
|
// Search in the full font list for a match
|
|
for( size_t i = 0; i < m_fullFontList.GetCount(); i++ )
|
|
{
|
|
wxString itemText = m_fullFontList[i].Lower();
|
|
|
|
if( itemText.StartsWith( lowerTestText ) )
|
|
{
|
|
// Found a match, return the full font name
|
|
return m_fullFontList[i];
|
|
}
|
|
}
|
|
|
|
// Remove the last character and try again
|
|
testText = testText.Left( testText.Length() - 1 );
|
|
}
|
|
|
|
// No decent partial match found (need at least 2 characters for a meaningful match)
|
|
return wxEmptyString;
|
|
}
|
|
|
|
|
|
wxString FONT_CHOICE::GetDefaultFontName() const
|
|
{
|
|
// Return KiCad font name as the default
|
|
return KICAD_FONT_NAME;
|
|
} |