1025 lines
32 KiB
C++
1025 lines
32 KiB
C++
// Copyright 1998 Microsoft
|
|
|
|
#include "priv.h"
|
|
#include "autocomp.h"
|
|
|
|
#define AC_GIVEUP_COUNT 1000
|
|
#define AC_TIMEOUT (60 * 1000)
|
|
|
|
//
|
|
// Thread messages
|
|
//
|
|
enum
|
|
{
|
|
ACM_FIRST = WM_USER,
|
|
ACM_STARTSEARCH,
|
|
ACM_STOPSEARCH,
|
|
ACM_SETFOCUS,
|
|
ACM_KILLFOCUS,
|
|
ACM_QUIT,
|
|
ACM_LAST,
|
|
};
|
|
|
|
|
|
// Special prefixes that we optionally filter out
|
|
const struct{
|
|
int cch;
|
|
LPCWSTR psz;
|
|
}
|
|
g_rgSpecialPrefix[] =
|
|
{
|
|
{4, L"www."},
|
|
{11, L"http://www."}, // This must be before "http://"
|
|
{7, L"http://"},
|
|
{8, L"https://"},
|
|
};
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// CACString functions - Hold autocomplete strings
|
|
//--------------------------------------------------------------------------
|
|
ULONG CACString::AddRef()
|
|
{
|
|
return InterlockedIncrement(&m_cRef);
|
|
}
|
|
|
|
ULONG CACString::Release()
|
|
{
|
|
ASSERT(m_cRef > 0);
|
|
|
|
if (InterlockedDecrement(&m_cRef))
|
|
{
|
|
return m_cRef;
|
|
}
|
|
|
|
delete this;
|
|
return 0;
|
|
}
|
|
|
|
CACString* CreateACString(LPCWSTR pszStr, int iIgnore, ULONG ulSortIndex)
|
|
{
|
|
ASSERT(pszStr);
|
|
|
|
int cChars = lstrlen(pszStr);
|
|
|
|
// Allocate the CACString class with enough room for the new string
|
|
CACString* pStr = (CACString*)LocalAlloc(LPTR, cChars * sizeof(WCHAR) + sizeof(CACString));
|
|
if (pStr)
|
|
{
|
|
StrCpy(pStr->m_sz, pszStr);
|
|
|
|
pStr->m_ulSortIndex = ulSortIndex;
|
|
pStr->m_cRef = 1;
|
|
pStr->m_cChars = cChars;
|
|
pStr->m_iIgnore = iIgnore;
|
|
}
|
|
return pStr;
|
|
}
|
|
|
|
int CACString::CompareSortingIndex(CACString& r)
|
|
{
|
|
int iRet;
|
|
|
|
// If the sorting indices are equal, just do a string compare
|
|
if (m_ulSortIndex == r.m_ulSortIndex)
|
|
{
|
|
iRet = StrCmpI(r);
|
|
}
|
|
else
|
|
{
|
|
iRet = (m_ulSortIndex > r.m_ulSortIndex) ? 1 : -1;
|
|
}
|
|
|
|
return iRet;
|
|
}
|
|
|
|
HRESULT CACThread::QueryInterface(REFIID riid, void **ppvObj)
|
|
{
|
|
static const QITAB qit[] = { { 0 }, };
|
|
return QISearch(this, qit, riid, ppvObj);
|
|
}
|
|
|
|
ULONG CACThread::AddRef(void)
|
|
{
|
|
return InterlockedIncrement(&m_cRef);
|
|
}
|
|
|
|
ULONG CACThread::Release(void)
|
|
{
|
|
ASSERT(m_cRef > 0);
|
|
|
|
if (InterlockedDecrement(&m_cRef))
|
|
{
|
|
return m_cRef;
|
|
}
|
|
|
|
delete this;
|
|
return 0;
|
|
}
|
|
|
|
CACThread::CACThread(CAutoComplete& rAutoComp) : m_pAutoComp(&rAutoComp), m_cRef(1)
|
|
{
|
|
ASSERT(!m_fWorkItemQueued);
|
|
ASSERT(!m_idThread);
|
|
ASSERT(!m_hCreateEvent);
|
|
ASSERT(!m_fDisabled);
|
|
ASSERT(!m_pszSearch);
|
|
ASSERT(!m_hdpa_list);
|
|
ASSERT(!m_pes);
|
|
ASSERT(!m_pacl);
|
|
|
|
DllAddRef();
|
|
}
|
|
|
|
CACThread::~CACThread()
|
|
{
|
|
SyncShutDownBGThread(); // In case somehow
|
|
|
|
// These should have been freed.
|
|
ASSERT(!m_idThread);
|
|
ASSERT(!m_hdpa_list);
|
|
|
|
SAFERELEASE(m_pes);
|
|
SAFERELEASE(m_peac);
|
|
SAFERELEASE(m_pacl);
|
|
|
|
DllRelease();
|
|
}
|
|
|
|
BOOL CACThread::Init(IEnumString* pes, // source of the autocomplete strings
|
|
IACList* pacl) // optional interface to call Expand
|
|
{
|
|
// REARCHITECT: We need to marshal these interfaces to this thread!
|
|
ASSERT(pes);
|
|
m_pes = pes;
|
|
m_pes->AddRef();
|
|
|
|
m_peac = NULL;
|
|
pes->QueryInterface(IID_PPV_ARG(IEnumACString, &m_peac));
|
|
|
|
if (pacl)
|
|
{
|
|
m_pacl = pacl;
|
|
m_pacl->AddRef();
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// Called when the edit box recieves focus. We use this event to create
|
|
// a background thread or to keep the backgroung thread from shutting down
|
|
//--------------------------------------------------------------------------
|
|
void CACThread::GotFocus()
|
|
{
|
|
TraceMsg(AC_GENERAL, "CACThread::GotFocus()");
|
|
|
|
// Should not be NULL if the foreground thread is calling us!
|
|
ASSERT(m_pAutoComp);
|
|
|
|
//
|
|
// Check to see if autocomplete is supposed to be enabled.
|
|
//
|
|
if (m_pAutoComp && m_pAutoComp->IsEnabled())
|
|
{
|
|
m_fDisabled = FALSE;
|
|
|
|
if (m_fWorkItemQueued)
|
|
{
|
|
// If the thread hasn't started yet, wait for a thread creation event
|
|
if (0 == m_idThread && m_hCreateEvent)
|
|
{
|
|
WaitForSingleObject(m_hCreateEvent, 1000);
|
|
}
|
|
|
|
if (m_idThread)
|
|
{
|
|
//
|
|
// Tell the thread to cancel its timeout and stay alive.
|
|
//
|
|
// REARCHITECT: We have a race condition here. The thread can be
|
|
// in the process of shutting down!
|
|
PostThreadMessage(m_idThread, ACM_SETFOCUS, 0, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// The background thread signals an event when it starts up.
|
|
// We wait on this event before trying a synchronous shutdown
|
|
// because any posted messages would be lost.
|
|
//
|
|
if (NULL == m_hCreateEvent)
|
|
{
|
|
m_hCreateEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
}
|
|
else
|
|
{
|
|
ResetEvent(m_hCreateEvent);
|
|
}
|
|
|
|
//
|
|
// Make sure we have a background search thread.
|
|
//
|
|
// If we start it any later, we run the risk of not
|
|
// having its message queue available by the time
|
|
// we post a message to it.
|
|
//
|
|
// AddRef ourselves now, to prevent us getting freed
|
|
// before the thread proc starts running.
|
|
//
|
|
AddRef();
|
|
|
|
// Call to Shlwapi thread pool
|
|
if (SHQueueUserWorkItem(_ThreadProc,
|
|
this,
|
|
0,
|
|
(DWORD_PTR)NULL,
|
|
NULL,
|
|
"browseui.dll",
|
|
TPS_LONGEXECTIME | TPS_DEMANDTHREAD
|
|
))
|
|
{
|
|
InterlockedExchange(&m_fWorkItemQueued, TRUE);
|
|
}
|
|
else
|
|
{
|
|
// Couldn't get thread
|
|
Release();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_fDisabled = TRUE;
|
|
_SendAsyncShutDownMsg(FALSE);
|
|
}
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// Called when the edit box loses focus.
|
|
//--------------------------------------------------------------------------
|
|
void CACThread::LostFocus()
|
|
{
|
|
TraceMsg(AC_GENERAL, "CACThread::LostFocus()");
|
|
|
|
//
|
|
// If there is a thread around, tell it to stop searching.
|
|
//
|
|
if (m_idThread)
|
|
{
|
|
StopSearch();
|
|
PostThreadMessage(m_idThread, ACM_KILLFOCUS, 0, 0);
|
|
}
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// Sends the search request to the background thread.
|
|
//--------------------------------------------------------------------------
|
|
BOOL CACThread::StartSearch
|
|
(
|
|
LPCWSTR pszSearch, // String to search
|
|
DWORD dwOptions // ACO_* flags
|
|
)
|
|
{
|
|
BOOL fRet = FALSE;
|
|
|
|
// If the thread hasn't started yet, wait for a thread creation event
|
|
if (0 == m_idThread && m_fWorkItemQueued && m_hCreateEvent)
|
|
{
|
|
WaitForSingleObject(m_hCreateEvent, 1000);
|
|
}
|
|
|
|
if (m_idThread)
|
|
{
|
|
LPWSTR pszSrch = StrDup(pszSearch);
|
|
if (pszSrch)
|
|
{
|
|
//
|
|
// This is being sent to another thread, remove it from this thread's
|
|
// memlist.
|
|
//
|
|
//
|
|
// If the background thread is already searching, abort that search
|
|
//
|
|
StopSearch();
|
|
|
|
//
|
|
// Send request off to the background search thread.
|
|
//
|
|
if (PostThreadMessage(m_idThread, ACM_STARTSEARCH, dwOptions, (LPARAM)pszSrch))
|
|
{
|
|
fRet = TRUE;
|
|
}
|
|
else
|
|
{
|
|
TraceMsg(AC_GENERAL, "CACThread::_StartSearch could not send message to thread!");
|
|
LocalFree(pszSrch);
|
|
}
|
|
}
|
|
}
|
|
return fRet;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// Tells the background thread to stop and pending search
|
|
//--------------------------------------------------------------------------
|
|
void CACThread::StopSearch()
|
|
{
|
|
TraceMsg(AC_GENERAL, "CACThread::_StopSearch()");
|
|
|
|
//
|
|
// Tell the thread to stop.
|
|
//
|
|
if (m_idThread)
|
|
{
|
|
PostThreadMessage(m_idThread, ACM_STOPSEARCH, 0, 0);
|
|
}
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// Posts a quit message to the background thread
|
|
//--------------------------------------------------------------------------
|
|
void CACThread::_SendAsyncShutDownMsg(BOOL fFinalShutDown)
|
|
{
|
|
if (0 == m_idThread && m_fWorkItemQueued && m_hCreateEvent)
|
|
{
|
|
//
|
|
// Make sure that the thread has started up before posting a quit
|
|
// message or the quit message will be lost!
|
|
//
|
|
WaitForSingleObject(m_hCreateEvent, 3000);
|
|
}
|
|
|
|
if (m_idThread)
|
|
{
|
|
// Stop the search because it can hold up the thread for quite a
|
|
// while by waiting for disk data.
|
|
StopSearch();
|
|
|
|
// Tell the thread to go away, we won't be needing it anymore. Note that we pass
|
|
// the dropdown window because during the final shutdown we need to asynchronously
|
|
// destroy the dropdown to avoid a crash. The background thread will keep browseui
|
|
// mapped in memory until the dropdown is destroyed.
|
|
HWND hwndDropDown = (fFinalShutDown ? m_pAutoComp->m_hwndDropDown : NULL);
|
|
|
|
PostThreadMessage(m_idThread, ACM_QUIT, 0, (LPARAM)hwndDropDown);
|
|
}
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// Synchroniously shutdown the background thread
|
|
//
|
|
// Note: this is no longer synchronous because we now orphan this object
|
|
// when the associated autocomplet shuts down.
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
void CACThread::SyncShutDownBGThread()
|
|
{
|
|
_SendAsyncShutDownMsg(TRUE);
|
|
|
|
// Block shutdown if background thread is about to use this variable
|
|
ENTERCRITICAL;
|
|
m_pAutoComp = NULL;
|
|
LEAVECRITICAL;
|
|
|
|
if (m_hCreateEvent)
|
|
{
|
|
CloseHandle(m_hCreateEvent);
|
|
m_hCreateEvent = NULL;
|
|
}
|
|
}
|
|
|
|
void CACThread::_FreeThreadData()
|
|
{
|
|
if (m_hdpa_list)
|
|
{
|
|
CAutoComplete::_FreeDPAPtrs(m_hdpa_list);
|
|
m_hdpa_list = NULL;
|
|
}
|
|
|
|
if (m_pszSearch)
|
|
{
|
|
LocalFree(m_pszSearch);
|
|
m_pszSearch = NULL;
|
|
}
|
|
|
|
InterlockedExchange(&m_idThread, 0);
|
|
InterlockedExchange(&m_fWorkItemQueued, 0);
|
|
}
|
|
|
|
DWORD WINAPI CACThread::_ThreadProc(void *pv)
|
|
{
|
|
CACThread *pThis = (CACThread *)pv;
|
|
HRESULT hrInit = SHCoInitialize();
|
|
if (SUCCEEDED(hrInit))
|
|
{
|
|
pThis->_ThreadLoop();
|
|
}
|
|
pThis->Release();
|
|
SHCoUninitialize(hrInit);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
HRESULT CACThread::_ProcessMessage(MSG * pMsg, DWORD * pdwTimeout, BOOL * pfStayAlive)
|
|
{
|
|
TraceMsg(AC_GENERAL, "AutoCompleteThread: Message %x received.", pMsg->message);
|
|
|
|
switch (pMsg->message)
|
|
{
|
|
case ACM_STARTSEARCH:
|
|
TraceMsg(AC_GENERAL, "AutoCompleteThread: Search started.");
|
|
*pdwTimeout = INFINITE;
|
|
_Search((LPWSTR)pMsg->lParam, (DWORD)pMsg->wParam);
|
|
TraceMsg(AC_GENERAL, "AutoCompleteThread: Search completed.");
|
|
break;
|
|
|
|
case ACM_STOPSEARCH:
|
|
while (PeekMessage(pMsg, pMsg->hwnd, ACM_STOPSEARCH, ACM_STOPSEARCH, PM_REMOVE))
|
|
{
|
|
NULL;
|
|
}
|
|
TraceMsg(AC_GENERAL, "AutoCompleteThread: Search stopped.");
|
|
break;
|
|
|
|
case ACM_SETFOCUS:
|
|
TraceMsg(AC_GENERAL, "AutoCompleteThread: Got Focus.");
|
|
*pdwTimeout = INFINITE;
|
|
break;
|
|
|
|
case ACM_KILLFOCUS:
|
|
TraceMsg(AC_GENERAL, "AutoCompleteThread: Lost Focus.");
|
|
*pdwTimeout = AC_TIMEOUT;
|
|
break;
|
|
|
|
case ACM_QUIT:
|
|
{
|
|
TraceMsg(AC_GENERAL, "AutoCompleteThread: ACM_QUIT received.");
|
|
*pfStayAlive = FALSE;
|
|
|
|
//
|
|
// If a hwnd was passed in then we are shutting down and we need to
|
|
// wait until the dropdown window is destroyed before exiting this
|
|
// thread. That way browseui will stay mapped in memory.
|
|
//
|
|
HWND hwndDropDown = (HWND)pMsg->lParam;
|
|
if (hwndDropDown)
|
|
{
|
|
// We wait 5 seconds for the window to go away, checking every 100ms
|
|
int cSleep = 50;
|
|
while (IsWindow(hwndDropDown) && (--cSleep > 0))
|
|
{
|
|
MsgWaitForMultipleObjects(0, NULL, FALSE, 100, QS_TIMER);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// pump any ole-based window message that might also be on this thread
|
|
TranslateMessage(pMsg);
|
|
DispatchMessage(pMsg);
|
|
break;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// Message pump for the background thread
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CACThread::_ThreadLoop()
|
|
{
|
|
MSG Msg;
|
|
DWORD dwTimeout = INFINITE;
|
|
BOOL fStayAlive = TRUE;
|
|
|
|
TraceMsg(AC_WARNING, "AutoComplete service thread started.");
|
|
|
|
//
|
|
// We need to call a window's api for a message queue to be created
|
|
// so we call peekmessage. Then we get the thread id and thread handle
|
|
// and we signal an event to tell the forground thread that we are listening.
|
|
//
|
|
while (PeekMessage(&Msg, NULL, ACM_FIRST, ACM_LAST, PM_REMOVE))
|
|
{
|
|
// purge any messages we care about from previous owners of this thread.
|
|
}
|
|
|
|
// The forground thread needs this is so that it can post us messages
|
|
InterlockedExchange(&m_idThread, GetCurrentThreadId());
|
|
|
|
if (m_hCreateEvent)
|
|
{
|
|
SetEvent(m_hCreateEvent);
|
|
}
|
|
|
|
HANDLE hThread = GetCurrentThread();
|
|
int nOldPriority = GetThreadPriority(hThread);
|
|
SetThreadPriority(hThread, THREAD_PRIORITY_BELOW_NORMAL);
|
|
|
|
while (fStayAlive)
|
|
{
|
|
while (fStayAlive && PeekMessage(&Msg, NULL, 0, (UINT)-1, PM_NOREMOVE))
|
|
{
|
|
if (-1 != GetMessage(&Msg, NULL, 0, 0))
|
|
{
|
|
if (!Msg.hwnd)
|
|
{
|
|
// No hwnd means it's a thread message, so it's ours.
|
|
_ProcessMessage(&Msg, &dwTimeout, &fStayAlive);
|
|
}
|
|
else
|
|
{
|
|
// It has an hwnd then it's not ours. We will not allow windows on our thread.
|
|
// If anyone creates their windows on their thread, file a bug against them
|
|
// to remove it.
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fStayAlive)
|
|
{
|
|
TraceMsg(AC_GENERAL, "AutoCompleteThread: Sleeping for%s.", dwTimeout == INFINITE ? "ever" : " one minute");
|
|
DWORD dwWait = MsgWaitForMultipleObjects(0, NULL, FALSE, dwTimeout, QS_ALLINPUT);
|
|
#ifdef DEBUG
|
|
switch (dwWait)
|
|
{
|
|
case 0xFFFFFFFF:
|
|
ASSERT(dwWait != 0xFFFFFFFF);
|
|
break;
|
|
|
|
case WAIT_TIMEOUT:
|
|
TraceMsg(AC_GENERAL, "AutoCompleteThread: Timeout expired.");
|
|
break;
|
|
}
|
|
#endif
|
|
fStayAlive = (dwWait == WAIT_OBJECT_0);
|
|
}
|
|
}
|
|
|
|
TraceMsg(AC_GENERAL, "AutoCompleteThread: Thread dying.");
|
|
|
|
_FreeThreadData();
|
|
SetThreadPriority(hThread, nOldPriority);
|
|
|
|
|
|
// Purge any remaining messages before returning this thread to the pool.
|
|
while (PeekMessage(&Msg, NULL, ACM_FIRST, ACM_LAST, PM_REMOVE))
|
|
{}
|
|
|
|
TraceMsg(AC_WARNING, "AutoCompleteThread: Thread dead.");
|
|
return S_OK;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// Returns true if the search string matches one or more characters of a
|
|
// prefix that we filter out matches to
|
|
//--------------------------------------------------------------------------
|
|
BOOL CACThread::MatchesSpecialPrefix(LPCWSTR pszSearch)
|
|
{
|
|
BOOL fRet = FALSE;
|
|
int cchSearch = lstrlen(pszSearch);
|
|
for (int i = 0; i < ARRAYSIZE(g_rgSpecialPrefix); ++i)
|
|
{
|
|
// See if the search string matches one or more characters of the prefix
|
|
if (cchSearch <= g_rgSpecialPrefix[i].cch &&
|
|
StrCmpNI(g_rgSpecialPrefix[i].psz, pszSearch, cchSearch) == 0)
|
|
{
|
|
fRet = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
return fRet;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// Returns the length of the prefix it the string starts with a special
|
|
// prefix that we filter out matches to. Otherwise returns zero.
|
|
//--------------------------------------------------------------------------
|
|
int CACThread::GetSpecialPrefixLen(LPCWSTR psz)
|
|
{
|
|
int nRet = 0;
|
|
int cch = lstrlen(psz);
|
|
for (int i = 0; i < ARRAYSIZE(g_rgSpecialPrefix); ++i)
|
|
{
|
|
if (cch >= g_rgSpecialPrefix[i].cch &&
|
|
StrCmpNI(g_rgSpecialPrefix[i].psz, psz, g_rgSpecialPrefix[i].cch) == 0)
|
|
{
|
|
nRet = g_rgSpecialPrefix[i].cch;
|
|
break;
|
|
}
|
|
}
|
|
return nRet;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// Returns the next autocomplete string
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CACThread::_Next(LPWSTR pszUrl, ULONG cchMax, ULONG* pulSortIndex)
|
|
{
|
|
ASSERT(pulSortIndex);
|
|
|
|
HRESULT hr;
|
|
|
|
// Use the new interface if we have it
|
|
if (m_peac)
|
|
{
|
|
hr = m_peac->NextItem(pszUrl, cchMax, pulSortIndex);
|
|
}
|
|
|
|
// Fall back to the old IEnumString interface
|
|
else
|
|
{
|
|
LPWSTR pszNext;
|
|
ULONG ulFetched;
|
|
|
|
hr = m_pes->Next(1, &pszNext, &ulFetched);
|
|
if (S_OK == hr)
|
|
{
|
|
StrCpyN(pszUrl, pszNext, cchMax);
|
|
if (pulSortIndex)
|
|
{
|
|
*pulSortIndex = 0;
|
|
}
|
|
CoTaskMemFree(pszNext);
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// Searches for items that match pszSearch.
|
|
//--------------------------------------------------------------------------
|
|
void CACThread::_Search
|
|
(
|
|
LPWSTR pszSearch, // String to search for (we must free this)
|
|
DWORD dwOptions // ACO_* flags
|
|
)
|
|
{
|
|
if (pszSearch)
|
|
{
|
|
TraceMsg(AC_GENERAL, "CACThread(BGThread)::_Search(pszSearch=0x%x)", pszSearch);
|
|
|
|
// Save the search string in our thread data so it is still freed if this thread is killed
|
|
m_pszSearch = pszSearch;
|
|
|
|
// If we were passed a wildcard string, then everything matches
|
|
BOOL fWildCard = ((pszSearch[0] == CH_WILDCARD) && (pszSearch[1] == L'\0'));
|
|
|
|
// To avoid huge number of useless matches, avoid matches
|
|
// to common prefixes
|
|
BOOL fFilter = (dwOptions & ACO_FILTERPREFIXES) && MatchesSpecialPrefix(pszSearch);
|
|
BOOL fAppendOnly = IsFlagSet(dwOptions, ACO_AUTOAPPEND) && IsFlagClear(dwOptions, ACO_AUTOSUGGEST);
|
|
|
|
if (m_pes) // paranoia
|
|
{
|
|
// If this fails, the m_pes->Next() will likely do something
|
|
// bad, so we will avoid it altogether.
|
|
if (SUCCEEDED(m_pes->Reset()))
|
|
{
|
|
BOOL fStopped = FALSE;
|
|
m_dwSearchStatus = 0;
|
|
|
|
_DoExpand(pszSearch);
|
|
int cchSearch = lstrlen(pszSearch);
|
|
|
|
WCHAR szUrl[MAX_URL_STRING];
|
|
ULONG ulSortIndex;
|
|
|
|
while (!fStopped && IsFlagClear(m_dwSearchStatus, SRCH_LIMITREACHED) &&
|
|
(_Next(szUrl, ARRAYSIZE(szUrl), &ulSortIndex) == S_OK))
|
|
{
|
|
//
|
|
// First check for a simple match
|
|
//
|
|
if (fWildCard ||
|
|
(StrCmpNI(szUrl, pszSearch, cchSearch) == 0) &&
|
|
|
|
// Filter out matches to common prefixes
|
|
(!fFilter || GetSpecialPrefixLen(szUrl) == 0))
|
|
{
|
|
_AddToList(szUrl, 0, ulSortIndex);
|
|
}
|
|
|
|
// If the dropdown is enabled, check for matches after common prefixes.
|
|
if (!fAppendOnly)
|
|
{
|
|
//
|
|
// Also check for a match if we skip the protocol. We
|
|
// assume that szUrl has been cononicalized (protocol
|
|
// in lower case).
|
|
//
|
|
LPCWSTR psz = szUrl;
|
|
if (StrCmpN(szUrl, L"http://", 7) == 0)
|
|
{
|
|
psz += 7;
|
|
}
|
|
if (StrCmpN(szUrl, L"https://", 8) == 0 ||
|
|
StrCmpN(szUrl, L"file:///", 8) == 0)
|
|
{
|
|
psz += 8;
|
|
}
|
|
|
|
if (psz != szUrl &&
|
|
StrCmpNI(psz, pszSearch, cchSearch) == 0 &&
|
|
|
|
// Filter out "www." prefixes
|
|
(!fFilter || GetSpecialPrefixLen(psz) == 0))
|
|
{
|
|
_AddToList(szUrl, (int)(psz - szUrl), ulSortIndex);
|
|
}
|
|
|
|
//
|
|
// Finally check for a match if we skip "www." after
|
|
// the optional protocol
|
|
//
|
|
if (StrCmpN(psz, L"www.", 4) == 0 &&
|
|
StrCmpNI(psz + 4, pszSearch, cchSearch) == 0)
|
|
{
|
|
_AddToList(szUrl, (int)(psz + 4 - szUrl), ulSortIndex);
|
|
}
|
|
}
|
|
|
|
// Check to see if the search was canceled
|
|
MSG msg;
|
|
fStopped = PeekMessage(&msg, NULL, ACM_STOPSEARCH, ACM_STOPSEARCH, PM_NOREMOVE);
|
|
#ifdef DEBUG
|
|
fStopped = FALSE;
|
|
if (fStopped)
|
|
TraceMsg(AC_GENERAL, "AutoCompleteThread: Search TERMINATED");
|
|
#endif
|
|
}
|
|
|
|
if (fStopped)
|
|
{
|
|
// Search aborted so free the results
|
|
if (m_hdpa_list)
|
|
{
|
|
// clear the list
|
|
CAutoComplete::_FreeDPAPtrs(m_hdpa_list);
|
|
m_hdpa_list = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Sort the results and remove duplicates
|
|
//
|
|
if (m_hdpa_list)
|
|
{
|
|
DPA_Sort(m_hdpa_list, _DpaCompare, 0);
|
|
|
|
//
|
|
// Perge duplicates.
|
|
//
|
|
for (int i = DPA_GetPtrCount(m_hdpa_list) - 1; i > 0; --i)
|
|
{
|
|
CACString& rStr1 = *(CACString*)DPA_GetPtr(m_hdpa_list, i-1);
|
|
CACString& rStr2 = *(CACString*)DPA_GetPtr(m_hdpa_list, i);
|
|
|
|
// Since URLs are case sensitive, we can't ignore case.
|
|
if (rStr1.StrCmpI(rStr2) == 0)
|
|
{
|
|
// We have a match, so keep the longest string.
|
|
if (rStr1.GetLength() > rStr2.GetLength())
|
|
{
|
|
// Use the smallest sort index
|
|
if (rStr2.GetSortIndex() < rStr1.GetSortIndex())
|
|
{
|
|
rStr1.SetSortIndex(rStr2.GetSortIndex());
|
|
}
|
|
DPA_DeletePtr(m_hdpa_list, i);
|
|
rStr2.Release();
|
|
}
|
|
else
|
|
{
|
|
// Use the smallest sort index
|
|
if (rStr1.GetSortIndex() < rStr2.GetSortIndex())
|
|
{
|
|
rStr2.SetSortIndex(rStr1.GetSortIndex());
|
|
}
|
|
DPA_DeletePtr(m_hdpa_list, i-1);
|
|
rStr1.Release();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Special case: If this is a web site and the entries
|
|
// are identical except one has an extra slash on the end
|
|
// from a redirect, remove the redirected one.
|
|
//
|
|
int cch1 = rStr1.GetLengthToCompare();
|
|
int cch2 = rStr2.GetLengthToCompare();
|
|
int cchDiff = cch1 - cch2;
|
|
|
|
if (
|
|
// Length must differ by one
|
|
(cchDiff == 1 || cchDiff == -1) &&
|
|
|
|
// One string must have a terminating slash
|
|
((cch1 > 0 && rStr1[rStr1.GetLength() - 1] == L'/') ||
|
|
(cch2 > 0 && rStr2[rStr2.GetLength() - 1] == L'/')) &&
|
|
|
|
// Must be a web site
|
|
((StrCmpN(rStr1, L"http://", 7) == 0 || StrCmpN(rStr1, L"https://", 8) == 0) ||
|
|
(StrCmpN(rStr2, L"http://", 7) == 0 || StrCmpN(rStr2, L"https://", 8) == 0)) &&
|
|
|
|
// Must be identical up to the slash (ignoring prefix)
|
|
StrCmpNI(rStr1.GetStrToCompare(), rStr2.GetStrToCompare(), (cchDiff > 0) ? cch2 : cch1) == 0)
|
|
{
|
|
// Remove the longer string with the extra slash
|
|
if (cchDiff > 0)
|
|
{
|
|
// Use the smallest sort index
|
|
if (rStr1.GetSortIndex() < rStr2.GetSortIndex())
|
|
{
|
|
rStr2.SetSortIndex(rStr1.GetSortIndex());
|
|
}
|
|
DPA_DeletePtr(m_hdpa_list, i-1);
|
|
rStr1.Release();
|
|
}
|
|
else
|
|
{
|
|
// Use the smallest sort index
|
|
if (rStr2.GetSortIndex() < rStr1.GetSortIndex())
|
|
{
|
|
rStr1.SetSortIndex(rStr2.GetSortIndex());
|
|
}
|
|
DPA_DeletePtr(m_hdpa_list, i);
|
|
rStr2.Release();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pass the results to the foreground thread
|
|
ENTERCRITICAL;
|
|
if (m_pAutoComp)
|
|
{
|
|
HWND hwndEdit = m_pAutoComp->m_hwndEdit;
|
|
UINT uMsgSearchComplete = m_pAutoComp->m_uMsgSearchComplete;
|
|
LEAVECRITICAL;
|
|
|
|
// Unix loses keys if we post the message, so we send the message
|
|
// outside our critical section
|
|
SendMessage(hwndEdit, uMsgSearchComplete, m_dwSearchStatus, (LPARAM)m_hdpa_list);
|
|
}
|
|
else
|
|
{
|
|
LEAVECRITICAL;
|
|
|
|
// We've been orphaned, so free the list and bail
|
|
CAutoComplete::_FreeDPAPtrs(m_hdpa_list);
|
|
}
|
|
|
|
// The foreground thread owns the list now
|
|
m_hdpa_list = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ASSERT(0); // m_pes->Reset Failed!!
|
|
}
|
|
}
|
|
|
|
// We must free the search string
|
|
m_pszSearch = NULL;
|
|
|
|
// Note if the thread is killed here, we leak the string
|
|
// but at least we will not try to free it twice (which is worse)
|
|
// because we nulled m_pszSearch first.
|
|
LocalFree(pszSearch);
|
|
}
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// Used to sort items alphabetically
|
|
//--------------------------------------------------------------------------
|
|
int CALLBACK CACThread::_DpaCompare(void *p1, void *p2, LPARAM lParam)
|
|
{
|
|
CACString* ps1 = (CACString*)p1;
|
|
CACString* ps2 = (CACString*)p2;
|
|
|
|
return ps1->StrCmpI(*ps2);
|
|
}
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// Adds a string to our HDPA. Returns TRUE is successful.
|
|
//--------------------------------------------------------------------------
|
|
BOOL CACThread::_AddToList
|
|
(
|
|
LPTSTR pszUrl, // string to add
|
|
int cchMatch, // offset into string where the match occurred
|
|
ULONG ulSortIndex // controls order of items displayed
|
|
)
|
|
{
|
|
TraceMsg(AC_GENERAL, "CACThread(BGThread)::_AddToList(pszUrl = %s)",
|
|
(pszUrl ? pszUrl : TEXT("(null)")));
|
|
|
|
BOOL fRet = TRUE;
|
|
|
|
//
|
|
// Create a new list if necessary.
|
|
//
|
|
if (!m_hdpa_list)
|
|
{
|
|
m_hdpa_list = DPA_Create(AC_LIST_GROWTH_CONST);
|
|
}
|
|
|
|
if (m_hdpa_list && DPA_GetPtrCount(m_hdpa_list) < AC_GIVEUP_COUNT)
|
|
{
|
|
CACString* pStr = CreateACString(pszUrl, cchMatch, ulSortIndex);
|
|
if (pStr)
|
|
{
|
|
if (DPA_AppendPtr(m_hdpa_list, pStr) == -1)
|
|
{
|
|
pStr->Release();
|
|
m_dwSearchStatus |= SRCH_LIMITREACHED;
|
|
fRet = FALSE;
|
|
}
|
|
|
|
// If we have a nonzero sort index, the forground thread will need
|
|
// to use it to order the results
|
|
else if (ulSortIndex)
|
|
{
|
|
m_dwSearchStatus |= SRCH_USESORTINDEX;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_dwSearchStatus |= SRCH_LIMITREACHED;
|
|
fRet = FALSE;
|
|
}
|
|
|
|
return fRet;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
// This function will attempt to use the autocomplete list to bind to a
|
|
// location in the Shell Name Space. If that succeeds, the AutoComplete List
|
|
// will then contain entries which are the display names in that ISF.
|
|
//--------------------------------------------------------------------------
|
|
void CACThread::_DoExpand(LPCWSTR pszSearch)
|
|
{
|
|
LPCWSTR psz;
|
|
|
|
if (!m_pacl)
|
|
{
|
|
//
|
|
// Doesn't support IAutoComplete, doesn't have Expand method.
|
|
//
|
|
return;
|
|
}
|
|
|
|
if (*pszSearch == 0)
|
|
{
|
|
//
|
|
// No string means no expansion necessary.
|
|
//
|
|
return;
|
|
}
|
|
|
|
//
|
|
// psz points to last character.
|
|
//
|
|
psz = pszSearch + lstrlen(pszSearch);
|
|
psz = CharPrev(pszSearch, psz);
|
|
|
|
//
|
|
// Search backwards for an expand break character.
|
|
//
|
|
while (psz != pszSearch && *psz != TEXT('/') && *psz != TEXT('\\'))
|
|
{
|
|
psz = CharPrev(pszSearch, psz);
|
|
}
|
|
|
|
if (*psz == TEXT('/') || *psz == TEXT('\\'))
|
|
{
|
|
SHSTR ss;
|
|
|
|
psz++;
|
|
if (SUCCEEDED(ss.SetStr(pszSearch)))
|
|
{
|
|
//
|
|
// Trim ss so that it contains everything up to the last
|
|
// expand break character.
|
|
//
|
|
LPTSTR pszTemp = ss.GetInplaceStr();
|
|
|
|
pszTemp[psz - pszSearch] = TEXT('\0');
|
|
|
|
//
|
|
// Call expand on the string.
|
|
//
|
|
m_pacl->Expand(ss);
|
|
}
|
|
}
|
|
}
|