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