mirror of
https://github.com/reactos/reactos.git
synced 2025-08-05 19:03:00 +00:00
[BROWSEUI] Multithreaded auto-completion (#3794)
- Make auto-completion multi-threaded. CORE-9281
This commit is contained in:
parent
92393a7517
commit
e2d8837986
2 changed files with 279 additions and 204 deletions
|
@ -21,6 +21,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "precomp.h"
|
#include "precomp.h"
|
||||||
|
#include <process.h> // _beginthreadex
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TODO:
|
TODO:
|
||||||
|
@ -32,7 +33,6 @@
|
||||||
#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 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
|
||||||
|
@ -53,7 +53,7 @@ static const PREFIX_INFO s_prefixes[] =
|
||||||
{ L"www.", 4 },
|
{ L"www.", 4 },
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline BOOL DropPrefix(const CStringW& str, CStringW& strBody)
|
static BOOL DropPrefix(const CStringW& str, CStringW& strBody)
|
||||||
{
|
{
|
||||||
for (size_t iPrefix = 0; iPrefix < _countof(s_prefixes); ++iPrefix)
|
for (size_t iPrefix = 0; iPrefix < _countof(s_prefixes); ++iPrefix)
|
||||||
{
|
{
|
||||||
|
@ -69,6 +69,21 @@ static inline BOOL DropPrefix(const CStringW& str, CStringW& strBody)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static BOOL DoesMatch(const CStringW& strTarget, const CStringW& strText)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
// mouse hook procedure to watch the mouse click
|
// mouse hook procedure to watch the mouse click
|
||||||
// https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms644988(v=vs.85)
|
// https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms644988(v=vs.85)
|
||||||
static LRESULT CALLBACK MouseProc(INT nCode, WPARAM wParam, LPARAM lParam)
|
static LRESULT CALLBACK MouseProc(INT nCode, WPARAM wParam, LPARAM lParam)
|
||||||
|
@ -291,7 +306,7 @@ LRESULT CAutoComplete::EditWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM l
|
||||||
return OnEditChar(wParam, lParam);
|
return OnEditChar(wParam, lParam);
|
||||||
case WM_CUT: case WM_PASTE: case WM_CLEAR:
|
case WM_CUT: case WM_PASTE: case WM_CLEAR:
|
||||||
ret = ::DefSubclassProc(hwnd, uMsg, wParam, lParam); // do default
|
ret = ::DefSubclassProc(hwnd, uMsg, wParam, lParam); // do default
|
||||||
UpdateCompletion(TRUE);
|
StartCompletion(TRUE);
|
||||||
return ret;
|
return ret;
|
||||||
case WM_GETDLGCODE:
|
case WM_GETDLGCODE:
|
||||||
ret = ::DefSubclassProc(hwnd, uMsg, wParam, lParam); // do default
|
ret = ::DefSubclassProc(hwnd, uMsg, wParam, lParam); // do default
|
||||||
|
@ -640,7 +655,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)
|
, m_hThread(NULL), m_pThread(NULL)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -658,6 +673,11 @@ HWND CAutoComplete::CreateDropDown()
|
||||||
CAutoComplete::~CAutoComplete()
|
CAutoComplete::~CAutoComplete()
|
||||||
{
|
{
|
||||||
TRACE("CAutoComplete::~CAutoComplete(%p)\n", this);
|
TRACE("CAutoComplete::~CAutoComplete(%p)\n", this);
|
||||||
|
if (m_hThread)
|
||||||
|
{
|
||||||
|
CloseHandle(m_hThread);
|
||||||
|
m_hThread = NULL;
|
||||||
|
}
|
||||||
if (m_hFont)
|
if (m_hFont)
|
||||||
{
|
{
|
||||||
::DeleteObject(m_hFont);
|
::DeleteObject(m_hFont);
|
||||||
|
@ -763,7 +783,7 @@ VOID CAutoComplete::SelectItem(INT iItem)
|
||||||
m_hwndList.EnsureVisible(iItem, FALSE);
|
m_hwndList.EnsureVisible(iItem, FALSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID CAutoComplete::DoAutoAppend()
|
VOID CAutoComplete::DoAutoAppend(AC_THREAD *pThread)
|
||||||
{
|
{
|
||||||
if (!CanAutoAppend()) // can we auto-append?
|
if (!CanAutoAppend()) // can we auto-append?
|
||||||
return; // don't append
|
return; // don't append
|
||||||
|
@ -772,7 +792,7 @@ VOID CAutoComplete::DoAutoAppend()
|
||||||
if (strText.IsEmpty())
|
if (strText.IsEmpty())
|
||||||
return; // don't append
|
return; // don't append
|
||||||
|
|
||||||
INT cItems = m_innerList.GetSize(); // get the number of items
|
INT cItems = m_outerList.GetSize(); // get the number of items
|
||||||
if (cItems == 0)
|
if (cItems == 0)
|
||||||
return; // don't append
|
return; // don't append
|
||||||
|
|
||||||
|
@ -781,7 +801,7 @@ VOID CAutoComplete::DoAutoAppend()
|
||||||
BOOL bFound = FALSE;
|
BOOL bFound = FALSE;
|
||||||
for (INT iItem = 0; iItem < cItems; ++iItem)
|
for (INT iItem = 0; iItem < cItems; ++iItem)
|
||||||
{
|
{
|
||||||
const CStringW& strItem = m_innerList[iItem]; // get the text of the item
|
const CStringW& strItem = m_outerList[iItem]; // get the text of the item
|
||||||
|
|
||||||
CStringW strBody;
|
CStringW strBody;
|
||||||
if (DropPrefix(strItem, strBody) &&
|
if (DropPrefix(strItem, strBody) &&
|
||||||
|
@ -950,7 +970,7 @@ BOOL CAutoComplete::OnEditKeyDown(WPARAM wParam, LPARAM lParam)
|
||||||
if (!CanAutoSuggest())
|
if (!CanAutoSuggest())
|
||||||
return FALSE; // do default
|
return FALSE; // do default
|
||||||
::DefSubclassProc(m_hwndEdit, WM_KEYDOWN, VK_DELETE, 0); // do default
|
::DefSubclassProc(m_hwndEdit, WM_KEYDOWN, VK_DELETE, 0); // do default
|
||||||
UpdateCompletion(FALSE);
|
StartCompletion(FALSE);
|
||||||
return TRUE; // eat
|
return TRUE; // eat
|
||||||
}
|
}
|
||||||
case VK_BACK:
|
case VK_BACK:
|
||||||
|
@ -973,7 +993,7 @@ LRESULT CAutoComplete::OnEditChar(WPARAM wParam, LPARAM lParam)
|
||||||
return 0; // eat
|
return 0; // eat
|
||||||
LRESULT ret = ::DefSubclassProc(m_hwndEdit, WM_CHAR, wParam, lParam); // do default
|
LRESULT ret = ::DefSubclassProc(m_hwndEdit, WM_CHAR, wParam, lParam); // do default
|
||||||
if (CanAutoSuggest() || CanAutoAppend())
|
if (CanAutoSuggest() || CanAutoAppend())
|
||||||
UpdateCompletion(wParam != VK_BACK);
|
StartCompletion(wParam != VK_BACK);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1212,7 +1232,8 @@ STDMETHODIMP CAutoComplete::ResetEnumerator()
|
||||||
FIXME("(%p): stub\n", this);
|
FIXME("(%p): stub\n", this);
|
||||||
|
|
||||||
Reset();
|
Reset();
|
||||||
m_innerList.RemoveAll();
|
m_hwndList.SendMessageW(LVM_SETITEMCOUNT, 0, 0);
|
||||||
|
m_outerList.RemoveAll();
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1444,217 +1465,59 @@ VOID CAutoComplete::RepositionDropDown()
|
||||||
ShowWindow(SW_SHOWNOACTIVATE);
|
ShowWindow(SW_SHOWNOACTIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline BOOL
|
VOID
|
||||||
CAutoComplete::DoesMatch(const CStringW& strTarget, const CStringW& strText) const
|
CAutoComplete::ExtractInnerList(CSimpleArray<CStringW>& outerList,
|
||||||
|
const CSimpleArray<CStringW>& innerList,
|
||||||
|
const CString& strText)
|
||||||
{
|
{
|
||||||
CStringW strBody;
|
for (INT iItem = 0; iItem < innerList.GetSize(); ++iItem)
|
||||||
if (DropPrefix(strTarget, strBody))
|
|
||||||
{
|
{
|
||||||
if (::StrCmpNIW(strBody, strText, strText.GetLength()) == 0)
|
if (m_pThread || !m_hThread)
|
||||||
return TRUE;
|
break;
|
||||||
}
|
|
||||||
else if (::StrCmpNIW(strTarget, strText, strText.GetLength()) == 0)
|
|
||||||
{
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
VOID CAutoComplete::ScrapeOffList(const CStringW& strText, CSimpleArray<CStringW>& array)
|
const CStringW& strTarget = innerList[iItem];
|
||||||
{
|
if (DoesMatch(strTarget, strText))
|
||||||
for (INT iItem = array.GetSize() - 1; iItem >= 0; --iItem)
|
{
|
||||||
{
|
outerList.Add(strTarget);
|
||||||
if (!DoesMatch(array[iItem], strText))
|
|
||||||
array.RemoveAt(iItem);
|
if (outerList.GetSize() >= MAX_ITEM_COUNT)
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID CAutoComplete::ReLoadInnerList(const CStringW& strText)
|
VOID CAutoComplete::ReLoadInnerList(PAC_THREAD pThread)
|
||||||
{
|
{
|
||||||
m_innerList.RemoveAll(); // clear contents
|
pThread->m_innerList.RemoveAll(); // clear contents
|
||||||
m_bPartialList = FALSE;
|
|
||||||
|
|
||||||
if (!m_pEnum || strText.IsEmpty())
|
if (!m_pEnum || pThread->m_strText.IsEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// reload the items
|
// reload the items
|
||||||
LPWSTR pszItem;
|
LPWSTR pszItem;
|
||||||
ULONG cGot;
|
ULONG cGot;
|
||||||
CStringW strTarget;
|
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
for (;;)
|
CSimpleArray<CStringW>& innerList = pThread->m_innerList;
|
||||||
|
while (!m_pThread && m_hThread)
|
||||||
{
|
{
|
||||||
// get next item
|
// get next item
|
||||||
hr = m_pEnum->Next(1, &pszItem, &cGot);
|
hr = m_pEnum->Next(1, &pszItem, &cGot);
|
||||||
//TRACE("m_pEnum->Next(%p): 0x%08lx\n", reinterpret_cast<IUnknown *>(m_pEnum), hr);
|
|
||||||
if (hr != S_OK)
|
if (hr != S_OK)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
strTarget = pszItem;
|
innerList.Add(pszItem); // append item to innerList
|
||||||
::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
|
|
||||||
if (::GetTickCount() - m_dwTick >= COMPLETION_TIMEOUT)
|
|
||||||
break; // too late
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// update inner list and m_strText and m_strStemText
|
VOID CAutoComplete::StartCompletion(BOOL bAppendOK)
|
||||||
VOID CAutoComplete::UpdateInnerList(const CStringW& strText)
|
|
||||||
{
|
{
|
||||||
BOOL bReset = FALSE, bExpand = FALSE; // flags
|
TRACE("CAutoComplete::StartCompletion(%p, %d)\n", this, bAppendOK);
|
||||||
|
|
||||||
// if previous text was empty
|
if (!m_pEnum || (!CanAutoSuggest() && !CanAutoAppend()))
|
||||||
if (m_strText.IsEmpty())
|
|
||||||
{
|
|
||||||
bReset = TRUE;
|
|
||||||
}
|
|
||||||
// save text
|
|
||||||
m_strText = strText;
|
|
||||||
|
|
||||||
// do expand the items if the stem is changed
|
|
||||||
CStringW strStemText = GetStemText(strText);
|
|
||||||
if (m_strStemText.CompareNoCase(strStemText) != 0)
|
|
||||||
{
|
|
||||||
m_strStemText = strStemText;
|
|
||||||
bReset = TRUE;
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
HRESULT hr = m_pEnum->Reset(); // IEnumString::Reset
|
|
||||||
TRACE("m_pEnum->Reset(%p): 0x%08lx\n",
|
|
||||||
static_cast<IUnknown *>(m_pEnum), hr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// update ac list if necessary
|
|
||||||
if (bExpand && m_pACList)
|
|
||||||
{
|
|
||||||
HRESULT hr = m_pACList->Expand(strStemText); // IACList::Expand
|
|
||||||
TRACE("m_pACList->Expand(%p, %S): 0x%08lx\n",
|
|
||||||
static_cast<IUnknown *>(m_pACList),
|
|
||||||
static_cast<LPCWSTR>(strStemText), hr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bExpand || m_innerList.GetSize() == 0)
|
|
||||||
{
|
|
||||||
// reload the inner list
|
|
||||||
ReLoadInnerList(strText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VOID CAutoComplete::UpdateOuterList(const CStringW& strText)
|
|
||||||
{
|
|
||||||
if (strText.IsEmpty())
|
|
||||||
{
|
|
||||||
m_outerList.RemoveAll();
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (m_bPartialList)
|
::SendMessageW(m_hWnd, AUTOCOMP_START, bAppendOK, 0);
|
||||||
{
|
|
||||||
// it is already filtered
|
|
||||||
m_outerList = m_innerList;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// do filtering
|
|
||||||
m_outerList.RemoveAll();
|
|
||||||
for (INT iItem = 0; iItem < m_innerList.GetSize(); ++iItem)
|
|
||||||
{
|
|
||||||
const CStringW& strTarget = m_innerList[iItem];
|
|
||||||
|
|
||||||
if (DoesMatch(strTarget, strText))
|
|
||||||
m_outerList.Add(strTarget);
|
|
||||||
|
|
||||||
// check the timeout
|
|
||||||
if (::GetTickCount() - m_dwTick >= COMPLETION_TIMEOUT)
|
|
||||||
break; // too late
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (::GetTickCount() - m_dwTick < COMPLETION_TIMEOUT)
|
|
||||||
{
|
|
||||||
// sort and unique
|
|
||||||
DoSort(m_outerList);
|
|
||||||
DoUniqueAndTrim(m_outerList);
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the item count of the virtual listview
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
// no change
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update inner list
|
|
||||||
UpdateInnerList(strText);
|
|
||||||
if (m_innerList.GetSize() <= 0) // no items
|
|
||||||
{
|
|
||||||
HideDropDown();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CanAutoSuggest()) // can we auto-suggest?
|
|
||||||
{
|
|
||||||
m_bInSelectItem = TRUE; // don't respond
|
|
||||||
SelectItem(-1); // select none
|
|
||||||
m_bInSelectItem = FALSE;
|
|
||||||
|
|
||||||
UpdateOuterList(strText);
|
|
||||||
if (m_outerList.GetSize() > 0)
|
|
||||||
RepositionDropDown();
|
|
||||||
else
|
|
||||||
HideDropDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CanAutoAppend() && bAppendOK) // can we auto-append?
|
|
||||||
{
|
|
||||||
DoAutoAppend();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -2116,3 +1979,189 @@ LRESULT CAutoComplete::OnVScroll(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline PAC_THREAD
|
||||||
|
InterlockedExchangeThreadData(volatile PAC_THREAD *Target, PAC_THREAD Value)
|
||||||
|
{
|
||||||
|
return reinterpret_cast<PAC_THREAD>(
|
||||||
|
::InterlockedExchangePointer(reinterpret_cast<volatile PVOID *>(Target), Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned __stdcall AutoCompThreadProc(void *arg)
|
||||||
|
{
|
||||||
|
CAutoComplete* pThis = reinterpret_cast<CAutoComplete*>(arg);
|
||||||
|
pThis->AutoCompThreadProc();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID CAutoComplete::AutoCompThreadProc()
|
||||||
|
{
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
PAC_THREAD pThread = InterlockedExchangeThreadData(&m_pThread, NULL);
|
||||||
|
if (!pThread)
|
||||||
|
break;
|
||||||
|
DoThreadWork(pThread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID CAutoComplete::DoThreadWork(PAC_THREAD pThread)
|
||||||
|
{
|
||||||
|
if (pThread->m_bExpand || m_innerList.GetSize() == 0)
|
||||||
|
{
|
||||||
|
ReLoadInnerList(pThread);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pThread->m_innerList = m_innerList;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_pThread || !m_hThread)
|
||||||
|
{
|
||||||
|
delete pThread;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtractInnerList(pThread->m_outerList, pThread->m_innerList, pThread->m_strText);
|
||||||
|
|
||||||
|
if (m_pThread || !m_hThread)
|
||||||
|
{
|
||||||
|
delete pThread;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DoSort(pThread->m_outerList);
|
||||||
|
DoUniqueAndTrim(pThread->m_outerList);
|
||||||
|
|
||||||
|
if (m_pThread || !m_hThread ||
|
||||||
|
!::PostMessageW(m_hWnd, AUTOCOMP_FINISH, 0, (LPARAM)pThread))
|
||||||
|
{
|
||||||
|
delete pThread;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AUTOCOMP_START
|
||||||
|
LRESULT CAutoComplete::OnAutoCompStart(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
|
||||||
|
{
|
||||||
|
BOOL bAppendOK = (BOOL)wParam;
|
||||||
|
|
||||||
|
CStringW strText = GetEditText();
|
||||||
|
if (m_strText.CompareNoCase(strText) == 0)
|
||||||
|
{
|
||||||
|
// no change
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PAC_THREAD pThread = new AC_THREAD { this, bAppendOK, strText };
|
||||||
|
|
||||||
|
// if previous text was empty
|
||||||
|
if (m_strText.IsEmpty())
|
||||||
|
{
|
||||||
|
pThread->m_bReset = TRUE;
|
||||||
|
}
|
||||||
|
m_strText = strText;
|
||||||
|
|
||||||
|
// do expand the items if the stem is changed
|
||||||
|
CStringW strStemText = GetStemText(pThread->m_strText);
|
||||||
|
if (m_strStemText.CompareNoCase(strStemText) != 0)
|
||||||
|
{
|
||||||
|
pThread->m_bReset = TRUE;
|
||||||
|
pThread->m_bExpand = !strStemText.IsEmpty();
|
||||||
|
m_strStemText = strStemText;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset if necessary
|
||||||
|
if (pThread->m_bReset && m_pEnum)
|
||||||
|
{
|
||||||
|
HRESULT hr = m_pEnum->Reset(); // IEnumString::Reset
|
||||||
|
TRACE("m_pEnum->Reset(%p): 0x%08lx\n",
|
||||||
|
static_cast<IUnknown *>(m_pEnum), hr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update ac list if necessary
|
||||||
|
if (pThread->m_bExpand && m_pACList)
|
||||||
|
{
|
||||||
|
HRESULT hr = m_pACList->Expand(strStemText); // IACList::Expand
|
||||||
|
TRACE("m_pACList->Expand(%p, %S): 0x%08lx\n",
|
||||||
|
static_cast<IUnknown *>(m_pACList),
|
||||||
|
static_cast<LPCWSTR>(strStemText), hr);
|
||||||
|
}
|
||||||
|
|
||||||
|
PAC_THREAD pOld = InterlockedExchangeThreadData(&m_pThread, pThread);
|
||||||
|
if (pOld)
|
||||||
|
delete pOld;
|
||||||
|
|
||||||
|
BOOL bDoStart = FALSE;
|
||||||
|
DWORD dwWait = WaitForSingleObject(m_hThread, 0);
|
||||||
|
if (dwWait != WAIT_TIMEOUT)
|
||||||
|
{
|
||||||
|
CloseHandle(m_hThread);
|
||||||
|
bDoStart = TRUE;
|
||||||
|
}
|
||||||
|
else if (!m_hThread)
|
||||||
|
{
|
||||||
|
bDoStart = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bDoStart)
|
||||||
|
m_hThread = (HANDLE)_beginthreadex(NULL, 0, ::AutoCompThreadProc, this, 0, NULL);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// AUTOCOMP_FINISH
|
||||||
|
LRESULT CAutoComplete::OnAutoCompFinish(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
|
||||||
|
{
|
||||||
|
PAC_THREAD pThread = reinterpret_cast<PAC_THREAD>(lParam);
|
||||||
|
if (m_pThread == NULL)
|
||||||
|
{
|
||||||
|
FinishCompletion(pThread);
|
||||||
|
}
|
||||||
|
CloseHandle(m_hThread);
|
||||||
|
m_hThread = NULL;
|
||||||
|
delete pThread;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID CAutoComplete::FinishCompletion(PAC_THREAD pThread)
|
||||||
|
{
|
||||||
|
if (m_pThread || !m_hThread)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!CanAutoSuggest() && !CanAutoAppend())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_pThread || !m_hThread)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// set inner list
|
||||||
|
m_innerList = pThread->m_innerList;
|
||||||
|
|
||||||
|
if (m_pThread || !m_hThread)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// set the items of the virtual listview
|
||||||
|
m_outerList = pThread->m_outerList; // FIXME: We need more speed!
|
||||||
|
m_hwndList.SendMessageW(LVM_SETITEMCOUNT, m_outerList.GetSize(), 0);
|
||||||
|
|
||||||
|
// save text
|
||||||
|
m_strText = pThread->m_strText;
|
||||||
|
m_strStemText = GetStemText(m_strText);
|
||||||
|
|
||||||
|
if (CanAutoSuggest()) // can we auto-suggest?
|
||||||
|
{
|
||||||
|
m_bInSelectItem = TRUE; // don't respond
|
||||||
|
SelectItem(-1); // select none
|
||||||
|
m_bInSelectItem = FALSE;
|
||||||
|
|
||||||
|
if (m_outerList.GetSize() > 0)
|
||||||
|
RepositionDropDown();
|
||||||
|
else
|
||||||
|
HideDropDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CanAutoAppend() && pThread->m_bAppendOK) // can we auto-append?
|
||||||
|
{
|
||||||
|
DoAutoAppend(pThread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -116,11 +116,30 @@ protected:
|
||||||
LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
|
LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// AC_THREAD --- Thread data for CAutoComplete
|
||||||
|
|
||||||
|
typedef struct AC_THREAD
|
||||||
|
{
|
||||||
|
CAutoComplete *m_pThis;
|
||||||
|
BOOL m_bAppendOK;
|
||||||
|
CStringW m_strText;
|
||||||
|
CSimpleArray<CStringW> m_innerList; // internal list
|
||||||
|
CSimpleArray<CStringW> m_outerList; // outer list
|
||||||
|
BOOL m_bReset;
|
||||||
|
BOOL m_bExpand;
|
||||||
|
|
||||||
|
VOID ReLoadInnerList(const CStringW& strText);
|
||||||
|
} AC_THREAD, *PAC_THREAD;
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
// CAutoComplete --- auto-completion drop-down window
|
// CAutoComplete --- auto-completion drop-down window
|
||||||
|
|
||||||
#define WC_DROPDOWNW L"Auto-Suggest Dropdown" // the window class name
|
#define WC_DROPDOWNW L"Auto-Suggest Dropdown" // the window class name
|
||||||
|
|
||||||
|
#define AUTOCOMP_START (WM_USER + 1)
|
||||||
|
#define AUTOCOMP_FINISH (WM_USER + 2)
|
||||||
|
|
||||||
class CAutoComplete
|
class CAutoComplete
|
||||||
: public CComCoClass<CAutoComplete, &CLSID_AutoComplete>
|
: public CComCoClass<CAutoComplete, &CLSID_AutoComplete>
|
||||||
, public CComObjectRootEx<CComMultiThreadModelNoCS>
|
, public CComObjectRootEx<CComMultiThreadModelNoCS>
|
||||||
|
@ -156,10 +175,15 @@ public:
|
||||||
VOID ShowDropDown();
|
VOID ShowDropDown();
|
||||||
VOID HideDropDown();
|
VOID HideDropDown();
|
||||||
VOID SelectItem(INT iItem);
|
VOID SelectItem(INT iItem);
|
||||||
VOID DoAutoAppend();
|
VOID DoAutoAppend(PAC_THREAD pThread);
|
||||||
|
VOID DoThreadWork(PAC_THREAD pThread);
|
||||||
VOID DoBackWord();
|
VOID DoBackWord();
|
||||||
VOID UpdateScrollBar();
|
VOID UpdateScrollBar();
|
||||||
|
|
||||||
|
VOID StartCompletion(BOOL bAppendOK);
|
||||||
|
VOID AutoCompThreadProc();
|
||||||
|
VOID FinishCompletion(PAC_THREAD pThread);
|
||||||
|
|
||||||
LRESULT EditWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
LRESULT EditWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
||||||
LRESULT OnEditChar(WPARAM wParam, LPARAM lParam);
|
LRESULT OnEditChar(WPARAM wParam, LPARAM lParam);
|
||||||
BOOL OnEditKeyDown(WPARAM wParam, LPARAM lParam);
|
BOOL OnEditKeyDown(WPARAM wParam, LPARAM lParam);
|
||||||
|
@ -194,8 +218,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?
|
HANDLE m_hThread;
|
||||||
DWORD m_dwTick; // to check timeout
|
AC_THREAD *m_pThread;
|
||||||
// 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 + '\\'
|
||||||
|
@ -205,22 +229,22 @@ protected:
|
||||||
CACSizeBox m_hwndSizeBox; // the size grip
|
CACSizeBox m_hwndSizeBox; // the size grip
|
||||||
CComPtr<IEnumString> m_pEnum; // used for enumeration
|
CComPtr<IEnumString> m_pEnum; // used for enumeration
|
||||||
CComPtr<IACList> m_pACList; // for IACList::Expand to update the list
|
CComPtr<IACList> m_pACList; // for IACList::Expand to update the list
|
||||||
CSimpleArray<CStringW> m_innerList; // internal list
|
CSimpleArray<CStringW> m_innerList; // inner list
|
||||||
CSimpleArray<CStringW> m_outerList; // owner data for virtual listview
|
CSimpleArray<CStringW> m_outerList; // outer list
|
||||||
// protected methods
|
// protected methods
|
||||||
VOID UpdateDropDownState();
|
VOID UpdateDropDownState();
|
||||||
VOID CalcRects(BOOL bDowner, RECT& rcListView, RECT& rcScrollBar, RECT& rcSizeBox) const;
|
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) const;
|
CStringW GetQuickEdit(LPCWSTR pszText) const;
|
||||||
VOID RepositionDropDown();
|
VOID RepositionDropDown();
|
||||||
VOID ReLoadInnerList(const CStringW& strText);
|
VOID ReLoadInnerList(PAC_THREAD pThread);
|
||||||
VOID UpdateInnerList(const CStringW& strText);
|
VOID ExtractInnerList(CSimpleArray<CStringW>& outerList,
|
||||||
VOID UpdateOuterList(const CStringW& strText);
|
const CSimpleArray<CStringW>& innerList,
|
||||||
VOID UpdateCompletion(BOOL bAppendOK);
|
const CString& strText);
|
||||||
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(AUTOCOMP_START, OnAutoCompStart)
|
||||||
|
MESSAGE_HANDLER(AUTOCOMP_FINISH, OnAutoCompFinish)
|
||||||
MESSAGE_HANDLER(WM_CREATE, OnCreate)
|
MESSAGE_HANDLER(WM_CREATE, OnCreate)
|
||||||
MESSAGE_HANDLER(WM_NCDESTROY, OnNCDestroy)
|
MESSAGE_HANDLER(WM_NCDESTROY, OnNCDestroy)
|
||||||
MESSAGE_HANDLER(WM_DRAWITEM, OnDrawItem)
|
MESSAGE_HANDLER(WM_DRAWITEM, OnDrawItem)
|
||||||
|
@ -253,6 +277,8 @@ protected:
|
||||||
LRESULT OnShowWindow(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
|
LRESULT OnShowWindow(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
|
||||||
LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
|
LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
|
||||||
LRESULT OnVScroll(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
|
LRESULT OnVScroll(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
|
||||||
|
LRESULT OnAutoCompStart(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
|
||||||
|
LRESULT OnAutoCompFinish(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
|
||||||
|
|
||||||
DECLARE_REGISTRY_RESOURCEID(IDR_AUTOCOMPLETE)
|
DECLARE_REGISTRY_RESOURCEID(IDR_AUTOCOMPLETE)
|
||||||
DECLARE_NOT_AGGREGATABLE(CAutoComplete)
|
DECLARE_NOT_AGGREGATABLE(CAutoComplete)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue