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