1 /* 2 * AutoComplete interfaces implementation. 3 * 4 * Copyright 2004 Maxime Belleng� <maxime.bellenge@laposte.net> 5 * Copyright 2009 Andrew Hill 6 * 7 * This library is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public 9 * License as published by the Free Software Foundation; either 10 * version 2.1 of the License, or (at your option) any later version. 11 * 12 * This library is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this library; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 20 */ 21 22 /* 23 Implemented: 24 - ACO_AUTOAPPEND style 25 - ACO_AUTOSUGGEST style 26 - ACO_UPDOWNKEYDROPSLIST style 27 28 - Handle pwzsRegKeyPath and pwszQuickComplete in Init 29 30 TODO: 31 - implement ACO_SEARCH style 32 - implement ACO_FILTERPREFIXES style 33 - implement ACO_USETAB style 34 - implement ACO_RTLREADING style 35 36 */ 37 38 #include "precomp.h" 39 40 static const WCHAR autocomplete_propertyW[] = {'W','i','n','e',' ','A','u','t','o', 41 'c','o','m','p','l','e','t','e',' ', 42 'c','o','n','t','r','o','l',0}; 43 44 /************************************************************************** 45 * IAutoComplete_Constructor 46 */ 47 CAutoComplete::CAutoComplete() 48 { 49 enabled = TRUE; 50 initialized = FALSE; 51 options = ACO_AUTOAPPEND; 52 wpOrigEditProc = NULL; 53 hwndListBox = NULL; 54 txtbackup = NULL; 55 quickComplete = NULL; 56 hwndEdit = NULL; 57 wpOrigLBoxProc = NULL; 58 } 59 60 /************************************************************************** 61 * IAutoComplete_Destructor 62 */ 63 CAutoComplete::~CAutoComplete() 64 { 65 TRACE(" destroying IAutoComplete(%p)\n", this); 66 HeapFree(GetProcessHeap(), 0, quickComplete); 67 HeapFree(GetProcessHeap(), 0, txtbackup); 68 if (wpOrigEditProc) 69 { 70 SetWindowLongPtrW(hwndEdit, GWLP_WNDPROC, (LONG_PTR)wpOrigEditProc); 71 RemovePropW(hwndEdit, autocomplete_propertyW); 72 } 73 if (hwndListBox) 74 DestroyWindow(hwndListBox); 75 } 76 77 /****************************************************************************** 78 * IAutoComplete_fnEnable 79 */ 80 HRESULT WINAPI CAutoComplete::Enable(BOOL fEnable) 81 { 82 HRESULT hr = S_OK; 83 84 TRACE("(%p)->(%s)\n", this, (fEnable) ? "true" : "false"); 85 86 enabled = fEnable; 87 88 return hr; 89 } 90 91 /****************************************************************************** 92 * create_listbox 93 */ 94 void CAutoComplete::CreateListbox() 95 { 96 HWND hwndParent = GetParent(hwndEdit); 97 98 /* FIXME : The listbox should be resizable with the mouse. WS_THICKFRAME looks ugly */ 99 hwndListBox = CreateWindowExW(0, WC_LISTBOXW, NULL, 100 WS_BORDER | WS_CHILD | WS_VSCROLL | LBS_HASSTRINGS | LBS_NOTIFY | LBS_NOINTEGRALHEIGHT, 101 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 102 hwndParent, NULL, 103 (HINSTANCE)GetWindowLongPtrW(hwndParent, GWLP_HINSTANCE), NULL); 104 105 if (hwndListBox) 106 { 107 wpOrigLBoxProc = (WNDPROC)SetWindowLongPtrW(hwndListBox, GWLP_WNDPROC, (LONG_PTR)ACLBoxSubclassProc); 108 SetWindowLongPtrW(hwndListBox, GWLP_USERDATA, (LONG_PTR)this); 109 } 110 } 111 112 113 /****************************************************************************** 114 * IAutoComplete_fnInit 115 */ 116 HRESULT WINAPI CAutoComplete::Init(HWND hwndEdit, IUnknown *punkACL, LPCOLESTR pwzsRegKeyPath, LPCOLESTR pwszQuickComplete) 117 { 118 TRACE("(%p)->(0x%08lx, %p, %s, %s)\n", 119 this, hwndEdit, punkACL, debugstr_w(pwzsRegKeyPath), debugstr_w(pwszQuickComplete)); 120 121 if (options & ACO_AUTOSUGGEST) 122 TRACE(" ACO_AUTOSUGGEST\n"); 123 if (options & ACO_AUTOAPPEND) 124 TRACE(" ACO_AUTOAPPEND\n"); 125 if (options & ACO_SEARCH) 126 FIXME(" ACO_SEARCH not supported\n"); 127 if (options & ACO_FILTERPREFIXES) 128 FIXME(" ACO_FILTERPREFIXES not supported\n"); 129 if (options & ACO_USETAB) 130 FIXME(" ACO_USETAB not supported\n"); 131 if (options & ACO_UPDOWNKEYDROPSLIST) 132 TRACE(" ACO_UPDOWNKEYDROPSLIST\n"); 133 if (options & ACO_RTLREADING) 134 FIXME(" ACO_RTLREADING not supported\n"); 135 136 if (!hwndEdit || !punkACL) 137 return E_INVALIDARG; 138 139 if (this->initialized) 140 { 141 WARN("Autocompletion object is already initialized\n"); 142 /* This->hwndEdit is set to NULL when the edit window is destroyed. */ 143 return this->hwndEdit ? E_FAIL : E_UNEXPECTED; 144 } 145 146 if (!SUCCEEDED(punkACL->QueryInterface(IID_PPV_ARG(IEnumString,&enumstr)))) 147 { 148 TRACE("No IEnumString interface\n"); 149 return E_NOINTERFACE; 150 } 151 152 this->hwndEdit = hwndEdit; 153 this->initialized = TRUE; 154 155 /* Keep at least one reference to the object until the edit window is destroyed. */ 156 this->AddRef(); 157 158 /* If another AutoComplete object was previously assigned to this edit control, 159 release it but keep the same callback on the control, to avoid an infinite 160 recursive loop in ACEditSubclassProc while the property is set to this object */ 161 CAutoComplete *prev = static_cast<CAutoComplete *>(GetPropW(hwndEdit, autocomplete_propertyW)); 162 163 if (prev && prev->initialized) 164 { 165 this->wpOrigEditProc = prev->wpOrigEditProc; 166 SetPropW(hwndEdit, autocomplete_propertyW, this); 167 prev->wpOrigEditProc = NULL; 168 prev->Release(); 169 } 170 else 171 { 172 SetPropW( this->hwndEdit, autocomplete_propertyW, (HANDLE)this ); 173 this->wpOrigEditProc = (WNDPROC)SetWindowLongPtrW(hwndEdit, GWLP_WNDPROC, (LONG_PTR)ACEditSubclassProc); 174 } 175 176 if (options & ACO_AUTOSUGGEST) 177 { 178 this->CreateListbox(); 179 } 180 181 if (pwzsRegKeyPath) 182 { 183 WCHAR *key; 184 WCHAR result[MAX_PATH]; 185 WCHAR *value; 186 HKEY hKey = 0; 187 LONG res; 188 LONG len; 189 190 /* pwszRegKeyPath contains the key as well as the value, so we split */ 191 key = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (wcslen(pwzsRegKeyPath) + 1) * sizeof(WCHAR)); 192 193 if (key) 194 { 195 wcscpy(key, pwzsRegKeyPath); 196 value = const_cast<WCHAR *>(wcsrchr(key, '\\')); 197 198 if (value) 199 { 200 *value = 0; 201 value++; 202 /* Now value contains the value and buffer the key */ 203 res = RegOpenKeyExW(HKEY_CURRENT_USER, key, 0, KEY_READ, &hKey); 204 205 if (res != ERROR_SUCCESS) 206 { 207 /* if the key is not found, MSDN states we must seek in HKEY_LOCAL_MACHINE */ 208 res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, key, 0, KEY_READ, &hKey); 209 } 210 211 if (res == ERROR_SUCCESS) 212 { 213 len = sizeof(result); 214 res = RegQueryValueW(hKey, value, result, &len); 215 if (res == ERROR_SUCCESS) 216 { 217 quickComplete = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len * sizeof(WCHAR)); 218 wcscpy(quickComplete, result); 219 } 220 RegCloseKey(hKey); 221 } 222 } 223 224 HeapFree(GetProcessHeap(), 0, key); 225 } 226 else 227 { 228 TRACE("HeapAlloc Failed when trying to alloca %d bytes\n", (wcslen(pwzsRegKeyPath) + 1) * sizeof(WCHAR)); 229 return S_FALSE; 230 } 231 } 232 233 if ((pwszQuickComplete) && (!quickComplete)) 234 { 235 quickComplete = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (wcslen(pwszQuickComplete) + 1) * sizeof(WCHAR)); 236 237 if (quickComplete) 238 { 239 wcscpy(quickComplete, pwszQuickComplete); 240 } 241 else 242 { 243 TRACE("HeapAlloc Failed when trying to alloca %d bytes\n", (wcslen(pwszQuickComplete) + 1) * sizeof(WCHAR)); 244 return S_FALSE; 245 } 246 } 247 248 return S_OK; 249 } 250 251 /************************************************************************** 252 * IAutoComplete_fnGetOptions 253 */ 254 HRESULT WINAPI CAutoComplete::GetOptions(DWORD *pdwFlag) 255 { 256 HRESULT hr = S_OK; 257 258 TRACE("(%p) -> (%p)\n", this, pdwFlag); 259 260 *pdwFlag = options; 261 262 return hr; 263 } 264 265 /************************************************************************** 266 * IAutoComplete_fnSetOptions 267 */ 268 HRESULT WINAPI CAutoComplete::SetOptions(DWORD dwFlag) 269 { 270 HRESULT hr = S_OK; 271 272 TRACE("(%p) -> (0x%x)\n", this, dwFlag); 273 274 options = (AUTOCOMPLETEOPTIONS)dwFlag; 275 276 if ((options & ACO_AUTOSUGGEST) && hwndEdit && !hwndListBox) 277 CreateListbox(); 278 279 return hr; 280 } 281 282 /* 283 Window procedure for autocompletion 284 */ 285 LRESULT APIENTRY CAutoComplete::ACEditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 286 { 287 CAutoComplete *pThis = static_cast<CAutoComplete *>(GetPropW(hwnd, autocomplete_propertyW)); 288 HRESULT hr; 289 WCHAR hwndText[255]; 290 WCHAR *hwndQCText; 291 RECT r; 292 BOOL control, filled, displayall = FALSE; 293 int cpt, height, sel; 294 ULONG fetched; 295 296 if (!pThis->enabled) 297 { 298 return CallWindowProcW(pThis->wpOrigEditProc, hwnd, uMsg, wParam, lParam); 299 } 300 301 switch (uMsg) 302 { 303 case CB_SHOWDROPDOWN: 304 { 305 ShowWindow(pThis->hwndListBox, SW_HIDE); 306 }; break; 307 308 case WM_KILLFOCUS: 309 { 310 if ((pThis->options & ACO_AUTOSUGGEST) && ((HWND)wParam != pThis->hwndListBox)) 311 { 312 ShowWindow(pThis->hwndListBox, SW_HIDE); 313 } 314 return CallWindowProcW(pThis->wpOrigEditProc, hwnd, uMsg, wParam, lParam); 315 }; break; 316 317 case WM_KEYUP: 318 { 319 GetWindowTextW(hwnd, (LPWSTR)hwndText, 255); 320 321 switch(wParam) 322 { 323 case VK_RETURN: 324 { 325 /* If quickComplete is set and control is pressed, replace the string */ 326 control = GetKeyState(VK_CONTROL) & 0x8000; 327 if (control && pThis->quickComplete) 328 { 329 hwndQCText = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 330 (wcslen(pThis->quickComplete)+wcslen(hwndText))*sizeof(WCHAR)); 331 sel = swprintf(hwndQCText, pThis->quickComplete, hwndText); 332 SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)hwndQCText); 333 SendMessageW(hwnd, EM_SETSEL, 0, sel); 334 HeapFree(GetProcessHeap(), 0, hwndQCText); 335 } 336 337 ShowWindow(pThis->hwndListBox, SW_HIDE); 338 return 0; 339 }; break; 340 341 case VK_LEFT: 342 case VK_RIGHT: 343 { 344 return 0; 345 }; break; 346 347 case VK_UP: 348 case VK_DOWN: 349 { 350 /* Two cases here : 351 - if the listbox is not visible, displays it 352 with all the entries if the style ACO_UPDOWNKEYDROPSLIST 353 is present but does not select anything. 354 - if the listbox is visible, change the selection 355 */ 356 if ( (pThis->options & (ACO_AUTOSUGGEST | ACO_UPDOWNKEYDROPSLIST)) 357 && (!IsWindowVisible(pThis->hwndListBox) && (! *hwndText)) ) 358 { 359 /* We must display all the entries */ 360 displayall = TRUE; 361 } 362 else 363 { 364 if (IsWindowVisible(pThis->hwndListBox)) 365 { 366 int count; 367 368 count = SendMessageW(pThis->hwndListBox, LB_GETCOUNT, 0, 0); 369 /* Change the selection */ 370 sel = SendMessageW(pThis->hwndListBox, LB_GETCURSEL, 0, 0); 371 if (wParam == VK_UP) 372 sel = ((sel-1) < 0) ? count-1 : sel-1; 373 else 374 sel = ((sel+1) >= count) ? -1 : sel+1; 375 376 SendMessageW(pThis->hwndListBox, LB_SETCURSEL, sel, 0); 377 378 if (sel != -1) 379 { 380 WCHAR *msg; 381 int len; 382 383 len = SendMessageW(pThis->hwndListBox, LB_GETTEXTLEN, sel, (LPARAM)NULL); 384 msg = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (len + 1) * sizeof(WCHAR)); 385 386 if (msg) 387 { 388 SendMessageW(pThis->hwndListBox, LB_GETTEXT, sel, (LPARAM)msg); 389 SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)msg); 390 SendMessageW(hwnd, EM_SETSEL, wcslen(msg), wcslen(msg)); 391 392 HeapFree(GetProcessHeap(), 0, msg); 393 } 394 else 395 { 396 TRACE("HeapAlloc failed to allocate %d bytes\n", (len + 1) * sizeof(WCHAR)); 397 } 398 } 399 else 400 { 401 SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)pThis->txtbackup); 402 SendMessageW(hwnd, EM_SETSEL, wcslen(pThis->txtbackup), wcslen(pThis->txtbackup)); 403 } 404 } 405 return 0; 406 } 407 }; break; 408 409 case VK_BACK: 410 case VK_DELETE: 411 { 412 if ((! *hwndText) && (pThis->options & ACO_AUTOSUGGEST)) 413 { 414 ShowWindow(pThis->hwndListBox, SW_HIDE); 415 return CallWindowProcW(pThis->wpOrigEditProc, hwnd, uMsg, wParam, lParam); 416 } 417 418 if (pThis->options & ACO_AUTOAPPEND) 419 { 420 DWORD b; 421 SendMessageW(hwnd, EM_GETSEL, (WPARAM)&b, (LPARAM)NULL); 422 if (b>1) 423 { 424 hwndText[b-1] = '\0'; 425 } 426 else 427 { 428 hwndText[0] = '\0'; 429 SetWindowTextW(hwnd, hwndText); 430 } 431 } 432 }; break; 433 434 default: 435 ; 436 } 437 438 SendMessageW(pThis->hwndListBox, LB_RESETCONTENT, 0, 0); 439 440 HeapFree(GetProcessHeap(), 0, pThis->txtbackup); 441 442 pThis->txtbackup = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (wcslen(hwndText)+1)*sizeof(WCHAR)); 443 444 if (pThis->txtbackup) 445 { 446 wcscpy(pThis->txtbackup, hwndText); 447 } 448 else 449 { 450 TRACE("HeapAlloc failed to allocate %d bytes\n", (wcslen(hwndText)+1)*sizeof(WCHAR)); 451 } 452 453 /* Returns if there is no text to search and we doesn't want to display all the entries */ 454 if ((!displayall) && (! *hwndText) ) 455 break; 456 457 pThis->enumstr->Reset(); 458 filled = FALSE; 459 size_t curlen = wcslen(hwndText); 460 461 for(cpt = 0;;) 462 { 463 CComHeapPtr<OLECHAR> strs; 464 hr = pThis->enumstr->Next(1, &strs, &fetched); 465 if (hr != S_OK) 466 break; 467 468 if (!_wcsnicmp(hwndText, strs, curlen)) 469 { 470 471 if (pThis->options & ACO_AUTOAPPEND && *hwndText) 472 { 473 CComBSTR str((PCWSTR)strs); 474 memcpy(str.m_str, hwndText, curlen * sizeof(WCHAR)); 475 SetWindowTextW(hwnd, str); 476 SendMessageW(hwnd, EM_SETSEL, curlen, str.Length()); 477 if (!(pThis->options & ACO_AUTOSUGGEST)) 478 break; 479 } 480 481 if (pThis->options & ACO_AUTOSUGGEST) 482 { 483 SendMessageW(pThis->hwndListBox, LB_ADDSTRING, 0, (LPARAM)(LPOLESTR)strs); 484 filled = TRUE; 485 cpt++; 486 } 487 } 488 } 489 490 if (pThis->options & ACO_AUTOSUGGEST) 491 { 492 if (filled) 493 { 494 height = SendMessageW(pThis->hwndListBox, LB_GETITEMHEIGHT, 0, 0); 495 SendMessageW(pThis->hwndListBox, LB_CARETOFF, 0, 0); 496 GetWindowRect(hwnd, &r); 497 SetParent(pThis->hwndListBox, HWND_DESKTOP); 498 /* It seems that Windows XP displays 7 lines at most 499 and otherwise displays a vertical scroll bar */ 500 SetWindowPos(pThis->hwndListBox, HWND_TOP, 501 r.left, r.bottom + 1, r.right - r.left, min(height * 7, height * (cpt + 1)), 502 SWP_SHOWWINDOW ); 503 } 504 else 505 { 506 ShowWindow(pThis->hwndListBox, SW_HIDE); 507 } 508 } 509 510 }; break; 511 512 case WM_DESTROY: 513 { 514 /* Release our reference that we had since ->Init() */ 515 pThis->Release(); 516 return 0; 517 } 518 519 520 default: 521 { 522 return CallWindowProcW(pThis->wpOrigEditProc, hwnd, uMsg, wParam, lParam); 523 } 524 525 } 526 527 return 0; 528 } 529 530 LRESULT APIENTRY CAutoComplete::ACLBoxSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 531 { 532 CAutoComplete *pThis = reinterpret_cast<CAutoComplete *>(GetWindowLongPtrW(hwnd, GWLP_USERDATA)); 533 WCHAR *msg; 534 int sel, len; 535 536 switch (uMsg) 537 { 538 case WM_MOUSEMOVE: 539 { 540 sel = SendMessageW(hwnd, LB_ITEMFROMPOINT, 0, lParam); 541 SendMessageW(hwnd, LB_SETCURSEL, (WPARAM)sel, (LPARAM)0); 542 }; break; 543 544 case WM_LBUTTONDOWN: 545 { 546 sel = SendMessageW(hwnd, LB_GETCURSEL, 0, 0); 547 548 if (sel < 0) 549 break; 550 551 len = SendMessageW(pThis->hwndListBox, LB_GETTEXTLEN, sel, 0); 552 msg = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (len + 1) * sizeof(WCHAR)); 553 554 if (msg) 555 { 556 SendMessageW(hwnd, LB_GETTEXT, sel, (LPARAM)msg); 557 SendMessageW(pThis->hwndEdit, WM_SETTEXT, 0, (LPARAM)msg); 558 SendMessageW(pThis->hwndEdit, EM_SETSEL, 0, wcslen(msg)); 559 ShowWindow(hwnd, SW_HIDE); 560 561 HeapFree(GetProcessHeap(), 0, msg); 562 } 563 else 564 { 565 TRACE("HeapAlloc failed to allocate %d bytes\n", (len + 1) * sizeof(WCHAR)); 566 } 567 568 }; break; 569 570 default: 571 return CallWindowProcW(pThis->wpOrigLBoxProc, hwnd, uMsg, wParam, lParam); 572 } 573 return 0; 574 } 575 576 /************************************************************************** 577 * IAutoCompleteDropDown 578 */ 579 HRESULT STDMETHODCALLTYPE CAutoComplete::GetDropDownStatus(DWORD *pdwFlags, LPWSTR *ppwszString) 580 { 581 BOOL dropped = IsWindowVisible(hwndListBox); 582 583 if (pdwFlags) 584 *pdwFlags = (dropped ? ACDD_VISIBLE : 0); 585 586 if (ppwszString) 587 { 588 *ppwszString = NULL; 589 590 if (dropped) 591 { 592 int sel = SendMessageW(hwndListBox, LB_GETCURSEL, 0, 0); 593 if (sel >= 0) 594 { 595 DWORD len = SendMessageW(hwndListBox, LB_GETTEXTLEN, sel, 0); 596 *ppwszString = (LPWSTR)CoTaskMemAlloc((len+1)*sizeof(WCHAR)); 597 SendMessageW(hwndListBox, LB_GETTEXT, sel, (LPARAM)*ppwszString); 598 } 599 } 600 } 601 602 return S_OK; 603 } 604 605 HRESULT STDMETHODCALLTYPE CAutoComplete::ResetEnumerator() 606 { 607 FIXME("(%p): stub\n", this); 608 return E_NOTIMPL; 609 } 610 611 /************************************************************************** 612 * IEnumString 613 */ 614 HRESULT STDMETHODCALLTYPE CAutoComplete::Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched) 615 { 616 FIXME("(%p, %d, %p, %p): stub\n", this, celt, rgelt, pceltFetched); 617 *pceltFetched = 0; 618 return E_NOTIMPL; 619 } 620 621 HRESULT STDMETHODCALLTYPE CAutoComplete::Skip(ULONG celt) 622 { 623 FIXME("(%p, %d): stub\n", this, celt); 624 return E_NOTIMPL; 625 } 626 627 HRESULT STDMETHODCALLTYPE CAutoComplete::Reset() 628 { 629 FIXME("(%p): stub\n", this); 630 return E_NOTIMPL; 631 } 632 633 HRESULT STDMETHODCALLTYPE CAutoComplete::Clone(IEnumString **ppOut) 634 { 635 FIXME("(%p, %p): stub\n", this, ppOut); 636 *ppOut = NULL; 637 return E_NOTIMPL; 638 } 639