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 250 // 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             OnEditUpdate(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 {
644 }
645 
646 HWND CAutoComplete::CreateDropDown()
647 {
648     ATLASSERT(m_hWnd == NULL);
649     DWORD dwStyle = WS_POPUP | /*WS_VISIBLE |*/ WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_BORDER;
650     DWORD dwExStyle = WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_NOPARENTNOTIFY;
651     Create(NULL, NULL, NULL, dwStyle, dwExStyle);
652     TRACE("CAutoComplete::CreateDropDown(%p): m_hWnd=%p, m_hwndEdit=%p\n",
653           this, m_hWnd, m_hwndEdit);
654     return m_hWnd;
655 }
656 
657 CAutoComplete::~CAutoComplete()
658 {
659     TRACE("CAutoComplete::~CAutoComplete(%p)\n", this);
660     if (m_hFont)
661     {
662         ::DeleteObject(m_hFont);
663         m_hFont = NULL;
664     }
665     // quit holding them
666     m_pEnum.Release();
667     m_pACList.Release();
668 }
669 
670 BOOL CAutoComplete::CanAutoSuggest()
671 {
672     return !!(m_dwOptions & ACO_AUTOSUGGEST) && m_bEnabled;
673 }
674 
675 BOOL CAutoComplete::CanAutoAppend()
676 {
677     return !!(m_dwOptions & ACO_AUTOAPPEND) && m_bEnabled;
678 }
679 
680 BOOL CAutoComplete::UseTab()
681 {
682     return !!(m_dwOptions & ACO_USETAB) && m_bEnabled;
683 }
684 
685 BOOL CAutoComplete::IsComboBoxDropped()
686 {
687     if (!::IsWindow(m_hwndCombo))
688         return FALSE;
689     return (BOOL)::SendMessageW(m_hwndCombo, CB_GETDROPPEDSTATE, 0, 0);
690 }
691 
692 BOOL CAutoComplete::FilterPrefixes()
693 {
694     return !!(m_dwOptions & ACO_FILTERPREFIXES) && m_bEnabled;
695 }
696 
697 INT CAutoComplete::GetItemCount()
698 {
699     return m_outerList.GetSize();
700 }
701 
702 CStringW CAutoComplete::GetItemText(INT iItem)
703 {
704     if (iItem < 0 || m_outerList.GetSize() <= iItem)
705         return L"";
706     return m_outerList[iItem];
707 }
708 
709 CStringW CAutoComplete::GetEditText()
710 {
711     WCHAR szText[L_MAX_URL_LENGTH];
712     if (::GetWindowTextW(m_hwndEdit, szText, _countof(szText)))
713         return szText;
714     return L"";
715 }
716 
717 VOID CAutoComplete::SetEditText(LPCWSTR pszText)
718 {
719     m_bInSetText = TRUE; // don't hide drop-down
720     ::CallWindowProcW(m_fnOldEditProc, m_hwndEdit, WM_SETTEXT, 0, reinterpret_cast<LPARAM>(pszText));
721     m_bInSetText = FALSE;
722 }
723 
724 CStringW CAutoComplete::GetStemText()
725 {
726     CStringW strText = GetEditText();
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_HOME: case VK_END:
887         case VK_UP: case VK_DOWN:
888         case VK_PRIOR: case VK_NEXT:
889             // is suggestion available?
890             if (!CanAutoSuggest())
891                 return FALSE; // do default
892             if (IsWindowVisible())
893                 return OnListUpDown(vk);
894             break;
895         case VK_ESCAPE:
896         {
897             // is suggestion available?
898             if (!CanAutoSuggest())
899                 return FALSE; // do default
900             if (IsWindowVisible())
901             {
902                 SetEditText(m_strText); // revert the edit text
903                 // select the end
904                 INT cch = m_strText.GetLength();
905                 SetEditSel(cch, cch);
906                 HideDropDown(); // hide
907                 return TRUE; // eat
908             }
909             break;
910         }
911         case VK_RETURN:
912         {
913             if (::GetKeyState(VK_CONTROL) < 0)
914             {
915                 // quick edit
916                 CStringW strText = GetEditText();
917                 SetEditText(GetQuickEdit(strText));
918             }
919             else
920             {
921                 // if item is selected, then update the edit text
922                 INT iItem = m_hwndList.GetCurSel();
923                 if (iItem != -1)
924                 {
925                     CStringW strText = GetItemText(iItem);
926                     SetEditText(strText);
927                 }
928             }
929             // select all
930             INT cch = ::CallWindowProcW(m_fnOldEditProc, m_hwndEdit, WM_GETTEXTLENGTH, 0, 0);
931             SetEditSel(0, cch);
932             // hide
933             HideDropDown();
934             break;
935         }
936         case VK_TAB:
937         {
938             // ACO_USETAB
939             if (IsWindowVisible() && UseTab())
940             {
941                 if (GetKeyState(VK_SHIFT) < 0)
942                     return OnListUpDown(VK_UP);
943                 else
944                     return OnListUpDown(VK_DOWN);
945             }
946             break;
947         }
948         case VK_DELETE:
949         {
950             // is suggestion available?
951             if (!CanAutoSuggest())
952                 return FALSE; // do default
953             ::DefSubclassProc(m_hwndEdit, WM_KEYDOWN, VK_DELETE, 0); // do default
954             OnEditUpdate(FALSE);
955             return TRUE; // eat
956         }
957         case VK_BACK:
958         {
959             if (::GetKeyState(VK_CONTROL) < 0)
960             {
961                 DoBackWord();
962                 return TRUE; // eat
963             }
964             break;
965         }
966     }
967     return FALSE; // default
968 }
969 
970 LRESULT CAutoComplete::OnEditChar(WPARAM wParam, LPARAM lParam)
971 {
972     TRACE("CACEditCtrl::OnEditChar(%p, %p)\n", this, wParam);
973     if (wParam == L'\n' || wParam == L'\t')
974         return 0; // eat
975     LRESULT ret = ::DefSubclassProc(m_hwndEdit, WM_CHAR, wParam, lParam); // do default
976     if (CanAutoSuggest() || CanAutoAppend())
977         OnEditUpdate(wParam != VK_BACK);
978     return ret;
979 }
980 
981 VOID CAutoComplete::OnEditUpdate(BOOL bAppendOK)
982 {
983     CStringW strText = GetEditText();
984     if (m_strText.CompareNoCase(strText) == 0)
985     {
986         // no change
987         return;
988     }
989     UpdateCompletion(bAppendOK);
990 }
991 
992 VOID CAutoComplete::OnListSelChange()
993 {
994     // update EDIT text
995     INT iItem = m_hwndList.GetCurSel();
996     CStringW text = ((iItem != -1) ? GetItemText(iItem) : m_strText);
997     SetEditText(text);
998     // ensure the item visible
999     m_hwndList.EnsureVisible(iItem, FALSE);
1000     // select the end
1001     INT cch = text.GetLength();
1002     SetEditSel(cch, cch);
1003 }
1004 
1005 BOOL CAutoComplete::OnListUpDown(UINT vk)
1006 {
1007     if (!CanAutoSuggest())
1008         return FALSE; // default
1009 
1010     if ((m_dwOptions & ACO_UPDOWNKEYDROPSLIST) && !IsWindowVisible())
1011     {
1012         ShowDropDown();
1013         return TRUE; // eat
1014     }
1015 
1016     INT iItem = m_hwndList.GetCurSel(); // current selection
1017     INT cItems = m_hwndList.GetItemCount(); // the number of items
1018     switch (vk)
1019     {
1020         case VK_HOME: case VK_END:
1021             m_hwndList.SendMessageW(WM_KEYDOWN, vk, 0);
1022             break;
1023         case VK_UP:
1024             if (iItem == -1)
1025                 iItem = cItems - 1;
1026             else if (iItem == 0)
1027                 iItem = -1;
1028             else
1029                 --iItem;
1030             m_hwndList.SetCurSel(iItem);
1031             break;
1032         case VK_DOWN:
1033             if (iItem == -1)
1034                 iItem = 0;
1035             else if (iItem == cItems - 1)
1036                 iItem = -1;
1037             else
1038                 ++iItem;
1039             m_hwndList.SetCurSel(iItem);
1040             break;
1041         case VK_PRIOR:
1042             if (iItem == -1)
1043             {
1044                 iItem = cItems - 1;
1045             }
1046             else if (iItem == 0)
1047             {
1048                 iItem = -1;
1049             }
1050             else
1051             {
1052                 iItem -= m_hwndList.GetVisibleCount() - 1;
1053                 if (iItem < 0)
1054                     iItem = 0;
1055             }
1056             m_hwndList.SetCurSel(iItem);
1057             break;
1058         case VK_NEXT:
1059             if (iItem == -1)
1060             {
1061                 iItem = 0;
1062             }
1063             else if (iItem == cItems - 1)
1064             {
1065                 iItem = -1;
1066             }
1067             else
1068             {
1069                 iItem += m_hwndList.GetVisibleCount() - 1;
1070                 if (iItem > cItems)
1071                     iItem = cItems - 1;
1072             }
1073             m_hwndList.SetCurSel(iItem);
1074             break;
1075         default:
1076         {
1077             ATLASSERT(FALSE);
1078             break;
1079         }
1080     }
1081 
1082     return TRUE; // eat
1083 }
1084 
1085 //////////////////////////////////////////////////////////////////////////////
1086 // CAutoComplete IAutoComplete methods
1087 
1088 // @implemented
1089 STDMETHODIMP CAutoComplete::Enable(BOOL fEnable)
1090 {
1091     TRACE("(%p)->Enable(%d)\n", this, fEnable);
1092     m_bEnabled = fEnable;
1093     return S_OK;
1094 }
1095 
1096 STDMETHODIMP
1097 CAutoComplete::Init(HWND hwndEdit, IUnknown *punkACL,
1098                     LPCOLESTR pwszRegKeyPath, LPCOLESTR pwszQuickComplete)
1099 {
1100     TRACE("(%p)->Init(0x%08lx, %p, %s, %s)\n",
1101           this, hwndEdit, punkACL, debugstr_w(pwszRegKeyPath), debugstr_w(pwszQuickComplete));
1102     // sanity check
1103     if (m_hwndEdit || !punkACL)
1104         return E_FAIL;
1105     if (!hwndEdit)
1106         return E_INVALIDARG;
1107     // do subclass textbox to watch messages
1108     m_fnOldEditProc = reinterpret_cast<WNDPROC>(::GetWindowLongPtrW(hwndEdit, GWLP_WNDPROC));
1109     if (!m_fnOldEditProc)
1110         return E_FAIL;
1111     if (!::SetWindowSubclass(hwndEdit, EditSubclassProc, 0, reinterpret_cast<DWORD_PTR>(this)))
1112         return E_FAIL;
1113     m_hwndEdit = hwndEdit;
1114     // add reference to m_hwndEdit
1115     AddRef();
1116     // set word break procedure
1117     m_fnOldWordBreakProc = reinterpret_cast<EDITWORDBREAKPROCW>(
1118         ::SendMessageW(m_hwndEdit, EM_SETWORDBREAKPROC, 0,
1119                        reinterpret_cast<LPARAM>(EditWordBreakProcW)));
1120     // save position
1121     ::GetWindowRect(m_hwndEdit, &m_rcEdit);
1122 
1123     // get an IEnumString
1124     ATLASSERT(!m_pEnum);
1125     punkACL->QueryInterface(IID_IEnumString, (VOID **)&m_pEnum);
1126     TRACE("m_pEnum: %p\n", static_cast<void *>(m_pEnum));
1127     if (m_pEnum)
1128         m_pEnum->AddRef(); // hold not to be freed
1129 
1130     // get an IACList
1131     ATLASSERT(!m_pACList);
1132     punkACL->QueryInterface(IID_IACList, (VOID **)&m_pACList);
1133     TRACE("m_pACList: %p\n", static_cast<void *>(m_pACList));
1134     if (m_pACList)
1135         m_pACList->AddRef(); // hold not to be freed
1136 
1137     UpdateDropDownState(); // create/hide the drop-down window if necessary
1138 
1139     // load quick completion info
1140     LoadQuickComplete(pwszRegKeyPath, pwszQuickComplete);
1141 
1142     // any combobox for m_hwndEdit?
1143     m_hwndCombo = NULL;
1144     HWND hwndParent = ::GetParent(m_hwndEdit);
1145     WCHAR szClass[16];
1146     if (::GetClassNameW(hwndParent, szClass, _countof(szClass)))
1147     {
1148         if (::StrCmpIW(szClass, WC_COMBOBOXW) == 0 ||
1149             ::StrCmpIW(szClass, WC_COMBOBOXEXW) == 0)
1150         {
1151             m_hwndCombo = hwndParent; // get combobox
1152         }
1153     }
1154 
1155     return S_OK;
1156 }
1157 
1158 //////////////////////////////////////////////////////////////////////////////
1159 // CAutoComplete IAutoComplete2 methods
1160 
1161 // @implemented
1162 STDMETHODIMP CAutoComplete::GetOptions(DWORD *pdwFlag)
1163 {
1164     TRACE("(%p) -> (%p)\n", this, pdwFlag);
1165     if (pdwFlag)
1166     {
1167         *pdwFlag = m_dwOptions;
1168         return S_OK;
1169     }
1170     return E_INVALIDARG;
1171 }
1172 
1173 // @implemented
1174 STDMETHODIMP CAutoComplete::SetOptions(DWORD dwFlag)
1175 {
1176     TRACE("(%p) -> (0x%x)\n", this, dwFlag);
1177     m_dwOptions = dwFlag;
1178 
1179     if (m_dwOptions & ACO_SEARCH)
1180         FIXME(" ACO_SEARCH not supported\n");
1181     if (m_dwOptions & ACO_FILTERPREFIXES)
1182         FIXME(" ACO_FILTERPREFIXES not supported\n");
1183     if (m_dwOptions & ACO_RTLREADING)
1184         FIXME(" ACO_RTLREADING not supported\n");
1185 
1186     UpdateDropDownState(); // create/hide the drop-down window if necessary
1187     return S_OK;
1188 }
1189 
1190 //////////////////////////////////////////////////////////////////////////////
1191 // CAutoComplete IAutoCompleteDropDown methods
1192 
1193 // @implemented
1194 STDMETHODIMP CAutoComplete::GetDropDownStatus(DWORD *pdwFlags, LPWSTR *ppwszString)
1195 {
1196     BOOL dropped = m_hwndList.IsWindowVisible();
1197 
1198     if (pdwFlags)
1199         *pdwFlags = (dropped ? ACDD_VISIBLE : 0);
1200 
1201     if (ppwszString)
1202     {
1203         *ppwszString = NULL;
1204 
1205         if (dropped)
1206         {
1207             // get selected item
1208             INT iItem = m_hwndList.GetCurSel();
1209             if (iItem >= 0)
1210             {
1211                 // get the text of item
1212                 CStringW strText = m_hwndList.GetItemText(iItem);
1213 
1214                 // store to *ppwszString
1215                 SHStrDupW(strText, ppwszString);
1216                 if (*ppwszString == NULL)
1217                     return E_OUTOFMEMORY;
1218             }
1219         }
1220     }
1221 
1222     return S_OK;
1223 }
1224 
1225 STDMETHODIMP CAutoComplete::ResetEnumerator()
1226 {
1227     FIXME("(%p): stub\n", this);
1228 
1229     Reset();
1230     m_innerList.RemoveAll();
1231     return S_OK;
1232 }
1233 
1234 //////////////////////////////////////////////////////////////////////////////
1235 // CAutoComplete IEnumString methods
1236 
1237 // @implemented
1238 STDMETHODIMP CAutoComplete::Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched)
1239 {
1240     TRACE("(%p, %d, %p, %p)\n", this, celt, rgelt, pceltFetched);
1241     if (rgelt)
1242         *rgelt = NULL;
1243     if (*pceltFetched)
1244         *pceltFetched = 0;
1245     if (celt != 1 || !rgelt || !pceltFetched || !m_pEnum)
1246         return E_INVALIDARG;
1247 
1248     LPWSTR pszText = NULL;
1249     HRESULT hr = m_pEnum->Next(1, &pszText, pceltFetched);
1250     if (hr == S_OK)
1251         *rgelt = pszText;
1252     else
1253         ::CoTaskMemFree(pszText);
1254     return hr;
1255 }
1256 
1257 // @implemented
1258 STDMETHODIMP CAutoComplete::Skip(ULONG celt)
1259 {
1260     TRACE("(%p, %d)\n", this, celt);
1261     return E_NOTIMPL;
1262 }
1263 
1264 // @implemented
1265 STDMETHODIMP CAutoComplete::Reset()
1266 {
1267     TRACE("(%p)\n", this);
1268     if (m_pEnum)
1269         return m_pEnum->Reset();
1270     return E_FAIL;
1271 }
1272 
1273 // @implemented
1274 STDMETHODIMP CAutoComplete::Clone(IEnumString **ppOut)
1275 {
1276     TRACE("(%p, %p)\n", this, ppOut);
1277     if (ppOut)
1278         *ppOut = NULL;
1279     return E_NOTIMPL;
1280 }
1281 
1282 //////////////////////////////////////////////////////////////////////////////
1283 // CAutoComplete protected methods
1284 
1285 VOID CAutoComplete::UpdateDropDownState()
1286 {
1287     if (CanAutoSuggest())
1288     {
1289         // create the drop-down window if not existed
1290         if (!m_hWnd)
1291             CreateDropDown();
1292     }
1293     else
1294     {
1295         // hide if existed
1296         if (m_hWnd)
1297             ShowWindow(SW_HIDE);
1298     }
1299 }
1300 
1301 // calculate the positions of the controls
1302 VOID CAutoComplete::CalcRects(BOOL bDowner, RECT& rcList, RECT& rcScrollBar, RECT& rcSizeBox)
1303 {
1304     // get the client rectangle
1305     RECT rcClient;
1306     GetClientRect(&rcClient);
1307 
1308     // the list
1309     rcList = rcClient;
1310     rcList.right = rcList.left + CX_LIST;
1311 
1312     // the scroll bar
1313     rcScrollBar = rcClient;
1314     rcScrollBar.left = rcClient.right - GetSystemMetrics(SM_CXVSCROLL);
1315     if (bDowner)
1316     {
1317         rcScrollBar.top = 0;
1318         rcScrollBar.bottom = rcClient.bottom - GetSystemMetrics(SM_CYHSCROLL);
1319     }
1320     else
1321     {
1322         rcScrollBar.top = GetSystemMetrics(SM_CYHSCROLL);
1323     }
1324 
1325     // the size box
1326     rcSizeBox = rcClient;
1327     rcSizeBox.left = rcClient.right - GetSystemMetrics(SM_CXVSCROLL);
1328     if (bDowner)
1329     {
1330         rcSizeBox.top = rcClient.bottom - GetSystemMetrics(SM_CYHSCROLL);
1331     }
1332     else
1333     {
1334         rcSizeBox.top = 0;
1335         rcSizeBox.bottom = rcClient.top + GetSystemMetrics(SM_CYHSCROLL);
1336     }
1337 }
1338 
1339 VOID CAutoComplete::LoadQuickComplete(LPCWSTR pwszRegKeyPath, LPCWSTR pwszQuickComplete)
1340 {
1341     m_strQuickComplete.Empty();
1342 
1343     if (pwszRegKeyPath)
1344     {
1345         CStringW strPath = pwszRegKeyPath;
1346         INT ichSep = strPath.ReverseFind(L'\\'); // find separator
1347         if (ichSep != -1) // found the last separator
1348         {
1349             // split by the separator
1350             CStringW strKey = strPath.Left(ichSep);
1351             CStringW strName = strPath.Mid(ichSep + 1);
1352 
1353             // load from registry
1354             WCHAR szValue[MAX_PATH] = L"";
1355             DWORD cbValue = sizeof(szValue), dwType = REG_NONE;
1356             SHRegGetUSValueW(pwszRegKeyPath, strName, &dwType,
1357                              szValue, &cbValue, FALSE, NULL, 0);
1358             if (szValue[0] != 0 && cbValue != 0 &&
1359                 (dwType == REG_SZ || dwType == REG_EXPAND_SZ))
1360             {
1361                 m_strQuickComplete = szValue;
1362             }
1363         }
1364     }
1365 
1366     if (pwszQuickComplete && m_strQuickComplete.IsEmpty())
1367     {
1368         m_strQuickComplete = pwszQuickComplete;
1369     }
1370 }
1371 
1372 CStringW CAutoComplete::GetQuickEdit(LPCWSTR pszText)
1373 {
1374     if (pszText[0] == 0 || m_strQuickComplete.IsEmpty())
1375         return pszText;
1376 
1377     // m_strQuickComplete will be "www.%s.com" etc.
1378     CStringW ret;
1379     ret.Format(m_strQuickComplete, pszText);
1380     return ret;
1381 }
1382 
1383 VOID CAutoComplete::RepositionDropDown()
1384 {
1385     // get nearest monitor from m_hwndEdit
1386     HMONITOR hMon = ::MonitorFromWindow(m_hwndEdit, MONITOR_DEFAULTTONEAREST);
1387     ATLASSERT(hMon != NULL);
1388     if (hMon == NULL)
1389         return;
1390 
1391     // get nearest monitor info
1392     MONITORINFO mi = { sizeof(mi) };
1393     if (!::GetMonitorInfo(hMon, &mi))
1394     {
1395         ATLASSERT(FALSE);
1396         return;
1397     }
1398 
1399     // get count and item height
1400     INT cItems = GetItemCount();
1401     INT cyItem = m_hwndList.m_cyItem;
1402     ATLASSERT(cyItem > 0);
1403 
1404     // get m_hwndEdit position
1405     RECT rcEdit;
1406     ::GetWindowRect(m_hwndEdit, &rcEdit);
1407     INT x = rcEdit.left, y = rcEdit.bottom;
1408 
1409     // get list extent
1410     RECT rcMon = mi.rcMonitor;
1411     INT cx = rcEdit.right - rcEdit.left, cy = cItems * cyItem;
1412     BOOL bLongList = FALSE;
1413     if (cy > CY_LIST)
1414     {
1415         cy = INT(CY_LIST / cyItem) * cyItem;
1416         bLongList = TRUE;
1417     }
1418 
1419     // convert rectangle for frame
1420     RECT rc = { 0, 0, cx, cy };
1421     AdjustWindowRectEx(&rc, GetStyle(), FALSE, GetExStyle());
1422     cy = rc.bottom - rc.top;
1423 
1424     if (!m_bResized)
1425     {
1426         // is the drop-down window a 'downer' or 'upper'?
1427         // NOTE: 'downer' is below the EDIT control. 'upper' is above the EDIT control.
1428         m_bDowner = (rcEdit.bottom + cy < rcMon.bottom);
1429     }
1430 
1431     // adjust y and cy
1432     if (m_bDowner)
1433     {
1434         if (rcMon.bottom < y + cy)
1435         {
1436             cy = ((rcMon.bottom - y) / cyItem) * cyItem;
1437             bLongList = TRUE;
1438         }
1439     }
1440     else
1441     {
1442         if (rcEdit.top < rcMon.top + cy)
1443         {
1444             cy = ((rcEdit.top - rcMon.top) / cyItem) * cyItem;
1445             bLongList = TRUE;
1446         }
1447         y = rcEdit.top - cy;
1448     }
1449 
1450     // set status
1451     m_hwndSizeBox.SetStatus(m_bDowner, bLongList);
1452 
1453     if (m_bResized) // already resized?
1454         PostMessageW(WM_SIZE, 0, 0); // re-layout
1455     else
1456         MoveWindow(x, y, cx, cy); // move
1457 
1458     // show without activation
1459     ShowWindow(SW_SHOWNOACTIVATE);
1460 }
1461 
1462 INT CAutoComplete::ReLoadInnerList()
1463 {
1464     m_innerList.RemoveAll(); // clear contents
1465 
1466     if (!m_pEnum)
1467         return 0;
1468 
1469     DWORD dwTick = ::GetTickCount(); // used for timeout
1470 
1471     // reload the items
1472     LPWSTR pszItem;
1473     ULONG cGot;
1474     HRESULT hr;
1475     for (ULONG cTotal = 0; cTotal < MAX_ITEM_COUNT; ++cTotal)
1476     {
1477         // get next item
1478         hr = m_pEnum->Next(1, &pszItem, &cGot);
1479         //TRACE("m_pEnum->Next(%p): 0x%08lx\n", reinterpret_cast<IUnknown *>(m_pEnum), hr);
1480         if (hr != S_OK)
1481             break;
1482 
1483         m_innerList.Add(pszItem); // append item to m_innerList
1484         ::CoTaskMemFree(pszItem); // free
1485 
1486         // check the timeout
1487         if (::GetTickCount() - dwTick >= COMPLETION_TIMEOUT)
1488             break; // too late
1489     }
1490 
1491     return m_innerList.GetSize(); // the number of items
1492 }
1493 
1494 // update inner list and m_strText and m_strStemText
1495 INT CAutoComplete::UpdateInnerList()
1496 {
1497     // get text
1498     CStringW strText = GetEditText();
1499 
1500     BOOL bReset = FALSE, bExpand = FALSE; // flags
1501 
1502     // if previous text was empty
1503     if (m_strText.IsEmpty())
1504     {
1505         bReset = TRUE;
1506     }
1507     // save text
1508     m_strText = strText;
1509 
1510     // do expand the items if the stem is changed
1511     CStringW strStemText = GetStemText();
1512     if (m_strStemText.CompareNoCase(strStemText) != 0)
1513     {
1514         m_strStemText = strStemText;
1515         bExpand = bReset = TRUE;
1516     }
1517 
1518     // reset if necessary
1519     if (bReset && m_pEnum)
1520     {
1521         HRESULT hr = m_pEnum->Reset(); // IEnumString::Reset
1522         TRACE("m_pEnum->Reset(%p): 0x%08lx\n",
1523               static_cast<IUnknown *>(m_pEnum), hr);
1524     }
1525 
1526     // update ac list if necessary
1527     if (bExpand && m_pACList)
1528     {
1529         HRESULT hr = m_pACList->Expand(strStemText); // IACList::Expand
1530         TRACE("m_pACList->Expand(%p, %S): 0x%08lx\n",
1531               static_cast<IUnknown *>(m_pACList),
1532               static_cast<LPCWSTR>(strStemText), hr);
1533     }
1534 
1535     if (bExpand || m_innerList.GetSize() == 0)
1536     {
1537         // reload the inner list
1538         ReLoadInnerList();
1539     }
1540 
1541     return m_innerList.GetSize();
1542 }
1543 
1544 INT CAutoComplete::UpdateOuterList()
1545 {
1546     CStringW strText = GetEditText(); // get the text
1547 
1548     // update the outer list from the inner list
1549     m_outerList.RemoveAll();
1550     for (INT iItem = 0; iItem < m_innerList.GetSize(); ++iItem)
1551     {
1552         // is the beginning matched?
1553         const CStringW& strTarget = m_innerList[iItem];
1554         CStringW strBody;
1555         if (DropPrefix(strTarget, strBody))
1556         {
1557             if (::StrCmpNIW(strBody, strText, strText.GetLength()) == 0)
1558             {
1559                 m_outerList.Add(strTarget);
1560                 continue;
1561             }
1562         }
1563         if (::StrCmpNIW(strTarget, strText, strText.GetLength()) == 0)
1564         {
1565             m_outerList.Add(strTarget);
1566         }
1567     }
1568 
1569     // sort the list
1570     DoSort(m_outerList);
1571     // unique
1572     DoUniqueAndTrim(m_outerList);
1573 
1574     // set the item count of the virtual listview
1575     INT cItems = m_outerList.GetSize();
1576     if (strText.IsEmpty())
1577         cItems = 0;
1578     m_hwndList.SendMessageW(LVM_SETITEMCOUNT, cItems, 0);
1579 
1580     return cItems; // the number of items
1581 }
1582 
1583 VOID CAutoComplete::UpdateCompletion(BOOL bAppendOK)
1584 {
1585     TRACE("CAutoComplete::UpdateCompletion(%p, %d)\n", this, bAppendOK);
1586 
1587     // update inner list
1588     UINT cItems = UpdateInnerList();
1589     if (cItems == 0) // no items
1590     {
1591         HideDropDown();
1592         return;
1593     }
1594 
1595     if (CanAutoSuggest()) // can we auto-suggest?
1596     {
1597         m_bInSelectItem = TRUE; // don't respond
1598         SelectItem(-1); // select none
1599         m_bInSelectItem = FALSE;
1600 
1601         if (UpdateOuterList())
1602             RepositionDropDown();
1603         else
1604             HideDropDown();
1605     }
1606 
1607     if (CanAutoAppend() && bAppendOK) // can we auto-append?
1608     {
1609         DoAutoAppend();
1610     }
1611 }
1612 
1613 //////////////////////////////////////////////////////////////////////////////
1614 // CAutoComplete message handlers
1615 
1616 // WM_CREATE
1617 // This message is sent when the window is about to be created after WM_NCCREATE.
1618 // The return value is -1 (failure) or zero (success).
1619 LRESULT CAutoComplete::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1620 {
1621     TRACE("CAutoComplete::OnCreate(%p)\n", this);
1622 
1623     // set the pointer of CAutoComplete
1624     m_hwndList.m_pDropDown = this;
1625     m_hwndScrollBar.m_pDropDown = this;
1626     m_hwndSizeBox.m_pDropDown = this;
1627 
1628     // create the children
1629     m_hwndList.Create(m_hWnd);
1630     if (!m_hwndList)
1631         return -1; // failure
1632     m_hwndSizeBox.Create(m_hWnd);
1633     if (!m_hwndSizeBox)
1634         return -1; // failure
1635     m_hwndScrollBar.Create(m_hWnd);
1636     if (!m_hwndScrollBar)
1637         return -1; // failure
1638 
1639     // show the controls
1640     m_hwndList.ShowWindow(SW_SHOWNOACTIVATE);
1641     m_hwndSizeBox.ShowWindow(SW_SHOWNOACTIVATE);
1642     m_hwndScrollBar.ShowWindow(SW_SHOWNOACTIVATE);
1643 
1644     // set the list font
1645     m_hFont = reinterpret_cast<HFONT>(::GetStockObject(DEFAULT_GUI_FONT));
1646     m_hwndList.SetFont(m_hFont);
1647 
1648     // add reference to CAutoComplete::m_hWnd
1649     AddRef();
1650     return 0; // success
1651 }
1652 
1653 // WM_NCDESTROY
1654 LRESULT CAutoComplete::OnNCDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1655 {
1656     TRACE("CAutoComplete::OnNCDestroy(%p)\n", this);
1657 
1658     // hide
1659     if (IsWindowVisible())
1660         HideDropDown();
1661 
1662     // clear CAutoComplete pointers
1663     m_hwndList.m_pDropDown = NULL;
1664     m_hwndScrollBar.m_pDropDown = NULL;
1665     m_hwndSizeBox.m_pDropDown = NULL;
1666 
1667     // destroy controls
1668     m_hwndList.DestroyWindow();
1669     m_hwndScrollBar.DestroyWindow();
1670     m_hwndSizeBox.DestroyWindow();
1671 
1672     // clean up
1673     m_hwndCombo = NULL;
1674     // remove reference to CAutoComplete::m_hWnd
1675     Release();
1676     return 0;
1677 }
1678 
1679 // WM_EXITSIZEMOVE
1680 // This message is sent once to a window after it has exited the moving or sizing mode.
1681 LRESULT CAutoComplete::OnExitSizeMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1682 {
1683     TRACE("CAutoComplete::OnExitSizeMove(%p)\n", this);
1684     m_bResized = TRUE; // remember resized
1685 
1686     ModifyStyle(WS_THICKFRAME, 0); // remove thick frame to resize
1687     // frame changed
1688     UINT uSWP_ = SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE;
1689     SetWindowPos(NULL, 0, 0, 0, 0, uSWP_);
1690 
1691     ::SetFocus(m_hwndEdit); // restore focus
1692     return 0;
1693 }
1694 
1695 // WM_DRAWITEM @implemented
1696 // This message is sent to the owner window to draw m_hwndList.
1697 LRESULT CAutoComplete::OnDrawItem(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1698 {
1699     LPDRAWITEMSTRUCT pDraw = reinterpret_cast<LPDRAWITEMSTRUCT>(lParam);
1700     ATLASSERT(pDraw != NULL);
1701     ATLASSERT(m_hwndList.GetStyle() & LVS_OWNERDRAWFIXED);
1702 
1703     // sanity check
1704     if (pDraw->CtlType != ODT_LISTVIEW || pDraw->hwndItem != m_hwndList)
1705         return FALSE;
1706 
1707     // item rectangle
1708     RECT rcItem = pDraw->rcItem;
1709 
1710     // get info
1711     UINT iItem = pDraw->itemID; // the index of item
1712     CStringW strItem = m_hwndList.GetItemText(iItem); // get text of item
1713 
1714     // draw background and set text color
1715     HDC hDC = pDraw->hDC;
1716     BOOL bSelected = (pDraw->itemState & ODS_SELECTED);
1717     if (bSelected)
1718     {
1719         ::FillRect(hDC, &rcItem, ::GetSysColorBrush(COLOR_HIGHLIGHT));
1720         ::SetTextColor(hDC, ::GetSysColor(COLOR_HIGHLIGHTTEXT));
1721     }
1722     else
1723     {
1724         ::FillRect(hDC, &rcItem, ::GetSysColorBrush(COLOR_WINDOW));
1725         ::SetTextColor(hDC, ::GetSysColor(COLOR_WINDOWTEXT));
1726     }
1727 
1728     // draw text
1729     rcItem.left += ::GetSystemMetrics(SM_CXBORDER);
1730     HGDIOBJ hFontOld = ::SelectObject(hDC, m_hFont);
1731     const UINT uDT_ = DT_LEFT | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER;
1732     ::SetBkMode(hDC, TRANSPARENT);
1733     ::DrawTextW(hDC, strItem, -1, &rcItem, uDT_);
1734     ::SelectObject(hDC, hFontOld);
1735 
1736     return TRUE;
1737 }
1738 
1739 // WM_GETMINMAXINFO @implemented
1740 // This message is sent to a window when the size or position of the window is about to change.
1741 LRESULT CAutoComplete::OnGetMinMaxInfo(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1742 {
1743     // restrict minimum size
1744     LPMINMAXINFO pInfo = reinterpret_cast<LPMINMAXINFO>(lParam);
1745     pInfo->ptMinTrackSize.x = ::GetSystemMetrics(SM_CXVSCROLL);
1746     pInfo->ptMinTrackSize.y = ::GetSystemMetrics(SM_CYHSCROLL);
1747     return 0;
1748 }
1749 
1750 // WM_MEASUREITEM @implemented
1751 // This message is sent to the owner window to get the item extent of m_hwndList.
1752 LRESULT CAutoComplete::OnMeasureItem(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1753 {
1754     LPMEASUREITEMSTRUCT pMeasure = reinterpret_cast<LPMEASUREITEMSTRUCT>(lParam);
1755     ATLASSERT(pMeasure != NULL);
1756     if (pMeasure->CtlType != ODT_LISTVIEW)
1757         return FALSE;
1758     if (!m_hwndList)
1759         return FALSE;
1760     ATLASSERT(m_hwndList.GetStyle() & LVS_OWNERDRAWFIXED);
1761     pMeasure->itemHeight = m_hwndList.m_cyItem; // height of item
1762     return TRUE;
1763 }
1764 
1765 // WM_MOUSEACTIVATE @implemented
1766 // The return value of this message specifies whether the window should be activated or not.
1767 LRESULT CAutoComplete::OnMouseActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1768 {
1769     return MA_NOACTIVATE; // don't activate by mouse
1770 }
1771 
1772 // WM_NCACTIVATE
1773 // This message is sent to a window to indicate an active or inactive state.
1774 LRESULT CAutoComplete::OnNCActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1775 {
1776     bHandled = FALSE; // do default
1777     return 0;
1778 }
1779 
1780 // WM_NCLBUTTONDOWN
1781 LRESULT CAutoComplete::OnNCLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1782 {
1783     switch (wParam)
1784     {
1785         case HTBOTTOMRIGHT: case HTTOPRIGHT:
1786         {
1787             // add thick frame to resize.
1788             ModifyStyle(0, WS_THICKFRAME);
1789             // frame changed
1790             UINT uSWP_ = SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE;
1791             SetWindowPos(NULL, 0, 0, 0, 0, uSWP_);
1792             break;
1793         }
1794     }
1795     bHandled = FALSE; // do default
1796     return 0;
1797 }
1798 
1799 // WM_NOTIFY
1800 // This message informs the parent window of a control that an event has occurred.
1801 LRESULT CAutoComplete::OnNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1802 {
1803     LPNMHDR pnmh = reinterpret_cast<LPNMHDR>(lParam);
1804     ATLASSERT(pnmh != NULL);
1805 
1806     switch (pnmh->code)
1807     {
1808         case NM_DBLCLK: // double-clicked
1809         {
1810             TRACE("NM_DBLCLK\n");
1811             HideDropDown();
1812             break;
1813         }
1814         case NM_HOVER: // mouse is hovering
1815         {
1816             POINT pt;
1817             ::GetCursorPos(&pt); // get cursor position in screen coordinates
1818             m_hwndList.ScreenToClient(&pt); // into client coordinates
1819             INT iItem = m_hwndList.ItemFromPoint(pt.x, pt.y);
1820             if (iItem != -1)
1821             {
1822                 m_bInSelectItem = TRUE; // don't respond
1823                 m_hwndList.SetCurSel(iItem); // select
1824                 m_bInSelectItem = FALSE;
1825             }
1826             return TRUE; // eat
1827         }
1828         case LVN_GETDISPINFOA: // for user's information only
1829         {
1830             TRACE("LVN_GETDISPINFOA\n");
1831             if (pnmh->hwndFrom != m_hwndList)
1832                 break;
1833 
1834             LV_DISPINFOA *pDispInfo = reinterpret_cast<LV_DISPINFOA *>(pnmh);
1835             LV_ITEMA *pItem = &pDispInfo->item;
1836             INT iItem = pItem->iItem;
1837             if (iItem == -1)
1838                 break;
1839 
1840             CStringW strText = GetItemText(iItem);
1841             if (pItem->mask & LVIF_TEXT)
1842                 SHUnicodeToAnsi(strText, pItem->pszText, pItem->cchTextMax);
1843             break;
1844         }
1845         case LVN_GETDISPINFOW: // for user's information only
1846         {
1847             TRACE("LVN_GETDISPINFOW\n");
1848             if (pnmh->hwndFrom != m_hwndList)
1849                 break;
1850 
1851             LV_DISPINFOW *pDispInfo = reinterpret_cast<LV_DISPINFOW *>(pnmh);
1852             LV_ITEMW *pItem = &pDispInfo->item;
1853             INT iItem = pItem->iItem;
1854             if (iItem == -1)
1855                 break;
1856 
1857             CStringW strText = GetItemText(iItem);
1858             if (pItem->mask & LVIF_TEXT)
1859                 StringCbCopyW(pItem->pszText, pItem->cchTextMax, strText);
1860             break;
1861         }
1862         case LVN_HOTTRACK: // enabled by LVS_EX_TRACKSELECT
1863         {
1864             TRACE("LVN_HOTTRACK\n");
1865             LPNMLISTVIEW pListView = reinterpret_cast<LPNMLISTVIEW>(pnmh);
1866             INT iItem = pListView->iItem;
1867             TRACE("LVN_HOTTRACK: iItem:%d\n", iItem);
1868             m_hwndList.SetCurSel(iItem);
1869             m_hwndList.EnsureVisible(iItem, FALSE);
1870             return TRUE;
1871         }
1872         case LVN_ITEMACTIVATE: // enabled by LVS_EX_ONECLICKACTIVATE
1873         {
1874             TRACE("LVN_ITEMACTIVATE\n");
1875             LPNMITEMACTIVATE pItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pnmh);
1876             INT iItem = pItemActivate->iItem;
1877             TRACE("LVN_ITEMACTIVATE: iItem:%d\n", iItem);
1878             if (iItem != -1) // the item is clicked
1879             {
1880                 SelectItem(iItem);
1881                 HideDropDown();
1882             }
1883             break;
1884         }
1885         case LVN_ITEMCHANGED: // item info is changed
1886         {
1887             TRACE("LVN_ITEMCHANGED\n");
1888             LPNMLISTVIEW pListView = reinterpret_cast<LPNMLISTVIEW>(pnmh);
1889             if (pListView->uChanged & LVIF_STATE) // selection changed
1890             {
1891                 // listview selection changed
1892                 if (!m_bInSelectItem)
1893                 {
1894                     OnListSelChange();
1895                 }
1896                 UpdateScrollBar();
1897             }
1898             break;
1899         }
1900     }
1901 
1902     return 0;
1903 }
1904 
1905 // WM_NCHITTEST @implemented
1906 // The return value is indicating the cursor shape and the behaviour.
1907 LRESULT CAutoComplete::OnNCHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1908 {
1909     TRACE("CAutoComplete::OnNCHitTest(%p)\n", this);
1910     POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; // in screen coordinates
1911     ScreenToClient(&pt); // into client coordinates
1912     if (ChildWindowFromPoint(pt) == m_hwndSizeBox) // hit?
1913     {
1914         // allow resizing (with cursor shape)
1915         return m_bDowner ? HTBOTTOMRIGHT : HTTOPRIGHT;
1916     }
1917     bHandled = FALSE; // do default
1918     return 0;
1919 }
1920 
1921 // WM_SIZE @implemented
1922 // This message is sent when the window size is changed.
1923 LRESULT CAutoComplete::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1924 {
1925     // calculate the positions of the controls
1926     CRect rcList, rcScrollBar, rcSizeBox;
1927     CalcRects(m_bDowner, rcList, rcScrollBar, rcSizeBox);
1928 
1929     // reposition the controls in smartest way
1930     UINT uSWP_ = SWP_NOACTIVATE | SWP_NOCOPYBITS;
1931     HDWP hDWP = ::BeginDeferWindowPos(3);
1932     hDWP = ::DeferWindowPos(hDWP, m_hwndScrollBar, HWND_TOP,
1933                             rcScrollBar.left, rcScrollBar.top,
1934                             rcScrollBar.Width(), rcScrollBar.Height(), uSWP_);
1935     hDWP = ::DeferWindowPos(hDWP, m_hwndSizeBox, m_hwndScrollBar,
1936                             rcSizeBox.left, rcSizeBox.top,
1937                             rcSizeBox.Width(), rcSizeBox.Height(), uSWP_);
1938     hDWP = ::DeferWindowPos(hDWP, m_hwndList, m_hwndSizeBox,
1939                             rcList.left, rcList.top,
1940                             rcList.Width(), rcList.Height(), uSWP_);
1941     ::EndDeferWindowPos(hDWP);
1942 
1943     UpdateScrollBar();
1944     return 0;
1945 }
1946 
1947 // WM_SHOWWINDOW
1948 LRESULT CAutoComplete::OnShowWindow(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
1949 {
1950     // hook mouse events
1951     BOOL bShow = (BOOL)wParam;
1952     if (bShow)
1953     {
1954         if (s_hWatchWnd != m_hWnd && ::IsWindowVisible(s_hWatchWnd))
1955             ::ShowWindowAsync(s_hWatchWnd, SW_HIDE);
1956         s_hWatchWnd = m_hWnd; // watch this
1957 
1958         // unhook mouse if any
1959         if (s_hMouseHook)
1960         {
1961             HHOOK hHookOld = s_hMouseHook;
1962             s_hMouseHook = NULL;
1963             ::UnhookWindowsHookEx(hHookOld);
1964         }
1965 
1966         // hook mouse
1967         s_hMouseHook = ::SetWindowsHookEx(WH_MOUSE, MouseProc, NULL, ::GetCurrentThreadId());
1968         ATLASSERT(s_hMouseHook != NULL);
1969 
1970         // set timer
1971         SetTimer(WATCH_TIMER_ID, WATCH_INTERVAL, NULL);
1972 
1973         bHandled = FALSE; // do default
1974         return 0;
1975     }
1976     else
1977     {
1978         // kill timer
1979         KillTimer(WATCH_TIMER_ID);
1980 
1981         s_hWatchWnd = NULL; // unwatch
1982 
1983         // unhook mouse if any
1984         if (s_hMouseHook)
1985         {
1986             HHOOK hHookOld = s_hMouseHook;
1987             s_hMouseHook = NULL;
1988             ::UnhookWindowsHookEx(hHookOld);
1989         }
1990 
1991         LRESULT ret = DefWindowProcW(uMsg, wParam, lParam); // do default
1992 
1993         if (m_hwndCombo)
1994             ::InvalidateRect(m_hwndCombo, NULL, TRUE); // redraw
1995 
1996         m_outerList.RemoveAll(); // no use
1997         return ret;
1998     }
1999 }
2000 
2001 // WM_TIMER
2002 LRESULT CAutoComplete::OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
2003 {
2004     if (wParam != WATCH_TIMER_ID) // sanity check
2005         return 0;
2006 
2007     // if the textbox is dead, then kill the timer
2008     if (!::IsWindow(m_hwndEdit))
2009     {
2010         KillTimer(WATCH_TIMER_ID);
2011         return 0;
2012     }
2013 
2014     // m_hwndEdit is moved?
2015     RECT rcEdit;
2016     ::GetWindowRect(m_hwndEdit, &rcEdit);
2017     if (!::EqualRect(&rcEdit, &m_rcEdit))
2018     {
2019         // if so, hide
2020         HideDropDown();
2021 
2022         m_rcEdit = rcEdit; // update rectangle
2023         m_bResized = FALSE; // clear flag
2024     }
2025 
2026     return 0;
2027 }
2028 
2029 // WM_VSCROLL
2030 // This message is sent when a scroll event occurs.
2031 LRESULT CAutoComplete::OnVScroll(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
2032 {
2033     TRACE("CAutoComplete::OnVScroll(%p)\n", this);
2034     WORD code = LOWORD(wParam);
2035     switch (code)
2036     {
2037         case SB_THUMBPOSITION: case SB_THUMBTRACK:
2038         {
2039             // get the scrolling info
2040             INT nPos = HIWORD(wParam);
2041             SCROLLINFO si = { sizeof(si), SIF_ALL };
2042             m_hwndList.GetScrollInfo(SB_VERT, &si);
2043 
2044             // scroll the list-view by CListView::EnsureVisible
2045             INT cItems = m_hwndList.GetItemCount();
2046             // iItem : cItems == (nPos - si.nMin) : (si.nMax - si.nMin).
2047             INT iItem = cItems * (nPos - si.nMin) / (si.nMax - si.nMin);
2048             if (nPos > si.nPos)
2049             {
2050                 iItem += m_hwndList.GetVisibleCount();
2051                 if (iItem >= cItems)
2052                     iItem = cItems - 1;
2053             }
2054             m_hwndList.EnsureVisible(iItem, FALSE);
2055 
2056             // update scrolling position of m_hwndScrollBar
2057             si.fMask = SIF_POS;
2058             m_hwndList.GetScrollInfo(SB_VERT, &si);
2059             m_hwndScrollBar.SetScrollInfo(SB_VERT, &si, FALSE);
2060             break;
2061         }
2062         default:
2063         {
2064             // pass it to m_hwndList
2065             m_hwndList.SendMessageW(WM_VSCROLL, wParam, lParam);
2066             UpdateScrollBar();
2067             break;
2068         }
2069     }
2070     return 0;
2071 }
2072