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