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