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