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