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(AC_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 ATLASSERT(!m_pEnum); 1130 punkACL->QueryInterface(IID_IEnumString, (VOID **)&m_pEnum); 1131 TRACE("m_pEnum: %p\n", static_cast<void *>(m_pEnum)); 1132 if (m_pEnum) 1133 m_pEnum->AddRef(); // hold not to be freed 1134 1135 // get an IACList 1136 ATLASSERT(!m_pACList); 1137 punkACL->QueryInterface(IID_IACList, (VOID **)&m_pACList); 1138 TRACE("m_pACList: %p\n", static_cast<void *>(m_pACList)); 1139 if (m_pACList) 1140 m_pACList->AddRef(); // hold not to be freed 1141 1142 UpdateDropDownState(); // create/hide the drop-down window if necessary 1143 1144 // load quick completion info 1145 LoadQuickComplete(pwszRegKeyPath, pwszQuickComplete); 1146 1147 // any combobox for m_hwndEdit? 1148 m_hwndCombo = NULL; 1149 HWND hwndParent = ::GetParent(m_hwndEdit); 1150 WCHAR szClass[16]; 1151 if (::GetClassNameW(hwndParent, szClass, _countof(szClass))) 1152 { 1153 if (::StrCmpIW(szClass, WC_COMBOBOXW) == 0 || 1154 ::StrCmpIW(szClass, WC_COMBOBOXEXW) == 0) 1155 { 1156 m_hwndCombo = hwndParent; // get combobox 1157 } 1158 } 1159 1160 return S_OK; 1161 } 1162 1163 ////////////////////////////////////////////////////////////////////////////// 1164 // CAutoComplete IAutoComplete2 methods 1165 1166 // @implemented 1167 STDMETHODIMP CAutoComplete::GetOptions(DWORD *pdwFlag) 1168 { 1169 TRACE("(%p) -> (%p)\n", this, pdwFlag); 1170 if (pdwFlag) 1171 { 1172 *pdwFlag = m_dwOptions; 1173 return S_OK; 1174 } 1175 return E_INVALIDARG; 1176 } 1177 1178 // @implemented 1179 STDMETHODIMP CAutoComplete::SetOptions(DWORD dwFlag) 1180 { 1181 TRACE("(%p) -> (0x%x)\n", this, dwFlag); 1182 m_dwOptions = dwFlag; 1183 1184 if (m_dwOptions & ACO_SEARCH) 1185 FIXME(" ACO_SEARCH not supported\n"); 1186 if (m_dwOptions & ACO_FILTERPREFIXES) 1187 FIXME(" ACO_FILTERPREFIXES not supported\n"); 1188 if (m_dwOptions & ACO_RTLREADING) 1189 FIXME(" ACO_RTLREADING not supported\n"); 1190 1191 UpdateDropDownState(); // create/hide the drop-down window if necessary 1192 return S_OK; 1193 } 1194 1195 ////////////////////////////////////////////////////////////////////////////// 1196 // CAutoComplete IAutoCompleteDropDown methods 1197 1198 // @implemented 1199 STDMETHODIMP CAutoComplete::GetDropDownStatus(DWORD *pdwFlags, LPWSTR *ppwszString) 1200 { 1201 BOOL dropped = m_hwndList.IsWindowVisible(); 1202 1203 if (pdwFlags) 1204 *pdwFlags = (dropped ? ACDD_VISIBLE : 0); 1205 1206 if (ppwszString) 1207 { 1208 *ppwszString = NULL; 1209 1210 if (dropped) 1211 { 1212 // get selected item 1213 INT iItem = m_hwndList.GetCurSel(); 1214 if (iItem >= 0) 1215 { 1216 // get the text of item 1217 CStringW strText = m_hwndList.GetItemText(iItem); 1218 1219 // store to *ppwszString 1220 SHStrDupW(strText, ppwszString); 1221 if (*ppwszString == NULL) 1222 return E_OUTOFMEMORY; 1223 } 1224 } 1225 } 1226 1227 return S_OK; 1228 } 1229 1230 STDMETHODIMP CAutoComplete::ResetEnumerator() 1231 { 1232 FIXME("(%p): stub\n", this); 1233 1234 Reset(); 1235 m_hwndList.SendMessageW(LVM_SETITEMCOUNT, 0, 0); 1236 m_outerList.RemoveAll(); 1237 return S_OK; 1238 } 1239 1240 ////////////////////////////////////////////////////////////////////////////// 1241 // CAutoComplete IEnumString methods 1242 1243 // @implemented 1244 STDMETHODIMP CAutoComplete::Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched) 1245 { 1246 TRACE("(%p, %d, %p, %p)\n", this, celt, rgelt, pceltFetched); 1247 if (rgelt) 1248 *rgelt = NULL; 1249 if (*pceltFetched) 1250 *pceltFetched = 0; 1251 if (celt != 1 || !rgelt || !pceltFetched || !m_pEnum) 1252 return E_INVALIDARG; 1253 1254 LPWSTR pszText = NULL; 1255 HRESULT hr = m_pEnum->Next(1, &pszText, pceltFetched); 1256 if (hr == S_OK) 1257 *rgelt = pszText; 1258 else 1259 ::CoTaskMemFree(pszText); 1260 return hr; 1261 } 1262 1263 // @implemented 1264 STDMETHODIMP CAutoComplete::Skip(ULONG celt) 1265 { 1266 TRACE("(%p, %d)\n", this, celt); 1267 return E_NOTIMPL; 1268 } 1269 1270 // @implemented 1271 STDMETHODIMP CAutoComplete::Reset() 1272 { 1273 TRACE("(%p)\n", this); 1274 if (m_pEnum) 1275 return m_pEnum->Reset(); 1276 return E_FAIL; 1277 } 1278 1279 // @implemented 1280 STDMETHODIMP CAutoComplete::Clone(IEnumString **ppOut) 1281 { 1282 TRACE("(%p, %p)\n", this, ppOut); 1283 if (ppOut) 1284 *ppOut = NULL; 1285 return E_NOTIMPL; 1286 } 1287 1288 ////////////////////////////////////////////////////////////////////////////// 1289 // CAutoComplete protected methods 1290 1291 VOID CAutoComplete::UpdateDropDownState() 1292 { 1293 if (CanAutoSuggest()) 1294 { 1295 // create the drop-down window if not existed 1296 if (!m_hWnd) 1297 CreateDropDown(); 1298 } 1299 else 1300 { 1301 // hide if existed 1302 if (m_hWnd) 1303 ShowWindow(SW_HIDE); 1304 } 1305 } 1306 1307 // calculate the positions of the controls 1308 VOID CAutoComplete::CalcRects(BOOL bDowner, RECT& rcList, RECT& rcScrollBar, RECT& rcSizeBox) const 1309 { 1310 // get the client rectangle 1311 RECT rcClient; 1312 GetClientRect(&rcClient); 1313 1314 // the list 1315 rcList = rcClient; 1316 rcList.right = rcList.left + CX_LIST; 1317 1318 // the scroll bar 1319 rcScrollBar = rcClient; 1320 rcScrollBar.left = rcClient.right - GetSystemMetrics(SM_CXVSCROLL); 1321 if (bDowner) 1322 { 1323 rcScrollBar.top = 0; 1324 rcScrollBar.bottom = rcClient.bottom - GetSystemMetrics(SM_CYHSCROLL); 1325 } 1326 else 1327 { 1328 rcScrollBar.top = GetSystemMetrics(SM_CYHSCROLL); 1329 } 1330 1331 // the size box 1332 rcSizeBox = rcClient; 1333 rcSizeBox.left = rcClient.right - GetSystemMetrics(SM_CXVSCROLL); 1334 if (bDowner) 1335 { 1336 rcSizeBox.top = rcClient.bottom - GetSystemMetrics(SM_CYHSCROLL); 1337 } 1338 else 1339 { 1340 rcSizeBox.top = 0; 1341 rcSizeBox.bottom = rcClient.top + GetSystemMetrics(SM_CYHSCROLL); 1342 } 1343 } 1344 1345 VOID CAutoComplete::LoadQuickComplete(LPCWSTR pwszRegKeyPath, LPCWSTR pwszQuickComplete) 1346 { 1347 m_strQuickComplete.Empty(); 1348 1349 if (pwszRegKeyPath) 1350 { 1351 CStringW strPath = pwszRegKeyPath; 1352 INT ichSep = strPath.ReverseFind(L'\\'); // find separator 1353 if (ichSep != -1) // found the last separator 1354 { 1355 // split by the separator 1356 CStringW strKey = strPath.Left(ichSep); 1357 CStringW strName = strPath.Mid(ichSep + 1); 1358 1359 // load from registry 1360 WCHAR szValue[MAX_PATH] = L""; 1361 DWORD cbValue = sizeof(szValue), dwType = REG_NONE; 1362 SHRegGetUSValueW(pwszRegKeyPath, strName, &dwType, 1363 szValue, &cbValue, FALSE, NULL, 0); 1364 if (szValue[0] != 0 && cbValue != 0 && 1365 (dwType == REG_SZ || dwType == REG_EXPAND_SZ)) 1366 { 1367 m_strQuickComplete = szValue; 1368 } 1369 } 1370 } 1371 1372 if (pwszQuickComplete && m_strQuickComplete.IsEmpty()) 1373 { 1374 m_strQuickComplete = pwszQuickComplete; 1375 } 1376 } 1377 1378 CStringW CAutoComplete::GetQuickEdit(LPCWSTR pszText) const 1379 { 1380 if (pszText[0] == 0 || m_strQuickComplete.IsEmpty()) 1381 return pszText; 1382 1383 // m_strQuickComplete will be "www.%s.com" etc. 1384 CStringW ret; 1385 ret.Format(m_strQuickComplete, pszText); 1386 return ret; 1387 } 1388 1389 VOID CAutoComplete::RepositionDropDown() 1390 { 1391 // get nearest monitor from m_hwndEdit 1392 HMONITOR hMon = ::MonitorFromWindow(m_hwndEdit, MONITOR_DEFAULTTONEAREST); 1393 ATLASSERT(hMon != NULL); 1394 if (hMon == NULL) 1395 return; 1396 1397 // get nearest monitor info 1398 MONITORINFO mi = { sizeof(mi) }; 1399 if (!::GetMonitorInfo(hMon, &mi)) 1400 { 1401 ATLASSERT(FALSE); 1402 return; 1403 } 1404 1405 // get count and item height 1406 INT cItems = GetItemCount(); 1407 INT cyItem = m_hwndList.m_cyItem; 1408 ATLASSERT(cyItem > 0); 1409 1410 // get m_hwndEdit position 1411 RECT rcEdit; 1412 ::GetWindowRect(m_hwndEdit, &rcEdit); 1413 INT x = rcEdit.left, y = rcEdit.bottom; 1414 1415 // get list extent 1416 RECT rcMon = mi.rcMonitor; 1417 INT cx = rcEdit.right - rcEdit.left, cy = cItems * cyItem; 1418 BOOL bLongList = FALSE; 1419 if (cy > CY_LIST) 1420 { 1421 cy = INT(CY_LIST / cyItem) * cyItem; 1422 bLongList = TRUE; 1423 } 1424 1425 // convert rectangle for frame 1426 RECT rc = { 0, 0, cx, cy }; 1427 AdjustWindowRectEx(&rc, GetStyle(), FALSE, GetExStyle()); 1428 cy = rc.bottom - rc.top; 1429 1430 if (!m_bResized) 1431 { 1432 // is the drop-down window a 'downer' or 'upper'? 1433 // NOTE: 'downer' is below the EDIT control. 'upper' is above the EDIT control. 1434 m_bDowner = (rcEdit.bottom + cy < rcMon.bottom); 1435 } 1436 1437 // adjust y and cy 1438 if (m_bDowner) 1439 { 1440 if (rcMon.bottom < y + cy) 1441 { 1442 cy = ((rcMon.bottom - y) / cyItem) * cyItem; 1443 bLongList = TRUE; 1444 } 1445 } 1446 else 1447 { 1448 if (rcEdit.top < rcMon.top + cy) 1449 { 1450 cy = ((rcEdit.top - rcMon.top) / cyItem) * cyItem; 1451 bLongList = TRUE; 1452 } 1453 y = rcEdit.top - cy; 1454 } 1455 1456 // set status 1457 m_hwndSizeBox.SetStatus(m_bDowner, bLongList); 1458 1459 if (m_bResized) // already resized? 1460 PostMessageW(WM_SIZE, 0, 0); // re-layout 1461 else 1462 MoveWindow(x, y, cx, cy); // move 1463 1464 // show without activation 1465 ShowWindow(SW_SHOWNOACTIVATE); 1466 } 1467 1468 VOID 1469 CAutoComplete::ExtractInnerList(CSimpleArray<CStringW>& outerList, 1470 const CSimpleArray<CStringW>& innerList, 1471 const CString& strText) 1472 { 1473 for (INT iItem = 0; iItem < innerList.GetSize(); ++iItem) 1474 { 1475 if (m_pThread || !m_hThread) 1476 break; 1477 1478 const CStringW& strTarget = innerList[iItem]; 1479 if (DoesMatch(strTarget, strText)) 1480 { 1481 outerList.Add(strTarget); 1482 1483 if (outerList.GetSize() >= MAX_ITEM_COUNT) 1484 break; 1485 } 1486 } 1487 } 1488 1489 VOID CAutoComplete::ReLoadInnerList(PAC_THREAD pThread) 1490 { 1491 pThread->m_innerList.RemoveAll(); // clear contents 1492 1493 if (!m_pEnum || pThread->m_strText.IsEmpty()) 1494 return; 1495 1496 // reload the items 1497 LPWSTR pszItem; 1498 ULONG cGot; 1499 HRESULT hr; 1500 CSimpleArray<CStringW>& innerList = pThread->m_innerList; 1501 while (!m_pThread && m_hThread) 1502 { 1503 // get next item 1504 hr = m_pEnum->Next(1, &pszItem, &cGot); 1505 if (hr != S_OK) 1506 break; 1507 1508 innerList.Add(pszItem); // append item to innerList 1509 ::CoTaskMemFree(pszItem); // free 1510 } 1511 } 1512 1513 VOID CAutoComplete::StartCompletion(BOOL bAppendOK) 1514 { 1515 TRACE("CAutoComplete::StartCompletion(%p, %d)\n", this, bAppendOK); 1516 1517 if (!m_pEnum || (!CanAutoSuggest() && !CanAutoAppend())) 1518 return; 1519 1520 ::SendMessageW(m_hWnd, AUTOCOMP_START, bAppendOK, 0); 1521 } 1522 1523 ////////////////////////////////////////////////////////////////////////////// 1524 // CAutoComplete message handlers 1525 1526 // WM_CREATE 1527 // This message is sent when the window is about to be created after WM_NCCREATE. 1528 // The return value is -1 (failure) or zero (success). 1529 LRESULT CAutoComplete::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 1530 { 1531 TRACE("CAutoComplete::OnCreate(%p)\n", this); 1532 1533 // set the pointer of CAutoComplete 1534 m_hwndList.m_pDropDown = this; 1535 m_hwndScrollBar.m_pDropDown = this; 1536 m_hwndSizeBox.m_pDropDown = this; 1537 1538 // create the children 1539 m_hwndList.Create(m_hWnd); 1540 if (!m_hwndList) 1541 return -1; // failure 1542 m_hwndSizeBox.Create(m_hWnd); 1543 if (!m_hwndSizeBox) 1544 return -1; // failure 1545 m_hwndScrollBar.Create(m_hWnd); 1546 if (!m_hwndScrollBar) 1547 return -1; // failure 1548 1549 // show the controls 1550 m_hwndList.ShowWindow(SW_SHOWNOACTIVATE); 1551 m_hwndSizeBox.ShowWindow(SW_SHOWNOACTIVATE); 1552 m_hwndScrollBar.ShowWindow(SW_SHOWNOACTIVATE); 1553 1554 // set the list font 1555 m_hFont = reinterpret_cast<HFONT>(::GetStockObject(DEFAULT_GUI_FONT)); 1556 m_hwndList.SetFont(m_hFont); 1557 1558 // add reference to CAutoComplete::m_hWnd 1559 AddRef(); 1560 return 0; // success 1561 } 1562 1563 // WM_NCDESTROY 1564 LRESULT CAutoComplete::OnNCDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 1565 { 1566 TRACE("CAutoComplete::OnNCDestroy(%p)\n", this); 1567 1568 // hide 1569 if (IsWindowVisible()) 1570 HideDropDown(); 1571 1572 // clear CAutoComplete pointers 1573 m_hwndList.m_pDropDown = NULL; 1574 m_hwndScrollBar.m_pDropDown = NULL; 1575 m_hwndSizeBox.m_pDropDown = NULL; 1576 1577 // destroy controls 1578 m_hwndList.DestroyWindow(); 1579 m_hwndScrollBar.DestroyWindow(); 1580 m_hwndSizeBox.DestroyWindow(); 1581 1582 // clean up 1583 m_hwndCombo = NULL; 1584 // remove reference to CAutoComplete::m_hWnd 1585 Release(); 1586 return 0; 1587 } 1588 1589 // WM_EXITSIZEMOVE 1590 // This message is sent once to a window after it has exited the moving or sizing mode. 1591 LRESULT CAutoComplete::OnExitSizeMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 1592 { 1593 TRACE("CAutoComplete::OnExitSizeMove(%p)\n", this); 1594 m_bResized = TRUE; // remember resized 1595 1596 ModifyStyle(WS_THICKFRAME, 0); // remove thick frame to resize 1597 // frame changed 1598 UINT uSWP_ = SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE; 1599 SetWindowPos(NULL, 0, 0, 0, 0, uSWP_); 1600 1601 ::SetFocus(m_hwndEdit); // restore focus 1602 return 0; 1603 } 1604 1605 // WM_DRAWITEM @implemented 1606 // This message is sent to the owner window to draw m_hwndList. 1607 LRESULT CAutoComplete::OnDrawItem(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 1608 { 1609 LPDRAWITEMSTRUCT pDraw = reinterpret_cast<LPDRAWITEMSTRUCT>(lParam); 1610 ATLASSERT(pDraw != NULL); 1611 ATLASSERT(m_hwndList.GetStyle() & LVS_OWNERDRAWFIXED); 1612 1613 // sanity check 1614 if (pDraw->CtlType != ODT_LISTVIEW || pDraw->hwndItem != m_hwndList) 1615 return FALSE; 1616 1617 // item rectangle 1618 RECT rcItem = pDraw->rcItem; 1619 1620 // get info 1621 UINT iItem = pDraw->itemID; // the index of item 1622 CStringW strItem = m_hwndList.GetItemText(iItem); // get text of item 1623 1624 // draw background and set text color 1625 HDC hDC = pDraw->hDC; 1626 BOOL bSelected = (pDraw->itemState & ODS_SELECTED); 1627 if (bSelected) 1628 { 1629 ::FillRect(hDC, &rcItem, ::GetSysColorBrush(COLOR_HIGHLIGHT)); 1630 ::SetTextColor(hDC, ::GetSysColor(COLOR_HIGHLIGHTTEXT)); 1631 } 1632 else 1633 { 1634 ::FillRect(hDC, &rcItem, ::GetSysColorBrush(COLOR_WINDOW)); 1635 ::SetTextColor(hDC, ::GetSysColor(COLOR_WINDOWTEXT)); 1636 } 1637 1638 // draw text 1639 rcItem.left += ::GetSystemMetrics(SM_CXBORDER); 1640 HGDIOBJ hFontOld = ::SelectObject(hDC, m_hFont); 1641 const UINT uDT_ = DT_LEFT | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER; 1642 ::SetBkMode(hDC, TRANSPARENT); 1643 ::DrawTextW(hDC, strItem, -1, &rcItem, uDT_); 1644 ::SelectObject(hDC, hFontOld); 1645 1646 return TRUE; 1647 } 1648 1649 // WM_GETMINMAXINFO @implemented 1650 // This message is sent to a window when the size or position of the window is about to change. 1651 LRESULT CAutoComplete::OnGetMinMaxInfo(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 1652 { 1653 // restrict minimum size 1654 LPMINMAXINFO pInfo = reinterpret_cast<LPMINMAXINFO>(lParam); 1655 pInfo->ptMinTrackSize.x = ::GetSystemMetrics(SM_CXVSCROLL); 1656 pInfo->ptMinTrackSize.y = ::GetSystemMetrics(SM_CYHSCROLL); 1657 return 0; 1658 } 1659 1660 // WM_MEASUREITEM @implemented 1661 // This message is sent to the owner window to get the item extent of m_hwndList. 1662 LRESULT CAutoComplete::OnMeasureItem(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 1663 { 1664 LPMEASUREITEMSTRUCT pMeasure = reinterpret_cast<LPMEASUREITEMSTRUCT>(lParam); 1665 ATLASSERT(pMeasure != NULL); 1666 if (pMeasure->CtlType != ODT_LISTVIEW) 1667 return FALSE; 1668 if (!m_hwndList) 1669 return FALSE; 1670 ATLASSERT(m_hwndList.GetStyle() & LVS_OWNERDRAWFIXED); 1671 pMeasure->itemHeight = m_hwndList.m_cyItem; // height of item 1672 return TRUE; 1673 } 1674 1675 // WM_MOUSEACTIVATE @implemented 1676 // The return value of this message specifies whether the window should be activated or not. 1677 LRESULT CAutoComplete::OnMouseActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 1678 { 1679 return MA_NOACTIVATE; // don't activate by mouse 1680 } 1681 1682 // WM_NCACTIVATE 1683 // This message is sent to a window to indicate an active or inactive state. 1684 LRESULT CAutoComplete::OnNCActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 1685 { 1686 bHandled = FALSE; // do default 1687 return 0; 1688 } 1689 1690 // WM_NCLBUTTONDOWN 1691 LRESULT CAutoComplete::OnNCLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 1692 { 1693 switch (wParam) 1694 { 1695 case HTBOTTOMRIGHT: case HTTOPRIGHT: 1696 { 1697 // add thick frame to resize. 1698 ModifyStyle(0, WS_THICKFRAME); 1699 // frame changed 1700 UINT uSWP_ = SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE; 1701 SetWindowPos(NULL, 0, 0, 0, 0, uSWP_); 1702 break; 1703 } 1704 } 1705 bHandled = FALSE; // do default 1706 return 0; 1707 } 1708 1709 // WM_NOTIFY 1710 // This message informs the parent window of a control that an event has occurred. 1711 LRESULT CAutoComplete::OnNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 1712 { 1713 LPNMHDR pnmh = reinterpret_cast<LPNMHDR>(lParam); 1714 ATLASSERT(pnmh != NULL); 1715 1716 switch (pnmh->code) 1717 { 1718 case NM_DBLCLK: // double-clicked 1719 { 1720 TRACE("NM_DBLCLK\n"); 1721 HideDropDown(); 1722 break; 1723 } 1724 case NM_HOVER: // mouse is hovering 1725 { 1726 POINT pt; 1727 ::GetCursorPos(&pt); // get cursor position in screen coordinates 1728 m_hwndList.ScreenToClient(&pt); // into client coordinates 1729 INT iItem = m_hwndList.ItemFromPoint(pt.x, pt.y); 1730 if (iItem != -1) 1731 { 1732 m_bInSelectItem = TRUE; // don't respond 1733 m_hwndList.SetCurSel(iItem); // select 1734 m_bInSelectItem = FALSE; 1735 } 1736 return TRUE; // eat 1737 } 1738 case LVN_GETDISPINFOA: // for user's information only 1739 { 1740 TRACE("LVN_GETDISPINFOA\n"); 1741 if (pnmh->hwndFrom != m_hwndList) 1742 break; 1743 1744 LV_DISPINFOA *pDispInfo = reinterpret_cast<LV_DISPINFOA *>(pnmh); 1745 LV_ITEMA *pItem = &pDispInfo->item; 1746 INT iItem = pItem->iItem; 1747 if (iItem == -1) 1748 break; 1749 1750 CStringW strText = GetItemText(iItem); 1751 if (pItem->mask & LVIF_TEXT) 1752 SHUnicodeToAnsi(strText, pItem->pszText, pItem->cchTextMax); 1753 break; 1754 } 1755 case LVN_GETDISPINFOW: // for user's information only 1756 { 1757 TRACE("LVN_GETDISPINFOW\n"); 1758 if (pnmh->hwndFrom != m_hwndList) 1759 break; 1760 1761 LV_DISPINFOW *pDispInfo = reinterpret_cast<LV_DISPINFOW *>(pnmh); 1762 LV_ITEMW *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 StringCbCopyW(pItem->pszText, pItem->cchTextMax, strText); 1770 break; 1771 } 1772 case LVN_HOTTRACK: // enabled by LVS_EX_TRACKSELECT 1773 { 1774 TRACE("LVN_HOTTRACK\n"); 1775 LPNMLISTVIEW pListView = reinterpret_cast<LPNMLISTVIEW>(pnmh); 1776 INT iItem = pListView->iItem; 1777 TRACE("LVN_HOTTRACK: iItem:%d\n", iItem); 1778 m_hwndList.SetCurSel(iItem); 1779 m_hwndList.EnsureVisible(iItem, FALSE); 1780 return TRUE; 1781 } 1782 case LVN_ITEMACTIVATE: // enabled by LVS_EX_ONECLICKACTIVATE 1783 { 1784 TRACE("LVN_ITEMACTIVATE\n"); 1785 LPNMITEMACTIVATE pItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pnmh); 1786 INT iItem = pItemActivate->iItem; 1787 TRACE("LVN_ITEMACTIVATE: iItem:%d\n", iItem); 1788 if (iItem != -1) // the item is clicked 1789 { 1790 SelectItem(iItem); 1791 HideDropDown(); 1792 } 1793 break; 1794 } 1795 case LVN_ITEMCHANGED: // item info is changed 1796 { 1797 TRACE("LVN_ITEMCHANGED\n"); 1798 LPNMLISTVIEW pListView = reinterpret_cast<LPNMLISTVIEW>(pnmh); 1799 if (pListView->uChanged & LVIF_STATE) // selection changed 1800 { 1801 // listview selection changed 1802 if (!m_bInSelectItem) 1803 { 1804 OnListSelChange(); 1805 } 1806 UpdateScrollBar(); 1807 } 1808 break; 1809 } 1810 } 1811 1812 return 0; 1813 } 1814 1815 // WM_NCHITTEST @implemented 1816 // The return value is indicating the cursor shape and the behaviour. 1817 LRESULT CAutoComplete::OnNCHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 1818 { 1819 TRACE("CAutoComplete::OnNCHitTest(%p)\n", this); 1820 POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; // in screen coordinates 1821 ScreenToClient(&pt); // into client coordinates 1822 if (ChildWindowFromPoint(pt) == m_hwndSizeBox) // hit? 1823 { 1824 // allow resizing (with cursor shape) 1825 return m_bDowner ? HTBOTTOMRIGHT : HTTOPRIGHT; 1826 } 1827 bHandled = FALSE; // do default 1828 return 0; 1829 } 1830 1831 // WM_SIZE @implemented 1832 // This message is sent when the window size is changed. 1833 LRESULT CAutoComplete::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 1834 { 1835 // calculate the positions of the controls 1836 CRect rcList, rcScrollBar, rcSizeBox; 1837 CalcRects(m_bDowner, rcList, rcScrollBar, rcSizeBox); 1838 1839 // reposition the controls in smartest way 1840 UINT uSWP_ = SWP_NOACTIVATE | SWP_NOCOPYBITS; 1841 HDWP hDWP = ::BeginDeferWindowPos(3); 1842 hDWP = ::DeferWindowPos(hDWP, m_hwndScrollBar, HWND_TOP, 1843 rcScrollBar.left, rcScrollBar.top, 1844 rcScrollBar.Width(), rcScrollBar.Height(), uSWP_); 1845 hDWP = ::DeferWindowPos(hDWP, m_hwndSizeBox, m_hwndScrollBar, 1846 rcSizeBox.left, rcSizeBox.top, 1847 rcSizeBox.Width(), rcSizeBox.Height(), uSWP_); 1848 hDWP = ::DeferWindowPos(hDWP, m_hwndList, m_hwndSizeBox, 1849 rcList.left, rcList.top, 1850 rcList.Width(), rcList.Height(), uSWP_); 1851 ::EndDeferWindowPos(hDWP); 1852 1853 UpdateScrollBar(); 1854 return 0; 1855 } 1856 1857 // WM_SHOWWINDOW 1858 LRESULT CAutoComplete::OnShowWindow(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 1859 { 1860 // hook mouse events 1861 BOOL bShow = (BOOL)wParam; 1862 if (bShow) 1863 { 1864 if (s_hWatchWnd != m_hWnd && ::IsWindowVisible(s_hWatchWnd)) 1865 ::ShowWindowAsync(s_hWatchWnd, SW_HIDE); 1866 s_hWatchWnd = m_hWnd; // watch this 1867 1868 // unhook mouse if any 1869 if (s_hMouseHook) 1870 { 1871 HHOOK hHookOld = s_hMouseHook; 1872 s_hMouseHook = NULL; 1873 ::UnhookWindowsHookEx(hHookOld); 1874 } 1875 1876 // hook mouse 1877 s_hMouseHook = ::SetWindowsHookEx(WH_MOUSE, MouseProc, NULL, ::GetCurrentThreadId()); 1878 ATLASSERT(s_hMouseHook != NULL); 1879 1880 // set timer 1881 SetTimer(WATCH_TIMER_ID, WATCH_INTERVAL, NULL); 1882 1883 bHandled = FALSE; // do default 1884 return 0; 1885 } 1886 else 1887 { 1888 // kill timer 1889 KillTimer(WATCH_TIMER_ID); 1890 1891 s_hWatchWnd = NULL; // unwatch 1892 1893 // unhook mouse if any 1894 if (s_hMouseHook) 1895 { 1896 HHOOK hHookOld = s_hMouseHook; 1897 s_hMouseHook = NULL; 1898 ::UnhookWindowsHookEx(hHookOld); 1899 } 1900 1901 LRESULT ret = DefWindowProcW(uMsg, wParam, lParam); // do default 1902 1903 if (m_hwndCombo) 1904 ::InvalidateRect(m_hwndCombo, NULL, TRUE); // redraw 1905 1906 m_outerList.RemoveAll(); // no use 1907 return ret; 1908 } 1909 } 1910 1911 // WM_TIMER 1912 LRESULT CAutoComplete::OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 1913 { 1914 if (wParam != WATCH_TIMER_ID) // sanity check 1915 return 0; 1916 1917 // if the textbox is dead, then kill the timer 1918 if (!::IsWindow(m_hwndEdit)) 1919 { 1920 KillTimer(WATCH_TIMER_ID); 1921 return 0; 1922 } 1923 1924 // m_hwndEdit is moved? 1925 RECT rcEdit; 1926 ::GetWindowRect(m_hwndEdit, &rcEdit); 1927 if (!::EqualRect(&rcEdit, &m_rcEdit)) 1928 { 1929 // if so, hide 1930 HideDropDown(); 1931 1932 m_rcEdit = rcEdit; // update rectangle 1933 m_bResized = FALSE; // clear flag 1934 } 1935 1936 return 0; 1937 } 1938 1939 // WM_VSCROLL 1940 // This message is sent when a scroll event occurs. 1941 LRESULT CAutoComplete::OnVScroll(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 1942 { 1943 TRACE("CAutoComplete::OnVScroll(%p)\n", this); 1944 WORD code = LOWORD(wParam); 1945 switch (code) 1946 { 1947 case SB_THUMBPOSITION: case SB_THUMBTRACK: 1948 { 1949 // get the scrolling info 1950 INT nPos = HIWORD(wParam); 1951 SCROLLINFO si = { sizeof(si), SIF_ALL }; 1952 m_hwndList.GetScrollInfo(SB_VERT, &si); 1953 1954 // scroll the list-view by CListView::EnsureVisible 1955 INT cItems = m_hwndList.GetItemCount(); 1956 // iItem : cItems == (nPos - si.nMin) : (si.nMax - si.nMin). 1957 INT iItem = cItems * (nPos - si.nMin) / (si.nMax - si.nMin); 1958 if (nPos > si.nPos) 1959 { 1960 iItem += m_hwndList.GetVisibleCount(); 1961 if (iItem >= cItems) 1962 iItem = cItems - 1; 1963 } 1964 m_hwndList.EnsureVisible(iItem, FALSE); 1965 1966 // update scrolling position of m_hwndScrollBar 1967 si.fMask = SIF_POS; 1968 m_hwndList.GetScrollInfo(SB_VERT, &si); 1969 m_hwndScrollBar.SetScrollInfo(SB_VERT, &si, FALSE); 1970 break; 1971 } 1972 default: 1973 { 1974 // pass it to m_hwndList 1975 m_hwndList.SendMessageW(WM_VSCROLL, wParam, lParam); 1976 UpdateScrollBar(); 1977 break; 1978 } 1979 } 1980 return 0; 1981 } 1982 1983 static inline PAC_THREAD 1984 InterlockedExchangeThreadData(volatile PAC_THREAD *Target, PAC_THREAD Value) 1985 { 1986 return reinterpret_cast<PAC_THREAD>( 1987 ::InterlockedExchangePointer(reinterpret_cast<volatile PVOID *>(Target), Value)); 1988 } 1989 1990 static unsigned __stdcall AutoCompThreadProc(void *arg) 1991 { 1992 CAutoComplete* pThis = reinterpret_cast<CAutoComplete*>(arg); 1993 pThis->AutoCompThreadProc(); 1994 return 0; 1995 } 1996 1997 VOID CAutoComplete::AutoCompThreadProc() 1998 { 1999 for (;;) 2000 { 2001 PAC_THREAD pThread = InterlockedExchangeThreadData(&m_pThread, NULL); 2002 if (!pThread) 2003 break; 2004 DoThreadWork(pThread); 2005 } 2006 } 2007 2008 VOID CAutoComplete::DoThreadWork(PAC_THREAD pThread) 2009 { 2010 if (pThread->m_bExpand || m_innerList.GetSize() == 0) 2011 { 2012 ReLoadInnerList(pThread); 2013 } 2014 else 2015 { 2016 pThread->m_innerList = m_innerList; 2017 } 2018 2019 if (m_pThread || !m_hThread) 2020 { 2021 delete pThread; 2022 return; 2023 } 2024 2025 ExtractInnerList(pThread->m_outerList, pThread->m_innerList, pThread->m_strText); 2026 2027 if (m_pThread || !m_hThread) 2028 { 2029 delete pThread; 2030 return; 2031 } 2032 2033 DoSort(pThread->m_outerList); 2034 DoUniqueAndTrim(pThread->m_outerList); 2035 2036 if (m_pThread || !m_hThread || 2037 !::PostMessageW(m_hWnd, AUTOCOMP_FINISH, 0, (LPARAM)pThread)) 2038 { 2039 delete pThread; 2040 } 2041 } 2042 2043 // AUTOCOMP_START 2044 LRESULT CAutoComplete::OnAutoCompStart(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 2045 { 2046 BOOL bAppendOK = (BOOL)wParam; 2047 2048 CStringW strText = GetEditText(); 2049 if (m_strText.CompareNoCase(strText) == 0) 2050 { 2051 // no change 2052 return 0; 2053 } 2054 2055 PAC_THREAD pThread = new AC_THREAD { this, bAppendOK, strText }; 2056 2057 // if previous text was empty 2058 if (m_strText.IsEmpty()) 2059 { 2060 pThread->m_bReset = TRUE; 2061 } 2062 m_strText = strText; 2063 2064 // do expand the items if the stem is changed 2065 CStringW strStemText = GetStemText(pThread->m_strText); 2066 if (m_strStemText.CompareNoCase(strStemText) != 0) 2067 { 2068 pThread->m_bReset = TRUE; 2069 pThread->m_bExpand = !strStemText.IsEmpty(); 2070 m_strStemText = strStemText; 2071 } 2072 2073 // reset if necessary 2074 if (pThread->m_bReset && m_pEnum) 2075 { 2076 HRESULT hr = m_pEnum->Reset(); // IEnumString::Reset 2077 TRACE("m_pEnum->Reset(%p): 0x%08lx\n", 2078 static_cast<IUnknown *>(m_pEnum), hr); 2079 } 2080 2081 // update ac list if necessary 2082 if (pThread->m_bExpand && m_pACList) 2083 { 2084 HRESULT hr = m_pACList->Expand(strStemText); // IACList::Expand 2085 TRACE("m_pACList->Expand(%p, %S): 0x%08lx\n", 2086 static_cast<IUnknown *>(m_pACList), 2087 static_cast<LPCWSTR>(strStemText), hr); 2088 } 2089 2090 PAC_THREAD pOld = InterlockedExchangeThreadData(&m_pThread, pThread); 2091 if (pOld) 2092 delete pOld; 2093 2094 BOOL bDoStart = FALSE; 2095 DWORD dwWait = WaitForSingleObject(m_hThread, 0); 2096 if (dwWait != WAIT_TIMEOUT) 2097 { 2098 CloseHandle(m_hThread); 2099 bDoStart = TRUE; 2100 } 2101 else if (!m_hThread) 2102 { 2103 bDoStart = TRUE; 2104 } 2105 2106 if (bDoStart) 2107 m_hThread = (HANDLE)_beginthreadex(NULL, 0, ::AutoCompThreadProc, this, 0, NULL); 2108 2109 return 0; 2110 } 2111 2112 // AUTOCOMP_FINISH 2113 LRESULT CAutoComplete::OnAutoCompFinish(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) 2114 { 2115 PAC_THREAD pThread = reinterpret_cast<PAC_THREAD>(lParam); 2116 if (m_pThread == NULL) 2117 { 2118 FinishCompletion(pThread); 2119 } 2120 CloseHandle(m_hThread); 2121 m_hThread = NULL; 2122 delete pThread; 2123 return 0; 2124 } 2125 2126 VOID CAutoComplete::FinishCompletion(PAC_THREAD pThread) 2127 { 2128 if (m_pThread || !m_hThread) 2129 return; 2130 2131 if (!CanAutoSuggest() && !CanAutoAppend()) 2132 return; 2133 2134 if (m_pThread || !m_hThread) 2135 return; 2136 2137 // set inner list 2138 m_innerList = pThread->m_innerList; 2139 2140 if (m_pThread || !m_hThread) 2141 return; 2142 2143 // set the items of the virtual listview 2144 m_outerList = pThread->m_outerList; // FIXME: We need more speed! 2145 m_hwndList.SendMessageW(LVM_SETITEMCOUNT, m_outerList.GetSize(), 0); 2146 2147 // save text 2148 m_strText = pThread->m_strText; 2149 m_strStemText = GetStemText(m_strText); 2150 2151 if (CanAutoSuggest()) // can we auto-suggest? 2152 { 2153 m_bInSelectItem = TRUE; // don't respond 2154 SelectItem(-1); // select none 2155 m_bInSelectItem = FALSE; 2156 2157 if (m_outerList.GetSize() > 0) 2158 RepositionDropDown(); 2159 else 2160 HideDropDown(); 2161 } 2162 2163 if (CanAutoAppend() && pThread->m_bAppendOK) // can we auto-append? 2164 { 2165 DoAutoAppend(pThread); 2166 } 2167 } 2168