1 /*
2  *    AutoComplete interfaces implementation.
3  *
4  *    Copyright 2004    Maxime Bellengé <maxime.bellenge@laposte.net>
5  *    Copyright 2009  Andrew Hill
6  *    Copyright 2020-2021 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21  */
22 
23 #include "precomp.h"
24 #include <process.h> // _beginthreadex
25 
26 /*
27   TODO:
28   - implement ACO_SEARCH style
29   - implement ACO_FILTERPREFIXES style
30   - implement ACO_RTLREADING style
31  */
32 
33 #define CX_LIST 30160 // width of m_hwndList (very wide but alright)
34 #define CY_LIST 288 // maximum height of drop-down window
35 #define CY_ITEM 18 // default height of listview item
36 #define MAX_ITEM_COUNT 1000 // the maximum number of items
37 #define WATCH_TIMER_ID 0xFEEDBEEF // timer ID to watch m_rcEdit
38 #define WATCH_INTERVAL 300 // in milliseconds
39 
40 static HHOOK s_hMouseHook = NULL; // hook handle
41 static HWND s_hWatchWnd = NULL; // the window handle to watch
42 
43 struct PREFIX_INFO
44 {
45     LPCWSTR psz;
46     INT cch;
47 };
48 static const PREFIX_INFO s_prefixes[] =
49 {
50     { L"https://", 8 },
51     { L"http://www.", 11 },
52     { L"http://", 7 },
53     { L"www.", 4 },
54 };
55 
56 static BOOL DropPrefix(const CStringW& str, CStringW& strBody)
57 {
58     for (size_t iPrefix = 0; iPrefix < _countof(s_prefixes); ++iPrefix)
59     {
60         LPCWSTR psz = s_prefixes[iPrefix].psz;
61         INT cch = s_prefixes[iPrefix].cch;
62         if (::StrCmpNIW(str, psz, cch) == 0)
63         {
64             strBody = str.Mid(cch);
65             return TRUE;
66         }
67     }
68     strBody = str;
69     return FALSE;
70 }
71 
72 static BOOL DoesMatch(const CStringW& strTarget, const CStringW& strText)
73 {
74     CStringW strBody;
75     if (DropPrefix(strTarget, strBody))
76     {
77         if (::StrCmpNIW(strBody, strText, strText.GetLength()) == 0)
78             return TRUE;
79     }
80     else if (::StrCmpNIW(strTarget, strText, strText.GetLength()) == 0)
81     {
82         return TRUE;
83     }
84     return FALSE;
85 }
86 
87 // mouse hook procedure to watch the mouse click
88 // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms644988(v=vs.85)
89 static LRESULT CALLBACK MouseProc(INT nCode, WPARAM wParam, LPARAM lParam)
90 {
91     if (s_hMouseHook == NULL)
92         return 0; // do default
93     // if the user clicked the outside of s_hWatchWnd, then hide the drop-down window
94     if (nCode == HC_ACTION && // an action?
95         s_hWatchWnd && ::IsWindow(s_hWatchWnd) && // s_hWatchWnd is valid?
96         ::GetCapture() == NULL) // no capture? (dragging something?)
97     {
98         RECT rc;
99         MOUSEHOOKSTRUCT *pMouseHook = reinterpret_cast<MOUSEHOOKSTRUCT *>(lParam);
100         switch (wParam)
101         {
102             case WM_LBUTTONDOWN: case WM_LBUTTONUP:
103             case WM_RBUTTONDOWN: case WM_RBUTTONUP:
104             case WM_MBUTTONDOWN: case WM_MBUTTONUP:
105             case WM_NCLBUTTONDOWN: case WM_NCLBUTTONUP:
106             case WM_NCRBUTTONDOWN: case WM_NCRBUTTONUP:
107             case WM_NCMBUTTONDOWN: case WM_NCMBUTTONUP:
108             {
109                 ::GetWindowRect(s_hWatchWnd, &rc);
110                 if (!::PtInRect(&rc, pMouseHook->pt)) // outside of s_hWatchWnd?
111                 {
112                     ::ShowWindowAsync(s_hWatchWnd, SW_HIDE); // hide it
113                 }
114                 break;
115             }
116         }
117     }
118     return ::CallNextHookEx(s_hMouseHook, nCode, wParam, lParam); // go next hook
119 }
120 
121 //////////////////////////////////////////////////////////////////////////////
122 // sorting algorithm
123 // http://www.ics.kagoshima-u.ac.jp/~fuchida/edu/algorithm/sort-algorithm/
124 
125 typedef CSimpleArray<CStringW> list_t;
126 
127 static inline INT compare1(const CStringW& str1, const CStringW& str2)
128 {
129     CStringW s1, s2;
130     DropPrefix(str1, s1);
131     DropPrefix(str2, s2);
132     return s1.CompareNoCase(s2);
133 }
134 
135 static inline INT pivot(list_t& list, INT i, INT j)
136 {
137     INT k = i + 1;
138     while (k <= j && compare1(list[i], list[k]) == 0)
139         k++;
140     if (k > j)
141         return -1;
142     if (compare1(list[i], list[k]) >= 0)
143         return i;
144     return k;
145 }
146 
147 static inline INT partition(list_t& list, INT i, INT j, const CStringW& x)
148 {
149     INT left = i, right = j;
150     while (left <= right)
151     {
152         while (left <= j && compare1(list[left], x) < 0)
153             left++;
154         while (right >= i && compare1(list[right], x) >= 0)
155             right--;
156         if (left > right)
157             break;
158 
159         CStringW tmp = list[left];
160         list[left] = list[right];
161         list[right] = tmp;
162 
163         left++;
164         right--;
165     }
166     return left;
167 }
168 
169 static void quicksort(list_t& list, INT i, INT j)
170 {
171     if (i == j)
172         return;
173     INT p = pivot(list, i, j);
174     if (p == -1)
175         return;
176     INT k = partition(list, i, j, list[p]);
177     quicksort(list, i, k - 1);
178     quicksort(list, k, j);
179 }
180 
181 static inline void DoSort(list_t& list)
182 {
183     if (list.GetSize() <= 1) // sanity check
184         return;
185     quicksort(list, 0, list.GetSize() - 1); // quick sort
186 }
187 
188 // std::unique
189 static INT DoUnique(list_t& list)
190 {
191     INT first = 0, last = list.GetSize();
192     if (first == last)
193         return last;
194     INT result = first;
195     while (++first != last)
196     {
197         if (compare1(list[result], list[first]) != 0)
198             list[++result] = list[first];
199     }
200     return ++result;
201 }
202 
203 static inline void DoUniqueAndTrim(list_t& list)
204 {
205     INT last = DoUnique(list);
206     while (list.GetSize() > last)
207     {
208         list.RemoveAt(last);
209     }
210 }
211 
212 //////////////////////////////////////////////////////////////////////////////
213 
214 // range of WCHAR (inclusive)
215 struct RANGE
216 {
217     WCHAR from, to;
218 };
219 
220 // a callback function for bsearch: comparison of two ranges
221 static inline int RangeCompare(const void *x, const void *y)
222 {
223     const RANGE *a = reinterpret_cast<const RANGE *>(x);
224     const RANGE *b = reinterpret_cast<const RANGE *>(y);
225     if (a->to < b->from)
226         return -1;
227     if (b->to < a->from)
228         return 1;
229     return 0;
230 }
231 
232 // is the WCHAR a word break?
233 static inline BOOL IsWordBreak(WCHAR ch)
234 {
235     // the ranges of word break characters
236     static const RANGE s_ranges[] =
237     {
238         { 0x0009, 0x0009 }, { 0x0020, 0x002f }, { 0x003a, 0x0040 }, { 0x005b, 0x0060 },
239         { 0x007b, 0x007e }, { 0x00ab, 0x00ab }, { 0x00ad, 0x00ad }, { 0x00bb, 0x00bb },
240         { 0x02c7, 0x02c7 }, { 0x02c9, 0x02c9 }, { 0x055d, 0x055d }, { 0x060c, 0x060c },
241         { 0x2002, 0x200b }, { 0x2013, 0x2014 }, { 0x2016, 0x2016 }, { 0x2018, 0x2018 },
242         { 0x201c, 0x201d }, { 0x2022, 0x2022 }, { 0x2025, 0x2027 }, { 0x2039, 0x203a },
243         { 0x2045, 0x2046 }, { 0x207d, 0x207e }, { 0x208d, 0x208e }, { 0x226a, 0x226b },
244         { 0x2574, 0x2574 }, { 0x3001, 0x3003 }, { 0x3005, 0x3005 }, { 0x3008, 0x3011 },
245         { 0x3014, 0x301b }, { 0x301d, 0x301e }, { 0x3041, 0x3041 }, { 0x3043, 0x3043 },
246         { 0x3045, 0x3045 }, { 0x3047, 0x3047 }, { 0x3049, 0x3049 }, { 0x3063, 0x3063 },
247         { 0x3083, 0x3083 }, { 0x3085, 0x3085 }, { 0x3087, 0x3087 }, { 0x308e, 0x308e },
248         { 0x309b, 0x309e }, { 0x30a1, 0x30a1 }, { 0x30a3, 0x30a3 }, { 0x30a5, 0x30a5 },
249         { 0x30a7, 0x30a7 }, { 0x30a9, 0x30a9 }, { 0x30c3, 0x30c3 }, { 0x30e3, 0x30e3 },
250         { 0x30e5, 0x30e5 }, { 0x30e7, 0x30e7 }, { 0x30ee, 0x30ee }, { 0x30f5, 0x30f6 },
251         { 0x30fc, 0x30fe }, { 0xfd3e, 0xfd3f }, { 0xfe30, 0xfe31 }, { 0xfe33, 0xfe44 },
252         { 0xfe4f, 0xfe51 }, { 0xfe59, 0xfe5e }, { 0xff08, 0xff09 }, { 0xff0c, 0xff0c },
253         { 0xff0e, 0xff0e }, { 0xff1c, 0xff1c }, { 0xff1e, 0xff1e }, { 0xff3b, 0xff3b },
254         { 0xff3d, 0xff3d }, { 0xff40, 0xff40 }, { 0xff5b, 0xff5e }, { 0xff61, 0xff64 },
255         { 0xff67, 0xff70 }, { 0xff9e, 0xff9f }, { 0xffe9, 0xffe9 }, { 0xffeb, 0xffeb },
256     };
257     // binary search
258     RANGE range = { ch, ch };
259     return !!bsearch(&range, s_ranges, _countof(s_ranges), sizeof(RANGE), RangeCompare);
260 }
261 
262 // This function is an application-defined callback function.
263 // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-editwordbreakprocw
264 static INT CALLBACK
265 EditWordBreakProcW(LPWSTR lpch, INT index, INT count, INT code)
266 {
267     switch (code)
268     {
269         case WB_ISDELIMITER:
270             return IsWordBreak(lpch[index]);
271         case WB_LEFT:
272         {
273             if (index)
274                 --index;
275             while (index && !IsWordBreak(lpch[index]))
276                 --index;
277             return index;
278         }
279         case WB_RIGHT:
280         {
281             if (!count)
282                 break;
283             while (index < count && lpch[index] && !IsWordBreak(lpch[index]))
284                 ++index;
285             return index;
286         }
287     }
288     return 0;
289 }
290 
291 static LRESULT CALLBACK
292 EditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
293                  UINT_PTR uSubclassID, DWORD_PTR dwData)
294 {
295     CAutoComplete *pThis = reinterpret_cast<CAutoComplete *>(dwData);
296     return pThis->EditWndProc(hwnd, uMsg, wParam, lParam);
297 }
298 
299 LRESULT CAutoComplete::EditWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
300 {
301     LRESULT ret;
302     HWND hwndGotFocus;
303     switch (uMsg)
304     {
305         case WM_CHAR:
306             return OnEditChar(wParam, lParam);
307         case WM_CUT: case WM_PASTE: case WM_CLEAR:
308             ret = ::DefSubclassProc(hwnd, uMsg, wParam, lParam); // do default
309             StartCompletion(TRUE);
310             return ret;
311         case WM_GETDLGCODE:
312             ret = ::DefSubclassProc(hwnd, uMsg, wParam, lParam); // do default
313             // some special keys need default processing. we handle them here
314             switch (wParam)
315             {
316                 case VK_RETURN:
317                     if (IsWindowVisible() || ::GetKeyState(VK_CONTROL) < 0)
318                         OnEditKeyDown(VK_RETURN, 0);
319                     break;
320                 case VK_TAB:
321                     if (IsWindowVisible() && UseTab())
322                         ret |= DLGC_WANTALLKEYS; // we want all keys to manipulate the list
323                     break;
324                 case VK_ESCAPE:
325                     if (IsWindowVisible())
326                         ret |= DLGC_WANTALLKEYS; // we want all keys to manipulate the list
327                     break;
328                 default:
329                 {
330                     ret |= DLGC_WANTALLKEYS; // we want all keys to manipulate the list
331                     break;
332                 }
333             }
334             return ret;
335         case WM_KEYDOWN:
336             if (OnEditKeyDown(wParam, lParam))
337                 return TRUE; // eat
338             break;
339         case WM_SETFOCUS:
340             break;
341         case WM_KILLFOCUS:
342             // hide the list if lost focus
343             hwndGotFocus = (HWND)wParam;
344             if (hwndGotFocus != m_hwndEdit && hwndGotFocus != m_hWnd)
345                 HideDropDown();
346             break;
347         case WM_SETTEXT:
348             if (!m_bInSetText)
349                 HideDropDown(); // it's mechanical WM_SETTEXT
350             break;
351         case WM_DESTROY:
352         {
353             if (m_fnOldWordBreakProc)
354             {
355                 ::SendMessageW(hwnd, EM_SETWORDBREAKPROC, 0, reinterpret_cast<LPARAM>(m_fnOldWordBreakProc));
356                 m_fnOldWordBreakProc = NULL;
357             }
358             ::RemoveWindowSubclass(hwnd, EditSubclassProc, 0);
359             if (::IsWindow(m_hWnd))
360                 PostMessageW(WM_CLOSE, 0, 0);
361             // remove reference to m_hwndEdit
362             Release();
363             break;
364         }
365     }
366 
367     return ::DefSubclassProc(hwnd, uMsg, wParam, lParam); // do default
368 }
369 
370 //////////////////////////////////////////////////////////////////////////////
371 // CACListView
372 
373 CACListView::CACListView() : m_pDropDown(NULL), m_cyItem(CY_ITEM)
374 {
375 }
376 
377 HWND CACListView::Create(HWND hwndParent)
378 {
379     ATLASSERT(m_hWnd == NULL);
380     DWORD dwStyle = WS_CHILD | /*WS_VISIBLE |*/ WS_CLIPSIBLINGS | LVS_NOCOLUMNHEADER |
381                     LVS_OWNERDATA | LVS_OWNERDRAWFIXED | LVS_SINGLESEL | LVS_REPORT;
382     HWND hWnd = ::CreateWindowExW(0, GetWndClassName(), L"Internet Explorer", dwStyle,
383                                   0, 0, 0, 0, hwndParent, NULL,
384                                   _AtlBaseModule.GetModuleInstance(), NULL);
385     SubclassWindow(hWnd); // do subclass to handle messages
386     // set extended listview style
387     DWORD exstyle = LVS_EX_ONECLICKACTIVATE | LVS_EX_FULLROWSELECT | LVS_EX_TRACKSELECT;
388     SetExtendedListViewStyle(exstyle, exstyle);
389     // insert one column (needed to insert items)
390     LV_COLUMNW column = { LVCF_FMT | LVCF_WIDTH };
391     column.fmt = LVCFMT_LEFT;
392     column.cx = CX_LIST - ::GetSystemMetrics(SM_CXVSCROLL);
393     InsertColumn(0, &column);
394     return m_hWnd;
395 }
396 
397 // set font handle
398 VOID CACListView::SetFont(HFONT hFont)
399 {
400     SendMessageW(WM_SETFONT, (WPARAM)hFont, TRUE); // set font
401 
402     // get listview item height
403     m_cyItem = CY_ITEM;
404     HDC hDC = GetDC();
405     if (hDC)
406     {
407         HGDIOBJ hFontOld = ::SelectObject(hDC, hFont);
408         TEXTMETRICW tm;
409         if (::GetTextMetricsW(hDC, &tm))
410         {
411             m_cyItem = (tm.tmHeight * 3) / 2; // 3/2 of text height
412         }
413         ::SelectObject(hDC, hFontOld);
414         ReleaseDC(hDC);
415     }
416 }
417 
418 // get the number of visible items
419 INT CACListView::GetVisibleCount()
420 {
421     if (m_cyItem <= 0) // avoid "division by zero"
422         return 0;
423     CRect rc;
424     GetClientRect(&rc);
425     return rc.Height() / m_cyItem;
426 }
427 
428 // get the text of an item
429 CStringW CACListView::GetItemText(INT iItem)
430 {
431     // NOTE: LVS_OWNERDATA doesn't support LVM_GETITEMTEXT.
432     ATLASSERT(m_pDropDown);
433     ATLASSERT(GetStyle() & LVS_OWNERDATA);
434     return m_pDropDown->GetItemText(iItem);
435 }
436 
437 // get the item index from position
438 INT CACListView::ItemFromPoint(INT x, INT y)
439 {
440     LV_HITTESTINFO hittest;
441     hittest.pt.x = x;
442     hittest.pt.y = y;
443     return HitTest(&hittest);
444 }
445 
446 // get current selection
447 INT CACListView::GetCurSel()
448 {
449     return GetNextItem(-1, LVNI_ALL | LVNI_SELECTED);
450 }
451 
452 // set current selection
453 VOID CACListView::SetCurSel(INT iItem)
454 {
455     if (iItem == -1)
456         SetItemState(-1, 0, LVIS_SELECTED); // select none
457     else
458         SetItemState(iItem, LVIS_SELECTED, LVIS_SELECTED);
459 }
460 
461 // select the specific position (in client coordinates)
462 VOID CACListView::SelectHere(INT x, INT y)
463 {
464     SetCurSel(ItemFromPoint(x, y));
465 }
466 
467 // WM_LBUTTONUP / WM_MBUTTONUP / WM_RBUTTONUP @implemented
468 LRESULT CACListView::OnButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
469 {
470     TRACE("CACListView::OnButtonUp(%p)\n", this);
471     return 0; // eat
472 }
473 
474 // WM_LBUTTONDOWN @implemented
475 // This message is posted when the user pressed the left mouse button while the cursor is inside.
476 LRESULT CACListView::OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
477 {
478     TRACE("CACListView::OnLButtonDown(%p)\n", this);
479     ATLASSERT(m_pDropDown);
480     INT iItem = ItemFromPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
481     if (iItem != -1)
482     {
483         m_pDropDown->SelectItem(iItem); // select the item
484         CStringW strText = GetItemText(iItem); // get text of item
485         m_pDropDown->SetEditText(strText); // set text
486         m_pDropDown->SetEditSel(0, strText.GetLength()); // select all
487         m_pDropDown->HideDropDown(); // hide
488     }
489     return 0;
490 }
491 
492 // WM_MBUTTONDOWN / WM_RBUTTONDOWN @implemented
493 LRESULT CACListView::OnMRButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
494 {
495     TRACE("CACListView::OnMRButtonDown(%p)\n", this);
496     return 0; // eat
497 }
498 
499 // WM_MOUSEWHEEL @implemented
500 LRESULT CACListView::OnMouseWheel(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
501 {
502     TRACE("CACListView::OnMouseWheel(%p)\n", this);
503     ATLASSERT(m_pDropDown);
504     LRESULT ret = DefWindowProcW(uMsg, wParam, lParam); // do default
505     m_pDropDown->UpdateScrollBar();
506     return ret;
507 }
508 
509 // WM_NCHITTEST
510 LRESULT CACListView::OnNCHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
511 {
512     TRACE("CACListView::OnNCHitTest(%p)\n", this);
513     ATLASSERT(m_pDropDown);
514     POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; // in screen coordinates
515     ScreenToClient(&pt); // into client coordinates
516     HWND hwndTarget = m_pDropDown->ChildWindowFromPoint(pt);
517     if (hwndTarget != m_hWnd)
518         return HTTRANSPARENT; // pass through (for resizing the drop-down window)
519     bHandled = FALSE; // do default
520     return 0;
521 }
522 
523 //////////////////////////////////////////////////////////////////////////////
524 // CACScrollBar
525 
526 HWND CACScrollBar::Create(HWND hwndParent)
527 {
528     ATLASSERT(m_hWnd == NULL);
529     DWORD dwStyle = WS_CHILD | /*WS_VISIBLE |*/ SBS_BOTTOMALIGN | SBS_VERT;
530     m_hWnd = ::CreateWindowExW(0, GetWndClassName(), NULL, dwStyle,
531                                0, 0, 0, 0, hwndParent, NULL,
532                                _AtlBaseModule.GetModuleInstance(), NULL);
533     // we don't subclass because no message handling is needed
534     return m_hWnd;
535 }
536 
537 //////////////////////////////////////////////////////////////////////////////
538 // CACSizeBox
539 
540 HWND CACSizeBox::Create(HWND hwndParent)
541 {
542     ATLASSERT(m_hWnd == NULL);
543     DWORD dwStyle = WS_CHILD | /*WS_VISIBLE |*/ SBS_SIZEBOX;
544     HWND hWnd = ::CreateWindowExW(0, GetWndClassName(), NULL, dwStyle,
545                                   0, 0, 0, 0, hwndParent, NULL,
546                                   _AtlBaseModule.GetModuleInstance(), NULL);
547     SubclassWindow(hWnd); // do subclass to handle message
548     return m_hWnd;
549 }
550 
551 VOID CACSizeBox::SetStatus(BOOL bDowner, BOOL bLongList)
552 {
553     // set flags
554     m_bDowner = bDowner;
555     m_bLongList = bLongList;
556 
557     if (bLongList)
558     {
559         SetWindowRgn(NULL, TRUE); // reset window region
560         return;
561     }
562 
563     RECT rc;
564     GetWindowRect(&rc); // get size-box size
565     ::OffsetRect(&rc, -rc.left, -rc.top); // window regions use special coordinates
566     ATLASSERT(rc.left == 0 && rc.top == 0);
567 
568     // create a trianglar region
569     HDC hDC = ::CreateCompatibleDC(NULL);
570     ::BeginPath(hDC);
571     if (m_bDowner)
572     {
573         ::MoveToEx(hDC, rc.right, 0, NULL);
574         ::LineTo(hDC, rc.right, rc.bottom);
575         ::LineTo(hDC, 0, rc.bottom);
576         ::LineTo(hDC, rc.right, 0);
577     }
578     else
579     {
580         ::MoveToEx(hDC, rc.right, rc.bottom, NULL);
581         ::LineTo(hDC, rc.right, 0);
582         ::LineTo(hDC, 0, 0);
583         ::LineTo(hDC, rc.right, rc.bottom);
584     }
585     ::EndPath(hDC);
586     HRGN hRgn = ::PathToRegion(hDC);
587     ::DeleteDC(hDC);
588 
589     SetWindowRgn(hRgn, TRUE); // set the trianglar region
590 }
591 
592 // WM_ERASEBKGND
593 LRESULT CACSizeBox::OnEraseBkGnd(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
594 {
595     return TRUE; // do nothing (for quick drawing)
596 }
597 
598 // WM_NCHITTEST
599 LRESULT CACSizeBox::OnNCHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
600 {
601     return HTTRANSPARENT; // pass through
602 }
603 
604 // WM_PAINT
605 LRESULT CACSizeBox::OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
606 {
607     CRect rc;
608     GetClientRect(&rc);
609 
610     PAINTSTRUCT ps;
611     HDC hDC = BeginPaint(&ps);
612     if (!hDC)
613         return 0;
614 
615     // fill background
616     ::FillRect(hDC, &rc, ::GetSysColorBrush(COLOR_3DFACE));
617 
618     // draw size-box
619     INT cxy = rc.Width();
620     for (INT shift = 0; shift < 2; ++shift)
621     {
622         // choose pen color
623         INT iColor = ((shift == 0) ? COLOR_HIGHLIGHTTEXT : COLOR_3DSHADOW);
624         HPEN hPen = ::CreatePen(PS_SOLID, 1, ::GetSysColor(iColor));
625         HGDIOBJ hPenOld = ::SelectObject(hDC, hPen);
626         // do loop to draw the slanted lines
627         for (INT delta = cxy / 4; delta < cxy; delta += cxy / 4)
628         {
629             // draw a grip line
630             if (m_bDowner)
631             {
632                 ::MoveToEx(hDC, rc.right, rc.top + delta + shift, NULL);
633                 ::LineTo(hDC, rc.left + delta + shift, rc.bottom);
634             }
635             else
636             {
637                 ::MoveToEx(hDC, rc.left + delta + shift, rc.top, NULL);
638                 ::LineTo(hDC, rc.right, rc.bottom - delta - shift);
639             }
640         }
641         // delete pen
642         ::SelectObject(hDC, hPenOld);
643         ::DeleteObject(hPen);
644     }
645 
646     EndPaint(&ps);
647     return 0;
648 }
649 
650 //////////////////////////////////////////////////////////////////////////////
651 // CAutoComplete public methods
652 
653 CAutoComplete::CAutoComplete()
654     : m_bInSetText(FALSE), m_bInSelectItem(FALSE)
655     , m_bDowner(TRUE), m_dwOptions(ACO_AUTOAPPEND | ACO_AUTOSUGGEST)
656     , m_bEnabled(TRUE), m_hwndCombo(NULL), m_hFont(NULL), m_bResized(FALSE)
657     , m_hwndEdit(NULL), m_fnOldEditProc(NULL), m_fnOldWordBreakProc(NULL)
658     , m_hThread(NULL), m_pThread(NULL)
659 {
660 }
661 
662 HWND CAutoComplete::CreateDropDown()
663 {
664     ATLASSERT(m_hWnd == NULL);
665     DWORD dwStyle = WS_POPUP | /*WS_VISIBLE |*/ WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_BORDER;
666     DWORD dwExStyle = WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_NOPARENTNOTIFY;
667     Create(NULL, NULL, NULL, dwStyle, dwExStyle);
668     TRACE("CAutoComplete::CreateDropDown(%p): m_hWnd=%p, m_hwndEdit=%p\n",
669           this, m_hWnd, m_hwndEdit);
670     return m_hWnd;
671 }
672 
673 CAutoComplete::~CAutoComplete()
674 {
675     TRACE("CAutoComplete::~CAutoComplete(%p)\n", this);
676     if (m_hThread)
677     {
678         CloseHandle(m_hThread);
679         m_hThread = NULL;
680     }
681     if (m_hFont)
682     {
683         ::DeleteObject(m_hFont);
684         m_hFont = NULL;
685     }
686     // quit holding them
687     m_pEnum.Release();
688     m_pACList.Release();
689 }
690 
691 inline BOOL CAutoComplete::CanAutoSuggest() const
692 {
693     return !!(m_dwOptions & ACO_AUTOSUGGEST) && m_bEnabled;
694 }
695 
696 inline BOOL CAutoComplete::CanAutoAppend() const
697 {
698     return !!(m_dwOptions & ACO_AUTOAPPEND) && m_bEnabled;
699 }
700 
701 inline BOOL CAutoComplete::UseTab() const
702 {
703     return !!(m_dwOptions & ACO_USETAB) && m_bEnabled;
704 }
705 
706 inline BOOL CAutoComplete::IsComboBoxDropped() const
707 {
708     if (!::IsWindow(m_hwndCombo))
709         return FALSE;
710     return (BOOL)::SendMessageW(m_hwndCombo, CB_GETDROPPEDSTATE, 0, 0);
711 }
712 
713 inline BOOL CAutoComplete::FilterPrefixes() const
714 {
715     return !!(m_dwOptions & ACO_FILTERPREFIXES) && m_bEnabled;
716 }
717 
718 inline INT CAutoComplete::GetItemCount() const
719 {
720     return m_outerList.GetSize();
721 }
722 
723 CStringW CAutoComplete::GetItemText(INT iItem) const
724 {
725     if (iItem < 0 || m_outerList.GetSize() <= iItem)
726         return L"";
727     return m_outerList[iItem];
728 }
729 
730 inline CStringW CAutoComplete::GetEditText() const
731 {
732     WCHAR szText[L_MAX_URL_LENGTH];
733     if (::GetWindowTextW(m_hwndEdit, szText, _countof(szText)))
734         return szText;
735     return L"";
736 }
737 
738 VOID CAutoComplete::SetEditText(LPCWSTR pszText)
739 {
740     m_bInSetText = TRUE; // don't hide drop-down
741     ::CallWindowProcW(m_fnOldEditProc, m_hwndEdit, WM_SETTEXT, 0, reinterpret_cast<LPARAM>(pszText));
742     m_bInSetText = FALSE;
743 }
744 
745 inline CStringW CAutoComplete::GetStemText(const CStringW& strText) const
746 {
747     INT ich = strText.ReverseFind(L'\\');
748     if (ich == -1)
749         return L""; // no stem
750     return strText.Left(ich + 1);
751 }
752 
753 VOID CAutoComplete::SetEditSel(INT ich0, INT ich1)
754 {
755     ::CallWindowProcW(m_fnOldEditProc, m_hwndEdit, EM_SETSEL, ich0, ich1);
756 }
757 
758 VOID CAutoComplete::ShowDropDown()
759 {
760     if (!m_hWnd || !CanAutoSuggest())
761         return;
762 
763     INT cItems = GetItemCount();
764     if (cItems == 0 || ::GetFocus() != m_hwndEdit || IsComboBoxDropped())
765     {
766         // hide the drop-down if necessary
767         HideDropDown();
768         return;
769     }
770 
771     RepositionDropDown();
772 }
773 
774 VOID CAutoComplete::HideDropDown()
775 {
776     ShowWindow(SW_HIDE);
777 }
778 
779 VOID CAutoComplete::SelectItem(INT iItem)
780 {
781     m_hwndList.SetCurSel(iItem);
782     if (iItem != -1)
783         m_hwndList.EnsureVisible(iItem, FALSE);
784 }
785 
786 VOID CAutoComplete::DoAutoAppend(PAC_THREAD pThread)
787 {
788     if (!CanAutoAppend()) // can we auto-append?
789         return; // don't append
790 
791     CStringW strText = GetEditText(); // get the text
792     if (strText.IsEmpty())
793         return; // don't append
794 
795     INT cItems = m_outerList.GetSize(); // get the number of items
796     if (cItems == 0)
797         return; // don't append
798 
799     // get the common string
800     CStringW strCommon;
801     BOOL bFound = FALSE;
802     for (INT iItem = 0; iItem < cItems; ++iItem)
803     {
804         const CStringW& strItem = m_outerList[iItem]; // get the text of the item
805 
806         CStringW strBody;
807         if (DropPrefix(strItem, strBody) &&
808             ::StrCmpNIW(strBody, strText, strText.GetLength()) == 0)
809         {
810             if (!bFound)
811             {
812                 bFound = TRUE;
813                 strCommon = strBody;
814                 continue;
815             }
816             for (INT ich = 0; ich < strBody.GetLength(); ++ich)
817             {
818                 if (strCommon.GetLength() <= ich)
819                     break;
820                 if (ChrCmpIW(strCommon[ich], strBody[ich]) != 0)
821                 {
822                     strCommon = strCommon.Left(ich);
823                     break;
824                 }
825             }
826             continue;
827         }
828 
829         if (::StrCmpNIW(strItem, strText, strText.GetLength()) == 0)
830         {
831             if (!bFound)
832             {
833                 bFound = TRUE;
834                 strCommon = strItem;
835                 continue;
836             }
837 
838             for (INT ich = 0; ich < strItem.GetLength(); ++ich)
839             {
840                 if (strCommon.GetLength() <= ich)
841                     break;
842                 if (ChrCmpIW(strCommon[ich], strItem[ich]) != 0)
843                 {
844                     strCommon = strCommon.Left(ich);
845                     break;
846                 }
847             }
848         }
849     }
850 
851     if (strCommon.IsEmpty() || strCommon.GetLength() <= strText.GetLength())
852         return; // no suggestion
853 
854     // append suggestion
855     SetEditText(strCommon);
856 
857     // select the appended suggestion
858     SetEditSel(strText.GetLength(), strCommon.GetLength());
859 }
860 
861 // go back a word ([Ctrl]+[Backspace])
862 VOID CAutoComplete::DoBackWord()
863 {
864     // get current selection
865     INT ich0, ich1;
866     ::CallWindowProcW(m_fnOldEditProc, m_hwndEdit, EM_GETSEL,
867                       reinterpret_cast<WPARAM>(&ich0), reinterpret_cast<LPARAM>(&ich1));
868     if (ich0 <= 0 || ich0 != ich1) // there is selection or left-side end
869         return; // don't do anything
870     // get text
871     CStringW str = GetEditText();
872     // extend the range
873     while (ich0 > 0 && IsWordBreak(str[ich0 - 1]))
874         --ich0;
875     while (ich0 > 0 && !IsWordBreak(str[ich0 - 1]))
876         --ich0;
877     // select range
878     SetEditSel(ich0, ich1);
879     // replace selection with empty text (this is actually deletion)
880     ::CallWindowProcW(m_fnOldEditProc, m_hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)L"");
881 }
882 
883 VOID CAutoComplete::UpdateScrollBar()
884 {
885     // copy scroll info from m_hwndList to m_hwndScrollBar
886     SCROLLINFO si = { sizeof(si), SIF_ALL };
887     m_hwndList.GetScrollInfo(SB_VERT, &si);
888     m_hwndScrollBar.SetScrollInfo(SB_CTL, &si, FALSE);
889 
890     // show/hide scroll bar
891     INT cVisibles = m_hwndList.GetVisibleCount();
892     INT cItems = m_hwndList.GetItemCount();
893     BOOL bShowScroll = (cItems > cVisibles);
894     m_hwndScrollBar.ShowWindow(bShowScroll ? SW_SHOWNOACTIVATE : SW_HIDE);
895     if (bShowScroll)
896         m_hwndScrollBar.InvalidateRect(NULL, FALSE); // redraw
897 }
898 
899 BOOL CAutoComplete::OnEditKeyDown(WPARAM wParam, LPARAM lParam)
900 {
901     TRACE("CAutoComplete::OnEditKeyDown(%p, %p)\n", this, wParam);
902 
903     UINT vk = (UINT)wParam; // virtual key
904     switch (vk)
905     {
906         case VK_UP: case VK_DOWN:
907         case VK_PRIOR: case VK_NEXT:
908             // is suggestion available?
909             if (!CanAutoSuggest())
910                 return FALSE; // do default
911             if (IsWindowVisible())
912                 return OnListUpDown(vk);
913             break;
914         case VK_ESCAPE:
915         {
916             // is suggestion available?
917             if (!CanAutoSuggest())
918                 return FALSE; // do default
919             if (IsWindowVisible())
920             {
921                 SetEditText(m_strText); // revert the edit text
922                 // select the end
923                 INT cch = m_strText.GetLength();
924                 SetEditSel(cch, cch);
925                 HideDropDown(); // hide
926                 return TRUE; // eat
927             }
928             break;
929         }
930         case VK_RETURN:
931         {
932             if (::GetKeyState(VK_CONTROL) < 0)
933             {
934                 // quick edit
935                 CStringW strText = GetEditText();
936                 SetEditText(GetQuickEdit(strText));
937             }
938             else
939             {
940                 // if item is selected, then update the edit text
941                 INT iItem = m_hwndList.GetCurSel();
942                 if (iItem != -1)
943                 {
944                     CStringW strText = GetItemText(iItem);
945                     SetEditText(strText);
946                 }
947             }
948             // select all
949             INT cch = ::CallWindowProcW(m_fnOldEditProc, m_hwndEdit, WM_GETTEXTLENGTH, 0, 0);
950             SetEditSel(0, cch);
951             // hide
952             HideDropDown();
953             break;
954         }
955         case VK_TAB:
956         {
957             // ACO_USETAB
958             if (IsWindowVisible() && UseTab())
959             {
960                 if (GetKeyState(VK_SHIFT) < 0)
961                     return OnListUpDown(VK_UP);
962                 else
963                     return OnListUpDown(VK_DOWN);
964             }
965             break;
966         }
967         case VK_DELETE:
968         {
969             // is suggestion available?
970             if (!CanAutoSuggest())
971                 return FALSE; // do default
972             ::DefSubclassProc(m_hwndEdit, WM_KEYDOWN, VK_DELETE, 0); // do default
973             StartCompletion(FALSE);
974             return TRUE; // eat
975         }
976         case VK_BACK:
977         {
978             if (::GetKeyState(VK_CONTROL) < 0)
979             {
980                 DoBackWord();
981                 return TRUE; // eat
982             }
983             break;
984         }
985     }
986     return FALSE; // default
987 }
988 
989 LRESULT CAutoComplete::OnEditChar(WPARAM wParam, LPARAM lParam)
990 {
991     TRACE("CACEditCtrl::OnEditChar(%p, %p)\n", this, wParam);
992     if (wParam == L'\n' || wParam == L'\t')
993         return 0; // eat
994     LRESULT ret = ::DefSubclassProc(m_hwndEdit, WM_CHAR, wParam, lParam); // do default
995     if (CanAutoSuggest() || CanAutoAppend())
996         StartCompletion(wParam != VK_BACK);
997     return ret;
998 }
999 
1000 VOID CAutoComplete::OnListSelChange()
1001 {
1002     // update EDIT text
1003     INT iItem = m_hwndList.GetCurSel();
1004     CStringW text = ((iItem != -1) ? GetItemText(iItem) : m_strText);
1005     SetEditText(text);
1006     // ensure the item visible
1007     m_hwndList.EnsureVisible(iItem, FALSE);
1008     // select the end
1009     INT cch = text.GetLength();
1010     SetEditSel(cch, cch);
1011 }
1012 
1013 BOOL CAutoComplete::OnListUpDown(UINT vk)
1014 {
1015     if (!CanAutoSuggest())
1016         return FALSE; // default
1017 
1018     if ((m_dwOptions & ACO_UPDOWNKEYDROPSLIST) && !IsWindowVisible())
1019     {
1020         ShowDropDown();
1021         return TRUE; // eat
1022     }
1023 
1024     INT iItem = m_hwndList.GetCurSel(); // current selection
1025     INT cItems = m_hwndList.GetItemCount(); // the number of items
1026     switch (vk)
1027     {
1028         case VK_UP:
1029             if (iItem == -1)
1030                 iItem = cItems - 1;
1031             else if (iItem == 0)
1032                 iItem = -1;
1033             else
1034                 --iItem;
1035             m_hwndList.SetCurSel(iItem);
1036             break;
1037         case VK_DOWN:
1038             if (iItem == -1)
1039                 iItem = 0;
1040             else if (iItem == cItems - 1)
1041                 iItem = -1;
1042             else
1043                 ++iItem;
1044             m_hwndList.SetCurSel(iItem);
1045             break;
1046         case VK_PRIOR:
1047             if (iItem == -1)
1048             {
1049                 iItem = cItems - 1;
1050             }
1051             else if (iItem == 0)
1052             {
1053                 iItem = -1;
1054             }
1055             else
1056             {
1057                 iItem -= m_hwndList.GetVisibleCount() - 1;
1058                 if (iItem < 0)
1059                     iItem = 0;
1060             }
1061             m_hwndList.SetCurSel(iItem);
1062             break;
1063         case VK_NEXT:
1064             if (iItem == -1)
1065             {
1066                 iItem = 0;
1067             }
1068             else if (iItem == cItems - 1)
1069             {
1070                 iItem = -1;
1071             }
1072             else
1073             {
1074                 iItem += m_hwndList.GetVisibleCount() - 1;
1075                 if (iItem > cItems)
1076                     iItem = cItems - 1;
1077             }
1078             m_hwndList.SetCurSel(iItem);
1079             break;
1080         default:
1081         {
1082             ATLASSERT(FALSE);
1083             break;
1084         }
1085     }
1086 
1087     return TRUE; // eat
1088 }
1089 
1090 //////////////////////////////////////////////////////////////////////////////
1091 // CAutoComplete IAutoComplete methods
1092 
1093 // @implemented
1094 STDMETHODIMP CAutoComplete::Enable(BOOL fEnable)
1095 {
1096     TRACE("(%p)->Enable(%d)\n", this, fEnable);
1097     m_bEnabled = fEnable;
1098     return S_OK;
1099 }
1100 
1101 STDMETHODIMP
1102 CAutoComplete::Init(HWND hwndEdit, IUnknown *punkACL,
1103                     LPCOLESTR pwszRegKeyPath, LPCOLESTR pwszQuickComplete)
1104 {
1105     TRACE("(%p)->Init(0x%08lx, %p, %s, %s)\n",
1106           this, hwndEdit, punkACL, debugstr_w(pwszRegKeyPath), debugstr_w(pwszQuickComplete));
1107     // sanity check
1108     if (m_hwndEdit || !punkACL)
1109         return E_FAIL;
1110     if (!hwndEdit)
1111         return E_INVALIDARG;
1112     // do subclass textbox to watch messages
1113     m_fnOldEditProc = reinterpret_cast<WNDPROC>(::GetWindowLongPtrW(hwndEdit, GWLP_WNDPROC));
1114     if (!m_fnOldEditProc)
1115         return E_FAIL;
1116     if (!::SetWindowSubclass(hwndEdit, EditSubclassProc, 0, reinterpret_cast<DWORD_PTR>(this)))
1117         return E_FAIL;
1118     m_hwndEdit = hwndEdit;
1119     // add reference to m_hwndEdit
1120     AddRef();
1121     // set word break procedure
1122     m_fnOldWordBreakProc = reinterpret_cast<EDITWORDBREAKPROCW>(
1123         ::SendMessageW(m_hwndEdit, EM_SETWORDBREAKPROC, 0,
1124                        reinterpret_cast<LPARAM>(EditWordBreakProcW)));
1125     // save position
1126     ::GetWindowRect(m_hwndEdit, &m_rcEdit);
1127 
1128     // get an IEnumString
1129     punkACL->QueryInterface(IID_IEnumString, (VOID **)&m_pEnum);
1130     TRACE("m_pEnum: %p\n", static_cast<void *>(m_pEnum));
1131 
1132     // get an IACList
1133     punkACL->QueryInterface(IID_IACList, (VOID **)&m_pACList);
1134     TRACE("m_pACList: %p\n", static_cast<void *>(m_pACList));
1135 
1136     UpdateDropDownState(); // create/hide the drop-down window if necessary
1137 
1138     // load quick completion info
1139     LoadQuickComplete(pwszRegKeyPath, pwszQuickComplete);
1140 
1141     // any combobox for m_hwndEdit?
1142     m_hwndCombo = NULL;
1143     HWND hwndParent = ::GetParent(m_hwndEdit);
1144     WCHAR szClass[16];
1145     if (::GetClassNameW(hwndParent, szClass, _countof(szClass)))
1146     {
1147         if (::StrCmpIW(szClass, WC_COMBOBOXW) == 0 ||
1148             ::StrCmpIW(szClass, WC_COMBOBOXEXW) == 0)
1149         {
1150             m_hwndCombo = hwndParent; // get combobox
1151         }
1152     }
1153 
1154     return S_OK;
1155 }
1156 
1157 //////////////////////////////////////////////////////////////////////////////
1158 // CAutoComplete IAutoComplete2 methods
1159 
1160 // @implemented
1161 STDMETHODIMP CAutoComplete::GetOptions(DWORD *pdwFlag)
1162 {
1163     TRACE("(%p) -> (%p)\n", this, pdwFlag);
1164     if (pdwFlag)
1165     {
1166         *pdwFlag = m_dwOptions;
1167         return S_OK;
1168     }
1169     return E_INVALIDARG;
1170 }
1171 
1172 // @implemented
1173 STDMETHODIMP CAutoComplete::SetOptions(DWORD dwFlag)
1174 {
1175     TRACE("(%p) -> (0x%x)\n", this, dwFlag);
1176     m_dwOptions = dwFlag;
1177 
1178     if (m_dwOptions & ACO_SEARCH)
1179         FIXME(" ACO_SEARCH not supported\n");
1180     if (m_dwOptions & ACO_FILTERPREFIXES)
1181         FIXME(" ACO_FILTERPREFIXES not supported\n");
1182     if (m_dwOptions & ACO_RTLREADING)
1183         FIXME(" ACO_RTLREADING not supported\n");
1184 
1185     UpdateDropDownState(); // create/hide the drop-down window if necessary
1186     return S_OK;
1187 }
1188 
1189 //////////////////////////////////////////////////////////////////////////////
1190 // CAutoComplete IAutoCompleteDropDown methods
1191 
1192 // @implemented
1193 STDMETHODIMP CAutoComplete::GetDropDownStatus(DWORD *pdwFlags, LPWSTR *ppwszString)
1194 {
1195     BOOL dropped = m_hwndList.IsWindowVisible();
1196 
1197     if (pdwFlags)
1198         *pdwFlags = (dropped ? ACDD_VISIBLE : 0);
1199 
1200     if (ppwszString)
1201     {
1202         *ppwszString = NULL;
1203 
1204         if (dropped)
1205         {
1206             // get selected item
1207             INT iItem = m_hwndList.GetCurSel();
1208             if (iItem >= 0)
1209             {
1210                 // get the text of item
1211                 CStringW strText = m_hwndList.GetItemText(iItem);
1212 
1213                 // store to *ppwszString
1214                 SHStrDupW(strText, ppwszString);
1215                 if (*ppwszString == NULL)
1216                     return E_OUTOFMEMORY;
1217             }
1218         }
1219     }
1220 
1221     return S_OK;
1222 }
1223 
1224 STDMETHODIMP CAutoComplete::ResetEnumerator()
1225 {
1226     FIXME("(%p): stub\n", this);
1227 
1228     Reset();
1229     m_hwndList.SendMessageW(LVM_SETITEMCOUNT, 0, 0);
1230     m_outerList.RemoveAll();
1231     return S_OK;
1232 }
1233 
1234 //////////////////////////////////////////////////////////////////////////////
1235 // CAutoComplete IEnumString methods
1236 
1237 // @implemented
1238 STDMETHODIMP CAutoComplete::Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched)
1239 {
1240     TRACE("(%p, %d, %p, %p)\n", this, celt, rgelt, pceltFetched);
1241     if (rgelt)
1242         *rgelt = NULL;
1243     if (*pceltFetched)
1244         *pceltFetched = 0;
1245     if (celt != 1 || !rgelt || !pceltFetched || !m_pEnum)
1246         return E_INVALIDARG;
1247 
1248     LPWSTR pszText = NULL;
1249     HRESULT hr = m_pEnum->Next(1, &pszText, pceltFetched);
1250     if (hr == S_OK)
1251         *rgelt = pszText;
1252     else
1253         ::CoTaskMemFree(pszText);
1254     return hr;
1255 }
1256 
1257 // @implemented
1258 STDMETHODIMP CAutoComplete::Skip(ULONG celt)
1259 {
1260     TRACE("(%p, %d)\n", this, celt);
1261     return E_NOTIMPL;
1262 }
1263 
1264 // @implemented
1265 STDMETHODIMP CAutoComplete::Reset()
1266 {
1267     TRACE("(%p)\n", this);
1268     if (m_pEnum)
1269         return m_pEnum->Reset();
1270     return E_FAIL;
1271 }
1272 
1273 // @implemented
1274 STDMETHODIMP CAutoComplete::Clone(IEnumString **ppOut)
1275 {
1276     TRACE("(%p, %p)\n", this, ppOut);
1277     if (ppOut)
1278         *ppOut = NULL;
1279     return E_NOTIMPL;
1280 }
1281 
1282 //////////////////////////////////////////////////////////////////////////////
1283 // CAutoComplete protected methods
1284 
1285 VOID CAutoComplete::UpdateDropDownState()
1286 {
1287     if (CanAutoSuggest())
1288     {
1289         // create the drop-down window if not existed
1290         if (!m_hWnd)
1291             CreateDropDown();
1292     }
1293     else
1294     {
1295         // hide if existed
1296         if (m_hWnd)
1297             ShowWindow(SW_HIDE);
1298     }
1299 }
1300 
1301 // calculate the positions of the controls
1302 VOID CAutoComplete::CalcRects(BOOL bDowner, RECT& rcList, RECT& rcScrollBar, RECT& rcSizeBox) const
1303 {
1304     // get the client rectangle
1305     RECT rcClient;
1306     GetClientRect(&rcClient);
1307 
1308     // the list
1309     rcList = rcClient;
1310     rcList.right = rcList.left + CX_LIST;
1311 
1312     // the scroll bar
1313     rcScrollBar = rcClient;
1314     rcScrollBar.left = rcClient.right - GetSystemMetrics(SM_CXVSCROLL);
1315     if (bDowner)
1316     {
1317         rcScrollBar.top = 0;
1318         rcScrollBar.bottom = rcClient.bottom - GetSystemMetrics(SM_CYHSCROLL);
1319     }
1320     else
1321     {
1322         rcScrollBar.top = GetSystemMetrics(SM_CYHSCROLL);
1323     }
1324 
1325     // the size box
1326     rcSizeBox = rcClient;
1327     rcSizeBox.left = rcClient.right - GetSystemMetrics(SM_CXVSCROLL);
1328     if (bDowner)
1329     {
1330         rcSizeBox.top = rcClient.bottom - GetSystemMetrics(SM_CYHSCROLL);
1331     }
1332     else
1333     {
1334         rcSizeBox.top = 0;
1335         rcSizeBox.bottom = rcClient.top + GetSystemMetrics(SM_CYHSCROLL);
1336     }
1337 }
1338 
1339 VOID CAutoComplete::LoadQuickComplete(LPCWSTR pwszRegKeyPath, LPCWSTR pwszQuickComplete)
1340 {
1341     m_strQuickComplete.Empty();
1342 
1343     if (pwszRegKeyPath)
1344     {
1345         CStringW strPath = pwszRegKeyPath;
1346         INT ichSep = strPath.ReverseFind(L'\\'); // find separator
1347         if (ichSep != -1) // found the last separator
1348         {
1349             // split by the separator
1350             CStringW strKey = strPath.Left(ichSep);
1351             CStringW strName = strPath.Mid(ichSep + 1);
1352 
1353             // load from registry
1354             WCHAR szValue[MAX_PATH] = L"";
1355             DWORD cbValue = sizeof(szValue), dwType = REG_NONE;
1356             SHRegGetUSValueW(pwszRegKeyPath, strName, &dwType,
1357                              szValue, &cbValue, FALSE, NULL, 0);
1358             if (szValue[0] != 0 && cbValue != 0 &&
1359                 (dwType == REG_SZ || dwType == REG_EXPAND_SZ))
1360             {
1361                 m_strQuickComplete = szValue;
1362             }
1363         }
1364     }
1365 
1366     if (pwszQuickComplete && m_strQuickComplete.IsEmpty())
1367     {
1368         m_strQuickComplete = pwszQuickComplete;
1369     }
1370 }
1371 
1372 CStringW CAutoComplete::GetQuickEdit(LPCWSTR pszText) const
1373 {
1374     if (pszText[0] == 0 || m_strQuickComplete.IsEmpty())
1375         return pszText;
1376 
1377     // m_strQuickComplete will be "www.%s.com" etc.
1378     CStringW ret;
1379     ret.Format(m_strQuickComplete, pszText);
1380     return ret;
1381 }
1382 
1383 VOID CAutoComplete::RepositionDropDown()
1384 {
1385     // get nearest monitor from m_hwndEdit
1386     HMONITOR hMon = ::MonitorFromWindow(m_hwndEdit, MONITOR_DEFAULTTONEAREST);
1387     ATLASSERT(hMon != NULL);
1388     if (hMon == NULL)
1389         return;
1390 
1391     // get nearest monitor info
1392     MONITORINFO mi = { sizeof(mi) };
1393     if (!::GetMonitorInfo(hMon, &mi))
1394     {
1395         ATLASSERT(FALSE);
1396         return;
1397     }
1398 
1399     // get count and item height
1400     INT cItems = GetItemCount();
1401     INT cyItem = m_hwndList.m_cyItem;
1402     ATLASSERT(cyItem > 0);
1403 
1404     // get m_hwndEdit position
1405     RECT rcEdit;
1406     ::GetWindowRect(m_hwndEdit, &rcEdit);
1407     INT x = rcEdit.left, y = rcEdit.bottom;
1408 
1409     // get list extent
1410     RECT rcMon = mi.rcMonitor;
1411     INT cx = rcEdit.right - rcEdit.left, cy = cItems * cyItem;
1412     BOOL bLongList = FALSE;
1413     if (cy > CY_LIST)
1414     {
1415         cy = INT(CY_LIST / cyItem) * cyItem;
1416         bLongList = TRUE;
1417     }
1418 
1419     // convert rectangle for frame
1420     RECT rc = { 0, 0, cx, cy };
1421     AdjustWindowRectEx(&rc, GetStyle(), FALSE, GetExStyle());
1422     cy = rc.bottom - rc.top;
1423 
1424     if (!m_bResized)
1425     {
1426         // is the drop-down window a 'downer' or 'upper'?
1427         // NOTE: 'downer' is below the EDIT control. 'upper' is above the EDIT control.
1428         m_bDowner = (rcEdit.bottom + cy < rcMon.bottom);
1429     }
1430 
1431     // adjust y and cy
1432     if (m_bDowner)
1433     {
1434         if (rcMon.bottom < y + cy)
1435         {
1436             cy = ((rcMon.bottom - y) / cyItem) * cyItem;
1437             bLongList = TRUE;
1438         }
1439     }
1440     else
1441     {
1442         if (rcEdit.top < rcMon.top + cy)
1443         {
1444             cy = ((rcEdit.top - rcMon.top) / cyItem) * cyItem;
1445             bLongList = TRUE;
1446         }
1447         y = rcEdit.top - cy;
1448     }
1449 
1450     // set status
1451     m_hwndSizeBox.SetStatus(m_bDowner, bLongList);
1452 
1453     if (m_bResized) // already resized?
1454         PostMessageW(WM_SIZE, 0, 0); // re-layout
1455     else
1456         MoveWindow(x, y, cx, cy); // move
1457 
1458     // show without activation
1459     ShowWindow(SW_SHOWNOACTIVATE);
1460 }
1461 
1462 VOID
1463 CAutoComplete::ExtractInnerList(CSimpleArray<CStringW>& outerList,
1464                                 const CSimpleArray<CStringW>& innerList,
1465                                 const CString& strText)
1466 {
1467     for (INT iItem = 0; iItem < innerList.GetSize(); ++iItem)
1468     {
1469         if (m_pThread || !m_hThread)
1470             break;
1471 
1472         const CStringW& strTarget = innerList[iItem];
1473         if (DoesMatch(strTarget, strText))
1474         {
1475             outerList.Add(strTarget);
1476 
1477             if (outerList.GetSize() >= MAX_ITEM_COUNT)
1478                 break;
1479         }
1480     }
1481 }
1482 
1483 VOID CAutoComplete::ReLoadInnerList(PAC_THREAD pThread)
1484 {
1485     pThread->m_innerList.RemoveAll(); // clear contents
1486 
1487     if (!m_pEnum || pThread->m_strText.IsEmpty())
1488         return;
1489 
1490     // reload the items
1491     LPWSTR pszItem;
1492     ULONG cGot;
1493     HRESULT hr;
1494     CSimpleArray<CStringW>& innerList = pThread->m_innerList;
1495     while (!m_pThread && m_hThread)
1496     {
1497         // get next item
1498         hr = m_pEnum->Next(1, &pszItem, &cGot);
1499         if (hr != S_OK)
1500             break;
1501 
1502         innerList.Add(pszItem); // append item to innerList
1503         ::CoTaskMemFree(pszItem); // free
1504     }
1505 }
1506 
1507 VOID CAutoComplete::StartCompletion(BOOL bAppendOK)
1508 {
1509     TRACE("CAutoComplete::StartCompletion(%p, %d)\n", this, bAppendOK);
1510 
1511     if (!m_pEnum || (!CanAutoSuggest() && !CanAutoAppend()))
1512         return;
1513 
1514     ::SendMessageW(m_hWnd, AUTOCOMP_START, bAppendOK, 0);
1515 }
1516 
1517 //////////////////////////////////////////////////////////////////////////////
1518 // CAutoComplete message handlers
1519 
1520 // WM_CREATE
1521 // This message is sent when the window is about to be created after WM_NCCREATE.
1522 // The return value is -1 (failure) or zero (success).
1523 LRESULT CAutoComplete::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1524 {
1525     TRACE("CAutoComplete::OnCreate(%p)\n", this);
1526 
1527     // set the pointer of CAutoComplete
1528     m_hwndList.m_pDropDown = this;
1529     m_hwndScrollBar.m_pDropDown = this;
1530     m_hwndSizeBox.m_pDropDown = this;
1531 
1532     // create the children
1533     m_hwndList.Create(m_hWnd);
1534     if (!m_hwndList)
1535         return -1; // failure
1536     m_hwndSizeBox.Create(m_hWnd);
1537     if (!m_hwndSizeBox)
1538         return -1; // failure
1539     m_hwndScrollBar.Create(m_hWnd);
1540     if (!m_hwndScrollBar)
1541         return -1; // failure
1542 
1543     // show the controls
1544     m_hwndList.ShowWindow(SW_SHOWNOACTIVATE);
1545     m_hwndSizeBox.ShowWindow(SW_SHOWNOACTIVATE);
1546     m_hwndScrollBar.ShowWindow(SW_SHOWNOACTIVATE);
1547 
1548     // set the list font
1549     m_hFont = reinterpret_cast<HFONT>(::GetStockObject(DEFAULT_GUI_FONT));
1550     m_hwndList.SetFont(m_hFont);
1551 
1552     // add reference so we won't be deleted during message processing
1553     AddRef();
1554     return 0; // success
1555 }
1556 
1557 // WM_NCDESTROY
1558 LRESULT CAutoComplete::OnNCDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1559 {
1560     TRACE("CAutoComplete::OnNCDestroy(%p)\n", this);
1561 
1562     // hide
1563     if (IsWindowVisible())
1564         HideDropDown();
1565 
1566     // clear CAutoComplete pointers
1567     m_hwndList.m_pDropDown = NULL;
1568     m_hwndScrollBar.m_pDropDown = NULL;
1569     m_hwndSizeBox.m_pDropDown = NULL;
1570 
1571     // destroy controls
1572     m_hwndList.DestroyWindow();
1573     m_hwndScrollBar.DestroyWindow();
1574     m_hwndSizeBox.DestroyWindow();
1575 
1576     // clean up
1577     m_hwndCombo = NULL;
1578 
1579     // Tell ATL to clean up
1580     bHandled = 0;
1581 
1582     return 0;
1583 }
1584 
1585 VOID CAutoComplete::OnFinalMessage(HWND)
1586 {
1587     // The message loop is finished, now we can safely destruct!
1588     Release();
1589 }
1590 
1591 // WM_EXITSIZEMOVE
1592 // This message is sent once to a window after it has exited the moving or sizing mode.
1593 LRESULT CAutoComplete::OnExitSizeMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1594 {
1595     TRACE("CAutoComplete::OnExitSizeMove(%p)\n", this);
1596     m_bResized = TRUE; // remember resized
1597 
1598     ModifyStyle(WS_THICKFRAME, 0); // remove thick frame to resize
1599     // frame changed
1600     UINT uSWP_ = SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE;
1601     SetWindowPos(NULL, 0, 0, 0, 0, uSWP_);
1602 
1603     ::SetFocus(m_hwndEdit); // restore focus
1604     return 0;
1605 }
1606 
1607 // WM_DRAWITEM @implemented
1608 // This message is sent to the owner window to draw m_hwndList.
1609 LRESULT CAutoComplete::OnDrawItem(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1610 {
1611     LPDRAWITEMSTRUCT pDraw = reinterpret_cast<LPDRAWITEMSTRUCT>(lParam);
1612     ATLASSERT(pDraw != NULL);
1613     ATLASSERT(m_hwndList.GetStyle() & LVS_OWNERDRAWFIXED);
1614 
1615     // sanity check
1616     if (pDraw->CtlType != ODT_LISTVIEW || pDraw->hwndItem != m_hwndList)
1617         return FALSE;
1618 
1619     // item rectangle
1620     RECT rcItem = pDraw->rcItem;
1621 
1622     // get info
1623     UINT iItem = pDraw->itemID; // the index of item
1624     CStringW strItem = m_hwndList.GetItemText(iItem); // get text of item
1625 
1626     // draw background and set text color
1627     HDC hDC = pDraw->hDC;
1628     BOOL bSelected = (pDraw->itemState & ODS_SELECTED);
1629     if (bSelected)
1630     {
1631         ::FillRect(hDC, &rcItem, ::GetSysColorBrush(COLOR_HIGHLIGHT));
1632         ::SetTextColor(hDC, ::GetSysColor(COLOR_HIGHLIGHTTEXT));
1633     }
1634     else
1635     {
1636         ::FillRect(hDC, &rcItem, ::GetSysColorBrush(COLOR_WINDOW));
1637         ::SetTextColor(hDC, ::GetSysColor(COLOR_WINDOWTEXT));
1638     }
1639 
1640     // draw text
1641     rcItem.left += ::GetSystemMetrics(SM_CXBORDER);
1642     HGDIOBJ hFontOld = ::SelectObject(hDC, m_hFont);
1643     const UINT uDT_ = DT_LEFT | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER;
1644     ::SetBkMode(hDC, TRANSPARENT);
1645     ::DrawTextW(hDC, strItem, -1, &rcItem, uDT_);
1646     ::SelectObject(hDC, hFontOld);
1647 
1648     return TRUE;
1649 }
1650 
1651 // WM_GETMINMAXINFO @implemented
1652 // This message is sent to a window when the size or position of the window is about to change.
1653 LRESULT CAutoComplete::OnGetMinMaxInfo(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1654 {
1655     // restrict minimum size
1656     LPMINMAXINFO pInfo = reinterpret_cast<LPMINMAXINFO>(lParam);
1657     pInfo->ptMinTrackSize.x = ::GetSystemMetrics(SM_CXVSCROLL);
1658     pInfo->ptMinTrackSize.y = ::GetSystemMetrics(SM_CYHSCROLL);
1659     return 0;
1660 }
1661 
1662 // WM_MEASUREITEM @implemented
1663 // This message is sent to the owner window to get the item extent of m_hwndList.
1664 LRESULT CAutoComplete::OnMeasureItem(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1665 {
1666     LPMEASUREITEMSTRUCT pMeasure = reinterpret_cast<LPMEASUREITEMSTRUCT>(lParam);
1667     ATLASSERT(pMeasure != NULL);
1668     if (pMeasure->CtlType != ODT_LISTVIEW)
1669         return FALSE;
1670     if (!m_hwndList)
1671         return FALSE;
1672     ATLASSERT(m_hwndList.GetStyle() & LVS_OWNERDRAWFIXED);
1673     pMeasure->itemHeight = m_hwndList.m_cyItem; // height of item
1674     return TRUE;
1675 }
1676 
1677 // WM_MOUSEACTIVATE @implemented
1678 // The return value of this message specifies whether the window should be activated or not.
1679 LRESULT CAutoComplete::OnMouseActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1680 {
1681     return MA_NOACTIVATE; // don't activate by mouse
1682 }
1683 
1684 // WM_NCACTIVATE
1685 // This message is sent to a window to indicate an active or inactive state.
1686 LRESULT CAutoComplete::OnNCActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1687 {
1688     bHandled = FALSE; // do default
1689     return 0;
1690 }
1691 
1692 // WM_NCLBUTTONDOWN
1693 LRESULT CAutoComplete::OnNCLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1694 {
1695     switch (wParam)
1696     {
1697         case HTBOTTOMRIGHT: case HTTOPRIGHT:
1698         {
1699             // add thick frame to resize.
1700             ModifyStyle(0, WS_THICKFRAME);
1701             // frame changed
1702             UINT uSWP_ = SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE;
1703             SetWindowPos(NULL, 0, 0, 0, 0, uSWP_);
1704             break;
1705         }
1706     }
1707     bHandled = FALSE; // do default
1708     return 0;
1709 }
1710 
1711 // WM_NOTIFY
1712 // This message informs the parent window of a control that an event has occurred.
1713 LRESULT CAutoComplete::OnNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1714 {
1715     LPNMHDR pnmh = reinterpret_cast<LPNMHDR>(lParam);
1716     ATLASSERT(pnmh != NULL);
1717 
1718     switch (pnmh->code)
1719     {
1720         case NM_DBLCLK: // double-clicked
1721         {
1722             TRACE("NM_DBLCLK\n");
1723             HideDropDown();
1724             break;
1725         }
1726         case NM_HOVER: // mouse is hovering
1727         {
1728             POINT pt;
1729             ::GetCursorPos(&pt); // get cursor position in screen coordinates
1730             m_hwndList.ScreenToClient(&pt); // into client coordinates
1731             INT iItem = m_hwndList.ItemFromPoint(pt.x, pt.y);
1732             if (iItem != -1)
1733             {
1734                 m_bInSelectItem = TRUE; // don't respond
1735                 m_hwndList.SetCurSel(iItem); // select
1736                 m_bInSelectItem = FALSE;
1737             }
1738             return TRUE; // eat
1739         }
1740         case LVN_GETDISPINFOA: // for user's information only
1741         {
1742             TRACE("LVN_GETDISPINFOA\n");
1743             if (pnmh->hwndFrom != m_hwndList)
1744                 break;
1745 
1746             LV_DISPINFOA *pDispInfo = reinterpret_cast<LV_DISPINFOA *>(pnmh);
1747             LV_ITEMA *pItem = &pDispInfo->item;
1748             INT iItem = pItem->iItem;
1749             if (iItem == -1)
1750                 break;
1751 
1752             CStringW strText = GetItemText(iItem);
1753             if (pItem->mask & LVIF_TEXT)
1754                 SHUnicodeToAnsi(strText, pItem->pszText, pItem->cchTextMax);
1755             break;
1756         }
1757         case LVN_GETDISPINFOW: // for user's information only
1758         {
1759             TRACE("LVN_GETDISPINFOW\n");
1760             if (pnmh->hwndFrom != m_hwndList)
1761                 break;
1762 
1763             LV_DISPINFOW *pDispInfo = reinterpret_cast<LV_DISPINFOW *>(pnmh);
1764             LV_ITEMW *pItem = &pDispInfo->item;
1765             INT iItem = pItem->iItem;
1766             if (iItem == -1)
1767                 break;
1768 
1769             CStringW strText = GetItemText(iItem);
1770             if (pItem->mask & LVIF_TEXT)
1771                 StringCbCopyW(pItem->pszText, pItem->cchTextMax, strText);
1772             break;
1773         }
1774         case LVN_HOTTRACK: // enabled by LVS_EX_TRACKSELECT
1775         {
1776             TRACE("LVN_HOTTRACK\n");
1777             LPNMLISTVIEW pListView = reinterpret_cast<LPNMLISTVIEW>(pnmh);
1778             INT iItem = pListView->iItem;
1779             TRACE("LVN_HOTTRACK: iItem:%d\n", iItem);
1780             m_hwndList.SetCurSel(iItem);
1781             m_hwndList.EnsureVisible(iItem, FALSE);
1782             return TRUE;
1783         }
1784         case LVN_ITEMACTIVATE: // enabled by LVS_EX_ONECLICKACTIVATE
1785         {
1786             TRACE("LVN_ITEMACTIVATE\n");
1787             LPNMITEMACTIVATE pItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pnmh);
1788             INT iItem = pItemActivate->iItem;
1789             TRACE("LVN_ITEMACTIVATE: iItem:%d\n", iItem);
1790             if (iItem != -1) // the item is clicked
1791             {
1792                 SelectItem(iItem);
1793                 HideDropDown();
1794             }
1795             break;
1796         }
1797         case LVN_ITEMCHANGED: // item info is changed
1798         {
1799             TRACE("LVN_ITEMCHANGED\n");
1800             LPNMLISTVIEW pListView = reinterpret_cast<LPNMLISTVIEW>(pnmh);
1801             if (pListView->uChanged & LVIF_STATE) // selection changed
1802             {
1803                 // listview selection changed
1804                 if (!m_bInSelectItem)
1805                 {
1806                     OnListSelChange();
1807                 }
1808                 UpdateScrollBar();
1809             }
1810             break;
1811         }
1812     }
1813 
1814     return 0;
1815 }
1816 
1817 // WM_NCHITTEST @implemented
1818 // The return value is indicating the cursor shape and the behaviour.
1819 LRESULT CAutoComplete::OnNCHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1820 {
1821     TRACE("CAutoComplete::OnNCHitTest(%p)\n", this);
1822     POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; // in screen coordinates
1823     ScreenToClient(&pt); // into client coordinates
1824     if (ChildWindowFromPoint(pt) == m_hwndSizeBox) // hit?
1825     {
1826         // allow resizing (with cursor shape)
1827         return m_bDowner ? HTBOTTOMRIGHT : HTTOPRIGHT;
1828     }
1829     bHandled = FALSE; // do default
1830     return 0;
1831 }
1832 
1833 // WM_SIZE @implemented
1834 // This message is sent when the window size is changed.
1835 LRESULT CAutoComplete::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1836 {
1837     // calculate the positions of the controls
1838     CRect rcList, rcScrollBar, rcSizeBox;
1839     CalcRects(m_bDowner, rcList, rcScrollBar, rcSizeBox);
1840 
1841     // reposition the controls in smartest way
1842     UINT uSWP_ = SWP_NOACTIVATE | SWP_NOCOPYBITS;
1843     HDWP hDWP = ::BeginDeferWindowPos(3);
1844     hDWP = ::DeferWindowPos(hDWP, m_hwndScrollBar, HWND_TOP,
1845                             rcScrollBar.left, rcScrollBar.top,
1846                             rcScrollBar.Width(), rcScrollBar.Height(), uSWP_);
1847     hDWP = ::DeferWindowPos(hDWP, m_hwndSizeBox, m_hwndScrollBar,
1848                             rcSizeBox.left, rcSizeBox.top,
1849                             rcSizeBox.Width(), rcSizeBox.Height(), uSWP_);
1850     hDWP = ::DeferWindowPos(hDWP, m_hwndList, m_hwndSizeBox,
1851                             rcList.left, rcList.top,
1852                             rcList.Width(), rcList.Height(), uSWP_);
1853     ::EndDeferWindowPos(hDWP);
1854 
1855     UpdateScrollBar();
1856     return 0;
1857 }
1858 
1859 // WM_SHOWWINDOW
1860 LRESULT CAutoComplete::OnShowWindow(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1861 {
1862     // hook mouse events
1863     BOOL bShow = (BOOL)wParam;
1864     if (bShow)
1865     {
1866         if (s_hWatchWnd != m_hWnd && ::IsWindowVisible(s_hWatchWnd))
1867             ::ShowWindowAsync(s_hWatchWnd, SW_HIDE);
1868         s_hWatchWnd = m_hWnd; // watch this
1869 
1870         // unhook mouse if any
1871         if (s_hMouseHook)
1872         {
1873             HHOOK hHookOld = s_hMouseHook;
1874             s_hMouseHook = NULL;
1875             ::UnhookWindowsHookEx(hHookOld);
1876         }
1877 
1878         // hook mouse
1879         s_hMouseHook = ::SetWindowsHookEx(WH_MOUSE, MouseProc, NULL, ::GetCurrentThreadId());
1880         ATLASSERT(s_hMouseHook != NULL);
1881 
1882         // set timer
1883         SetTimer(WATCH_TIMER_ID, WATCH_INTERVAL, NULL);
1884 
1885         bHandled = FALSE; // do default
1886         return 0;
1887     }
1888     else
1889     {
1890         // kill timer
1891         KillTimer(WATCH_TIMER_ID);
1892 
1893         s_hWatchWnd = NULL; // unwatch
1894 
1895         // unhook mouse if any
1896         if (s_hMouseHook)
1897         {
1898             HHOOK hHookOld = s_hMouseHook;
1899             s_hMouseHook = NULL;
1900             ::UnhookWindowsHookEx(hHookOld);
1901         }
1902 
1903         LRESULT ret = DefWindowProcW(uMsg, wParam, lParam); // do default
1904 
1905         if (m_hwndCombo)
1906             ::InvalidateRect(m_hwndCombo, NULL, TRUE); // redraw
1907 
1908         m_outerList.RemoveAll(); // no use
1909         return ret;
1910     }
1911 }
1912 
1913 // WM_TIMER
1914 LRESULT CAutoComplete::OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1915 {
1916     if (wParam != WATCH_TIMER_ID) // sanity check
1917         return 0;
1918 
1919     // if the textbox is dead, then kill the timer
1920     if (!::IsWindow(m_hwndEdit))
1921     {
1922         KillTimer(WATCH_TIMER_ID);
1923         return 0;
1924     }
1925 
1926     // m_hwndEdit is moved?
1927     RECT rcEdit;
1928     ::GetWindowRect(m_hwndEdit, &rcEdit);
1929     if (!::EqualRect(&rcEdit, &m_rcEdit))
1930     {
1931         // if so, hide
1932         HideDropDown();
1933 
1934         m_rcEdit = rcEdit; // update rectangle
1935         m_bResized = FALSE; // clear flag
1936     }
1937 
1938     return 0;
1939 }
1940 
1941 // WM_VSCROLL
1942 // This message is sent when a scroll event occurs.
1943 LRESULT CAutoComplete::OnVScroll(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1944 {
1945     TRACE("CAutoComplete::OnVScroll(%p)\n", this);
1946     WORD code = LOWORD(wParam);
1947     switch (code)
1948     {
1949         case SB_THUMBPOSITION: case SB_THUMBTRACK:
1950         {
1951             // get the scrolling info
1952             INT nPos = HIWORD(wParam);
1953             SCROLLINFO si = { sizeof(si), SIF_ALL };
1954             m_hwndList.GetScrollInfo(SB_VERT, &si);
1955 
1956             // scroll the list-view by CListView::EnsureVisible
1957             INT cItems = m_hwndList.GetItemCount();
1958             // iItem : cItems == (nPos - si.nMin) : (si.nMax - si.nMin).
1959             INT iItem = cItems * (nPos - si.nMin) / (si.nMax - si.nMin);
1960             if (nPos > si.nPos)
1961             {
1962                 iItem += m_hwndList.GetVisibleCount();
1963                 if (iItem >= cItems)
1964                     iItem = cItems - 1;
1965             }
1966             m_hwndList.EnsureVisible(iItem, FALSE);
1967 
1968             // update scrolling position of m_hwndScrollBar
1969             si.fMask = SIF_POS;
1970             m_hwndList.GetScrollInfo(SB_VERT, &si);
1971             m_hwndScrollBar.SetScrollInfo(SB_VERT, &si, FALSE);
1972             break;
1973         }
1974         default:
1975         {
1976             // pass it to m_hwndList
1977             m_hwndList.SendMessageW(WM_VSCROLL, wParam, lParam);
1978             UpdateScrollBar();
1979             break;
1980         }
1981     }
1982     return 0;
1983 }
1984 
1985 static inline PAC_THREAD
1986 InterlockedExchangeThreadData(volatile PAC_THREAD *Target, PAC_THREAD Value)
1987 {
1988     return reinterpret_cast<PAC_THREAD>(
1989         ::InterlockedExchangePointer(reinterpret_cast<volatile PVOID *>(Target), Value));
1990 }
1991 
1992 static unsigned __stdcall AutoCompThreadProc(void *arg)
1993 {
1994     CAutoComplete* pThis = reinterpret_cast<CAutoComplete*>(arg);
1995     pThis->AutoCompThreadProc();
1996     return 0;
1997 }
1998 
1999 VOID CAutoComplete::AutoCompThreadProc()
2000 {
2001     for (;;)
2002     {
2003         PAC_THREAD pThread = InterlockedExchangeThreadData(&m_pThread, NULL);
2004         if (!pThread)
2005             break;
2006         DoThreadWork(pThread);
2007     }
2008 }
2009 
2010 VOID CAutoComplete::DoThreadWork(PAC_THREAD pThread)
2011 {
2012     if (pThread->m_bExpand || m_innerList.GetSize() == 0)
2013     {
2014         ReLoadInnerList(pThread);
2015     }
2016     else
2017     {
2018         pThread->m_innerList = m_innerList;
2019     }
2020 
2021     if (m_pThread || !m_hThread)
2022     {
2023         delete pThread;
2024         return;
2025     }
2026 
2027     ExtractInnerList(pThread->m_outerList, pThread->m_innerList, pThread->m_strText);
2028 
2029     if (m_pThread || !m_hThread)
2030     {
2031         delete pThread;
2032         return;
2033     }
2034 
2035     DoSort(pThread->m_outerList);
2036     DoUniqueAndTrim(pThread->m_outerList);
2037 
2038     if (m_pThread || !m_hThread ||
2039         !::PostMessageW(m_hWnd, AUTOCOMP_FINISH, 0, (LPARAM)pThread))
2040     {
2041         delete pThread;
2042     }
2043 }
2044 
2045 // AUTOCOMP_START
2046 LRESULT CAutoComplete::OnAutoCompStart(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
2047 {
2048     BOOL bAppendOK = (BOOL)wParam;
2049 
2050     CStringW strText = GetEditText();
2051     if (m_strText.CompareNoCase(strText) == 0)
2052     {
2053         // no change
2054         return 0;
2055     }
2056 
2057     PAC_THREAD pThread = new AC_THREAD { this, bAppendOK, strText };
2058 
2059     // if previous text was empty
2060     if (m_strText.IsEmpty())
2061     {
2062         pThread->m_bReset = TRUE;
2063     }
2064     m_strText = strText;
2065 
2066     // do expand the items if the stem is changed
2067     CStringW strStemText = GetStemText(pThread->m_strText);
2068     if (m_strStemText.CompareNoCase(strStemText) != 0)
2069     {
2070         pThread->m_bReset = TRUE;
2071         pThread->m_bExpand = !strStemText.IsEmpty();
2072         m_strStemText = strStemText;
2073     }
2074 
2075     // reset if necessary
2076     if (pThread->m_bReset && m_pEnum)
2077     {
2078         HRESULT hr = m_pEnum->Reset(); // IEnumString::Reset
2079         TRACE("m_pEnum->Reset(%p): 0x%08lx\n",
2080               static_cast<IUnknown *>(m_pEnum), hr);
2081     }
2082 
2083     // update ac list if necessary
2084     if (pThread->m_bExpand && m_pACList)
2085     {
2086         HRESULT hr = m_pACList->Expand(strStemText); // IACList::Expand
2087         TRACE("m_pACList->Expand(%p, %S): 0x%08lx\n",
2088               static_cast<IUnknown *>(m_pACList),
2089               static_cast<LPCWSTR>(strStemText), hr);
2090     }
2091 
2092     PAC_THREAD pOld = InterlockedExchangeThreadData(&m_pThread, pThread);
2093     if (pOld)
2094         delete pOld;
2095 
2096     BOOL bDoStart = FALSE;
2097     if (m_hThread)
2098     {
2099         if (WaitForSingleObject(m_hThread, 0) != WAIT_TIMEOUT)
2100         {
2101             CloseHandle(m_hThread);
2102             m_hThread = NULL;
2103             bDoStart = TRUE;
2104         }
2105     }
2106     else
2107     {
2108         bDoStart = TRUE;
2109     }
2110 
2111     if (bDoStart)
2112         m_hThread = (HANDLE)_beginthreadex(NULL, 0, ::AutoCompThreadProc, this, 0, NULL);
2113 
2114     return 0;
2115 }
2116 
2117 // AUTOCOMP_FINISH
2118 LRESULT CAutoComplete::OnAutoCompFinish(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
2119 {
2120     PAC_THREAD pThread = reinterpret_cast<PAC_THREAD>(lParam);
2121     if (m_pThread == NULL)
2122     {
2123         FinishCompletion(pThread);
2124     }
2125     CloseHandle(m_hThread);
2126     m_hThread = NULL;
2127     delete pThread;
2128     return 0;
2129 }
2130 
2131 VOID CAutoComplete::FinishCompletion(PAC_THREAD pThread)
2132 {
2133     if (m_pThread || !m_hThread)
2134         return;
2135 
2136     if (!CanAutoSuggest() && !CanAutoAppend())
2137         return;
2138 
2139     // set inner list
2140     m_innerList = pThread->m_innerList;
2141 
2142     // set the items of the virtual listview
2143     m_outerList = pThread->m_outerList; // FIXME: We need more speed!
2144     m_hwndList.SendMessageW(LVM_SETITEMCOUNT, m_outerList.GetSize(), 0);
2145 
2146     // save text
2147     m_strText = pThread->m_strText;
2148     m_strStemText = GetStemText(m_strText);
2149 
2150     if (CanAutoSuggest()) // can we auto-suggest?
2151     {
2152         m_bInSelectItem = TRUE; // don't respond
2153         SelectItem(-1); // select none
2154         m_bInSelectItem = FALSE;
2155 
2156         if (m_outerList.GetSize() > 0)
2157             RepositionDropDown();
2158         else
2159             HideDropDown();
2160     }
2161 
2162     if (CanAutoAppend() && pThread->m_bAppendOK) // can we auto-append?
2163     {
2164         DoAutoAppend(pThread);
2165     }
2166 }
2167