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