|
|
// 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( 0 != m_cRef ); ULONG cRef = InterlockedDecrement(&m_cRef); if ( 0 == cRef ) { delete this; } return cRef; }
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) { StringCchCopy(pStr->m_sz, cChars + sizeof(CACString)/sizeof(WCHAR), 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( 0 != m_cRef ); ULONG cRef = InterlockedDecrement(&m_cRef); if ( 0 == cRef ) { delete this; } return cRef; }
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) { StringCchCopy(pszUrl, cchMax, pszNext); 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); } } }
|