You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
879 lines
20 KiB
879 lines
20 KiB
|
|
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//============================================================================= |
|
|
|
#include "stdafx.h" |
|
#include "FilteredComboBox.h" |
|
|
|
|
|
BEGIN_MESSAGE_MAP(CFilteredComboBox, CComboBox) |
|
//{{AFX_MSG_MAP(CFilteredComboBox) |
|
ON_CONTROL_REFLECT_EX(CBN_SELCHANGE, OnSelChange) |
|
ON_CONTROL_REFLECT_EX(CBN_EDITCHANGE, OnEditChange) |
|
ON_CONTROL_REFLECT_EX(CBN_CLOSEUP, OnCloseUp) |
|
ON_CONTROL_REFLECT_EX(CBN_DROPDOWN, OnDropDown) |
|
ON_CONTROL_REFLECT_EX(CBN_SELENDOK, OnSelEndOK) |
|
ON_WM_CTLCOLOR() |
|
//}}AFX_MSG_MAP |
|
END_MESSAGE_MAP() |
|
|
|
|
|
static const char *s_pStringToMatch = NULL; |
|
static int s_iStringToMatchLen; |
|
|
|
|
|
// This can help debug events in the combo box. |
|
static int g_iFunctionMarkerEvent = 1; |
|
class CFunctionMarker |
|
{ |
|
public: |
|
CFunctionMarker( const char *p ) |
|
{ |
|
#if 0 |
|
m_iEvent = g_iFunctionMarkerEvent++; |
|
|
|
char str[512]; |
|
Q_snprintf( str, sizeof( str ), "enter %d: %s\n", m_iEvent, p ); |
|
OutputDebugString( str ); |
|
m_p = p; |
|
#endif |
|
} |
|
|
|
~CFunctionMarker() |
|
{ |
|
#if 0 |
|
char str[512]; |
|
Q_snprintf( str, sizeof( str ), "exit %d: %s\n", m_iEvent, m_p ); |
|
OutputDebugString( str ); |
|
#endif |
|
} |
|
const char *m_p; |
|
int m_iEvent; |
|
}; |
|
|
|
// ------------------------------------------------------------------------------------------------------------ // |
|
// CFilteredComboBox implementation. |
|
// ------------------------------------------------------------------------------------------------------------ // |
|
CFilteredComboBox::CFilteredComboBox( CFilteredComboBox::ICallbacks *pCallbacks ) |
|
: m_pCallbacks( pCallbacks ) |
|
{ |
|
m_hQueuedFont = NULL; |
|
m_bInSelChange = false; |
|
m_bNotifyParent = true; |
|
m_dwTextColor = RGB(0, 0, 0); |
|
m_bOnlyProvideSuggestions = true; |
|
m_hEditControlFont = NULL; |
|
m_bInEnterKeyPressedHandler = false; |
|
} |
|
|
|
|
|
void CFilteredComboBox::SetSuggestions( CUtlVector<CString> &suggestions, int flags ) |
|
{ |
|
CreateFonts(); |
|
|
|
// Verify some of the window styles. This class requires these, and it doesn't get a change to set them |
|
// unless you call Create on it. |
|
// If we use owner draw variable, we get the bug described here: http://support.microsoft.com/kb/813791. |
|
Assert( GetStyle() & CBS_OWNERDRAWFIXED ); |
|
Assert( GetStyle() & CBS_HASSTRINGS ); |
|
Assert( !( GetStyle() & CBS_SORT ) ); |
|
|
|
// Copy the list. |
|
m_Suggestions = suggestions; |
|
|
|
CString str; |
|
GetWindowText( str ); |
|
DWORD sel = GetEditSel(); |
|
|
|
FillDropdownList( NULL, false ); |
|
|
|
// Force it to provide the first one if they only want suggestions and the current text in there is not valid. |
|
bool bSelectFirst = ((flags & SETSUGGESTIONS_SELECTFIRST) != 0); |
|
bool bCallback = ((flags & SETSUGGESTIONS_CALLBACK) != 0); |
|
bool bForceFirst = (m_bOnlyProvideSuggestions && FindSuggestion( str ) == -1); |
|
if ( bSelectFirst || bForceFirst ) |
|
{ |
|
SetCurSel( 0 ); |
|
|
|
if ( GetCount() > 0 ) |
|
{ |
|
CString strLB; |
|
GetLBText( 0, strLB ); |
|
if ( bCallback ) |
|
DoTextChangedCallback( strLB ); |
|
} |
|
else |
|
{ |
|
m_LastTextChangedValue = ""; |
|
} |
|
} |
|
else |
|
{ |
|
SetWindowText( str ); |
|
SetEditSel( LOWORD( sel ), HIWORD( sel ) ); |
|
if ( bCallback ) |
|
DoTextChangedCallback( str ); |
|
} |
|
|
|
SetRedraw( true ); |
|
Invalidate(); |
|
} |
|
|
|
|
|
void CFilteredComboBox::AddSuggestion( const CString &suggestion ) |
|
{ |
|
if ( FindSuggestion( suggestion ) == -1 ) |
|
m_Suggestions.AddToTail( suggestion ); |
|
} |
|
|
|
|
|
void CFilteredComboBox::Clear() |
|
{ |
|
m_Suggestions.Purge(); |
|
SetWindowText( "" ); |
|
} |
|
|
|
|
|
void CFilteredComboBox::ForceEditControlText( const char *pStr ) |
|
{ |
|
SetWindowText( pStr ); |
|
} |
|
|
|
|
|
void CFilteredComboBox::SelectItem( const char *pStr ) |
|
{ |
|
if ( !pStr ) |
|
{ |
|
SetEditControlText( "" ); |
|
return; |
|
} |
|
|
|
// See if we already have this item selected. If so, don't do anything. |
|
int iCurSel = GetCurSel(); |
|
if ( iCurSel != CB_ERR ) |
|
{ |
|
CString str; |
|
GetLBText( iCurSel, str ); |
|
if ( Q_stricmp( pStr, str ) == 0 ) |
|
{ |
|
// Make sure the edit control has the right text in there. If they called ForceEditControlText, |
|
// then it might not. |
|
CString strWindow; |
|
GetWindowText( strWindow ); |
|
if ( Q_stricmp( strWindow, pStr ) != 0 ) |
|
{ |
|
SetWindowText( pStr ); |
|
} |
|
|
|
m_LastTextChangedValue = pStr; |
|
return; |
|
} |
|
} |
|
|
|
if ( m_bOnlyProvideSuggestions && FindSuggestion( pStr ) == -1 ) |
|
{ |
|
// This item doesn't match any suggestion. We can get rid of this assert |
|
// if it becomes a nuissance, but for now it's good to note that this |
|
// is a weird situation. |
|
Assert( false ); |
|
SetEditControlText( pStr ); |
|
return; |
|
} |
|
|
|
FillDropdownList( pStr ); |
|
} |
|
|
|
|
|
CString CFilteredComboBox::GetCurrentItem() |
|
{ |
|
return m_LastTextChangedValue; |
|
} |
|
|
|
|
|
void CFilteredComboBox::SetEditControlFont( HFONT hFont ) |
|
{ |
|
if ( !hFont ) |
|
return; |
|
|
|
if ( m_bInSelChange ) |
|
{ |
|
m_hQueuedFont = hFont; |
|
return; |
|
} |
|
|
|
CString str; |
|
GetWindowText( str ); |
|
DWORD sel = GetEditSel(); |
|
|
|
InternalSetEditControlFont( hFont, str, sel ); |
|
} |
|
|
|
|
|
void CFilteredComboBox::InternalSetEditControlFont( HFONT hFont, const char *pEditText, DWORD sel ) |
|
{ |
|
if ( hFont != m_hEditControlFont ) |
|
{ |
|
CFunctionMarker marker( "InternalSetEditControlFont" ); |
|
|
|
// Don't let it mess with everything here. |
|
SetRedraw( false ); |
|
|
|
CRect rcMyRect; |
|
GetWindowRect( rcMyRect ); |
|
CWnd *pParent = GetParent(); |
|
if ( pParent ) |
|
pParent->ScreenToClient( &rcMyRect ); |
|
|
|
BOOL bWasDropped = GetDroppedState(); |
|
|
|
|
|
m_hEditControlFont = hFont; |
|
SetFont( CFont::FromHandle( m_hEditControlFont ), false ); |
|
|
|
|
|
SetWindowText( pEditText ); |
|
SetEditSel( LOWORD( sel ), HIWORD( sel ) ); |
|
|
|
if ( pParent ) |
|
MoveWindow( rcMyRect ); |
|
|
|
if ( bWasDropped ) |
|
ShowDropDown( true ); |
|
|
|
|
|
SetRedraw( true ); |
|
Invalidate(); |
|
} |
|
} |
|
|
|
|
|
HFONT CFilteredComboBox::GetEditControlFont() const |
|
{ |
|
return m_hEditControlFont; |
|
} |
|
|
|
|
|
void CFilteredComboBox::SetEditControlTextColor(COLORREF dwColor) |
|
{ |
|
m_dwTextColor = dwColor; |
|
} |
|
|
|
|
|
COLORREF CFilteredComboBox::GetEditControlTextColor() const |
|
{ |
|
return m_dwTextColor; |
|
} |
|
|
|
|
|
void CFilteredComboBox::SetEditControlText( const char *pText ) |
|
{ |
|
SetWindowText( pText ); |
|
} |
|
|
|
|
|
CString CFilteredComboBox::GetEditControlText() const |
|
{ |
|
CString ret; |
|
GetWindowText( ret ); |
|
return ret; |
|
} |
|
|
|
bool CFilteredComboBox::IsWindowEnabled() const |
|
{ |
|
return (BaseClass::IsWindowEnabled() == TRUE); |
|
} |
|
|
|
|
|
void CFilteredComboBox::EnableWindow( bool bEnable ) |
|
{ |
|
BaseClass::EnableWindow( bEnable ); |
|
} |
|
|
|
|
|
void CFilteredComboBox::SetOnlyProvideSuggestions( bool bOnlyProvideSuggestions ) |
|
{ |
|
m_bOnlyProvideSuggestions = bOnlyProvideSuggestions; |
|
} |
|
|
|
|
|
void CFilteredComboBox::FillDropdownList( const char *pInitialSel, bool bEnableRedraw ) |
|
{ |
|
CFunctionMarker marker( "FillDropdownList" ); |
|
|
|
SetRedraw( FALSE ); |
|
ResetContent(); |
|
|
|
// Fill the box with the initial set of values. |
|
CUtlVector<CString> items; |
|
GetItemsMatchingString( "", items ); |
|
|
|
for ( int i=0; i < items.Count(); i++ ) |
|
AddString( items[i] ); |
|
|
|
if ( pInitialSel ) |
|
{ |
|
CString str = pInitialSel; |
|
if ( m_bOnlyProvideSuggestions ) |
|
{ |
|
str = GetBestSuggestion( pInitialSel ); |
|
if ( !InternalSelectItemByName( pInitialSel) ) |
|
{ |
|
Assert( false ); |
|
} |
|
} |
|
else |
|
{ |
|
// Make sure we're putting the item they requested in there. |
|
if ( !InternalSelectItemByName( str ) ) |
|
{ |
|
// Add the typed text to the combobox here otherwise it'll select the nearest match when they drop it down with the mouse. |
|
AddString( str ); |
|
InternalSelectItemByName( str ); |
|
} |
|
} |
|
|
|
DoTextChangedCallback( str ); |
|
} |
|
|
|
if ( bEnableRedraw ) |
|
{ |
|
SetRedraw( TRUE ); |
|
Invalidate(); |
|
} |
|
} |
|
|
|
|
|
LRESULT CFilteredComboBox::DefWindowProc( |
|
UINT message, |
|
WPARAM wParam, |
|
LPARAM lParam |
|
) |
|
{ |
|
// We handle the enter key specifically because the default combo box behavior is to |
|
// reset the text and all this stuff we don't want. |
|
if ( message == WM_KEYDOWN ) |
|
{ |
|
if ( wParam == '\r' ) |
|
{ |
|
OnEnterKeyPressed( NULL ); |
|
return 0; |
|
} |
|
else if ( wParam == 27 ) |
|
{ |
|
// Escape.. |
|
OnEscapeKeyPressed(); |
|
return 0; |
|
} |
|
} |
|
|
|
return BaseClass::DefWindowProc( message, wParam, lParam ); |
|
} |
|
|
|
|
|
BOOL CFilteredComboBox::PreCreateWindow( CREATESTRUCT& cs ) |
|
{ |
|
// We need these styles in order for owner draw to work. |
|
// If we use CBS_OWNERDRAWVARIABLE, then we run into this bug: http://support.microsoft.com/kb/813791. |
|
cs.style |= CBS_OWNERDRAWFIXED | CBS_HASSTRINGS; |
|
cs.style &= ~CBS_SORT; |
|
return BaseClass::PreCreateWindow( cs ); |
|
} |
|
|
|
void CFilteredComboBox::OnEnterKeyPressed( const char *pForceText ) |
|
{ |
|
if ( m_bInEnterKeyPressedHandler ) |
|
return; |
|
|
|
CFunctionMarker marker( "OnEnterKeyPressed" ); |
|
|
|
m_bInEnterKeyPressedHandler = true; |
|
|
|
// Must do this before ShowDropDown because that will change these variables underneath us. |
|
CString szTypedText; |
|
DWORD sel; |
|
if ( pForceText ) |
|
{ |
|
szTypedText = pForceText; |
|
sel = 0; |
|
} |
|
else |
|
{ |
|
GetWindowText( szTypedText ); |
|
sel = GetEditSel(); |
|
} |
|
|
|
CRect rcMyRect; |
|
GetWindowRect( rcMyRect ); |
|
CWnd *pParent = GetParent(); |
|
if ( pParent ) |
|
pParent->ScreenToClient( &rcMyRect ); |
|
|
|
SetRedraw( false ); |
|
ShowDropDown( FALSE ); |
|
|
|
// They can get into here a variety of ways. Editing followed by enter. Editing+arrow keys, followed by enter, etc. |
|
if ( m_bOnlyProvideSuggestions ) |
|
{ |
|
CString str; |
|
if ( FindSuggestion( szTypedText ) == -1 && m_pCallbacks->OnUnknownEntry( szTypedText ) ) |
|
{ |
|
// They want us to KEEP this unknown entry, so add it to our list and select it. |
|
m_Suggestions.AddToTail( szTypedText ); |
|
str = szTypedText; |
|
} |
|
else |
|
{ |
|
// They returned false, so do the default behavior: go to the best match we can find. |
|
str = GetBestSuggestion( szTypedText ); |
|
} |
|
|
|
DoTextChangedCallback( str ); |
|
FillDropdownList( str, false ); |
|
|
|
if ( GetCurSel() == CB_ERR ) |
|
SetCurSel( 0 ); |
|
} |
|
else |
|
{ |
|
FillDropdownList( szTypedText, false ); |
|
SetWindowText( szTypedText ); |
|
SetEditSel( LOWORD(sel), HIWORD(sel) ); |
|
} |
|
|
|
// Restore our window if necessary. |
|
if ( pParent ) |
|
MoveWindow( &rcMyRect ); |
|
SetRedraw( true ); |
|
Invalidate(); |
|
|
|
DoTextChangedCallback( GetEditControlText() ); |
|
m_bInEnterKeyPressedHandler = false; |
|
} |
|
|
|
|
|
void CFilteredComboBox::OnEscapeKeyPressed() |
|
{ |
|
// Fill it with everything and force it to select whatever we last selected. |
|
m_bInEnterKeyPressedHandler = true; |
|
ShowDropDown( FALSE ); |
|
m_bInEnterKeyPressedHandler = false; |
|
|
|
FillDropdownList( m_LastTextChangedValue, true ); |
|
} |
|
|
|
|
|
BOOL CFilteredComboBox::OnDropDown() |
|
{ |
|
CFunctionMarker marker( "OnDropDown" ); |
|
// This is necessary to keep the cursor from disappearing. |
|
SendMessage( WM_SETCURSOR, 0, 0 ); |
|
return !m_bNotifyParent; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Attaches this object to the given dialog item. |
|
//----------------------------------------------------------------------------- |
|
void CFilteredComboBox::SubclassDlgItem(UINT nID, CWnd *pParent) |
|
{ |
|
// |
|
// Disable parent notifications for CControlBar-derived classes. This is |
|
// necessary because these classes result in multiple message reflections |
|
// unless we return TRUE from our message handler. |
|
// |
|
if (pParent->IsKindOf(RUNTIME_CLASS(CControlBar))) |
|
{ |
|
m_bNotifyParent = false; |
|
} |
|
else |
|
{ |
|
m_bNotifyParent = true; |
|
} |
|
|
|
BaseClass::SubclassDlgItem(nID, pParent); |
|
} |
|
|
|
BOOL CFilteredComboBox::OnSelChange() |
|
{ |
|
if ( !m_bInSelChange ) |
|
{ |
|
CFunctionMarker marker( "OnSelChange" ); |
|
|
|
CString strOriginalText; |
|
GetWindowText( strOriginalText ); |
|
DWORD dwOriginalEditSel = GetEditSel(); |
|
|
|
|
|
m_bInSelChange = true; |
|
|
|
int iSel = GetCurSel(); |
|
if ( iSel != CB_ERR ) |
|
{ |
|
CString str; |
|
GetLBText( iSel, str ); |
|
strOriginalText = str; |
|
DoTextChangedCallback( str ); |
|
} |
|
|
|
m_bInSelChange = false; |
|
|
|
if ( m_hQueuedFont ) |
|
{ |
|
HFONT hFont = m_hQueuedFont; |
|
m_hQueuedFont = NULL; |
|
m_bInSelChange = false; |
|
InternalSetEditControlFont( hFont, strOriginalText, dwOriginalEditSel ); |
|
} |
|
} |
|
|
|
// |
|
// Despite MSDN's lies, returning FALSE here allows the parent |
|
// window to hook the notification message as well, not TRUE. |
|
// |
|
return !m_bNotifyParent; |
|
} |
|
|
|
BOOL CFilteredComboBox::OnCloseUp() |
|
{ |
|
if ( !m_bInEnterKeyPressedHandler ) |
|
{ |
|
CFunctionMarker marker( "OnCloseUp" ); |
|
|
|
CString str; |
|
if ( GetCurSel() == CB_ERR || GetCount() == 0 ) |
|
str = m_LastTextChangedValue; |
|
else |
|
GetLBText( GetCurSel(), str ); |
|
OnEnterKeyPressed( str ); |
|
} |
|
|
|
// |
|
// Despite MSDN's lies, returning FALSE here allows the parent |
|
// window to hook the notification message as well, not TRUE. |
|
// |
|
return !m_bNotifyParent; |
|
} |
|
|
|
BOOL CFilteredComboBox::OnSelEndOK() |
|
{ |
|
// |
|
// Despite MSDN's lies, returning FALSE here allows the parent |
|
// window to hook the notification message as well, not TRUE. |
|
// |
|
return !m_bNotifyParent; |
|
} |
|
|
|
BOOL CFilteredComboBox::OnEditChange() |
|
{ |
|
CFunctionMarker marker( "OnEditChange" ); |
|
|
|
// Remember the text in the edit control because we're going to slam the |
|
// contents of the list and we'll want to put the text back in. |
|
CString szTypedText; |
|
DWORD dwEditSel; |
|
GetWindowText( szTypedText ); |
|
dwEditSel = GetEditSel(); |
|
|
|
// Show all the matching autosuggestions. |
|
CUtlVector<CString> items; |
|
GetItemsMatchingString( szTypedText, items ); |
|
|
|
SetRedraw( FALSE ); |
|
ResetContent(); |
|
|
|
for ( int i=0; i < items.Count(); i++ ) |
|
{ |
|
AddString( items[i] ); |
|
} |
|
|
|
// Add the typed text to the combobox here otherwise it'll select the nearest match when they drop it down with the mouse. |
|
if ( !m_bOnlyProvideSuggestions && FindSuggestion( szTypedText ) == -1 ) |
|
AddString( szTypedText ); |
|
|
|
// Note: for arcane and unspeakable MFC reasons, the placement of this call is VERY sensitive. |
|
// For example, if CTargetNameComboBox changes from a bold font to a normal font, then if this |
|
// call comes before ResetContent(), it will resize the dropdown listbox to a small size and not |
|
// size it back until it is cloesd and opened again. |
|
ShowDropDown(); |
|
|
|
SetRedraw( TRUE ); |
|
Invalidate(); |
|
|
|
// Possibly tell the app about this change. |
|
if ( m_bOnlyProvideSuggestions ) |
|
{ |
|
if ( FindSuggestion( szTypedText ) != -1 ) |
|
DoTextChangedCallback( szTypedText ); |
|
} |
|
else |
|
{ |
|
DoTextChangedCallback( szTypedText ); |
|
} |
|
|
|
// Put the text BACK in there. |
|
SetWindowText( szTypedText ); |
|
SetEditSel( LOWORD( dwEditSel ), HIWORD( dwEditSel ) ); |
|
|
|
// |
|
// Despite MSDN's lies, returning FALSE here allows the parent |
|
// window to hook the notification message as well, not TRUE. |
|
// |
|
return !m_bNotifyParent; |
|
} |
|
|
|
int CFilteredComboBox::FindSuggestion( const char *pTest ) const |
|
{ |
|
for ( int i=0; i < m_Suggestions.Count(); i++ ) |
|
{ |
|
if ( Q_stricmp( m_Suggestions[i], pTest ) == 0 ) |
|
return i; |
|
} |
|
return -1; |
|
} |
|
|
|
|
|
CString CFilteredComboBox::GetBestSuggestion( const char *pTest ) |
|
{ |
|
// If it's an exact match, use that. |
|
if ( FindSuggestion( pTest ) != -1 ) |
|
return pTest; |
|
|
|
// Look for the first autocomplete suggestion. |
|
CUtlVector<CString> matches; |
|
GetItemsMatchingString( pTest, matches ); |
|
if ( matches.Count() > 0 ) |
|
return matches[0]; |
|
|
|
// Ok, fall back to the last known good one. |
|
return m_LastTextChangedValue; |
|
} |
|
|
|
|
|
CFont& CFilteredComboBox::GetNormalFont() |
|
{ |
|
CreateFonts(); |
|
return m_NormalFont; |
|
} |
|
|
|
|
|
void CFilteredComboBox::GetItemsMatchingString( const char *pStringToMatch, CUtlVector<CString> &matchingItems ) |
|
{ |
|
for ( int i=0; i < m_Suggestions.Count(); i++ ) |
|
{ |
|
if ( MatchString( pStringToMatch, m_Suggestions[i] ) ) |
|
matchingItems.AddToTail( m_Suggestions[i] ); |
|
} |
|
|
|
s_pStringToMatch = pStringToMatch; |
|
s_iStringToMatchLen = V_strlen( pStringToMatch ); |
|
matchingItems.Sort( &CFilteredComboBox::SortFn ); |
|
s_pStringToMatch = NULL; |
|
} |
|
|
|
|
|
int CFilteredComboBox::SortFn( const CString *pItem1, const CString *pItem2 ) |
|
{ |
|
// If one of them matches the prefix we're looking at, then that one should be listed first. |
|
// Otherwise, just do an alphabetical sort. |
|
bool bPrefixMatch1=false, bPrefixMatch2=false; |
|
if ( s_pStringToMatch ) |
|
{ |
|
bPrefixMatch1 = ( V_strnistr( *pItem1, s_pStringToMatch, s_iStringToMatchLen ) != NULL ); |
|
bPrefixMatch2 = ( V_strnistr( *pItem2, s_pStringToMatch, s_iStringToMatchLen ) != NULL ); |
|
} |
|
|
|
if ( bPrefixMatch1 == bPrefixMatch2 ) |
|
{ |
|
return Q_stricmp( *pItem1, *pItem2 ); |
|
} |
|
else |
|
{ |
|
return bPrefixMatch1 ? -1 : 1; |
|
} |
|
} |
|
|
|
|
|
bool CFilteredComboBox::MatchString( const char *pStringToMatchStart, const char *pTestStringStart ) |
|
{ |
|
if ( !pStringToMatchStart || pStringToMatchStart[0] == 0 ) |
|
return true; |
|
|
|
while ( *pTestStringStart ) |
|
{ |
|
const char *pStringToMatch = pStringToMatchStart; |
|
const char *pTestString = pTestStringStart; |
|
|
|
while ( 1 ) |
|
{ |
|
// Skip underscores in both strings. |
|
while ( *pStringToMatch == '_' ) |
|
++pStringToMatch; |
|
|
|
while ( *pTestString == '_' ) |
|
++pTestString; |
|
|
|
// If we're at the end of pStringToMatch with no mismatch, then treat this as a prefix match. |
|
// If we're at the end of pTestString, but pStringToMatch has more to go, then it's not a match. |
|
if ( *pStringToMatch == 0 ) |
|
return true; |
|
else if ( *pTestString == 0 ) |
|
break; |
|
|
|
// Match this character. |
|
if ( toupper( *pStringToMatch ) != toupper( *pTestString ) ) |
|
break; |
|
|
|
++pStringToMatch; |
|
++pTestString; |
|
} |
|
|
|
++pTestStringStart; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called before painting to override default colors. |
|
// Input : pDC - DEvice context being painted into. |
|
// pWnd - Control asking for color. |
|
// nCtlColor - Type of control asking for color. |
|
// Output : Returns the handle of a brush to use as the background color. |
|
//----------------------------------------------------------------------------- |
|
HBRUSH CFilteredComboBox::OnCtlColor(CDC *pDC, CWnd *pWnd, UINT nCtlColor) |
|
{ |
|
HBRUSH hBrush = CComboBox::OnCtlColor(pDC, pWnd, nCtlColor); |
|
|
|
if (nCtlColor == CTLCOLOR_EDIT) |
|
{ |
|
pDC->SetTextColor(m_dwTextColor); |
|
} |
|
|
|
return(hBrush); |
|
} |
|
|
|
|
|
void CFilteredComboBox::DoTextChangedCallback( const char *pText ) |
|
{ |
|
// Sometimes it'll call here from a few places in a row. Only pass the result |
|
// to the owner once. |
|
if ( Q_stricmp( pText, m_LastTextChangedValue ) == 0 ) |
|
return; |
|
|
|
m_LastTextChangedValue = pText; |
|
m_pCallbacks->OnTextChanged( pText ); |
|
} |
|
|
|
|
|
void CFilteredComboBox::CreateFonts() |
|
{ |
|
// |
|
// Create a normal and bold font. |
|
// |
|
if (!m_NormalFont.m_hObject) |
|
{ |
|
CFont *pFont = GetFont(); |
|
if (pFont) |
|
{ |
|
LOGFONT LogFont; |
|
pFont->GetLogFont(&LogFont); |
|
m_NormalFont.CreateFontIndirect(&LogFont); |
|
} |
|
} |
|
} |
|
|
|
|
|
void CFilteredComboBox::MeasureItem(LPMEASUREITEMSTRUCT pStruct) |
|
{ |
|
HFONT hFont; |
|
CFont *pFont = GetFont(); |
|
if ( pFont ) |
|
hFont = *pFont; |
|
else |
|
hFont = (HFONT)GetStockObject( DEFAULT_GUI_FONT ); |
|
|
|
CFont *pActualFont = CFont::FromHandle( hFont ); |
|
if ( pActualFont ) |
|
{ |
|
LOGFONT logFont; |
|
pActualFont->GetLogFont( &logFont ); |
|
pStruct->itemHeight = abs( logFont.lfHeight ) + 5; |
|
} |
|
else |
|
{ |
|
pStruct->itemHeight = 16; |
|
} |
|
} |
|
|
|
|
|
void CFilteredComboBox::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) |
|
{ |
|
if ( GetCount() == 0 ) |
|
return; |
|
|
|
CString str; |
|
GetLBText( lpDrawItemStruct->itemID, str ); |
|
|
|
CDC dc; |
|
dc.Attach( lpDrawItemStruct->hDC ); |
|
|
|
// Save these values to restore them when done drawing. |
|
COLORREF crOldTextColor = dc.GetTextColor(); |
|
COLORREF crOldBkColor = dc.GetBkColor(); |
|
|
|
// If this item is selected, set the background color |
|
// and the text color to appropriate values. Erase |
|
// the rect by filling it with the background color. |
|
// The left side of this expression was originally |
|
// "(lpDrawItemStruct->itemAction | ODA_SELECT)", which is always true. |
|
// To suppress the associated /analyze warning without changing |
|
// behavior the expression was fixed but commented out. |
|
if ( /*(lpDrawItemStruct->itemAction & ODA_SELECT) &&*/ (lpDrawItemStruct->itemState & ODS_SELECTED) ) |
|
{ |
|
dc.SetTextColor( ::GetSysColor(COLOR_HIGHLIGHTTEXT) ); |
|
dc.SetBkColor( ::GetSysColor(COLOR_HIGHLIGHT) ); |
|
dc.FillSolidRect( &lpDrawItemStruct->rcItem, ::GetSysColor(COLOR_HIGHLIGHT) ); |
|
} |
|
else |
|
{ |
|
dc.FillSolidRect(&lpDrawItemStruct->rcItem, crOldBkColor); |
|
} |
|
|
|
CFont *pOldFont = dc.SelectObject( &m_NormalFont ); |
|
|
|
// Draw the text. |
|
RECT rcDraw = lpDrawItemStruct->rcItem; |
|
rcDraw.left += 1; |
|
dc.DrawText( str, -1, &rcDraw, DT_LEFT|DT_SINGLELINE|DT_VCENTER ); |
|
|
|
// Restore stuff. |
|
dc.SelectObject( pOldFont ); |
|
dc.SetTextColor(crOldTextColor); |
|
dc.SetBkColor(crOldBkColor); |
|
|
|
dc.Detach(); |
|
} |
|
|
|
|
|
bool CFilteredComboBox::InternalSelectItemByName( const char *pName ) |
|
{ |
|
int i = FindStringExact( -1, pName ); |
|
if ( i == CB_ERR ) |
|
{ |
|
return false; |
|
} |
|
else |
|
{ |
|
SetCurSel( i ); |
|
|
|
CString str; |
|
GetWindowText( str ); |
|
if ( Q_stricmp( str, pName ) != 0 ) |
|
SetWindowText( pName ); |
|
|
|
return true; |
|
} |
|
}
|
|
|