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