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(AC_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     ATLASSERT(!m_pEnum);
1130     punkACL->QueryInterface(IID_IEnumString, (VOID **)&m_pEnum);
1131     TRACE("m_pEnum: %p\n", static_cast<void *>(m_pEnum));
1132     if (m_pEnum)
1133         m_pEnum->AddRef(); // hold not to be freed
1134 
1135     // get an IACList
1136     ATLASSERT(!m_pACList);
1137     punkACL->QueryInterface(IID_IACList, (VOID **)&m_pACList);
1138     TRACE("m_pACList: %p\n", static_cast<void *>(m_pACList));
1139     if (m_pACList)
1140         m_pACList->AddRef(); // hold not to be freed
1141 
1142     UpdateDropDownState(); // create/hide the drop-down window if necessary
1143 
1144     // load quick completion info
1145     LoadQuickComplete(pwszRegKeyPath, pwszQuickComplete);
1146 
1147     // any combobox for m_hwndEdit?
1148     m_hwndCombo = NULL;
1149     HWND hwndParent = ::GetParent(m_hwndEdit);
1150     WCHAR szClass[16];
1151     if (::GetClassNameW(hwndParent, szClass, _countof(szClass)))
1152     {
1153         if (::StrCmpIW(szClass, WC_COMBOBOXW) == 0 ||
1154             ::StrCmpIW(szClass, WC_COMBOBOXEXW) == 0)
1155         {
1156             m_hwndCombo = hwndParent; // get combobox
1157         }
1158     }
1159 
1160     return S_OK;
1161 }
1162 
1163 //////////////////////////////////////////////////////////////////////////////
1164 // CAutoComplete IAutoComplete2 methods
1165 
1166 // @implemented
1167 STDMETHODIMP CAutoComplete::GetOptions(DWORD *pdwFlag)
1168 {
1169     TRACE("(%p) -> (%p)\n", this, pdwFlag);
1170     if (pdwFlag)
1171     {
1172         *pdwFlag = m_dwOptions;
1173         return S_OK;
1174     }
1175     return E_INVALIDARG;
1176 }
1177 
1178 // @implemented
1179 STDMETHODIMP CAutoComplete::SetOptions(DWORD dwFlag)
1180 {
1181     TRACE("(%p) -> (0x%x)\n", this, dwFlag);
1182     m_dwOptions = dwFlag;
1183 
1184     if (m_dwOptions & ACO_SEARCH)
1185         FIXME(" ACO_SEARCH not supported\n");
1186     if (m_dwOptions & ACO_FILTERPREFIXES)
1187         FIXME(" ACO_FILTERPREFIXES not supported\n");
1188     if (m_dwOptions & ACO_RTLREADING)
1189         FIXME(" ACO_RTLREADING not supported\n");
1190 
1191     UpdateDropDownState(); // create/hide the drop-down window if necessary
1192     return S_OK;
1193 }
1194 
1195 //////////////////////////////////////////////////////////////////////////////
1196 // CAutoComplete IAutoCompleteDropDown methods
1197 
1198 // @implemented
1199 STDMETHODIMP CAutoComplete::GetDropDownStatus(DWORD *pdwFlags, LPWSTR *ppwszString)
1200 {
1201     BOOL dropped = m_hwndList.IsWindowVisible();
1202 
1203     if (pdwFlags)
1204         *pdwFlags = (dropped ? ACDD_VISIBLE : 0);
1205 
1206     if (ppwszString)
1207     {
1208         *ppwszString = NULL;
1209 
1210         if (dropped)
1211         {
1212             // get selected item
1213             INT iItem = m_hwndList.GetCurSel();
1214             if (iItem >= 0)
1215             {
1216                 // get the text of item
1217                 CStringW strText = m_hwndList.GetItemText(iItem);
1218 
1219                 // store to *ppwszString
1220                 SHStrDupW(strText, ppwszString);
1221                 if (*ppwszString == NULL)
1222                     return E_OUTOFMEMORY;
1223             }
1224         }
1225     }
1226 
1227     return S_OK;
1228 }
1229 
1230 STDMETHODIMP CAutoComplete::ResetEnumerator()
1231 {
1232     FIXME("(%p): stub\n", this);
1233 
1234     Reset();
1235     m_hwndList.SendMessageW(LVM_SETITEMCOUNT, 0, 0);
1236     m_outerList.RemoveAll();
1237     return S_OK;
1238 }
1239 
1240 //////////////////////////////////////////////////////////////////////////////
1241 // CAutoComplete IEnumString methods
1242 
1243 // @implemented
1244 STDMETHODIMP CAutoComplete::Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched)
1245 {
1246     TRACE("(%p, %d, %p, %p)\n", this, celt, rgelt, pceltFetched);
1247     if (rgelt)
1248         *rgelt = NULL;
1249     if (*pceltFetched)
1250         *pceltFetched = 0;
1251     if (celt != 1 || !rgelt || !pceltFetched || !m_pEnum)
1252         return E_INVALIDARG;
1253 
1254     LPWSTR pszText = NULL;
1255     HRESULT hr = m_pEnum->Next(1, &pszText, pceltFetched);
1256     if (hr == S_OK)
1257         *rgelt = pszText;
1258     else
1259         ::CoTaskMemFree(pszText);
1260     return hr;
1261 }
1262 
1263 // @implemented
1264 STDMETHODIMP CAutoComplete::Skip(ULONG celt)
1265 {
1266     TRACE("(%p, %d)\n", this, celt);
1267     return E_NOTIMPL;
1268 }
1269 
1270 // @implemented
1271 STDMETHODIMP CAutoComplete::Reset()
1272 {
1273     TRACE("(%p)\n", this);
1274     if (m_pEnum)
1275         return m_pEnum->Reset();
1276     return E_FAIL;
1277 }
1278 
1279 // @implemented
1280 STDMETHODIMP CAutoComplete::Clone(IEnumString **ppOut)
1281 {
1282     TRACE("(%p, %p)\n", this, ppOut);
1283     if (ppOut)
1284         *ppOut = NULL;
1285     return E_NOTIMPL;
1286 }
1287 
1288 //////////////////////////////////////////////////////////////////////////////
1289 // CAutoComplete protected methods
1290 
1291 VOID CAutoComplete::UpdateDropDownState()
1292 {
1293     if (CanAutoSuggest())
1294     {
1295         // create the drop-down window if not existed
1296         if (!m_hWnd)
1297             CreateDropDown();
1298     }
1299     else
1300     {
1301         // hide if existed
1302         if (m_hWnd)
1303             ShowWindow(SW_HIDE);
1304     }
1305 }
1306 
1307 // calculate the positions of the controls
1308 VOID CAutoComplete::CalcRects(BOOL bDowner, RECT& rcList, RECT& rcScrollBar, RECT& rcSizeBox) const
1309 {
1310     // get the client rectangle
1311     RECT rcClient;
1312     GetClientRect(&rcClient);
1313 
1314     // the list
1315     rcList = rcClient;
1316     rcList.right = rcList.left + CX_LIST;
1317 
1318     // the scroll bar
1319     rcScrollBar = rcClient;
1320     rcScrollBar.left = rcClient.right - GetSystemMetrics(SM_CXVSCROLL);
1321     if (bDowner)
1322     {
1323         rcScrollBar.top = 0;
1324         rcScrollBar.bottom = rcClient.bottom - GetSystemMetrics(SM_CYHSCROLL);
1325     }
1326     else
1327     {
1328         rcScrollBar.top = GetSystemMetrics(SM_CYHSCROLL);
1329     }
1330 
1331     // the size box
1332     rcSizeBox = rcClient;
1333     rcSizeBox.left = rcClient.right - GetSystemMetrics(SM_CXVSCROLL);
1334     if (bDowner)
1335     {
1336         rcSizeBox.top = rcClient.bottom - GetSystemMetrics(SM_CYHSCROLL);
1337     }
1338     else
1339     {
1340         rcSizeBox.top = 0;
1341         rcSizeBox.bottom = rcClient.top + GetSystemMetrics(SM_CYHSCROLL);
1342     }
1343 }
1344 
1345 VOID CAutoComplete::LoadQuickComplete(LPCWSTR pwszRegKeyPath, LPCWSTR pwszQuickComplete)
1346 {
1347     m_strQuickComplete.Empty();
1348 
1349     if (pwszRegKeyPath)
1350     {
1351         CStringW strPath = pwszRegKeyPath;
1352         INT ichSep = strPath.ReverseFind(L'\\'); // find separator
1353         if (ichSep != -1) // found the last separator
1354         {
1355             // split by the separator
1356             CStringW strKey = strPath.Left(ichSep);
1357             CStringW strName = strPath.Mid(ichSep + 1);
1358 
1359             // load from registry
1360             WCHAR szValue[MAX_PATH] = L"";
1361             DWORD cbValue = sizeof(szValue), dwType = REG_NONE;
1362             SHRegGetUSValueW(pwszRegKeyPath, strName, &dwType,
1363                              szValue, &cbValue, FALSE, NULL, 0);
1364             if (szValue[0] != 0 && cbValue != 0 &&
1365                 (dwType == REG_SZ || dwType == REG_EXPAND_SZ))
1366             {
1367                 m_strQuickComplete = szValue;
1368             }
1369         }
1370     }
1371 
1372     if (pwszQuickComplete && m_strQuickComplete.IsEmpty())
1373     {
1374         m_strQuickComplete = pwszQuickComplete;
1375     }
1376 }
1377 
1378 CStringW CAutoComplete::GetQuickEdit(LPCWSTR pszText) const
1379 {
1380     if (pszText[0] == 0 || m_strQuickComplete.IsEmpty())
1381         return pszText;
1382 
1383     // m_strQuickComplete will be "www.%s.com" etc.
1384     CStringW ret;
1385     ret.Format(m_strQuickComplete, pszText);
1386     return ret;
1387 }
1388 
1389 VOID CAutoComplete::RepositionDropDown()
1390 {
1391     // get nearest monitor from m_hwndEdit
1392     HMONITOR hMon = ::MonitorFromWindow(m_hwndEdit, MONITOR_DEFAULTTONEAREST);
1393     ATLASSERT(hMon != NULL);
1394     if (hMon == NULL)
1395         return;
1396 
1397     // get nearest monitor info
1398     MONITORINFO mi = { sizeof(mi) };
1399     if (!::GetMonitorInfo(hMon, &mi))
1400     {
1401         ATLASSERT(FALSE);
1402         return;
1403     }
1404 
1405     // get count and item height
1406     INT cItems = GetItemCount();
1407     INT cyItem = m_hwndList.m_cyItem;
1408     ATLASSERT(cyItem > 0);
1409 
1410     // get m_hwndEdit position
1411     RECT rcEdit;
1412     ::GetWindowRect(m_hwndEdit, &rcEdit);
1413     INT x = rcEdit.left, y = rcEdit.bottom;
1414 
1415     // get list extent
1416     RECT rcMon = mi.rcMonitor;
1417     INT cx = rcEdit.right - rcEdit.left, cy = cItems * cyItem;
1418     BOOL bLongList = FALSE;
1419     if (cy > CY_LIST)
1420     {
1421         cy = INT(CY_LIST / cyItem) * cyItem;
1422         bLongList = TRUE;
1423     }
1424 
1425     // convert rectangle for frame
1426     RECT rc = { 0, 0, cx, cy };
1427     AdjustWindowRectEx(&rc, GetStyle(), FALSE, GetExStyle());
1428     cy = rc.bottom - rc.top;
1429 
1430     if (!m_bResized)
1431     {
1432         // is the drop-down window a 'downer' or 'upper'?
1433         // NOTE: 'downer' is below the EDIT control. 'upper' is above the EDIT control.
1434         m_bDowner = (rcEdit.bottom + cy < rcMon.bottom);
1435     }
1436 
1437     // adjust y and cy
1438     if (m_bDowner)
1439     {
1440         if (rcMon.bottom < y + cy)
1441         {
1442             cy = ((rcMon.bottom - y) / cyItem) * cyItem;
1443             bLongList = TRUE;
1444         }
1445     }
1446     else
1447     {
1448         if (rcEdit.top < rcMon.top + cy)
1449         {
1450             cy = ((rcEdit.top - rcMon.top) / cyItem) * cyItem;
1451             bLongList = TRUE;
1452         }
1453         y = rcEdit.top - cy;
1454     }
1455 
1456     // set status
1457     m_hwndSizeBox.SetStatus(m_bDowner, bLongList);
1458 
1459     if (m_bResized) // already resized?
1460         PostMessageW(WM_SIZE, 0, 0); // re-layout
1461     else
1462         MoveWindow(x, y, cx, cy); // move
1463 
1464     // show without activation
1465     ShowWindow(SW_SHOWNOACTIVATE);
1466 }
1467 
1468 VOID
1469 CAutoComplete::ExtractInnerList(CSimpleArray<CStringW>& outerList,
1470                                 const CSimpleArray<CStringW>& innerList,
1471                                 const CString& strText)
1472 {
1473     for (INT iItem = 0; iItem < innerList.GetSize(); ++iItem)
1474     {
1475         if (m_pThread || !m_hThread)
1476             break;
1477 
1478         const CStringW& strTarget = innerList[iItem];
1479         if (DoesMatch(strTarget, strText))
1480         {
1481             outerList.Add(strTarget);
1482 
1483             if (outerList.GetSize() >= MAX_ITEM_COUNT)
1484                 break;
1485         }
1486     }
1487 }
1488 
1489 VOID CAutoComplete::ReLoadInnerList(PAC_THREAD pThread)
1490 {
1491     pThread->m_innerList.RemoveAll(); // clear contents
1492 
1493     if (!m_pEnum || pThread->m_strText.IsEmpty())
1494         return;
1495 
1496     // reload the items
1497     LPWSTR pszItem;
1498     ULONG cGot;
1499     HRESULT hr;
1500     CSimpleArray<CStringW>& innerList = pThread->m_innerList;
1501     while (!m_pThread && m_hThread)
1502     {
1503         // get next item
1504         hr = m_pEnum->Next(1, &pszItem, &cGot);
1505         if (hr != S_OK)
1506             break;
1507 
1508         innerList.Add(pszItem); // append item to innerList
1509         ::CoTaskMemFree(pszItem); // free
1510     }
1511 }
1512 
1513 VOID CAutoComplete::StartCompletion(BOOL bAppendOK)
1514 {
1515     TRACE("CAutoComplete::StartCompletion(%p, %d)\n", this, bAppendOK);
1516 
1517     if (!m_pEnum || (!CanAutoSuggest() && !CanAutoAppend()))
1518         return;
1519 
1520     ::SendMessageW(m_hWnd, AUTOCOMP_START, bAppendOK, 0);
1521 }
1522 
1523 //////////////////////////////////////////////////////////////////////////////
1524 // CAutoComplete message handlers
1525 
1526 // WM_CREATE
1527 // This message is sent when the window is about to be created after WM_NCCREATE.
1528 // The return value is -1 (failure) or zero (success).
1529 LRESULT CAutoComplete::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1530 {
1531     TRACE("CAutoComplete::OnCreate(%p)\n", this);
1532 
1533     // set the pointer of CAutoComplete
1534     m_hwndList.m_pDropDown = this;
1535     m_hwndScrollBar.m_pDropDown = this;
1536     m_hwndSizeBox.m_pDropDown = this;
1537 
1538     // create the children
1539     m_hwndList.Create(m_hWnd);
1540     if (!m_hwndList)
1541         return -1; // failure
1542     m_hwndSizeBox.Create(m_hWnd);
1543     if (!m_hwndSizeBox)
1544         return -1; // failure
1545     m_hwndScrollBar.Create(m_hWnd);
1546     if (!m_hwndScrollBar)
1547         return -1; // failure
1548 
1549     // show the controls
1550     m_hwndList.ShowWindow(SW_SHOWNOACTIVATE);
1551     m_hwndSizeBox.ShowWindow(SW_SHOWNOACTIVATE);
1552     m_hwndScrollBar.ShowWindow(SW_SHOWNOACTIVATE);
1553 
1554     // set the list font
1555     m_hFont = reinterpret_cast<HFONT>(::GetStockObject(DEFAULT_GUI_FONT));
1556     m_hwndList.SetFont(m_hFont);
1557 
1558     // add reference to CAutoComplete::m_hWnd
1559     AddRef();
1560     return 0; // success
1561 }
1562 
1563 // WM_NCDESTROY
1564 LRESULT CAutoComplete::OnNCDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1565 {
1566     TRACE("CAutoComplete::OnNCDestroy(%p)\n", this);
1567 
1568     // hide
1569     if (IsWindowVisible())
1570         HideDropDown();
1571 
1572     // clear CAutoComplete pointers
1573     m_hwndList.m_pDropDown = NULL;
1574     m_hwndScrollBar.m_pDropDown = NULL;
1575     m_hwndSizeBox.m_pDropDown = NULL;
1576 
1577     // destroy controls
1578     m_hwndList.DestroyWindow();
1579     m_hwndScrollBar.DestroyWindow();
1580     m_hwndSizeBox.DestroyWindow();
1581 
1582     // clean up
1583     m_hwndCombo = NULL;
1584     // remove reference to CAutoComplete::m_hWnd
1585     Release();
1586     return 0;
1587 }
1588 
1589 // WM_EXITSIZEMOVE
1590 // This message is sent once to a window after it has exited the moving or sizing mode.
1591 LRESULT CAutoComplete::OnExitSizeMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1592 {
1593     TRACE("CAutoComplete::OnExitSizeMove(%p)\n", this);
1594     m_bResized = TRUE; // remember resized
1595 
1596     ModifyStyle(WS_THICKFRAME, 0); // remove thick frame to resize
1597     // frame changed
1598     UINT uSWP_ = SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE;
1599     SetWindowPos(NULL, 0, 0, 0, 0, uSWP_);
1600 
1601     ::SetFocus(m_hwndEdit); // restore focus
1602     return 0;
1603 }
1604 
1605 // WM_DRAWITEM @implemented
1606 // This message is sent to the owner window to draw m_hwndList.
1607 LRESULT CAutoComplete::OnDrawItem(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1608 {
1609     LPDRAWITEMSTRUCT pDraw = reinterpret_cast<LPDRAWITEMSTRUCT>(lParam);
1610     ATLASSERT(pDraw != NULL);
1611     ATLASSERT(m_hwndList.GetStyle() & LVS_OWNERDRAWFIXED);
1612 
1613     // sanity check
1614     if (pDraw->CtlType != ODT_LISTVIEW || pDraw->hwndItem != m_hwndList)
1615         return FALSE;
1616 
1617     // item rectangle
1618     RECT rcItem = pDraw->rcItem;
1619 
1620     // get info
1621     UINT iItem = pDraw->itemID; // the index of item
1622     CStringW strItem = m_hwndList.GetItemText(iItem); // get text of item
1623 
1624     // draw background and set text color
1625     HDC hDC = pDraw->hDC;
1626     BOOL bSelected = (pDraw->itemState & ODS_SELECTED);
1627     if (bSelected)
1628     {
1629         ::FillRect(hDC, &rcItem, ::GetSysColorBrush(COLOR_HIGHLIGHT));
1630         ::SetTextColor(hDC, ::GetSysColor(COLOR_HIGHLIGHTTEXT));
1631     }
1632     else
1633     {
1634         ::FillRect(hDC, &rcItem, ::GetSysColorBrush(COLOR_WINDOW));
1635         ::SetTextColor(hDC, ::GetSysColor(COLOR_WINDOWTEXT));
1636     }
1637 
1638     // draw text
1639     rcItem.left += ::GetSystemMetrics(SM_CXBORDER);
1640     HGDIOBJ hFontOld = ::SelectObject(hDC, m_hFont);
1641     const UINT uDT_ = DT_LEFT | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER;
1642     ::SetBkMode(hDC, TRANSPARENT);
1643     ::DrawTextW(hDC, strItem, -1, &rcItem, uDT_);
1644     ::SelectObject(hDC, hFontOld);
1645 
1646     return TRUE;
1647 }
1648 
1649 // WM_GETMINMAXINFO @implemented
1650 // This message is sent to a window when the size or position of the window is about to change.
1651 LRESULT CAutoComplete::OnGetMinMaxInfo(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1652 {
1653     // restrict minimum size
1654     LPMINMAXINFO pInfo = reinterpret_cast<LPMINMAXINFO>(lParam);
1655     pInfo->ptMinTrackSize.x = ::GetSystemMetrics(SM_CXVSCROLL);
1656     pInfo->ptMinTrackSize.y = ::GetSystemMetrics(SM_CYHSCROLL);
1657     return 0;
1658 }
1659 
1660 // WM_MEASUREITEM @implemented
1661 // This message is sent to the owner window to get the item extent of m_hwndList.
1662 LRESULT CAutoComplete::OnMeasureItem(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1663 {
1664     LPMEASUREITEMSTRUCT pMeasure = reinterpret_cast<LPMEASUREITEMSTRUCT>(lParam);
1665     ATLASSERT(pMeasure != NULL);
1666     if (pMeasure->CtlType != ODT_LISTVIEW)
1667         return FALSE;
1668     if (!m_hwndList)
1669         return FALSE;
1670     ATLASSERT(m_hwndList.GetStyle() & LVS_OWNERDRAWFIXED);
1671     pMeasure->itemHeight = m_hwndList.m_cyItem; // height of item
1672     return TRUE;
1673 }
1674 
1675 // WM_MOUSEACTIVATE @implemented
1676 // The return value of this message specifies whether the window should be activated or not.
1677 LRESULT CAutoComplete::OnMouseActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1678 {
1679     return MA_NOACTIVATE; // don't activate by mouse
1680 }
1681 
1682 // WM_NCACTIVATE
1683 // This message is sent to a window to indicate an active or inactive state.
1684 LRESULT CAutoComplete::OnNCActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1685 {
1686     bHandled = FALSE; // do default
1687     return 0;
1688 }
1689 
1690 // WM_NCLBUTTONDOWN
1691 LRESULT CAutoComplete::OnNCLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1692 {
1693     switch (wParam)
1694     {
1695         case HTBOTTOMRIGHT: case HTTOPRIGHT:
1696         {
1697             // add thick frame to resize.
1698             ModifyStyle(0, WS_THICKFRAME);
1699             // frame changed
1700             UINT uSWP_ = SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE;
1701             SetWindowPos(NULL, 0, 0, 0, 0, uSWP_);
1702             break;
1703         }
1704     }
1705     bHandled = FALSE; // do default
1706     return 0;
1707 }
1708 
1709 // WM_NOTIFY
1710 // This message informs the parent window of a control that an event has occurred.
1711 LRESULT CAutoComplete::OnNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1712 {
1713     LPNMHDR pnmh = reinterpret_cast<LPNMHDR>(lParam);
1714     ATLASSERT(pnmh != NULL);
1715 
1716     switch (pnmh->code)
1717     {
1718         case NM_DBLCLK: // double-clicked
1719         {
1720             TRACE("NM_DBLCLK\n");
1721             HideDropDown();
1722             break;
1723         }
1724         case NM_HOVER: // mouse is hovering
1725         {
1726             POINT pt;
1727             ::GetCursorPos(&pt); // get cursor position in screen coordinates
1728             m_hwndList.ScreenToClient(&pt); // into client coordinates
1729             INT iItem = m_hwndList.ItemFromPoint(pt.x, pt.y);
1730             if (iItem != -1)
1731             {
1732                 m_bInSelectItem = TRUE; // don't respond
1733                 m_hwndList.SetCurSel(iItem); // select
1734                 m_bInSelectItem = FALSE;
1735             }
1736             return TRUE; // eat
1737         }
1738         case LVN_GETDISPINFOA: // for user's information only
1739         {
1740             TRACE("LVN_GETDISPINFOA\n");
1741             if (pnmh->hwndFrom != m_hwndList)
1742                 break;
1743 
1744             LV_DISPINFOA *pDispInfo = reinterpret_cast<LV_DISPINFOA *>(pnmh);
1745             LV_ITEMA *pItem = &pDispInfo->item;
1746             INT iItem = pItem->iItem;
1747             if (iItem == -1)
1748                 break;
1749 
1750             CStringW strText = GetItemText(iItem);
1751             if (pItem->mask & LVIF_TEXT)
1752                 SHUnicodeToAnsi(strText, pItem->pszText, pItem->cchTextMax);
1753             break;
1754         }
1755         case LVN_GETDISPINFOW: // for user's information only
1756         {
1757             TRACE("LVN_GETDISPINFOW\n");
1758             if (pnmh->hwndFrom != m_hwndList)
1759                 break;
1760 
1761             LV_DISPINFOW *pDispInfo = reinterpret_cast<LV_DISPINFOW *>(pnmh);
1762             LV_ITEMW *pItem = &pDispInfo->item;
1763             INT iItem = pItem->iItem;
1764             if (iItem == -1)
1765                 break;
1766 
1767             CStringW strText = GetItemText(iItem);
1768             if (pItem->mask & LVIF_TEXT)
1769                 StringCbCopyW(pItem->pszText, pItem->cchTextMax, strText);
1770             break;
1771         }
1772         case LVN_HOTTRACK: // enabled by LVS_EX_TRACKSELECT
1773         {
1774             TRACE("LVN_HOTTRACK\n");
1775             LPNMLISTVIEW pListView = reinterpret_cast<LPNMLISTVIEW>(pnmh);
1776             INT iItem = pListView->iItem;
1777             TRACE("LVN_HOTTRACK: iItem:%d\n", iItem);
1778             m_hwndList.SetCurSel(iItem);
1779             m_hwndList.EnsureVisible(iItem, FALSE);
1780             return TRUE;
1781         }
1782         case LVN_ITEMACTIVATE: // enabled by LVS_EX_ONECLICKACTIVATE
1783         {
1784             TRACE("LVN_ITEMACTIVATE\n");
1785             LPNMITEMACTIVATE pItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pnmh);
1786             INT iItem = pItemActivate->iItem;
1787             TRACE("LVN_ITEMACTIVATE: iItem:%d\n", iItem);
1788             if (iItem != -1) // the item is clicked
1789             {
1790                 SelectItem(iItem);
1791                 HideDropDown();
1792             }
1793             break;
1794         }
1795         case LVN_ITEMCHANGED: // item info is changed
1796         {
1797             TRACE("LVN_ITEMCHANGED\n");
1798             LPNMLISTVIEW pListView = reinterpret_cast<LPNMLISTVIEW>(pnmh);
1799             if (pListView->uChanged & LVIF_STATE) // selection changed
1800             {
1801                 // listview selection changed
1802                 if (!m_bInSelectItem)
1803                 {
1804                     OnListSelChange();
1805                 }
1806                 UpdateScrollBar();
1807             }
1808             break;
1809         }
1810     }
1811 
1812     return 0;
1813 }
1814 
1815 // WM_NCHITTEST @implemented
1816 // The return value is indicating the cursor shape and the behaviour.
1817 LRESULT CAutoComplete::OnNCHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1818 {
1819     TRACE("CAutoComplete::OnNCHitTest(%p)\n", this);
1820     POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; // in screen coordinates
1821     ScreenToClient(&pt); // into client coordinates
1822     if (ChildWindowFromPoint(pt) == m_hwndSizeBox) // hit?
1823     {
1824         // allow resizing (with cursor shape)
1825         return m_bDowner ? HTBOTTOMRIGHT : HTTOPRIGHT;
1826     }
1827     bHandled = FALSE; // do default
1828     return 0;
1829 }
1830 
1831 // WM_SIZE @implemented
1832 // This message is sent when the window size is changed.
1833 LRESULT CAutoComplete::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1834 {
1835     // calculate the positions of the controls
1836     CRect rcList, rcScrollBar, rcSizeBox;
1837     CalcRects(m_bDowner, rcList, rcScrollBar, rcSizeBox);
1838 
1839     // reposition the controls in smartest way
1840     UINT uSWP_ = SWP_NOACTIVATE | SWP_NOCOPYBITS;
1841     HDWP hDWP = ::BeginDeferWindowPos(3);
1842     hDWP = ::DeferWindowPos(hDWP, m_hwndScrollBar, HWND_TOP,
1843                             rcScrollBar.left, rcScrollBar.top,
1844                             rcScrollBar.Width(), rcScrollBar.Height(), uSWP_);
1845     hDWP = ::DeferWindowPos(hDWP, m_hwndSizeBox, m_hwndScrollBar,
1846                             rcSizeBox.left, rcSizeBox.top,
1847                             rcSizeBox.Width(), rcSizeBox.Height(), uSWP_);
1848     hDWP = ::DeferWindowPos(hDWP, m_hwndList, m_hwndSizeBox,
1849                             rcList.left, rcList.top,
1850                             rcList.Width(), rcList.Height(), uSWP_);
1851     ::EndDeferWindowPos(hDWP);
1852 
1853     UpdateScrollBar();
1854     return 0;
1855 }
1856 
1857 // WM_SHOWWINDOW
1858 LRESULT CAutoComplete::OnShowWindow(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1859 {
1860     // hook mouse events
1861     BOOL bShow = (BOOL)wParam;
1862     if (bShow)
1863     {
1864         if (s_hWatchWnd != m_hWnd && ::IsWindowVisible(s_hWatchWnd))
1865             ::ShowWindowAsync(s_hWatchWnd, SW_HIDE);
1866         s_hWatchWnd = m_hWnd; // watch this
1867 
1868         // unhook mouse if any
1869         if (s_hMouseHook)
1870         {
1871             HHOOK hHookOld = s_hMouseHook;
1872             s_hMouseHook = NULL;
1873             ::UnhookWindowsHookEx(hHookOld);
1874         }
1875 
1876         // hook mouse
1877         s_hMouseHook = ::SetWindowsHookEx(WH_MOUSE, MouseProc, NULL, ::GetCurrentThreadId());
1878         ATLASSERT(s_hMouseHook != NULL);
1879 
1880         // set timer
1881         SetTimer(WATCH_TIMER_ID, WATCH_INTERVAL, NULL);
1882 
1883         bHandled = FALSE; // do default
1884         return 0;
1885     }
1886     else
1887     {
1888         // kill timer
1889         KillTimer(WATCH_TIMER_ID);
1890 
1891         s_hWatchWnd = NULL; // unwatch
1892 
1893         // unhook mouse if any
1894         if (s_hMouseHook)
1895         {
1896             HHOOK hHookOld = s_hMouseHook;
1897             s_hMouseHook = NULL;
1898             ::UnhookWindowsHookEx(hHookOld);
1899         }
1900 
1901         LRESULT ret = DefWindowProcW(uMsg, wParam, lParam); // do default
1902 
1903         if (m_hwndCombo)
1904             ::InvalidateRect(m_hwndCombo, NULL, TRUE); // redraw
1905 
1906         m_outerList.RemoveAll(); // no use
1907         return ret;
1908     }
1909 }
1910 
1911 // WM_TIMER
1912 LRESULT CAutoComplete::OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1913 {
1914     if (wParam != WATCH_TIMER_ID) // sanity check
1915         return 0;
1916 
1917     // if the textbox is dead, then kill the timer
1918     if (!::IsWindow(m_hwndEdit))
1919     {
1920         KillTimer(WATCH_TIMER_ID);
1921         return 0;
1922     }
1923 
1924     // m_hwndEdit is moved?
1925     RECT rcEdit;
1926     ::GetWindowRect(m_hwndEdit, &rcEdit);
1927     if (!::EqualRect(&rcEdit, &m_rcEdit))
1928     {
1929         // if so, hide
1930         HideDropDown();
1931 
1932         m_rcEdit = rcEdit; // update rectangle
1933         m_bResized = FALSE; // clear flag
1934     }
1935 
1936     return 0;
1937 }
1938 
1939 // WM_VSCROLL
1940 // This message is sent when a scroll event occurs.
1941 LRESULT CAutoComplete::OnVScroll(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1942 {
1943     TRACE("CAutoComplete::OnVScroll(%p)\n", this);
1944     WORD code = LOWORD(wParam);
1945     switch (code)
1946     {
1947         case SB_THUMBPOSITION: case SB_THUMBTRACK:
1948         {
1949             // get the scrolling info
1950             INT nPos = HIWORD(wParam);
1951             SCROLLINFO si = { sizeof(si), SIF_ALL };
1952             m_hwndList.GetScrollInfo(SB_VERT, &si);
1953 
1954             // scroll the list-view by CListView::EnsureVisible
1955             INT cItems = m_hwndList.GetItemCount();
1956             // iItem : cItems == (nPos - si.nMin) : (si.nMax - si.nMin).
1957             INT iItem = cItems * (nPos - si.nMin) / (si.nMax - si.nMin);
1958             if (nPos > si.nPos)
1959             {
1960                 iItem += m_hwndList.GetVisibleCount();
1961                 if (iItem >= cItems)
1962                     iItem = cItems - 1;
1963             }
1964             m_hwndList.EnsureVisible(iItem, FALSE);
1965 
1966             // update scrolling position of m_hwndScrollBar
1967             si.fMask = SIF_POS;
1968             m_hwndList.GetScrollInfo(SB_VERT, &si);
1969             m_hwndScrollBar.SetScrollInfo(SB_VERT, &si, FALSE);
1970             break;
1971         }
1972         default:
1973         {
1974             // pass it to m_hwndList
1975             m_hwndList.SendMessageW(WM_VSCROLL, wParam, lParam);
1976             UpdateScrollBar();
1977             break;
1978         }
1979     }
1980     return 0;
1981 }
1982 
1983 static inline PAC_THREAD
1984 InterlockedExchangeThreadData(volatile PAC_THREAD *Target, PAC_THREAD Value)
1985 {
1986     return reinterpret_cast<PAC_THREAD>(
1987         ::InterlockedExchangePointer(reinterpret_cast<volatile PVOID *>(Target), Value));
1988 }
1989 
1990 static unsigned __stdcall AutoCompThreadProc(void *arg)
1991 {
1992     CAutoComplete* pThis = reinterpret_cast<CAutoComplete*>(arg);
1993     pThis->AutoCompThreadProc();
1994     return 0;
1995 }
1996 
1997 VOID CAutoComplete::AutoCompThreadProc()
1998 {
1999     for (;;)
2000     {
2001         PAC_THREAD pThread = InterlockedExchangeThreadData(&m_pThread, NULL);
2002         if (!pThread)
2003             break;
2004         DoThreadWork(pThread);
2005     }
2006 }
2007 
2008 VOID CAutoComplete::DoThreadWork(PAC_THREAD pThread)
2009 {
2010     if (pThread->m_bExpand || m_innerList.GetSize() == 0)
2011     {
2012         ReLoadInnerList(pThread);
2013     }
2014     else
2015     {
2016         pThread->m_innerList = m_innerList;
2017     }
2018 
2019     if (m_pThread || !m_hThread)
2020     {
2021         delete pThread;
2022         return;
2023     }
2024 
2025     ExtractInnerList(pThread->m_outerList, pThread->m_innerList, pThread->m_strText);
2026 
2027     if (m_pThread || !m_hThread)
2028     {
2029         delete pThread;
2030         return;
2031     }
2032 
2033     DoSort(pThread->m_outerList);
2034     DoUniqueAndTrim(pThread->m_outerList);
2035 
2036     if (m_pThread || !m_hThread ||
2037         !::PostMessageW(m_hWnd, AUTOCOMP_FINISH, 0, (LPARAM)pThread))
2038     {
2039         delete pThread;
2040     }
2041 }
2042 
2043 // AUTOCOMP_START
2044 LRESULT CAutoComplete::OnAutoCompStart(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
2045 {
2046     BOOL bAppendOK = (BOOL)wParam;
2047 
2048     CStringW strText = GetEditText();
2049     if (m_strText.CompareNoCase(strText) == 0)
2050     {
2051         // no change
2052         return 0;
2053     }
2054 
2055     PAC_THREAD pThread = new AC_THREAD { this, bAppendOK, strText };
2056 
2057     // if previous text was empty
2058     if (m_strText.IsEmpty())
2059     {
2060         pThread->m_bReset = TRUE;
2061     }
2062     m_strText = strText;
2063 
2064     // do expand the items if the stem is changed
2065     CStringW strStemText = GetStemText(pThread->m_strText);
2066     if (m_strStemText.CompareNoCase(strStemText) != 0)
2067     {
2068         pThread->m_bReset = TRUE;
2069         pThread->m_bExpand = !strStemText.IsEmpty();
2070         m_strStemText = strStemText;
2071     }
2072 
2073     // reset if necessary
2074     if (pThread->m_bReset && m_pEnum)
2075     {
2076         HRESULT hr = m_pEnum->Reset(); // IEnumString::Reset
2077         TRACE("m_pEnum->Reset(%p): 0x%08lx\n",
2078               static_cast<IUnknown *>(m_pEnum), hr);
2079     }
2080 
2081     // update ac list if necessary
2082     if (pThread->m_bExpand && m_pACList)
2083     {
2084         HRESULT hr = m_pACList->Expand(strStemText); // IACList::Expand
2085         TRACE("m_pACList->Expand(%p, %S): 0x%08lx\n",
2086               static_cast<IUnknown *>(m_pACList),
2087               static_cast<LPCWSTR>(strStemText), hr);
2088     }
2089 
2090     PAC_THREAD pOld = InterlockedExchangeThreadData(&m_pThread, pThread);
2091     if (pOld)
2092         delete pOld;
2093 
2094     BOOL bDoStart = FALSE;
2095     DWORD dwWait = WaitForSingleObject(m_hThread, 0);
2096     if (dwWait != WAIT_TIMEOUT)
2097     {
2098         CloseHandle(m_hThread);
2099         bDoStart = TRUE;
2100     }
2101     else if (!m_hThread)
2102     {
2103         bDoStart = TRUE;
2104     }
2105 
2106     if (bDoStart)
2107         m_hThread = (HANDLE)_beginthreadex(NULL, 0, ::AutoCompThreadProc, this, 0, NULL);
2108 
2109     return 0;
2110 }
2111 
2112 // AUTOCOMP_FINISH
2113 LRESULT CAutoComplete::OnAutoCompFinish(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
2114 {
2115     PAC_THREAD pThread = reinterpret_cast<PAC_THREAD>(lParam);
2116     if (m_pThread == NULL)
2117     {
2118         FinishCompletion(pThread);
2119     }
2120     CloseHandle(m_hThread);
2121     m_hThread = NULL;
2122     delete pThread;
2123     return 0;
2124 }
2125 
2126 VOID CAutoComplete::FinishCompletion(PAC_THREAD pThread)
2127 {
2128     if (m_pThread || !m_hThread)
2129         return;
2130 
2131     if (!CanAutoSuggest() && !CanAutoAppend())
2132         return;
2133 
2134     if (m_pThread || !m_hThread)
2135         return;
2136 
2137     // set inner list
2138     m_innerList = pThread->m_innerList;
2139 
2140     if (m_pThread || !m_hThread)
2141         return;
2142 
2143     // set the items of the virtual listview
2144     m_outerList = pThread->m_outerList; // FIXME: We need more speed!
2145     m_hwndList.SendMessageW(LVM_SETITEMCOUNT, m_outerList.GetSize(), 0);
2146 
2147     // save text
2148     m_strText = pThread->m_strText;
2149     m_strStemText = GetStemText(m_strText);
2150 
2151     if (CanAutoSuggest()) // can we auto-suggest?
2152     {
2153         m_bInSelectItem = TRUE; // don't respond
2154         SelectItem(-1); // select none
2155         m_bInSelectItem = FALSE;
2156 
2157         if (m_outerList.GetSize() > 0)
2158             RepositionDropDown();
2159         else
2160             HideDropDown();
2161     }
2162 
2163     if (CanAutoAppend() && pThread->m_bAppendOK) // can we auto-append?
2164     {
2165         DoAutoAppend(pThread);
2166     }
2167 }
2168