https://git.reactos.org/?p=reactos.git;a=commitdiff;h=773ad7aebccad04744b206...
commit 773ad7aebccad04744b2064cfed7fe917d0bd367 Author: Katayama Hirofumi MZ katayama.hirofumi.mz@gmail.com AuthorDate: Thu Apr 8 15:02:05 2021 +0900 Commit: GitHub noreply@github.com CommitDate: Thu Apr 8 15:02:05 2021 +0900
[BROWSEUI] auto-completion: Support large number items (#3592)
If the items are too many, enable filtering in item enumeration. CORE-9281 --- dll/win32/browseui/CAutoComplete.cpp | 176 +++++++++++++++++++++++------------ dll/win32/browseui/CAutoComplete.h | 32 ++++--- 2 files changed, 133 insertions(+), 75 deletions(-)
diff --git a/dll/win32/browseui/CAutoComplete.cpp b/dll/win32/browseui/CAutoComplete.cpp index 6f2033d9f9f..e7a078c10a3 100644 --- a/dll/win32/browseui/CAutoComplete.cpp +++ b/dll/win32/browseui/CAutoComplete.cpp @@ -32,7 +32,7 @@ #define CX_LIST 30160 // width of m_hwndList (very wide but alright) #define CY_LIST 288 // maximum height of drop-down window #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 WATCH_TIMER_ID 0xFEEDBEEF // timer ID to watch m_rcEdit #define WATCH_INTERVAL 300 // in milliseconds @@ -640,6 +640,7 @@ CAutoComplete::CAutoComplete() , m_bDowner(TRUE), m_dwOptions(ACO_AUTOAPPEND | ACO_AUTOSUGGEST) , m_bEnabled(TRUE), m_hwndCombo(NULL), m_hFont(NULL), m_bResized(FALSE) , m_hwndEdit(NULL), m_fnOldEditProc(NULL), m_fnOldWordBreakProc(NULL) + , m_bPartialList(FALSE), m_dwTick(0) { }
@@ -667,46 +668,46 @@ CAutoComplete::~CAutoComplete() m_pACList.Release(); }
-BOOL CAutoComplete::CanAutoSuggest() +inline BOOL CAutoComplete::CanAutoSuggest() const { return !!(m_dwOptions & ACO_AUTOSUGGEST) && m_bEnabled; }
-BOOL CAutoComplete::CanAutoAppend() +inline BOOL CAutoComplete::CanAutoAppend() const { return !!(m_dwOptions & ACO_AUTOAPPEND) && m_bEnabled; }
-BOOL CAutoComplete::UseTab() +inline BOOL CAutoComplete::UseTab() const { return !!(m_dwOptions & ACO_USETAB) && m_bEnabled; }
-BOOL CAutoComplete::IsComboBoxDropped() +inline BOOL CAutoComplete::IsComboBoxDropped() const { if (!::IsWindow(m_hwndCombo)) return FALSE; return (BOOL)::SendMessageW(m_hwndCombo, CB_GETDROPPEDSTATE, 0, 0); }
-BOOL CAutoComplete::FilterPrefixes() +inline BOOL CAutoComplete::FilterPrefixes() const { return !!(m_dwOptions & ACO_FILTERPREFIXES) && m_bEnabled; }
-INT CAutoComplete::GetItemCount() +inline INT CAutoComplete::GetItemCount() const { return m_outerList.GetSize(); }
-CStringW CAutoComplete::GetItemText(INT iItem) +CStringW CAutoComplete::GetItemText(INT iItem) const { if (iItem < 0 || m_outerList.GetSize() <= iItem) return L""; return m_outerList[iItem]; }
-CStringW CAutoComplete::GetEditText() +inline CStringW CAutoComplete::GetEditText() const { WCHAR szText[L_MAX_URL_LENGTH]; if (::GetWindowTextW(m_hwndEdit, szText, _countof(szText))) @@ -721,9 +722,8 @@ VOID CAutoComplete::SetEditText(LPCWSTR pszText) m_bInSetText = FALSE; }
-CStringW CAutoComplete::GetStemText() +inline CStringW CAutoComplete::GetStemText(const CStringW& strText) const { - CStringW strText = GetEditText(); INT ich = strText.ReverseFind(L'\'); if (ich == -1) return L""; // no stem @@ -1284,7 +1284,7 @@ VOID CAutoComplete::UpdateDropDownState() }
// 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 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()) return pszText; @@ -1444,20 +1444,45 @@ VOID CAutoComplete::RepositionDropDown() ShowWindow(SW_SHOWNOACTIVATE); }
-INT CAutoComplete::ReLoadInnerList() +inline BOOL +CAutoComplete::DoesMatch(const CStringW& strTarget, const CStringW& strText) const { - m_innerList.RemoveAll(); // clear contents + 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; +}
- if (!m_pEnum) - return 0; +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_bPartialList = FALSE;
- DWORD dwTick = ::GetTickCount(); // used for timeout + if (!m_pEnum || strText.IsEmpty()) + return;
// reload the items LPWSTR pszItem; ULONG cGot; + CStringW strTarget; HRESULT hr; - for (ULONG cTotal = 0; cTotal < MAX_ITEM_COUNT; ++cTotal) + for (;;) { // get next item hr = m_pEnum->Next(1, &pszItem, &cGot); @@ -1465,23 +1490,45 @@ INT CAutoComplete::ReLoadInnerList() if (hr != S_OK) break;
- m_innerList.Add(pszItem); // append item to m_innerList + strTarget = pszItem; ::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 - if (::GetTickCount() - dwTick >= COMPLETION_TIMEOUT) + if (::GetTickCount() - m_dwTick >= COMPLETION_TIMEOUT) break; // too late } - - return m_innerList.GetSize(); // the number of items }
// 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
// if previous text was empty @@ -1493,7 +1540,7 @@ INT CAutoComplete::UpdateInnerList() m_strText = strText;
// do expand the items if the stem is changed - CStringW strStemText = GetStemText(); + CStringW strStemText = GetStemText(strText); if (m_strStemText.CompareNoCase(strStemText) != 0) { m_strStemText = strStemText; @@ -1501,6 +1548,10 @@ INT CAutoComplete::UpdateInnerList() bExpand = !m_strStemText.IsEmpty(); }
+ // if the previous enumeration is too large + if (m_bPartialList) + bReset = bExpand = TRUE; // retry enumeratation + // reset if necessary if (bReset && m_pEnum) { @@ -1521,55 +1572,57 @@ INT CAutoComplete::UpdateInnerList() if (bExpand || m_innerList.GetSize() == 0) { // 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()) + { + m_outerList.RemoveAll(); + return; + }
- // update the outer list from the inner list - m_outerList.RemoveAll(); - for (INT iItem = 0; iItem < m_innerList.GetSize(); ++iItem) + if (m_bPartialList) { - // is the beginning matched? - const CStringW& strTarget = m_innerList[iItem]; - CStringW strBody; - if (DropPrefix(strTarget, strBody)) + // 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); - continue; - } - } - if (::StrCmpNIW(strTarget, strText, strText.GetLength()) == 0) - { - m_outerList.Add(strTarget); + + // check the timeout + if (::GetTickCount() - m_dwTick >= COMPLETION_TIMEOUT) + break; // too late } }
- // sort the list - DoSort(m_outerList); - // unique - DoUniqueAndTrim(m_outerList); + if (::GetTickCount() - m_dwTick < COMPLETION_TIMEOUT) + { + // sort and unique + DoSort(m_outerList); + DoUniqueAndTrim(m_outerList); + }
// set the item count of the virtual listview - INT cItems = m_outerList.GetSize(); - if (strText.IsEmpty()) - cItems = 0; - m_hwndList.SendMessageW(LVM_SETITEMCOUNT, cItems, 0); - - return cItems; // the number of items + m_hwndList.SendMessageW(LVM_SETITEMCOUNT, m_outerList.GetSize(), 0); }
VOID CAutoComplete::UpdateCompletion(BOOL bAppendOK) { TRACE("CAutoComplete::UpdateCompletion(%p, %d)\n", this, bAppendOK);
+ m_dwTick = GetTickCount(); // to check the timeout + CStringW strText = GetEditText(); if (m_strText.CompareNoCase(strText) == 0) { @@ -1578,8 +1631,8 @@ VOID CAutoComplete::UpdateCompletion(BOOL bAppendOK) }
// update inner list - UINT cItems = UpdateInnerList(); - if (cItems == 0) // no items + UpdateInnerList(strText); + if (m_innerList.GetSize() <= 0) // no items { HideDropDown(); return; @@ -1591,7 +1644,8 @@ VOID CAutoComplete::UpdateCompletion(BOOL bAppendOK) SelectItem(-1); // select none m_bInSelectItem = FALSE;
- if (UpdateOuterList()) + UpdateOuterList(strText); + if (m_outerList.GetSize() > 0) RepositionDropDown(); else HideDropDown(); diff --git a/dll/win32/browseui/CAutoComplete.h b/dll/win32/browseui/CAutoComplete.h index 5da346ebb85..51ef81b135a 100644 --- a/dll/win32/browseui/CAutoComplete.h +++ b/dll/win32/browseui/CAutoComplete.h @@ -140,17 +140,17 @@ public: HWND CreateDropDown(); virtual ~CAutoComplete();
- BOOL CanAutoSuggest(); - BOOL CanAutoAppend(); - BOOL UseTab(); - BOOL IsComboBoxDropped(); - BOOL FilterPrefixes(); - INT GetItemCount(); - CStringW GetItemText(INT iItem); + BOOL CanAutoSuggest() const; + BOOL CanAutoAppend() const; + BOOL UseTab() const; + BOOL IsComboBoxDropped() const; + BOOL FilterPrefixes() const; + INT GetItemCount() const; + CStringW GetItemText(INT iItem) const;
- CStringW GetEditText(); + CStringW GetEditText() const; VOID SetEditText(LPCWSTR pszText); - CStringW GetStemText(); + CStringW GetStemText(const CStringW& strText) const; VOID SetEditSel(INT ich0, INT ich1);
VOID ShowDropDown(); @@ -194,6 +194,8 @@ protected: HWND m_hwndEdit; // the textbox WNDPROC m_fnOldEditProc; // old textbox procedure EDITWORDBREAKPROCW m_fnOldWordBreakProc; + BOOL m_bPartialList; // is the list partial? + DWORD m_dwTick; // to check timeout // The following variables are non-POD: CStringW m_strText; // internal text (used in selecting item and reverting text) CStringW m_strStemText; // dirname + '\' @@ -207,14 +209,16 @@ protected: CSimpleArray<CStringW> m_outerList; // owner data for virtual listview // protected methods 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); - CStringW GetQuickEdit(LPCWSTR pszText); + CStringW GetQuickEdit(LPCWSTR pszText) const; VOID RepositionDropDown(); - INT ReLoadInnerList(); - INT UpdateInnerList(); - INT UpdateOuterList(); + VOID ReLoadInnerList(const CStringW& strText); + VOID UpdateInnerList(const CStringW& strText); + VOID UpdateOuterList(const CStringW& strText); VOID UpdateCompletion(BOOL bAppendOK); + VOID ScrapeOffList(const CStringW& strText, CSimpleArray<CStringW>& array); + BOOL DoesMatch(const CStringW& strTarget, const CStringW& strText) const; // message map BEGIN_MSG_MAP(CAutoComplete) MESSAGE_HANDLER(WM_CREATE, OnCreate)