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