[BROWSEUI] auto-completion: Support large number items (#3592)

If the items are too many, enable filtering in item enumeration. CORE-9281
This commit is contained in:
Katayama Hirofumi MZ 2021-04-08 15:02:05 +09:00 committed by GitHub
parent 82c908195c
commit 773ad7aebc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 134 additions and 76 deletions

View file

@ -32,7 +32,7 @@
#define CX_LIST 30160 // width of m_hwndList (very wide but alright) #define CX_LIST 30160 // width of m_hwndList (very wide but alright)
#define CY_LIST 288 // maximum height of drop-down window #define CY_LIST 288 // maximum height of drop-down window
#define CY_ITEM 18 // default height of listview item #define CY_ITEM 18 // default height of listview item
#define COMPLETION_TIMEOUT 250 // in milliseconds #define COMPLETION_TIMEOUT 300 // in milliseconds
#define MAX_ITEM_COUNT 1000 // the maximum number of items #define MAX_ITEM_COUNT 1000 // the maximum number of items
#define WATCH_TIMER_ID 0xFEEDBEEF // timer ID to watch m_rcEdit #define WATCH_TIMER_ID 0xFEEDBEEF // timer ID to watch m_rcEdit
#define WATCH_INTERVAL 300 // in milliseconds #define WATCH_INTERVAL 300 // in milliseconds
@ -640,6 +640,7 @@ CAutoComplete::CAutoComplete()
, m_bDowner(TRUE), m_dwOptions(ACO_AUTOAPPEND | ACO_AUTOSUGGEST) , m_bDowner(TRUE), m_dwOptions(ACO_AUTOAPPEND | ACO_AUTOSUGGEST)
, m_bEnabled(TRUE), m_hwndCombo(NULL), m_hFont(NULL), m_bResized(FALSE) , m_bEnabled(TRUE), m_hwndCombo(NULL), m_hFont(NULL), m_bResized(FALSE)
, m_hwndEdit(NULL), m_fnOldEditProc(NULL), m_fnOldWordBreakProc(NULL) , m_hwndEdit(NULL), m_fnOldEditProc(NULL), m_fnOldWordBreakProc(NULL)
, m_bPartialList(FALSE), m_dwTick(0)
{ {
} }
@ -667,46 +668,46 @@ CAutoComplete::~CAutoComplete()
m_pACList.Release(); m_pACList.Release();
} }
BOOL CAutoComplete::CanAutoSuggest() inline BOOL CAutoComplete::CanAutoSuggest() const
{ {
return !!(m_dwOptions & ACO_AUTOSUGGEST) && m_bEnabled; return !!(m_dwOptions & ACO_AUTOSUGGEST) && m_bEnabled;
} }
BOOL CAutoComplete::CanAutoAppend() inline BOOL CAutoComplete::CanAutoAppend() const
{ {
return !!(m_dwOptions & ACO_AUTOAPPEND) && m_bEnabled; return !!(m_dwOptions & ACO_AUTOAPPEND) && m_bEnabled;
} }
BOOL CAutoComplete::UseTab() inline BOOL CAutoComplete::UseTab() const
{ {
return !!(m_dwOptions & ACO_USETAB) && m_bEnabled; return !!(m_dwOptions & ACO_USETAB) && m_bEnabled;
} }
BOOL CAutoComplete::IsComboBoxDropped() inline BOOL CAutoComplete::IsComboBoxDropped() const
{ {
if (!::IsWindow(m_hwndCombo)) if (!::IsWindow(m_hwndCombo))
return FALSE; return FALSE;
return (BOOL)::SendMessageW(m_hwndCombo, CB_GETDROPPEDSTATE, 0, 0); return (BOOL)::SendMessageW(m_hwndCombo, CB_GETDROPPEDSTATE, 0, 0);
} }
BOOL CAutoComplete::FilterPrefixes() inline BOOL CAutoComplete::FilterPrefixes() const
{ {
return !!(m_dwOptions & ACO_FILTERPREFIXES) && m_bEnabled; return !!(m_dwOptions & ACO_FILTERPREFIXES) && m_bEnabled;
} }
INT CAutoComplete::GetItemCount() inline INT CAutoComplete::GetItemCount() const
{ {
return m_outerList.GetSize(); return m_outerList.GetSize();
} }
CStringW CAutoComplete::GetItemText(INT iItem) CStringW CAutoComplete::GetItemText(INT iItem) const
{ {
if (iItem < 0 || m_outerList.GetSize() <= iItem) if (iItem < 0 || m_outerList.GetSize() <= iItem)
return L""; return L"";
return m_outerList[iItem]; return m_outerList[iItem];
} }
CStringW CAutoComplete::GetEditText() inline CStringW CAutoComplete::GetEditText() const
{ {
WCHAR szText[L_MAX_URL_LENGTH]; WCHAR szText[L_MAX_URL_LENGTH];
if (::GetWindowTextW(m_hwndEdit, szText, _countof(szText))) if (::GetWindowTextW(m_hwndEdit, szText, _countof(szText)))
@ -721,9 +722,8 @@ VOID CAutoComplete::SetEditText(LPCWSTR pszText)
m_bInSetText = FALSE; m_bInSetText = FALSE;
} }
CStringW CAutoComplete::GetStemText() inline CStringW CAutoComplete::GetStemText(const CStringW& strText) const
{ {
CStringW strText = GetEditText();
INT ich = strText.ReverseFind(L'\\'); INT ich = strText.ReverseFind(L'\\');
if (ich == -1) if (ich == -1)
return L""; // no stem return L""; // no stem
@ -1284,7 +1284,7 @@ VOID CAutoComplete::UpdateDropDownState()
} }
// calculate the positions of the controls // calculate the positions of the controls
VOID CAutoComplete::CalcRects(BOOL bDowner, RECT& rcList, RECT& rcScrollBar, RECT& rcSizeBox) VOID CAutoComplete::CalcRects(BOOL bDowner, RECT& rcList, RECT& rcScrollBar, RECT& rcSizeBox) const
{ {
// get the client rectangle // get the client rectangle
RECT rcClient; RECT rcClient;
@ -1354,7 +1354,7 @@ VOID CAutoComplete::LoadQuickComplete(LPCWSTR pwszRegKeyPath, LPCWSTR pwszQuickC
} }
} }
CStringW CAutoComplete::GetQuickEdit(LPCWSTR pszText) CStringW CAutoComplete::GetQuickEdit(LPCWSTR pszText) const
{ {
if (pszText[0] == 0 || m_strQuickComplete.IsEmpty()) if (pszText[0] == 0 || m_strQuickComplete.IsEmpty())
return pszText; return pszText;
@ -1444,20 +1444,45 @@ VOID CAutoComplete::RepositionDropDown()
ShowWindow(SW_SHOWNOACTIVATE); ShowWindow(SW_SHOWNOACTIVATE);
} }
INT CAutoComplete::ReLoadInnerList() inline BOOL
CAutoComplete::DoesMatch(const CStringW& strTarget, const CStringW& strText) const
{
CStringW strBody;
if (DropPrefix(strTarget, strBody))
{
if (::StrCmpNIW(strBody, strText, strText.GetLength()) == 0)
return TRUE;
}
else if (::StrCmpNIW(strTarget, strText, strText.GetLength()) == 0)
{
return TRUE;
}
return FALSE;
}
VOID CAutoComplete::ScrapeOffList(const CStringW& strText, CSimpleArray<CStringW>& array)
{
for (INT iItem = array.GetSize() - 1; iItem >= 0; --iItem)
{
if (!DoesMatch(array[iItem], strText))
array.RemoveAt(iItem);
}
}
VOID CAutoComplete::ReLoadInnerList(const CStringW& strText)
{ {
m_innerList.RemoveAll(); // clear contents m_innerList.RemoveAll(); // clear contents
m_bPartialList = FALSE;
if (!m_pEnum) if (!m_pEnum || strText.IsEmpty())
return 0; return;
DWORD dwTick = ::GetTickCount(); // used for timeout
// reload the items // reload the items
LPWSTR pszItem; LPWSTR pszItem;
ULONG cGot; ULONG cGot;
CStringW strTarget;
HRESULT hr; HRESULT hr;
for (ULONG cTotal = 0; cTotal < MAX_ITEM_COUNT; ++cTotal) for (;;)
{ {
// get next item // get next item
hr = m_pEnum->Next(1, &pszItem, &cGot); hr = m_pEnum->Next(1, &pszItem, &cGot);
@ -1465,23 +1490,45 @@ INT CAutoComplete::ReLoadInnerList()
if (hr != S_OK) if (hr != S_OK)
break; break;
m_innerList.Add(pszItem); // append item to m_innerList strTarget = pszItem;
::CoTaskMemFree(pszItem); // free ::CoTaskMemFree(pszItem); // free
if (m_bPartialList) // if items are too many
{
// do filter the items
if (DoesMatch(strTarget, strText))
{
m_innerList.Add(strTarget);
if (m_innerList.GetSize() >= MAX_ITEM_COUNT)
break;
}
}
else
{
m_innerList.Add(strTarget); // append item to m_innerList
// if items are too many
if (m_innerList.GetSize() >= MAX_ITEM_COUNT)
{
// filter the items now
m_bPartialList = TRUE;
ScrapeOffList(strText, m_innerList);
if (m_innerList.GetSize() >= MAX_ITEM_COUNT)
break;
}
}
// check the timeout // check the timeout
if (::GetTickCount() - dwTick >= COMPLETION_TIMEOUT) if (::GetTickCount() - m_dwTick >= COMPLETION_TIMEOUT)
break; // too late break; // too late
} }
return m_innerList.GetSize(); // the number of items
} }
// update inner list and m_strText and m_strStemText // update inner list and m_strText and m_strStemText
INT CAutoComplete::UpdateInnerList() VOID CAutoComplete::UpdateInnerList(const CStringW& strText)
{ {
// get text
CStringW strText = GetEditText();
BOOL bReset = FALSE, bExpand = FALSE; // flags BOOL bReset = FALSE, bExpand = FALSE; // flags
// if previous text was empty // if previous text was empty
@ -1493,7 +1540,7 @@ INT CAutoComplete::UpdateInnerList()
m_strText = strText; m_strText = strText;
// do expand the items if the stem is changed // do expand the items if the stem is changed
CStringW strStemText = GetStemText(); CStringW strStemText = GetStemText(strText);
if (m_strStemText.CompareNoCase(strStemText) != 0) if (m_strStemText.CompareNoCase(strStemText) != 0)
{ {
m_strStemText = strStemText; m_strStemText = strStemText;
@ -1501,6 +1548,10 @@ INT CAutoComplete::UpdateInnerList()
bExpand = !m_strStemText.IsEmpty(); bExpand = !m_strStemText.IsEmpty();
} }
// if the previous enumeration is too large
if (m_bPartialList)
bReset = bExpand = TRUE; // retry enumeratation
// reset if necessary // reset if necessary
if (bReset && m_pEnum) if (bReset && m_pEnum)
{ {
@ -1521,55 +1572,57 @@ INT CAutoComplete::UpdateInnerList()
if (bExpand || m_innerList.GetSize() == 0) if (bExpand || m_innerList.GetSize() == 0)
{ {
// reload the inner list // reload the inner list
ReLoadInnerList(); ReLoadInnerList(strText);
} }
return m_innerList.GetSize();
} }
INT CAutoComplete::UpdateOuterList() VOID CAutoComplete::UpdateOuterList(const CStringW& strText)
{ {
CStringW strText = GetEditText(); // get the text if (strText.IsEmpty())
// update the outer list from the inner list
m_outerList.RemoveAll();
for (INT iItem = 0; iItem < m_innerList.GetSize(); ++iItem)
{ {
// is the beginning matched? m_outerList.RemoveAll();
const CStringW& strTarget = m_innerList[iItem]; return;
CStringW strBody; }
if (DropPrefix(strTarget, strBody))
if (m_bPartialList)
{
// it is already filtered
m_outerList = m_innerList;
}
else
{
// do filtering
m_outerList.RemoveAll();
for (INT iItem = 0; iItem < m_innerList.GetSize(); ++iItem)
{ {
if (::StrCmpNIW(strBody, strText, strText.GetLength()) == 0) const CStringW& strTarget = m_innerList[iItem];
{
if (DoesMatch(strTarget, strText))
m_outerList.Add(strTarget); m_outerList.Add(strTarget);
continue;
} // check the timeout
} if (::GetTickCount() - m_dwTick >= COMPLETION_TIMEOUT)
if (::StrCmpNIW(strTarget, strText, strText.GetLength()) == 0) break; // too late
{
m_outerList.Add(strTarget);
} }
} }
// sort the list if (::GetTickCount() - m_dwTick < COMPLETION_TIMEOUT)
DoSort(m_outerList); {
// unique // sort and unique
DoUniqueAndTrim(m_outerList); DoSort(m_outerList);
DoUniqueAndTrim(m_outerList);
}
// set the item count of the virtual listview // set the item count of the virtual listview
INT cItems = m_outerList.GetSize(); m_hwndList.SendMessageW(LVM_SETITEMCOUNT, m_outerList.GetSize(), 0);
if (strText.IsEmpty())
cItems = 0;
m_hwndList.SendMessageW(LVM_SETITEMCOUNT, cItems, 0);
return cItems; // the number of items
} }
VOID CAutoComplete::UpdateCompletion(BOOL bAppendOK) VOID CAutoComplete::UpdateCompletion(BOOL bAppendOK)
{ {
TRACE("CAutoComplete::UpdateCompletion(%p, %d)\n", this, bAppendOK); TRACE("CAutoComplete::UpdateCompletion(%p, %d)\n", this, bAppendOK);
m_dwTick = GetTickCount(); // to check the timeout
CStringW strText = GetEditText(); CStringW strText = GetEditText();
if (m_strText.CompareNoCase(strText) == 0) if (m_strText.CompareNoCase(strText) == 0)
{ {
@ -1578,8 +1631,8 @@ VOID CAutoComplete::UpdateCompletion(BOOL bAppendOK)
} }
// update inner list // update inner list
UINT cItems = UpdateInnerList(); UpdateInnerList(strText);
if (cItems == 0) // no items if (m_innerList.GetSize() <= 0) // no items
{ {
HideDropDown(); HideDropDown();
return; return;
@ -1591,7 +1644,8 @@ VOID CAutoComplete::UpdateCompletion(BOOL bAppendOK)
SelectItem(-1); // select none SelectItem(-1); // select none
m_bInSelectItem = FALSE; m_bInSelectItem = FALSE;
if (UpdateOuterList()) UpdateOuterList(strText);
if (m_outerList.GetSize() > 0)
RepositionDropDown(); RepositionDropDown();
else else
HideDropDown(); HideDropDown();

View file

@ -140,17 +140,17 @@ public:
HWND CreateDropDown(); HWND CreateDropDown();
virtual ~CAutoComplete(); virtual ~CAutoComplete();
BOOL CanAutoSuggest(); BOOL CanAutoSuggest() const;
BOOL CanAutoAppend(); BOOL CanAutoAppend() const;
BOOL UseTab(); BOOL UseTab() const;
BOOL IsComboBoxDropped(); BOOL IsComboBoxDropped() const;
BOOL FilterPrefixes(); BOOL FilterPrefixes() const;
INT GetItemCount(); INT GetItemCount() const;
CStringW GetItemText(INT iItem); CStringW GetItemText(INT iItem) const;
CStringW GetEditText(); CStringW GetEditText() const;
VOID SetEditText(LPCWSTR pszText); VOID SetEditText(LPCWSTR pszText);
CStringW GetStemText(); CStringW GetStemText(const CStringW& strText) const;
VOID SetEditSel(INT ich0, INT ich1); VOID SetEditSel(INT ich0, INT ich1);
VOID ShowDropDown(); VOID ShowDropDown();
@ -194,6 +194,8 @@ protected:
HWND m_hwndEdit; // the textbox HWND m_hwndEdit; // the textbox
WNDPROC m_fnOldEditProc; // old textbox procedure WNDPROC m_fnOldEditProc; // old textbox procedure
EDITWORDBREAKPROCW m_fnOldWordBreakProc; EDITWORDBREAKPROCW m_fnOldWordBreakProc;
BOOL m_bPartialList; // is the list partial?
DWORD m_dwTick; // to check timeout
// The following variables are non-POD: // The following variables are non-POD:
CStringW m_strText; // internal text (used in selecting item and reverting text) CStringW m_strText; // internal text (used in selecting item and reverting text)
CStringW m_strStemText; // dirname + '\\' CStringW m_strStemText; // dirname + '\\'
@ -207,14 +209,16 @@ protected:
CSimpleArray<CStringW> m_outerList; // owner data for virtual listview CSimpleArray<CStringW> m_outerList; // owner data for virtual listview
// protected methods // protected methods
VOID UpdateDropDownState(); VOID UpdateDropDownState();
VOID CalcRects(BOOL bDowner, RECT& rcListView, RECT& rcScrollBar, RECT& rcSizeBox); VOID CalcRects(BOOL bDowner, RECT& rcListView, RECT& rcScrollBar, RECT& rcSizeBox) const;
VOID LoadQuickComplete(LPCWSTR pwszRegKeyPath, LPCWSTR pwszQuickComplete); VOID LoadQuickComplete(LPCWSTR pwszRegKeyPath, LPCWSTR pwszQuickComplete);
CStringW GetQuickEdit(LPCWSTR pszText); CStringW GetQuickEdit(LPCWSTR pszText) const;
VOID RepositionDropDown(); VOID RepositionDropDown();
INT ReLoadInnerList(); VOID ReLoadInnerList(const CStringW& strText);
INT UpdateInnerList(); VOID UpdateInnerList(const CStringW& strText);
INT UpdateOuterList(); VOID UpdateOuterList(const CStringW& strText);
VOID UpdateCompletion(BOOL bAppendOK); VOID UpdateCompletion(BOOL bAppendOK);
VOID ScrapeOffList(const CStringW& strText, CSimpleArray<CStringW>& array);
BOOL DoesMatch(const CStringW& strTarget, const CStringW& strText) const;
// message map // message map
BEGIN_MSG_MAP(CAutoComplete) BEGIN_MSG_MAP(CAutoComplete)
MESSAGE_HANDLER(WM_CREATE, OnCreate) MESSAGE_HANDLER(WM_CREATE, OnCreate)