https://git.reactos.org/?p=reactos.git;a=commitdiff;h=c19d9df2593d6376f783da...
commit c19d9df2593d6376f783dae78ba67fe53280fa4a Author: Katayama Hirofumi MZ katayama.hirofumi.mz@gmail.com AuthorDate: Wed Mar 10 16:22:57 2021 +0900 Commit: GitHub noreply@github.com CommitDate: Wed Mar 10 16:22:57 2021 +0900
[BROWSEUI] Implement auto-completion (#3507)
Implement IAutoComplete to realize input auto completion. CORE-9281, CORE-1419
- The main features of this PR are Auto-Suggest and Auto-Append. - Auto-Suggest shows a list near the textbox (an EDIT control) when the user has typed partial pathname into the textbox. - Auto-Append appends complement text into the textbox to complete the pathname with selected status. - The list of AutoSuggest is a top-level window whose window class is "Auto-Suggest Dropdown". We call it "the drop-down window". - The drop-down window contains three controls: a listview, a scrollbar and a sizebox. - The drop-down window watches the input into the textbox. If the textbox changed, then the window updates the list. - The sizebox control enables the user to resize the drop-down window. --- dll/win32/browseui/CAutoComplete.cpp | 2457 ++++++++++++++++++++++++++-------- dll/win32/browseui/CAutoComplete.h | 319 ++++- 2 files changed, 2202 insertions(+), 574 deletions(-)
diff --git a/dll/win32/browseui/CAutoComplete.cpp b/dll/win32/browseui/CAutoComplete.cpp index ffd14267d3e..d1ba30d4b86 100644 --- a/dll/win32/browseui/CAutoComplete.cpp +++ b/dll/win32/browseui/CAutoComplete.cpp @@ -20,669 +20,2076 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */
-/* - Implemented: - - ACO_AUTOAPPEND style - - ACO_AUTOSUGGEST style - - ACO_UPDOWNKEYDROPSLIST style - - - Handle pwzsRegKeyPath and pwszQuickComplete in Init +#include "precomp.h"
+/* TODO: - implement ACO_SEARCH style - implement ACO_FILTERPREFIXES style - implement ACO_USETAB style - implement ACO_RTLREADING style - */
-#include "precomp.h" +#define CX_LIST 30160 // width of m_hwndList (very wide but alright) +#define CY_LIST 288 // maximum height of drop-down window +#define CY_ITEM 18 // default height of listview item +#define COMPLETION_TIMEOUT 250 // in milliseconds +#define MAX_ITEM_COUNT 1000 // the maximum number of items +#define WATCH_TIMER_ID 0xFEEDBEEF // timer ID to watch m_rcEdit +#define WATCH_INTERVAL 300 // in milliseconds
-static const WCHAR autocomplete_propertyW[] = {'W','i','n','e',' ','A','u','t','o', - 'c','o','m','p','l','e','t','e',' ', - 'c','o','n','t','r','o','l',0}; +static HHOOK s_hMouseHook = NULL; // hook handle +static HWND s_hWatchWnd = NULL; // the window handle to watch
-/************************************************************************** - * IAutoComplete_Constructor - */ -CAutoComplete::CAutoComplete() +// mouse hook procedure to watch the mouse click +// https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms...) +static LRESULT CALLBACK MouseProc(INT nCode, WPARAM wParam, LPARAM lParam) { - m_enabled = TRUE; - m_initialized = FALSE; - m_options = ACO_AUTOAPPEND; - m_wpOrigEditProc = NULL; - m_hwndListBox = NULL; - m_txtbackup = NULL; - m_quickComplete = NULL; - m_hwndEdit = NULL; - m_wpOrigLBoxProc = NULL; + if (s_hMouseHook == NULL) + return 0; // do default + // if the user clicked the outside of s_hWatchWnd, then hide the drop-down window + if (nCode == HC_ACTION && // an action? + s_hWatchWnd && ::IsWindow(s_hWatchWnd) && // s_hWatchWnd is valid? + ::GetCapture() == NULL) // no capture? (dragging something?) + { + RECT rc; + MOUSEHOOKSTRUCT *pMouseHook = reinterpret_cast<MOUSEHOOKSTRUCT *>(lParam); + switch (wParam) + { + case WM_LBUTTONDOWN: case WM_LBUTTONUP: + case WM_RBUTTONDOWN: case WM_RBUTTONUP: + case WM_MBUTTONDOWN: case WM_MBUTTONUP: + case WM_NCLBUTTONDOWN: case WM_NCLBUTTONUP: + case WM_NCRBUTTONDOWN: case WM_NCRBUTTONUP: + case WM_NCMBUTTONDOWN: case WM_NCMBUTTONUP: + { + ::GetWindowRect(s_hWatchWnd, &rc); + if (!::PtInRect(&rc, pMouseHook->pt)) // outside of s_hWatchWnd? + { + ::ShowWindowAsync(s_hWatchWnd, SW_HIDE); // hide it + } + break; + } + } + } + return ::CallNextHookEx(s_hMouseHook, nCode, wParam, lParam); // go next hook }
-/************************************************************************** - * IAutoComplete_Destructor - */ -CAutoComplete::~CAutoComplete() +////////////////////////////////////////////////////////////////////////////// +// sorting algorithm +// http://www.ics.kagoshima-u.ac.jp/~fuchida/edu/algorithm/sort-algorithm/ + +typedef CSimpleArray<CStringW> list_t; + +static inline INT pivot(list_t& a, INT i, INT j) +{ + INT k = i + 1; + while (k <= j && a[i].CompareNoCase(a[k]) == 0) + k++; + if (k > j) + return -1; + if (a[i].CompareNoCase(a[k]) >= 0) + return i; + return k; + } + +static inline INT partition(list_t& a, INT i, INT j, const CStringW& x) { - TRACE(" destroying IAutoComplete(%p)\n", this); - HeapFree(GetProcessHeap(), 0, m_quickComplete); - HeapFree(GetProcessHeap(), 0, m_txtbackup); - if (m_wpOrigEditProc) + INT left = i, right = j; + while (left <= right) { - SetWindowLongPtrW(m_hwndEdit, GWLP_WNDPROC, (LONG_PTR)m_wpOrigEditProc); - RemovePropW(m_hwndEdit, autocomplete_propertyW); + while (left <= j && a[left].CompareNoCase(x) < 0) + left++; + while (right >= i && a[right].CompareNoCase(x) >= 0) + right--; + if (left > right) + break; + + CStringW tmp = a[left]; + a[left] = a[right]; + a[right] = tmp; + + left++; + right--; } - if (m_hwndListBox) - DestroyWindow(m_hwndListBox); + return left; }
-/****************************************************************************** - * IAutoComplete_fnEnable - */ -HRESULT WINAPI CAutoComplete::Enable(BOOL fEnable) +static void quicksort(list_t& a, INT i, INT j) { - HRESULT hr = S_OK; - - TRACE("(%p)->(%s)\n", this, (fEnable) ? "true" : "false"); - - m_enabled = fEnable; - - return hr; + if (i == j) + return; + INT p = pivot(a, i, j); + if (p == -1) + return; + INT k = partition(a, i, j, a[p]); + quicksort(a, i, k - 1); + quicksort(a, k, j); }
-/****************************************************************************** - * create_listbox - */ -void CAutoComplete::CreateListbox() +static inline void DoSort(list_t& list) { - HWND hwndParent = GetParent(m_hwndEdit); + if (list.GetSize() <= 1) // sanity check + return; + quicksort(list, 0, list.GetSize() - 1); // quick sort +}
- /* FIXME : The listbox should be resizable with the mouse. WS_THICKFRAME looks ugly */ - m_hwndListBox = CreateWindowExW(0, WC_LISTBOXW, NULL, - WS_BORDER | WS_CHILD | WS_VSCROLL | LBS_HASSTRINGS | LBS_NOTIFY | LBS_NOINTEGRALHEIGHT, - CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, - hwndParent, NULL, - (HINSTANCE)GetWindowLongPtrW(hwndParent, GWLP_HINSTANCE), NULL); +// std::unique +static INT DoUnique(list_t& list) +{ + INT first = 0, last = list.GetSize(); + if (first == last) + return last; + INT result = first; + while (++first != last) + { + if (list[result].CompareNoCase(list[first]) != 0) + list[++result] = list[first]; + } + return ++result; +}
- if (m_hwndListBox) +static inline void DoUniqueAndTrim(list_t& list) +{ + INT last = DoUnique(list); + while (list.GetSize() > last) { - m_wpOrigLBoxProc = (WNDPROC)SetWindowLongPtrW(m_hwndListBox, GWLP_WNDPROC, (LONG_PTR)ACLBoxSubclassProc); - SetWindowLongPtrW(m_hwndListBox, GWLP_USERDATA, (LONG_PTR)this); + list.RemoveAt(last); } }
+////////////////////////////////////////////////////////////////////////////// +// CACEditCtrl
-/****************************************************************************** - * IAutoComplete_fnInit - */ -HRESULT WINAPI CAutoComplete::Init(HWND hwndEdit, IUnknown *punkACL, LPCOLESTR pwzsRegKeyPath, LPCOLESTR pwszQuickComplete) +// range of WCHAR (inclusive) +struct RANGE { - TRACE("(%p)->(0x%08lx, %p, %s, %s)\n", - this, hwndEdit, punkACL, debugstr_w(pwzsRegKeyPath), debugstr_w(pwszQuickComplete)); - - if (m_options & ACO_AUTOSUGGEST) - TRACE(" ACO_AUTOSUGGEST\n"); - if (m_options & ACO_AUTOAPPEND) - TRACE(" ACO_AUTOAPPEND\n"); - if (m_options & ACO_SEARCH) - FIXME(" ACO_SEARCH not supported\n"); - if (m_options & ACO_FILTERPREFIXES) - FIXME(" ACO_FILTERPREFIXES not supported\n"); - if (m_options & ACO_USETAB) - FIXME(" ACO_USETAB not supported\n"); - if (m_options & ACO_UPDOWNKEYDROPSLIST) - TRACE(" ACO_UPDOWNKEYDROPSLIST\n"); - if (m_options & ACO_RTLREADING) - FIXME(" ACO_RTLREADING not supported\n"); + WCHAR from, to; +};
- if (!hwndEdit || !punkACL) - return E_INVALIDARG; +// a callback function for bsearch: comparison of two ranges +static inline int RangeCompare(const void *x, const void *y) +{ + const RANGE *a = reinterpret_cast<const RANGE *>(x); + const RANGE *b = reinterpret_cast<const RANGE *>(y); + if (a->to < b->from) + return -1; + if (b->to < a->from) + return 1; + return 0; +}
- if (m_initialized) +// is the WCHAR a word break? +static inline BOOL IsWordBreak(WCHAR ch) +{ + // the ranges of word break characters + static const RANGE s_ranges[] = { - WARN("Autocompletion object is already initialized\n"); - /* This->hwndEdit is set to NULL when the edit window is destroyed. */ - return m_hwndEdit ? E_FAIL : E_UNEXPECTED; - } + { 0x0009, 0x0009 }, { 0x0020, 0x002f }, { 0x003a, 0x0040 }, { 0x005b, 0x0060 }, + { 0x007b, 0x007e }, { 0x00ab, 0x00ab }, { 0x00ad, 0x00ad }, { 0x00bb, 0x00bb }, + { 0x02c7, 0x02c7 }, { 0x02c9, 0x02c9 }, { 0x055d, 0x055d }, { 0x060c, 0x060c }, + { 0x2002, 0x200b }, { 0x2013, 0x2014 }, { 0x2016, 0x2016 }, { 0x2018, 0x2018 }, + { 0x201c, 0x201d }, { 0x2022, 0x2022 }, { 0x2025, 0x2027 }, { 0x2039, 0x203a }, + { 0x2045, 0x2046 }, { 0x207d, 0x207e }, { 0x208d, 0x208e }, { 0x226a, 0x226b }, + { 0x2574, 0x2574 }, { 0x3001, 0x3003 }, { 0x3005, 0x3005 }, { 0x3008, 0x3011 }, + { 0x3014, 0x301b }, { 0x301d, 0x301e }, { 0x3041, 0x3041 }, { 0x3043, 0x3043 }, + { 0x3045, 0x3045 }, { 0x3047, 0x3047 }, { 0x3049, 0x3049 }, { 0x3063, 0x3063 }, + { 0x3083, 0x3083 }, { 0x3085, 0x3085 }, { 0x3087, 0x3087 }, { 0x308e, 0x308e }, + { 0x309b, 0x309e }, { 0x30a1, 0x30a1 }, { 0x30a3, 0x30a3 }, { 0x30a5, 0x30a5 }, + { 0x30a7, 0x30a7 }, { 0x30a9, 0x30a9 }, { 0x30c3, 0x30c3 }, { 0x30e3, 0x30e3 }, + { 0x30e5, 0x30e5 }, { 0x30e7, 0x30e7 }, { 0x30ee, 0x30ee }, { 0x30f5, 0x30f6 }, + { 0x30fc, 0x30fe }, { 0xfd3e, 0xfd3f }, { 0xfe30, 0xfe31 }, { 0xfe33, 0xfe44 }, + { 0xfe4f, 0xfe51 }, { 0xfe59, 0xfe5e }, { 0xff08, 0xff09 }, { 0xff0c, 0xff0c }, + { 0xff0e, 0xff0e }, { 0xff1c, 0xff1c }, { 0xff1e, 0xff1e }, { 0xff3b, 0xff3b }, + { 0xff3d, 0xff3d }, { 0xff40, 0xff40 }, { 0xff5b, 0xff5e }, { 0xff61, 0xff64 }, + { 0xff67, 0xff70 }, { 0xff9e, 0xff9f }, { 0xffe9, 0xffe9 }, { 0xffeb, 0xffeb }, + }; + // binary search + RANGE range = { ch, ch }; + return !!bsearch(&range, s_ranges, _countof(s_ranges), sizeof(RANGE), RangeCompare); +}
- if (!SUCCEEDED(punkACL->QueryInterface(IID_PPV_ARG(IEnumString, &m_enumstr)))) +// This function is an application-defined callback function. +// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-editwo... +static INT CALLBACK +EditWordBreakProcW(LPWSTR lpch, INT index, INT count, INT code) +{ + switch (code) { - TRACE("No IEnumString interface\n"); - return E_NOINTERFACE; - } - - m_hwndEdit = hwndEdit; - m_initialized = TRUE; + case WB_ISDELIMITER: + return IsWordBreak(lpch[index]); + + case WB_LEFT: + if (index) + --index; + while (index && !IsWordBreak(lpch[index])) + --index; + return index; + + case WB_RIGHT: + if (!count) + break; + while (index < count && lpch[index] && !IsWordBreak(lpch[index])) + ++index; + return index;
- /* Keep at least one reference to the object until the edit window is destroyed. */ - AddRef(); + default: + break; + } + return 0; +}
- /* If another AutoComplete object was previously assigned to this edit control, - release it but keep the same callback on the control, to avoid an infinite - recursive loop in ACEditSubclassProc while the property is set to this object */ - CAutoComplete *prev = static_cast<CAutoComplete *>(GetPropW(m_hwndEdit, autocomplete_propertyW)); +CACEditCtrl::CACEditCtrl() : m_pDropDown(NULL), m_fnOldWordBreakProc(NULL) +{ +}
- if (prev && prev->m_initialized) +VOID CACEditCtrl::HookWordBreakProc(BOOL bHook) +{ + if (bHook) { - m_wpOrigEditProc = prev->m_wpOrigEditProc; - SetPropW(m_hwndEdit, autocomplete_propertyW, this); - prev->m_wpOrigEditProc = NULL; - prev->Release(); + m_fnOldWordBreakProc = reinterpret_cast<EDITWORDBREAKPROCW>( + SendMessageW(EM_SETWORDBREAKPROC, 0, + reinterpret_cast<LPARAM>(EditWordBreakProcW))); } else { - SetPropW(m_hwndEdit, autocomplete_propertyW, (HANDLE)this); - m_wpOrigEditProc = (WNDPROC)SetWindowLongPtrW(m_hwndEdit, GWLP_WNDPROC, (LONG_PTR)ACEditSubclassProc); - } - - if (m_options & ACO_AUTOSUGGEST) - { - CreateListbox(); + SendMessageW(EM_SETWORDBREAKPROC, 0, + reinterpret_cast<LPARAM>(m_fnOldWordBreakProc)); } +}
- if (pwzsRegKeyPath) - { - WCHAR *key; - WCHAR result[MAX_PATH]; - WCHAR *value; - HKEY hKey = 0; - LONG res; - LONG len; +// WM_CHAR +// This message is posted to the window with the keyboard focus when WM_KEYDOWN is translated. +LRESULT CACEditCtrl::OnChar(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + TRACE("CACEditCtrl::OnChar(%p, %p)\n", this, wParam); + ATLASSERT(m_pDropDown); + return m_pDropDown->OnEditChar(wParam, lParam); +}
- /* pwszRegKeyPath contains the key as well as the value, so we split */ - key = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (wcslen(pwzsRegKeyPath) + 1) * sizeof(WCHAR)); +// WM_CUT / WM_PASTE / WM_CLEAR @implemented +LRESULT CACEditCtrl::OnCutPasteClear(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + TRACE("CACEditCtrl::OnCutPasteClear(%p)\n", this); + ATLASSERT(m_pDropDown); + LRESULT ret = DefWindowProcW(uMsg, wParam, lParam); // do default + m_pDropDown->OnEditUpdate(TRUE); + return ret; +}
- if (key) - { - wcscpy(key, pwzsRegKeyPath); - value = const_cast<WCHAR *>(wcsrchr(key, '\')); +// WM_DESTROY +// This message is sent when a window is being destroyed. +LRESULT CACEditCtrl::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + TRACE("CACEditCtrl::OnDestroy(%p)\n", this); + ATLASSERT(m_pDropDown); + CAutoComplete *pDropDown = m_pDropDown;
- if (value) - { - *value = 0; - value++; - /* Now value contains the value and buffer the key */ - res = RegOpenKeyExW(HKEY_CURRENT_USER, key, 0, KEY_READ, &hKey); - - if (res != ERROR_SUCCESS) - { - /* if the key is not found, MSDN states we must seek in HKEY_LOCAL_MACHINE */ - res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, key, 0, KEY_READ, &hKey); - } - - if (res == ERROR_SUCCESS) - { - len = sizeof(result); - res = RegQueryValueW(hKey, value, result, &len); - if (res == ERROR_SUCCESS) - { - m_quickComplete = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len * sizeof(WCHAR)); - wcscpy(m_quickComplete, result); - } - RegCloseKey(hKey); - } - } + // unhook word break procedure + HookWordBreakProc(FALSE);
- HeapFree(GetProcessHeap(), 0, key); - } - else - { - TRACE("HeapAlloc Failed when trying to alloca %d bytes\n", (wcslen(pwzsRegKeyPath) + 1) * sizeof(WCHAR)); - return S_FALSE; - } - } + // unsubclass because we don't watch any more + HWND hwndEdit = UnsubclassWindow();
- if ((pwszQuickComplete) && (!m_quickComplete)) + // close the drop-down window + if (pDropDown) { - m_quickComplete = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (wcslen(pwszQuickComplete) + 1) * sizeof(WCHAR)); - - if (m_quickComplete) - { - wcscpy(m_quickComplete, pwszQuickComplete); - } - else - { - TRACE("HeapAlloc Failed when trying to alloca %d bytes\n", (wcslen(pwszQuickComplete) + 1) * sizeof(WCHAR)); - return S_FALSE; - } + pDropDown->PostMessageW(WM_CLOSE, 0, 0); }
- return S_OK; + return ::DefWindowProcW(hwndEdit, uMsg, wParam, lParam); // do default }
-/************************************************************************** - * IAutoComplete_fnGetOptions - */ -HRESULT WINAPI CAutoComplete::GetOptions(DWORD *pdwFlag) +// WM_GETDLGCODE +// By responding to this message, an application can take control of a particular type of +// input and process the input itself. +LRESULT CACEditCtrl::OnGetDlgCode(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) { - HRESULT hr = S_OK; + TRACE("CACEditCtrl::OnGetDlgCode(%p)\n", this); + ATLASSERT(m_pDropDown);
- TRACE("(%p) -> (%p)\n", this, pdwFlag); + LRESULT ret = DefWindowProcW(uMsg, wParam, lParam); // get default
- *pdwFlag = m_options; + if (m_pDropDown) + { + // some special keys need default processing. we handle them here + switch (wParam) + { + case VK_RETURN: + if (m_pDropDown->IsWindowVisible() || ::GetKeyState(VK_CONTROL) < 0) + m_pDropDown->OnEditKeyDown(VK_RETURN, 0); + break; + case VK_TAB: + if (m_pDropDown->IsWindowVisible() && m_pDropDown->UseTab()) + m_pDropDown->OnEditKeyDown(VK_TAB, 0); + break; + case VK_ESCAPE: + if (m_pDropDown->IsWindowVisible()) + ret |= DLGC_WANTALLKEYS; // we want all keys to manipulate the list + break; + default: + { + ret |= DLGC_WANTALLKEYS; // we want all keys to manipulate the list + break; + } + } + }
- return hr; + return ret; }
-/************************************************************************** - * IAutoComplete_fnSetOptions - */ -HRESULT WINAPI CAutoComplete::SetOptions(DWORD dwFlag) +// WM_KEYDOWN +// This message is posted to the window with the keyboard focus when a non-system key is pressed. +LRESULT CACEditCtrl::OnKeyDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) { - HRESULT hr = S_OK; + TRACE("CACEditCtrl::OnKeyDown(%p, %p)\n", this, wParam); + ATLASSERT(m_pDropDown); + if (m_pDropDown->OnEditKeyDown(wParam, lParam)) + return 1; // eat + bHandled = FALSE; // do default + return 0; +}
- TRACE("(%p) -> (0x%x)\n", this, dwFlag); +// WM_KILLFOCUS @implemented +// This message is sent to a window immediately before it loses the keyboard focus. +LRESULT CACEditCtrl::OnKillFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + TRACE("CACEditCtrl::OnKillFocus(%p)\n", this); + ATLASSERT(m_pDropDown);
- m_options = (AUTOCOMPLETEOPTIONS)dwFlag; + // hide the list if lost focus + HWND hwndGotFocus = (HWND)wParam; + if (hwndGotFocus != m_hWnd && hwndGotFocus != m_pDropDown->m_hWnd) + { + m_pDropDown->HideDropDown(); + }
- if ((m_options & ACO_AUTOSUGGEST) && m_hwndEdit && !m_hwndListBox) - CreateListbox(); + bHandled = FALSE; // do default + return 0; +}
- return hr; +// WM_SETFOCUS +// This message is sent to a window after it has gained the keyboard focus. +LRESULT CACEditCtrl::OnSetFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + TRACE("CACEditCtrl::OnSetFocus(%p)\n", this); + ATLASSERT(m_pDropDown); + bHandled = FALSE; // do default + return 0; }
-/* Edit_BackWord --- Delete previous word in text box */ -static void Edit_BackWord(HWND hwndEdit) +// WM_SETTEXT +// An application sends this message to set the text of a window. +LRESULT CACEditCtrl::OnSetText(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) { - INT iStart, iEnd; - iStart = iEnd = 0; - SendMessageW(hwndEdit, EM_GETSEL, (WPARAM)&iStart, (LPARAM)&iEnd); + TRACE("CACEditCtrl::OnSetText(%p)\n", this); + ATLASSERT(m_pDropDown); + if (!m_pDropDown->m_bInSetText) + m_pDropDown->HideDropDown(); // it's mechanical WM_SETTEXT + bHandled = FALSE; // do default + return 0; +}
- if (iStart != iEnd || iStart < 0) - return; +////////////////////////////////////////////////////////////////////////////// +// CACListView
- size_t cchText = GetWindowTextLengthW(hwndEdit); - if (cchText < (size_t)iStart || (INT)cchText <= 0) - return; +CACListView::CACListView() : m_pDropDown(NULL), m_cyItem(CY_ITEM) +{ +}
- CComHeapPtr<WCHAR> pszText; - if (!pszText.Allocate(cchText + 1)) - return; +HWND CACListView::Create(HWND hwndParent) +{ + ATLASSERT(m_hWnd == NULL); + DWORD dwStyle = WS_CHILD | /*WS_VISIBLE |*/ WS_CLIPSIBLINGS | LVS_NOCOLUMNHEADER | + LVS_OWNERDATA | LVS_OWNERDRAWFIXED | LVS_SINGLESEL | LVS_REPORT; + HWND hWnd = ::CreateWindowExW(0, GetWndClassName(), L"Internet Explorer", dwStyle, + 0, 0, 0, 0, hwndParent, NULL, + _AtlBaseModule.GetModuleInstance(), NULL); + SubclassWindow(hWnd); // do subclass to handle messages + // set extended listview style + DWORD exstyle = LVS_EX_ONECLICKACTIVATE | LVS_EX_FULLROWSELECT | LVS_EX_TRACKSELECT; + SetExtendedListViewStyle(exstyle, exstyle); + // insert one column (needed to insert items) + LV_COLUMNW column = { LVCF_FMT | LVCF_WIDTH }; + column.fmt = LVCFMT_LEFT; + column.cx = CX_LIST - ::GetSystemMetrics(SM_CXVSCROLL); + InsertColumn(0, &column); + return m_hWnd; +}
- if (GetWindowTextW(hwndEdit, pszText, cchText + 1) <= 0) - return; +// set font handle +VOID CACListView::SetFont(HFONT hFont) +{ + SendMessageW(WM_SETFONT, (WPARAM)hFont, TRUE); // set font
- WORD types[2]; - for (--iStart; 0 < iStart; --iStart) + // get listview item height + m_cyItem = CY_ITEM; + HDC hDC = GetDC(); + if (hDC) { - GetStringTypeW(CT_CTYPE1, &pszText[iStart - 1], 2, types); - if (((types[0] & C1_PUNCT) && !(types[1] & C1_SPACE)) || - ((types[0] & C1_SPACE) && (types[1] & (C1_ALPHA | C1_DIGIT)))) + HGDIOBJ hFontOld = ::SelectObject(hDC, hFont); + TEXTMETRICW tm; + if (::GetTextMetricsW(hDC, &tm)) { - SendMessageW(hwndEdit, EM_SETSEL, iStart, iEnd); - SendMessageW(hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)L""); - return; + m_cyItem = (tm.tmHeight * 3) / 2; // 3/2 of text height } + ::SelectObject(hDC, hFontOld); + ReleaseDC(hDC); } +}
- if (iStart == 0) - { - SendMessageW(hwndEdit, EM_SETSEL, iStart, iEnd); - SendMessageW(hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)L""); - } +// get the number of visible items +INT CACListView::GetVisibleCount() +{ + if (m_cyItem <= 0) // avoid "division by zero" + return 0; + CRect rc; + GetClientRect(&rc); + return rc.Height() / m_cyItem; }
-/* - Window procedure for autocompletion - */ -LRESULT APIENTRY CAutoComplete::ACEditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +// get the text of an item +CStringW CACListView::GetItemText(INT iItem) { - CAutoComplete *pThis = static_cast<CAutoComplete *>(GetPropW(hwnd, autocomplete_propertyW)); - HRESULT hr; - WCHAR hwndText[255]; - WCHAR *hwndQCText; - RECT r; - BOOL control, filled, displayall = FALSE; - int cpt, height, sel; - ULONG fetched; + // NOTE: LVS_OWNERDATA doesn't support LVM_GETITEMTEXT. + ATLASSERT(m_pDropDown); + ATLASSERT(GetStyle() & LVS_OWNERDATA); + return m_pDropDown->GetItemText(iItem); +}
- if (!pThis->m_enabled) - { - return CallWindowProcW(pThis->m_wpOrigEditProc, hwnd, uMsg, wParam, lParam); - } +// get the item index from position +INT CACListView::ItemFromPoint(INT x, INT y) +{ + LV_HITTESTINFO hittest; + hittest.pt.x = x; + hittest.pt.y = y; + return HitTest(&hittest); +}
- switch (uMsg) - { - case CB_SHOWDROPDOWN: - { - ShowWindow(pThis->m_hwndListBox, SW_HIDE); - }; break; +// get current selection +INT CACListView::GetCurSel() +{ + return GetNextItem(-1, LVNI_ALL | LVNI_SELECTED); +}
- case WM_KILLFOCUS: - { - if ((pThis->m_options & ACO_AUTOSUGGEST) && ((HWND)wParam != pThis->m_hwndListBox)) - { - ShowWindow(pThis->m_hwndListBox, SW_HIDE); - } - return CallWindowProcW(pThis->m_wpOrigEditProc, hwnd, uMsg, wParam, lParam); - }; break; +// set current selection +VOID CACListView::SetCurSel(INT iItem) +{ + if (iItem == -1) + SetItemState(-1, 0, LVIS_SELECTED); // select none + else + SetItemState(iItem, LVIS_SELECTED, LVIS_SELECTED); +}
- case WM_KEYUP: - { - GetWindowTextW(hwnd, (LPWSTR)hwndText, 255); +// select the specific position (in client coordinates) +VOID CACListView::SelectHere(INT x, INT y) +{ + SetCurSel(ItemFromPoint(x, y)); +}
- switch(wParam) - { - case VK_RETURN: - { - /* If quickComplete is set and control is pressed, replace the string */ - control = GetKeyState(VK_CONTROL) & 0x8000; - if (control && pThis->m_quickComplete) - { - hwndQCText = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, - (wcslen(pThis->m_quickComplete)+wcslen(hwndText))*sizeof(WCHAR)); - sel = swprintf(hwndQCText, pThis->m_quickComplete, hwndText); - SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)hwndQCText); - SendMessageW(hwnd, EM_SETSEL, 0, sel); - HeapFree(GetProcessHeap(), 0, hwndQCText); - } - - ShowWindow(pThis->m_hwndListBox, SW_HIDE); - return 0; - }; break; - - case VK_LEFT: - case VK_RIGHT: - { - return 0; - }; break; +// WM_LBUTTONUP / WM_MBUTTONUP / WM_RBUTTONUP @implemented +LRESULT CACListView::OnButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + TRACE("CACListView::OnButtonUp(%p)\n", this); + return 0; // eat +}
- case VK_UP: - case VK_DOWN: - { - /* Two cases here : - - if the listbox is not visible, displays it - with all the entries if the style ACO_UPDOWNKEYDROPSLIST - is present but does not select anything. - - if the listbox is visible, change the selection - */ - if ( (pThis->m_options & (ACO_AUTOSUGGEST | ACO_UPDOWNKEYDROPSLIST)) - && (!IsWindowVisible(pThis->m_hwndListBox) && (! *hwndText)) ) - { - /* We must display all the entries */ - displayall = TRUE; - } - else - { - if (IsWindowVisible(pThis->m_hwndListBox)) - { - int count; - - count = SendMessageW(pThis->m_hwndListBox, LB_GETCOUNT, 0, 0); - /* Change the selection */ - sel = SendMessageW(pThis->m_hwndListBox, LB_GETCURSEL, 0, 0); - if (wParam == VK_UP) - sel = ((sel-1) < 0) ? count-1 : sel-1; - else - sel = ((sel+1) >= count) ? -1 : sel+1; - - SendMessageW(pThis->m_hwndListBox, LB_SETCURSEL, sel, 0); - - if (sel != -1) - { - WCHAR *msg; - int len; - - len = SendMessageW(pThis->m_hwndListBox, LB_GETTEXTLEN, sel, (LPARAM)NULL); - msg = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (len + 1) * sizeof(WCHAR)); - - if (msg) - { - SendMessageW(pThis->m_hwndListBox, LB_GETTEXT, sel, (LPARAM)msg); - SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)msg); - SendMessageW(hwnd, EM_SETSEL, wcslen(msg), wcslen(msg)); - - HeapFree(GetProcessHeap(), 0, msg); - } - else - { - TRACE("HeapAlloc failed to allocate %d bytes\n", (len + 1) * sizeof(WCHAR)); - } - } - else - { - SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)pThis->m_txtbackup); - SendMessageW(hwnd, EM_SETSEL, wcslen(pThis->m_txtbackup), wcslen(pThis->m_txtbackup)); - } - } - return 0; - } - }; break; - - case VK_BACK: - { - if (GetKeyState(VK_CONTROL) < 0) // Ctrl+Backspace - { - Edit_BackWord(hwnd); - return 0; - } - } - // FALL THROUGH - case VK_DELETE: - { - if ((! *hwndText) && (pThis->m_options & ACO_AUTOSUGGEST)) - { - ShowWindow(pThis->m_hwndListBox, SW_HIDE); - return CallWindowProcW(pThis->m_wpOrigEditProc, hwnd, uMsg, wParam, lParam); - } - - if (pThis->m_options & ACO_AUTOAPPEND) - { - DWORD b; - SendMessageW(hwnd, EM_GETSEL, (WPARAM)&b, (LPARAM)NULL); - if (b>1) - { - hwndText[b-1] = '\0'; - } - else - { - hwndText[0] = '\0'; - SetWindowTextW(hwnd, hwndText); - } - } - }; break; - - default: - ; - } +// WM_LBUTTONDOWN @implemented +// This message is posted when the user pressed the left mouse button while the cursor is inside. +LRESULT CACListView::OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + TRACE("CACListView::OnLButtonDown(%p)\n", this); + ATLASSERT(m_pDropDown); + INT iItem = ItemFromPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + if (iItem != -1) + { + m_pDropDown->SelectItem(iItem); // select the item + CString strText = GetItemText(iItem); // get text of item + m_pDropDown->SetEditText(strText); // set text + m_pDropDown->SetEditSel(0, strText.GetLength()); // select all + m_pDropDown->HideDropDown(); // hide + } + return 0; +}
- SendMessageW(pThis->m_hwndListBox, LB_RESETCONTENT, 0, 0); +// WM_MBUTTONDOWN / WM_RBUTTONDOWN @implemented +LRESULT CACListView::OnMRButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + TRACE("CACListView::OnMRButtonDown(%p)\n", this); + return 0; // eat +}
- HeapFree(GetProcessHeap(), 0, pThis->m_txtbackup); +// WM_MOUSEWHEEL @implemented +LRESULT CACListView::OnMouseWheel(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + TRACE("CACListView::OnMouseWheel(%p)\n", this); + ATLASSERT(m_pDropDown); + LRESULT ret = DefWindowProcW(uMsg, wParam, lParam); // do default + m_pDropDown->UpdateScrollBar(); + return ret; +}
- pThis->m_txtbackup = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (wcslen(hwndText)+1)*sizeof(WCHAR)); +// WM_NCHITTEST +LRESULT CACListView::OnNCHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + TRACE("CACListView::OnNCHitTest(%p)\n", this); + ATLASSERT(m_pDropDown); + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; // in screen coordinates + ScreenToClient(&pt); // into client coordinates + HWND hwndTarget = m_pDropDown->ChildWindowFromPoint(pt); + if (hwndTarget != m_hWnd) + return HTTRANSPARENT; // pass through (for resizing the drop-down window) + bHandled = FALSE; // do default + return 0; +}
- if (pThis->m_txtbackup) - { - wcscpy(pThis->m_txtbackup, hwndText); - } - else - { - TRACE("HeapAlloc failed to allocate %d bytes\n", (wcslen(hwndText)+1)*sizeof(WCHAR)); - } +////////////////////////////////////////////////////////////////////////////// +// CACScrollBar
- /* Returns if there is no text to search and we doesn't want to display all the entries */ - if ((!displayall) && (! *hwndText) ) - break; +CACScrollBar::CACScrollBar() : m_pDropDown(NULL) +{ +}
- pThis->m_enumstr->Reset(); - filled = FALSE; - size_t curlen = wcslen(hwndText); +HWND CACScrollBar::Create(HWND hwndParent) +{ + ATLASSERT(m_hWnd == NULL); + DWORD dwStyle = WS_CHILD | /*WS_VISIBLE |*/ SBS_BOTTOMALIGN | SBS_VERT; + m_hWnd = ::CreateWindowExW(0, GetWndClassName(), NULL, dwStyle, + 0, 0, 0, 0, hwndParent, NULL, + _AtlBaseModule.GetModuleInstance(), NULL); + // we don't subclass because no message handling is needed + return m_hWnd; +}
- for(cpt = 0;;) - { - CComHeapPtr<OLECHAR> strs; - hr = pThis->m_enumstr->Next(1, &strs, &fetched); - if (hr != S_OK) - break; +////////////////////////////////////////////////////////////////////////////// +// CACSizeBox
- if (!_wcsnicmp(hwndText, strs, curlen)) - { +CACSizeBox::CACSizeBox() : m_pDropDown(NULL), m_bDowner(TRUE), m_bLongList(FALSE) +{ +}
- if (pThis->m_options & ACO_AUTOAPPEND && *hwndText) - { - CComBSTR str((PCWSTR)strs); - memcpy(str.m_str, hwndText, curlen * sizeof(WCHAR)); - SetWindowTextW(hwnd, str); - SendMessageW(hwnd, EM_SETSEL, curlen, str.Length()); - if (!(pThis->m_options & ACO_AUTOSUGGEST)) - break; - } - - if (pThis->m_options & ACO_AUTOSUGGEST) - { - SendMessageW(pThis->m_hwndListBox, LB_ADDSTRING, 0, (LPARAM)(LPOLESTR)strs); - filled = TRUE; - cpt++; - } - } - } +HWND CACSizeBox::Create(HWND hwndParent) +{ + ATLASSERT(m_hWnd == NULL); + DWORD dwStyle = WS_CHILD | /*WS_VISIBLE |*/ SBS_SIZEBOX; + HWND hWnd = ::CreateWindowExW(0, GetWndClassName(), NULL, dwStyle, + 0, 0, 0, 0, hwndParent, NULL, + _AtlBaseModule.GetModuleInstance(), NULL); + SubclassWindow(hWnd); // do subclass to handle message + return m_hWnd; +}
- if (pThis->m_options & ACO_AUTOSUGGEST) - { - if (filled) - { - height = SendMessageW(pThis->m_hwndListBox, LB_GETITEMHEIGHT, 0, 0); - SendMessageW(pThis->m_hwndListBox, LB_CARETOFF, 0, 0); - GetWindowRect(hwnd, &r); - SetParent(pThis->m_hwndListBox, HWND_DESKTOP); - /* It seems that Windows XP displays 7 lines at most - and otherwise displays a vertical scroll bar */ - SetWindowPos(pThis->m_hwndListBox, HWND_TOP, - r.left, r.bottom + 1, r.right - r.left, min(height * 7, height * (cpt + 1)), - SWP_SHOWWINDOW ); - } - else - { - ShowWindow(pThis->m_hwndListBox, SW_HIDE); - } - } +VOID CACSizeBox::SetStatus(BOOL bDowner, BOOL bLongList) +{ + // set flags + m_bDowner = bDowner; + m_bLongList = bLongList;
- }; break; + if (bLongList) + { + SetWindowRgn(NULL, TRUE); // reset window region + return; + }
- case WM_DESTROY: - { - /* Release our reference that we had since ->Init() */ - pThis->Release(); - return 0; - } + RECT rc; + GetWindowRect(&rc); // get size-box size + ::OffsetRect(&rc, -rc.left, -rc.top); // window regions use special coordinates + ATLASSERT(rc.left == 0 && rc.top == 0);
+ // create a trianglar region + HDC hDC = ::CreateCompatibleDC(NULL); + ::BeginPath(hDC); + if (m_bDowner) + { + ::MoveToEx(hDC, rc.right, 0, NULL); + ::LineTo(hDC, rc.right, rc.bottom); + ::LineTo(hDC, 0, rc.bottom); + ::LineTo(hDC, rc.right, 0); + } + else + { + ::MoveToEx(hDC, rc.right, rc.bottom, NULL); + ::LineTo(hDC, rc.right, 0); + ::LineTo(hDC, 0, 0); + ::LineTo(hDC, rc.right, rc.bottom); + } + ::EndPath(hDC); + HRGN hRgn = ::PathToRegion(hDC); + ::DeleteDC(hDC);
- default: - { - return CallWindowProcW(pThis->m_wpOrigEditProc, hwnd, uMsg, wParam, lParam); - } + SetWindowRgn(hRgn, TRUE); // set the trianglar region +}
- } +// WM_ERASEBKGND +LRESULT CACSizeBox::OnEraseBkGnd(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + return TRUE; // do nothing (for quick drawing) +}
- return 0; +// WM_NCHITTEST +LRESULT CACSizeBox::OnNCHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + return HTTRANSPARENT; // pass through }
-LRESULT APIENTRY CAutoComplete::ACLBoxSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +// WM_PAINT +LRESULT CACSizeBox::OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) { - CAutoComplete *pThis = reinterpret_cast<CAutoComplete *>(GetWindowLongPtrW(hwnd, GWLP_USERDATA)); - WCHAR *msg; - int sel, len; + CRect rc; + GetClientRect(&rc); + + PAINTSTRUCT ps; + HDC hDC = BeginPaint(&ps); + if (!hDC) + return 0;
- switch (uMsg) + // fill background + ::FillRect(hDC, &rc, ::GetSysColorBrush(COLOR_3DFACE)); + + // draw size-box + INT cxy = rc.Width(); + for (INT shift = 0; shift < 2; ++shift) { - case WM_MOUSEMOVE: - { - sel = SendMessageW(hwnd, LB_ITEMFROMPOINT, 0, lParam); - SendMessageW(hwnd, LB_SETCURSEL, (WPARAM)sel, (LPARAM)0); - }; break; - - case WM_LBUTTONDOWN: + // choose pen color + INT iColor = ((shift == 0) ? COLOR_HIGHLIGHTTEXT : COLOR_3DSHADOW); + HPEN hPen = ::CreatePen(PS_SOLID, 1, ::GetSysColor(iColor)); + HGDIOBJ hPenOld = ::SelectObject(hDC, hPen); + // do loop to draw the slanted lines + for (INT delta = cxy / 4; delta < cxy; delta += cxy / 4) { - sel = SendMessageW(hwnd, LB_GETCURSEL, 0, 0); - - if (sel < 0) - break; - - len = SendMessageW(pThis->m_hwndListBox, LB_GETTEXTLEN, sel, 0); - msg = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (len + 1) * sizeof(WCHAR)); - - if (msg) + // draw a grip line + if (m_bDowner) { - SendMessageW(hwnd, LB_GETTEXT, sel, (LPARAM)msg); - SendMessageW(pThis->m_hwndEdit, WM_SETTEXT, 0, (LPARAM)msg); - SendMessageW(pThis->m_hwndEdit, EM_SETSEL, 0, wcslen(msg)); - ShowWindow(hwnd, SW_HIDE); - - HeapFree(GetProcessHeap(), 0, msg); + ::MoveToEx(hDC, rc.right, rc.top + delta + shift, NULL); + ::LineTo(hDC, rc.left + delta + shift, rc.bottom); } else { - TRACE("HeapAlloc failed to allocate %d bytes\n", (len + 1) * sizeof(WCHAR)); + ::MoveToEx(hDC, rc.left + delta + shift, rc.top, NULL); + ::LineTo(hDC, rc.right, rc.bottom - delta - shift); } - - }; break; - - default: - return CallWindowProcW(pThis->m_wpOrigLBoxProc, hwnd, uMsg, wParam, lParam); + } + // delete pen + ::SelectObject(hDC, hPenOld); + ::DeleteObject(hPen); } + + EndPaint(&ps); return 0; }
-/************************************************************************** - * IAutoCompleteDropDown - */ -HRESULT STDMETHODCALLTYPE CAutoComplete::GetDropDownStatus(DWORD *pdwFlags, LPWSTR *ppwszString) +////////////////////////////////////////////////////////////////////////////// +// CAutoComplete public methods + +CAutoComplete::CAutoComplete() + : m_bInSetText(FALSE), m_bInSelectItem(FALSE) + , m_bDowner(TRUE), m_dwOptions(ACO_AUTOAPPEND | ACO_AUTOSUGGEST) + , m_bEnabled(TRUE), m_hwndCombo(NULL), m_hFont(NULL), m_bResized(FALSE) { - BOOL dropped = IsWindowVisible(m_hwndListBox); +}
- if (pdwFlags) - *pdwFlags = (dropped ? ACDD_VISIBLE : 0); +HWND CAutoComplete::CreateDropDown() +{ + ATLASSERT(m_hWnd == NULL); + DWORD dwStyle = WS_POPUP | /*WS_VISIBLE |*/ WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_BORDER; + DWORD dwExStyle = WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_NOPARENTNOTIFY; + Create(NULL, NULL, NULL, dwStyle, dwExStyle); + TRACE("CAutoComplete::CreateDropDown(%p): m_hWnd == %p\n", this, m_hWnd); + return m_hWnd; +}
- if (ppwszString) +CAutoComplete::~CAutoComplete() +{ + TRACE("CAutoComplete::~CAutoComplete(%p)\n", this); + if (m_hFont) { - *ppwszString = NULL; - - if (dropped) - { - int sel = SendMessageW(m_hwndListBox, LB_GETCURSEL, 0, 0); - if (sel >= 0) - { - DWORD len = SendMessageW(m_hwndListBox, LB_GETTEXTLEN, sel, 0); - *ppwszString = (LPWSTR)CoTaskMemAlloc((len+1)*sizeof(WCHAR)); - SendMessageW(m_hwndListBox, LB_GETTEXT, sel, (LPARAM)*ppwszString); - } - } + ::DeleteObject(m_hFont); + m_hFont = NULL; } +}
- return S_OK; +BOOL CAutoComplete::CanAutoSuggest() +{ + return !!(m_dwOptions & ACO_AUTOSUGGEST) && m_bEnabled; }
-HRESULT STDMETHODCALLTYPE CAutoComplete::ResetEnumerator() +BOOL CAutoComplete::CanAutoAppend() { - FIXME("(%p): stub\n", this); - return E_NOTIMPL; + return !!(m_dwOptions & ACO_AUTOAPPEND) && m_bEnabled; }
-/************************************************************************** - * IEnumString - */ -HRESULT STDMETHODCALLTYPE CAutoComplete::Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched) +BOOL CAutoComplete::UseTab() { - FIXME("(%p, %d, %p, %p): stub\n", this, celt, rgelt, pceltFetched); - *pceltFetched = 0; - return E_NOTIMPL; + return !!(m_dwOptions & ACO_USETAB) && m_bEnabled; }
-HRESULT STDMETHODCALLTYPE CAutoComplete::Skip(ULONG celt) +BOOL CAutoComplete::IsComboBoxDropped() { - FIXME("(%p, %d): stub\n", this, celt); - return E_NOTIMPL; + if (!::IsWindow(m_hwndCombo)) + return FALSE; + return (BOOL)::SendMessageW(m_hwndCombo, CB_GETDROPPEDSTATE, 0, 0); }
-HRESULT STDMETHODCALLTYPE CAutoComplete::Reset() +INT CAutoComplete::GetItemCount() { - FIXME("(%p): stub\n", this); - return E_NOTIMPL; + return m_outerList.GetSize(); }
-HRESULT STDMETHODCALLTYPE CAutoComplete::Clone(IEnumString **ppOut) +CStringW CAutoComplete::GetItemText(INT iItem) { - FIXME("(%p, %p): stub\n", this, ppOut); - *ppOut = NULL; + if (iItem < 0 || m_outerList.GetSize() <= iItem) + return L""; + return m_outerList[iItem]; +} + +CStringW CAutoComplete::GetEditText() +{ + BSTR bstrText = NULL; + CStringW strText; + if (m_hwndEdit.GetWindowTextW(bstrText)) + { + strText = bstrText; + ::SysFreeString(bstrText); + } + return strText; +} + +VOID CAutoComplete::SetEditText(LPCWSTR pszText) +{ + m_bInSetText = TRUE; // don't hide drop-down + m_hwndEdit.SetWindowTextW(pszText); + m_bInSetText = FALSE; +} + +CStringW CAutoComplete::GetStemText() +{ + CStringW strText = GetEditText(); + INT ich = strText.ReverseFind(L'\'); + if (ich == -1) + return L""; // no stem + return strText.Left(ich + 1); +} + +VOID CAutoComplete::SetEditSel(INT ich0, INT ich1) +{ + m_hwndEdit.SendMessageW(EM_SETSEL, ich0, ich1); +} + +VOID CAutoComplete::ShowDropDown() +{ + if (!m_hWnd || !CanAutoSuggest()) + return; + + INT cItems = GetItemCount(); + if (cItems == 0 || ::GetFocus() != m_hwndEdit || IsComboBoxDropped()) + { + // hide the drop-down if necessary + HideDropDown(); + return; + } + + RepositionDropDown(); +} + +VOID CAutoComplete::HideDropDown() +{ + ShowWindow(SW_HIDE); +} + +VOID CAutoComplete::SelectItem(INT iItem) +{ + m_hwndList.SetCurSel(iItem); + if (iItem != -1) + m_hwndList.EnsureVisible(iItem, FALSE); +} + +VOID CAutoComplete::DoAutoAppend() +{ + if (!CanAutoAppend()) // can we auto-append? + return; // don't append + + CStringW strText = GetEditText(); // get the text + if (strText.IsEmpty()) + return; // don't append + + INT cItems = m_innerList.GetSize(); // get the number of items + if (cItems == 0) + return; // don't append + + // get the common string + CStringW strCommon; + for (INT iItem = 0; iItem < cItems; ++iItem) + { + const CString& strItem = m_innerList[iItem]; // get the text of the item + + if (iItem == 0) // the first item + { + strCommon = strItem; // store the text + continue; + } + + for (INT ich = 0; ich < strCommon.GetLength(); ++ich) + { + if (ich < strItem.GetLength() && strCommon[ich] != strItem[ich]) + { + strCommon = strCommon.Left(ich); // shrink the common string + break; + } + } + } + + if (strCommon.IsEmpty() || strCommon.GetLength() <= strText.GetLength()) + return; // no suggestion + + // append suggestion + INT cchOld = strText.GetLength(); + INT cchAppend = strCommon.GetLength() - cchOld; + strText += strCommon.Right(cchAppend); + SetEditText(strText); + + // select the appended suggestion + SetEditSel(cchOld, strText.GetLength()); +} + +// go back a word ([Ctrl]+[Backspace]) +VOID CAutoComplete::DoBackWord() +{ + // get current selection + INT ich0, ich1; + m_hwndEdit.SendMessageW(EM_GETSEL, reinterpret_cast<WPARAM>(&ich0), + reinterpret_cast<LPARAM>(&ich1)); + if (ich0 <= 0 || ich0 != ich1) // there is selection or left-side end + return; // don't do anything + // get text + CStringW str = GetEditText(); + // extend the range + while (ich0 > 0 && IsWordBreak(str[ich0 - 1])) + --ich0; + while (ich0 > 0 && !IsWordBreak(str[ich0 - 1])) + --ich0; + // select range + SetEditSel(ich0, ich1); + // replace selection with empty text (this is actually deletion) + m_hwndEdit.SendMessageW(EM_REPLACESEL, TRUE, (LPARAM)L""); +} + +VOID CAutoComplete::UpdateScrollBar() +{ + // copy scroll info from m_hwndList to m_hwndScrollBar + SCROLLINFO si = { sizeof(si), SIF_ALL }; + m_hwndList.GetScrollInfo(SB_VERT, &si); + m_hwndScrollBar.SetScrollInfo(SB_CTL, &si, FALSE); + + // show/hide scroll bar + INT cVisibles = m_hwndList.GetVisibleCount(); + INT cItems = m_hwndList.GetItemCount(); + BOOL bShowScroll = (cItems > cVisibles); + m_hwndScrollBar.ShowWindow(bShowScroll ? SW_SHOWNOACTIVATE : SW_HIDE); + if (bShowScroll) + m_hwndScrollBar.InvalidateRect(NULL, FALSE); // redraw +} + +BOOL CAutoComplete::OnEditKeyDown(WPARAM wParam, LPARAM lParam) +{ + TRACE("CAutoComplete::OnEditKeyDown(%p, %p)\n", this, wParam); + + UINT vk = (UINT)wParam; // virtual key + switch (vk) + { + case VK_HOME: case VK_END: + case VK_UP: case VK_DOWN: + case VK_PRIOR: case VK_NEXT: + // is suggestion available? + if (!CanAutoSuggest()) + return FALSE; // do default + if (IsWindowVisible()) + return OnListUpDown(vk); + break; + case VK_ESCAPE: + { + // is suggestion available? + if (!CanAutoSuggest()) + return FALSE; // do default + if (IsWindowVisible()) + { + SetEditText(m_strText); // revert the edit text + // select the end + INT cch = m_strText.GetLength(); + SetEditSel(cch, cch); + HideDropDown(); // hide + return TRUE; // eat + } + break; + } + case VK_RETURN: + { + if (::GetKeyState(VK_CONTROL) < 0) + { + // quick edit + CStringW strText = GetEditText(); + SetEditText(GetQuickEdit(strText)); + } + else + { + // if item is selected, then update the edit text + INT iItem = m_hwndList.GetCurSel(); + if (iItem != -1) + { + CStringW strText = GetItemText(iItem); + SetEditText(strText); + } + } + // select all + INT cch = m_hwndEdit.GetWindowTextLengthW(); + SetEditSel(0, cch); + // hide + HideDropDown(); + break; + } + case VK_TAB: + { + if (IsWindowVisible() && UseTab()) + { + FIXME("ACO_USETAB\n"); + } + break; + } + case VK_DELETE: + { + // is suggestion available? + if (!CanAutoSuggest()) + return FALSE; // do default + m_hwndEdit.DefWindowProcW(WM_KEYDOWN, VK_DELETE, 0); // do default + if (IsWindowVisible()) + OnEditUpdate(FALSE); + return TRUE; // eat + } + case VK_BACK: + { + if (::GetKeyState(VK_CONTROL) < 0) + { + DoBackWord(); + return TRUE; // eat + } + break; + } + default: + { + break; + } + } + return FALSE; // default +} + +LRESULT CAutoComplete::OnEditChar(WPARAM wParam, LPARAM lParam) +{ + TRACE("CACEditCtrl::OnEditChar(%p, %p)\n", this, wParam); + if (wParam == L'\n' || wParam == L'\t') + return 0; // eat + LRESULT ret = m_hwndEdit.DefWindowProcW(WM_CHAR, wParam, lParam); // do default + if (CanAutoSuggest() || CanAutoAppend()) + OnEditUpdate(wParam != VK_BACK); + return ret; +} + +VOID CAutoComplete::OnEditUpdate(BOOL bAppendOK) +{ + CString strText = GetEditText(); + if (m_strText.CompareNoCase(strText) == 0) + { + // no change + return; + } + UpdateCompletion(bAppendOK); +} + +VOID CAutoComplete::OnListSelChange() +{ + // update EDIT text + INT iItem = m_hwndList.GetCurSel(); + CStringW text = ((iItem != -1) ? GetItemText(iItem) : m_strText); + SetEditText(text); + // ensure the item visible + m_hwndList.EnsureVisible(iItem, FALSE); + // select the end + INT cch = text.GetLength(); + SetEditSel(cch, cch); +} + +BOOL CAutoComplete::OnListUpDown(UINT vk) +{ + if (!CanAutoSuggest()) + return FALSE; // default + + if ((m_dwOptions & ACO_UPDOWNKEYDROPSLIST) && !IsWindowVisible()) + { + ShowDropDown(); + return TRUE; // eat + } + + INT iItem = m_hwndList.GetCurSel(); // current selection + INT cItems = m_hwndList.GetItemCount(); // the number of items + switch (vk) + { + case VK_HOME: case VK_END: + m_hwndList.SendMessageW(WM_KEYDOWN, vk, 0); + break; + case VK_UP: + if (iItem == -1) + iItem = cItems - 1; + else if (iItem == 0) + iItem = -1; + else + --iItem; + m_hwndList.SetCurSel(iItem); + break; + case VK_DOWN: + if (iItem == -1) + iItem = 0; + else if (iItem == cItems - 1) + iItem = -1; + else + ++iItem; + m_hwndList.SetCurSel(iItem); + break; + case VK_PRIOR: + if (iItem == -1) + { + iItem = cItems - 1; + } + else if (iItem == 0) + { + iItem = -1; + } + else + { + iItem -= m_hwndList.GetVisibleCount() - 1; + if (iItem < 0) + iItem = 0; + } + m_hwndList.SetCurSel(iItem); + break; + case VK_NEXT: + if (iItem == -1) + { + iItem = 0; + } + else if (iItem == cItems - 1) + { + iItem = -1; + } + else + { + iItem += m_hwndList.GetVisibleCount() - 1; + if (iItem > cItems) + iItem = cItems - 1; + } + m_hwndList.SetCurSel(iItem); + break; + default: + { + ATLASSERT(FALSE); + break; + } + } + + return TRUE; // eat +} + +////////////////////////////////////////////////////////////////////////////// +// CAutoComplete IAutoComplete methods + +// @implemented +STDMETHODIMP CAutoComplete::Enable(BOOL fEnable) +{ + TRACE("(%p)->(%d)\n", this, fEnable); + m_bEnabled = fEnable; + return S_OK; +} + +STDMETHODIMP +CAutoComplete::Init(HWND hwndEdit, IUnknown *punkACL, + LPCOLESTR pwszRegKeyPath, LPCOLESTR pwszQuickComplete) +{ + TRACE("(%p)->(0x%08lx, %p, %s, %s)\n", + this, hwndEdit, punkACL, debugstr_w(pwszRegKeyPath), debugstr_w(pwszQuickComplete)); + + if (m_dwOptions & ACO_AUTOSUGGEST) + TRACE(" ACO_AUTOSUGGEST\n"); + if (m_dwOptions & ACO_AUTOAPPEND) + TRACE(" ACO_AUTOAPPEND\n"); + if (m_dwOptions & ACO_SEARCH) + FIXME(" ACO_SEARCH not supported\n"); + if (m_dwOptions & ACO_FILTERPREFIXES) + FIXME(" ACO_FILTERPREFIXES not supported\n"); + if (m_dwOptions & ACO_USETAB) + FIXME(" ACO_USETAB not supported\n"); + if (m_dwOptions & ACO_UPDOWNKEYDROPSLIST) + TRACE(" ACO_UPDOWNKEYDROPSLIST\n"); + if (m_dwOptions & ACO_RTLREADING) + FIXME(" ACO_RTLREADING not supported\n"); + + // sanity check + if (m_hwndEdit || !punkACL) + { + ATLASSERT(0); + return E_INVALIDARG; + } + + // set this pointer to m_hwndEdit + m_hwndEdit.m_pDropDown = this; + + // do subclass textbox to watch messages + m_hwndEdit.SubclassWindow(hwndEdit); + // set word break procedure + m_hwndEdit.HookWordBreakProc(TRUE); + // save position + m_hwndEdit.GetWindowRect(&m_rcEdit); + + // get an IEnumString + ATLASSERT(!m_pEnum); + punkACL->QueryInterface(IID_IEnumString, (VOID **)&m_pEnum); + TRACE("m_pEnum: %p\n", static_cast<void *>(m_pEnum)); + + // get an IACList + ATLASSERT(!m_pACList); + punkACL->QueryInterface(IID_IACList, (VOID **)&m_pACList); + TRACE("m_pACList: %p\n", static_cast<void *>(m_pACList)); + + AddRef(); // add reference + + UpdateDropDownState(); // create/hide the drop-down window if necessary + + // load quick completion info + LoadQuickComplete(pwszRegKeyPath, pwszQuickComplete); + + // any combobox for m_hwndEdit? + m_hwndCombo = NULL; + HWND hwndParent = ::GetParent(m_hwndEdit); + WCHAR szClass[16]; + if (::GetClassNameW(hwndParent, szClass, _countof(szClass))) + { + if (::StrCmpIW(szClass, WC_COMBOBOXW) == 0 || + ::StrCmpIW(szClass, WC_COMBOBOXEXW) == 0) + { + m_hwndCombo = hwndParent; // get combobox + } + } + + return S_OK; +} + +////////////////////////////////////////////////////////////////////////////// +// CAutoComplete IAutoComplete2 methods + +// @implemented +STDMETHODIMP CAutoComplete::GetOptions(DWORD *pdwFlag) +{ + TRACE("(%p) -> (%p)\n", this, pdwFlag); + if (pdwFlag) + { + *pdwFlag = m_dwOptions; + return S_OK; + } + return E_INVALIDARG; +} + +// @implemented +STDMETHODIMP CAutoComplete::SetOptions(DWORD dwFlag) +{ + TRACE("(%p) -> (0x%x)\n", this, dwFlag); + m_dwOptions = dwFlag; + UpdateDropDownState(); // create/hide the drop-down window if necessary + return S_OK; +} + +////////////////////////////////////////////////////////////////////////////// +// CAutoComplete IAutoCompleteDropDown methods + +// @implemented +STDMETHODIMP CAutoComplete::GetDropDownStatus(DWORD *pdwFlags, LPWSTR *ppwszString) +{ + BOOL dropped = m_hwndList.IsWindowVisible(); + + if (pdwFlags) + *pdwFlags = (dropped ? ACDD_VISIBLE : 0); + + if (ppwszString) + { + *ppwszString = NULL; + + if (dropped) + { + // get selected item + INT iItem = m_hwndList.GetCurSel(); + if (iItem >= 0) + { + // get the text of item + CStringW strText = m_hwndList.GetItemText(iItem); + + // store to *ppwszString + SHStrDupW(strText, ppwszString); + if (*ppwszString == NULL) + return E_OUTOFMEMORY; + } + } + } + + return S_OK; +} + +STDMETHODIMP CAutoComplete::ResetEnumerator() +{ + FIXME("(%p): stub\n", this); + + HideDropDown(); + + if (IsWindowVisible()) + { + OnEditUpdate(FALSE); + } + + return S_OK; +} + +////////////////////////////////////////////////////////////////////////////// +// CAutoComplete IEnumString methods + +// @implemented +STDMETHODIMP CAutoComplete::Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched) +{ + TRACE("(%p, %d, %p, %p)\n", this, celt, rgelt, pceltFetched); + if (rgelt) + *rgelt = NULL; + if (*pceltFetched) + *pceltFetched = 0; + if (celt != 1 || !rgelt || !pceltFetched || !m_pEnum) + return E_INVALIDARG; + + LPWSTR pszText = NULL; + HRESULT hr = m_pEnum->Next(1, &pszText, pceltFetched); + if (hr == S_OK) + *rgelt = pszText; + else + ::CoTaskMemFree(pszText); + return hr; +} + +// @implemented +STDMETHODIMP CAutoComplete::Skip(ULONG celt) +{ + TRACE("(%p, %d)\n", this, celt); return E_NOTIMPL; } + +// @implemented +STDMETHODIMP CAutoComplete::Reset() +{ + TRACE("(%p)\n", this); + if (m_pEnum) + return m_pEnum->Reset(); + return E_FAIL; +} + +// @implemented +STDMETHODIMP CAutoComplete::Clone(IEnumString **ppOut) +{ + TRACE("(%p, %p)\n", this, ppOut); + if (ppOut) + *ppOut = NULL; + return E_NOTIMPL; +} + +////////////////////////////////////////////////////////////////////////////// +// CAutoComplete protected methods + +VOID CAutoComplete::UpdateDropDownState() +{ + if (CanAutoSuggest()) + { + // create the drop-down window if not existed + if (!m_hWnd) + { + AddRef(); + if (!CreateDropDown()) + Release(); + } + } + else + { + // hide if existed + if (m_hWnd) + ShowWindow(SW_HIDE); + } +} + +// calculate the positions of the controls +VOID CAutoComplete::CalcRects(BOOL bDowner, RECT& rcList, RECT& rcScrollBar, RECT& rcSizeBox) +{ + // get the client rectangle + RECT rcClient; + GetClientRect(&rcClient); + + // the list + rcList = rcClient; + rcList.right = rcList.left + CX_LIST; + + // the scroll bar + rcScrollBar = rcClient; + rcScrollBar.left = rcClient.right - GetSystemMetrics(SM_CXVSCROLL); + if (bDowner) + { + rcScrollBar.top = 0; + rcScrollBar.bottom = rcClient.bottom - GetSystemMetrics(SM_CYHSCROLL); + } + else + { + rcScrollBar.top = GetSystemMetrics(SM_CYHSCROLL); + } + + // the size box + rcSizeBox = rcClient; + rcSizeBox.left = rcClient.right - GetSystemMetrics(SM_CXVSCROLL); + if (bDowner) + { + rcSizeBox.top = rcClient.bottom - GetSystemMetrics(SM_CYHSCROLL); + } + else + { + rcSizeBox.top = 0; + rcSizeBox.bottom = rcClient.top + GetSystemMetrics(SM_CYHSCROLL); + } +} + +VOID CAutoComplete::LoadQuickComplete(LPCWSTR pwszRegKeyPath, LPCWSTR pwszQuickComplete) +{ + m_strQuickComplete.Empty(); + + if (pwszRegKeyPath) + { + CStringW strPath = pwszRegKeyPath; + INT ichSep = strPath.ReverseFind(L'\'); // find separator + if (ichSep != -1) // found the last separator + { + // split by the separator + CStringW strKey = strPath.Left(ichSep); + CStringW strName = strPath.Mid(ichSep + 1); + + // load from registry + WCHAR szValue[MAX_PATH] = L""; + DWORD cbValue = sizeof(szValue), dwType = REG_NONE; + SHRegGetUSValueW(pwszRegKeyPath, strName, &dwType, + szValue, &cbValue, FALSE, NULL, 0); + if (szValue[0] != 0 && cbValue != 0 && + (dwType == REG_SZ || dwType == REG_EXPAND_SZ)) + { + m_strQuickComplete = szValue; + } + } + } + + if (pwszQuickComplete && m_strQuickComplete.IsEmpty()) + { + m_strQuickComplete = pwszQuickComplete; + } +} + +CStringW CAutoComplete::GetQuickEdit(LPCWSTR pszText) +{ + if (pszText[0] == 0 || m_strQuickComplete.IsEmpty()) + return pszText; + + // m_strQuickComplete will be "www.%s.com" etc. + CStringW ret; + ret.Format(m_strQuickComplete, pszText); + return ret; +} + +VOID CAutoComplete::RepositionDropDown() +{ + // get nearest monitor from m_hwndEdit + HMONITOR hMon = ::MonitorFromWindow(m_hwndEdit, MONITOR_DEFAULTTONEAREST); + ATLASSERT(hMon != NULL); + if (hMon == NULL) + return; + + // get nearest monitor info + MONITORINFO mi = { sizeof(mi) }; + if (!::GetMonitorInfo(hMon, &mi)) + { + ATLASSERT(FALSE); + return; + } + + // get count and item height + INT cItems = GetItemCount(); + INT cyItem = m_hwndList.m_cyItem; + ATLASSERT(cyItem > 0); + + // get m_hwndEdit position + RECT rcEdit; + m_hwndEdit.GetWindowRect(&rcEdit); + INT x = rcEdit.left, y = rcEdit.bottom; + + // get list extent + RECT rcMon = mi.rcMonitor; + INT cx = rcEdit.right - rcEdit.left, cy = cItems * cyItem; + BOOL bLongList = FALSE; + if (cy > CY_LIST) + { + cy = INT(CY_LIST / cyItem) * cyItem; + bLongList = TRUE; + } + + // convert rectangle for frame + RECT rc = { 0, 0, cx, cy }; + AdjustWindowRectEx(&rc, GetStyle(), FALSE, GetExStyle()); + cy = rc.bottom - rc.top; + + if (!m_bResized) + { + // is the drop-down window a 'downer' or 'upper'? + // NOTE: 'downer' is below the EDIT control. 'upper' is above the EDIT control. + m_bDowner = (rcEdit.bottom + cy < rcMon.bottom); + } + + // adjust y and cy + if (m_bDowner) + { + if (rcMon.bottom < y + cy) + { + cy = ((rcMon.bottom - y) / cyItem) * cyItem; + bLongList = TRUE; + } + } + else + { + if (rcEdit.top < rcMon.top + cy) + { + cy = ((rcEdit.top - rcMon.top) / cyItem) * cyItem; + bLongList = TRUE; + } + y = rcEdit.top - cy; + } + + // set status + m_hwndSizeBox.SetStatus(m_bDowner, bLongList); + + if (m_bResized) // already resized? + PostMessageW(WM_SIZE, 0, 0); // re-layout + else + MoveWindow(x, y, cx, cy); // move + + // show without activation + ShowWindow(SW_SHOWNOACTIVATE); +} + +INT CAutoComplete::ReLoadInnerList() +{ + m_innerList.RemoveAll(); // clear contents + + if (!m_pEnum) + return 0; + + DWORD dwTick = ::GetTickCount(); // used for timeout + + // reload the items + LPWSTR pszItem; + ULONG cGot; + HRESULT hr; + for (ULONG cTotal = 0; cTotal < MAX_ITEM_COUNT; ++cTotal) + { + // get next item + hr = m_pEnum->Next(1, &pszItem, &cGot); + //TRACE("m_pEnum->Next(%p): 0x%08lx\n", reinterpret_cast<IUnknown *>(m_pEnum), hr); + if (hr != S_OK) + break; + + m_innerList.Add(pszItem); // append item to m_innerList + ::CoTaskMemFree(pszItem); // free + + // check the timeout + if (::GetTickCount() - dwTick >= COMPLETION_TIMEOUT) + break; // too late + } + + return m_innerList.GetSize(); // the number of items +} + +// update inner list and m_strText and m_strStemText +INT CAutoComplete::UpdateInnerList() +{ + // get text + CStringW strText = GetEditText(); + + BOOL bReset = FALSE, bExpand = FALSE; // flags + + // if previous text was empty + if (m_strText.IsEmpty()) + { + bReset = TRUE; + } + // save text + m_strText = strText; + + // do expand the items if the stem is changed + CStringW strStemText = GetStemText(); + if (m_strStemText.CompareNoCase(strStemText) != 0) + { + m_strStemText = strStemText; + bExpand = bReset = TRUE; + } + + // reset if necessary + if (bReset && m_pEnum) + { + HRESULT hr = m_pEnum->Reset(); // IEnumString::Reset + TRACE("m_pEnum->Reset(%p): 0x%08lx\n", + static_cast<IUnknown *>(m_pEnum), hr); + } + + // update ac list if necessary + if (bExpand && m_pACList) + { + HRESULT hr = m_pACList->Expand(strStemText); // IACList::Expand + TRACE("m_pACList->Expand(%p, %S): 0x%08lx\n", + static_cast<IUnknown *>(m_pACList), + static_cast<LPCWSTR>(strStemText), hr); + } + + if (bExpand || m_innerList.GetSize() == 0) + { + // reload the inner list + ReLoadInnerList(); + } + + return m_innerList.GetSize(); +} + +INT CAutoComplete::UpdateOuterList() +{ + CStringW strText = GetEditText(); // get the text + + // update the outer list from the inner list + m_outerList.RemoveAll(); + for (INT iItem = 0; iItem < m_innerList.GetSize(); ++iItem) + { + // is the beginning matched? + const CStringW& strTarget = m_innerList[iItem]; + if (::StrCmpNIW(strTarget, strText, strText.GetLength()) == 0) + { + m_outerList.Add(strTarget); + } + } + + // sort the list + DoSort(m_outerList); + // unique + DoUniqueAndTrim(m_outerList); + + // set the item count of the virtual listview + INT cItems = m_outerList.GetSize(); + if (strText.IsEmpty()) + cItems = 0; + m_hwndList.SendMessageW(LVM_SETITEMCOUNT, cItems, 0); + + return cItems; // the number of items +} + +VOID CAutoComplete::UpdateCompletion(BOOL bAppendOK) +{ + TRACE("CAutoComplete::UpdateCompletion(%p, %d)\n", this, bAppendOK); + + // update inner list + UINT cItems = UpdateInnerList(); + if (cItems == 0) // no items + { + HideDropDown(); + return; + } + + if (CanAutoSuggest()) // can we auto-suggest? + { + m_bInSelectItem = TRUE; // don't respond + SelectItem(-1); // select none + m_bInSelectItem = FALSE; + + if (UpdateOuterList()) + RepositionDropDown(); + else + HideDropDown(); + return; + } + + if (CanAutoAppend() && bAppendOK) // can we auto-append? + { + DoAutoAppend(); + return; + } +} + +////////////////////////////////////////////////////////////////////////////// +// CAutoComplete message handlers + +// WM_CREATE +// This message is sent when the window is about to be created after WM_NCCREATE. +// The return value is -1 (failure) or zero (success). +LRESULT CAutoComplete::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + TRACE("CAutoComplete::OnCreate(%p)\n", this); + + // set the pointer of CAutoComplete + m_hwndList.m_pDropDown = this; + m_hwndScrollBar.m_pDropDown = this; + m_hwndSizeBox.m_pDropDown = this; + + // create the children + m_hwndList.Create(m_hWnd); + if (!m_hwndList) + return -1; // failure + m_hwndSizeBox.Create(m_hWnd); + if (!m_hwndSizeBox) + return -1; // failure + m_hwndScrollBar.Create(m_hWnd); + if (!m_hwndScrollBar) + return -1; // failure + + // show the controls + m_hwndList.ShowWindow(SW_SHOWNOACTIVATE); + m_hwndSizeBox.ShowWindow(SW_SHOWNOACTIVATE); + m_hwndScrollBar.ShowWindow(SW_SHOWNOACTIVATE); + + // set the list font + m_hFont = reinterpret_cast<HFONT>(::GetStockObject(DEFAULT_GUI_FONT)); + m_hwndList.SetFont(m_hFont); + + return 0; // success +} + +// WM_DESTROY +// This message is sent when a window is being destroyed. +LRESULT CAutoComplete::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + TRACE("CAutoComplete::OnDestroy(%p)\n", this); + + // hide + if (IsWindowVisible()) + HideDropDown(); + + // unsubclass EDIT control + if (m_hwndEdit) + { + m_hwndEdit.HookWordBreakProc(FALSE); + m_hwndEdit.UnsubclassWindow(); + } + + // clear CAutoComplete pointers + m_hwndEdit.m_pDropDown = NULL; + m_hwndList.m_pDropDown = NULL; + m_hwndScrollBar.m_pDropDown = NULL; + m_hwndSizeBox.m_pDropDown = NULL; + + // destroy controls + m_hwndList.DestroyWindow(); + m_hwndScrollBar.DestroyWindow(); + m_hwndSizeBox.DestroyWindow(); + + // clean up + m_hwndCombo = NULL; + Release(); + + return 0; +} + +// WM_EXITSIZEMOVE +// This message is sent once to a window after it has exited the moving or sizing mode. +LRESULT CAutoComplete::OnExitSizeMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + TRACE("CAutoComplete::OnExitSizeMove(%p)\n", this); + m_bResized = TRUE; // remember resized + + ModifyStyle(WS_THICKFRAME, 0); // remove thick frame to resize + // frame changed + UINT uSWP_ = SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE; + SetWindowPos(NULL, 0, 0, 0, 0, uSWP_); + + m_hwndEdit.SetFocus(); // restore focus + return 0; +} + +// WM_DRAWITEM @implemented +// This message is sent to the owner window to draw m_hwndList. +LRESULT CAutoComplete::OnDrawItem(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + LPDRAWITEMSTRUCT pDraw = reinterpret_cast<LPDRAWITEMSTRUCT>(lParam); + ATLASSERT(pDraw != NULL); + ATLASSERT(m_hwndList.GetStyle() & LVS_OWNERDRAWFIXED); + + // sanity check + if (pDraw->CtlType != ODT_LISTVIEW || pDraw->hwndItem != m_hwndList) + return FALSE; + + // item rectangle + RECT rcItem = pDraw->rcItem; + + // get info + UINT iItem = pDraw->itemID; // the index of item + CStringW strItem = m_hwndList.GetItemText(iItem); // get text of item + + // draw background and set text color + HDC hDC = pDraw->hDC; + BOOL bSelected = (pDraw->itemState & ODS_SELECTED); + if (bSelected) + { + ::FillRect(hDC, &rcItem, ::GetSysColorBrush(COLOR_HIGHLIGHT)); + ::SetTextColor(hDC, ::GetSysColor(COLOR_HIGHLIGHTTEXT)); + } + else + { + ::FillRect(hDC, &rcItem, ::GetSysColorBrush(COLOR_WINDOW)); + ::SetTextColor(hDC, ::GetSysColor(COLOR_WINDOWTEXT)); + } + + // draw text + rcItem.left += ::GetSystemMetrics(SM_CXBORDER); + HGDIOBJ hFontOld = ::SelectObject(hDC, m_hFont); + const UINT uDT_ = DT_LEFT | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER; + ::SetBkMode(hDC, TRANSPARENT); + ::DrawTextW(hDC, strItem, -1, &rcItem, uDT_); + ::SelectObject(hDC, hFontOld); + + return TRUE; +} + +// WM_GETMINMAXINFO @implemented +// This message is sent to a window when the size or position of the window is about to change. +LRESULT CAutoComplete::OnGetMinMaxInfo(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + // restrict minimum size + LPMINMAXINFO pInfo = reinterpret_cast<LPMINMAXINFO>(lParam); + pInfo->ptMinTrackSize.x = ::GetSystemMetrics(SM_CXVSCROLL); + pInfo->ptMinTrackSize.y = ::GetSystemMetrics(SM_CYHSCROLL); + return 0; +} + +// WM_MEASUREITEM @implemented +// This message is sent to the owner window to get the item extent of m_hwndList. +LRESULT CAutoComplete::OnMeasureItem(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + LPMEASUREITEMSTRUCT pMeasure = reinterpret_cast<LPMEASUREITEMSTRUCT>(lParam); + ATLASSERT(pMeasure != NULL); + if (pMeasure->CtlType != ODT_LISTVIEW) + return FALSE; + + ATLASSERT(m_hwndList.GetStyle() & LVS_OWNERDRAWFIXED); + pMeasure->itemHeight = m_hwndList.m_cyItem; // height of item + return TRUE; +} + +// WM_MOUSEACTIVATE @implemented +// The return value of this message specifies whether the window should be activated or not. +LRESULT CAutoComplete::OnMouseActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + return MA_NOACTIVATE; // don't activate by mouse +} + +// WM_NCACTIVATE +// This message is sent to a window to indicate an active or inactive state. +LRESULT CAutoComplete::OnNCActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + bHandled = FALSE; // do default + return 0; +} + +// WM_NCLBUTTONDOWN +LRESULT CAutoComplete::OnNCLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + switch (wParam) + { + case HTBOTTOMRIGHT: case HTTOPRIGHT: + { + // add thick frame to resize. + ModifyStyle(0, WS_THICKFRAME); + // frame changed + UINT uSWP_ = SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE; + SetWindowPos(NULL, 0, 0, 0, 0, uSWP_); + break; + } + } + bHandled = FALSE; // do default + return 0; +} + +// WM_NOTIFY +// This message informs the parent window of a control that an event has occurred. +LRESULT CAutoComplete::OnNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + LPNMHDR pnmh = reinterpret_cast<LPNMHDR>(lParam); + ATLASSERT(pnmh != NULL); + + switch (pnmh->code) + { + case NM_DBLCLK: // double-clicked + { + TRACE("NM_DBLCLK\n"); + HideDropDown(); + break; + } + case NM_HOVER: // mouse is hovering + { + POINT pt; + ::GetCursorPos(&pt); // get cursor position in screen coordinates + m_hwndList.ScreenToClient(&pt); // into client coordinates + INT iItem = m_hwndList.ItemFromPoint(pt.x, pt.y); + if (iItem != -1) + { + m_bInSelectItem = TRUE; // don't respond + m_hwndList.SetCurSel(iItem); // select + m_bInSelectItem = FALSE; + } + return TRUE; // eat + } + case LVN_GETDISPINFOA: // for user's information only + { + TRACE("LVN_GETDISPINFOA\n"); + if (pnmh->hwndFrom != m_hwndList) + break; + + LV_DISPINFOA *pDispInfo = reinterpret_cast<LV_DISPINFOA *>(pnmh); + LV_ITEMA *pItem = &pDispInfo->item; + INT iItem = pItem->iItem; + if (iItem == -1) + break; + + CStringW strText = GetItemText(iItem); + if (pItem->mask & LVIF_TEXT) + SHUnicodeToAnsi(strText, pItem->pszText, pItem->cchTextMax); + break; + } + case LVN_GETDISPINFOW: // for user's information only + { + TRACE("LVN_GETDISPINFOW\n"); + if (pnmh->hwndFrom != m_hwndList) + break; + + LV_DISPINFOW *pDispInfo = reinterpret_cast<LV_DISPINFOW *>(pnmh); + LV_ITEMW *pItem = &pDispInfo->item; + INT iItem = pItem->iItem; + if (iItem == -1) + break; + + CStringW strText = GetItemText(iItem); + if (pItem->mask & LVIF_TEXT) + StringCbCopyW(pItem->pszText, pItem->cchTextMax, strText); + break; + } + case LVN_HOTTRACK: // enabled by LVS_EX_TRACKSELECT + { + TRACE("LVN_HOTTRACK\n"); + LPNMLISTVIEW pListView = reinterpret_cast<LPNMLISTVIEW>(pnmh); + INT iItem = pListView->iItem; + TRACE("LVN_HOTTRACK: iItem:%d\n", iItem); + m_hwndList.SetCurSel(iItem); + m_hwndList.EnsureVisible(iItem, FALSE); + return TRUE; + } + case LVN_ITEMACTIVATE: // enabled by LVS_EX_ONECLICKACTIVATE + { + TRACE("LVN_ITEMACTIVATE\n"); + LPNMITEMACTIVATE pItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pnmh); + INT iItem = pItemActivate->iItem; + TRACE("LVN_ITEMACTIVATE: iItem:%d\n", iItem); + if (iItem != -1) // the item is clicked + { + SelectItem(iItem); + HideDropDown(); + } + break; + } + case LVN_ITEMCHANGED: // item info is changed + { + TRACE("LVN_ITEMCHANGED\n"); + LPNMLISTVIEW pListView = reinterpret_cast<LPNMLISTVIEW>(pnmh); + if (pListView->uChanged & LVIF_STATE) // selection changed + { + // listview selection changed + if (!m_bInSelectItem) + { + OnListSelChange(); + } + UpdateScrollBar(); + } + break; + } + } + + return 0; +} + +// WM_NCHITTEST @implemented +// The return value is indicating the cursor shape and the behaviour. +LRESULT CAutoComplete::OnNCHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + TRACE("CAutoComplete::OnNCHitTest(%p)\n", this); + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; // in screen coordinates + ScreenToClient(&pt); // into client coordinates + if (ChildWindowFromPoint(pt) == m_hwndSizeBox) // hit? + { + // allow resizing (with cursor shape) + return m_bDowner ? HTBOTTOMRIGHT : HTTOPRIGHT; + } + bHandled = FALSE; // do default + return 0; +} + +// WM_SIZE @implemented +// This message is sent when the window size is changed. +LRESULT CAutoComplete::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + TRACE("CAutoComplete::OnSize(%p)\n", this); + + // calculate the positions of the controls + CRect rcList, rcScrollBar, rcSizeBox; + CalcRects(m_bDowner, rcList, rcScrollBar, rcSizeBox); + + // reposition the controls in smartest way + UINT uSWP_ = SWP_NOACTIVATE | SWP_NOCOPYBITS; + HDWP hDWP = ::BeginDeferWindowPos(3); + hDWP = ::DeferWindowPos(hDWP, m_hwndScrollBar, HWND_TOP, + rcScrollBar.left, rcScrollBar.top, + rcScrollBar.Width(), rcScrollBar.Height(), uSWP_); + hDWP = ::DeferWindowPos(hDWP, m_hwndSizeBox, m_hwndScrollBar, + rcSizeBox.left, rcSizeBox.top, + rcSizeBox.Width(), rcSizeBox.Height(), uSWP_); + hDWP = ::DeferWindowPos(hDWP, m_hwndList, m_hwndSizeBox, + rcList.left, rcList.top, + rcList.Width(), rcList.Height(), uSWP_); + ::EndDeferWindowPos(hDWP); + + UpdateScrollBar(); + return 0; +} + +// WM_SHOWWINDOW +LRESULT CAutoComplete::OnShowWindow(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + // hook mouse events + BOOL bShow = (BOOL)wParam; + if (bShow) + { + s_hWatchWnd = m_hWnd; // watch this + + // unhook mouse if any + if (s_hMouseHook) + { + HHOOK hHookOld = s_hMouseHook; + s_hMouseHook = NULL; + ::UnhookWindowsHookEx(hHookOld); + } + + // hook mouse + s_hMouseHook = ::SetWindowsHookEx(WH_MOUSE, MouseProc, NULL, ::GetCurrentThreadId()); + ATLASSERT(s_hMouseHook != NULL); + + // set timer + SetTimer(WATCH_TIMER_ID, WATCH_INTERVAL, NULL); + + bHandled = FALSE; // do default + return 0; + } + else + { + // kill timer + KillTimer(WATCH_TIMER_ID); + + s_hWatchWnd = NULL; // unwatch + + // unhook mouse if any + if (s_hMouseHook) + { + HHOOK hHookOld = s_hMouseHook; + s_hMouseHook = NULL; + ::UnhookWindowsHookEx(hHookOld); + } + + LRESULT ret = DefWindowProcW(uMsg, wParam, lParam); // do default + + if (m_hwndCombo) + ::InvalidateRect(m_hwndCombo, NULL, TRUE); // redraw + + m_outerList.RemoveAll(); // no use + return ret; + } +} + +// WM_TIMER +LRESULT CAutoComplete::OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + if (wParam != WATCH_TIMER_ID) // sanity check + return 0; + + // if the textbox is dead, then kill the timer + if (!::IsWindow(m_hwndEdit)) + { + KillTimer(WATCH_TIMER_ID); + return 0; + } + + // m_hwndEdit is moved? + RECT rcEdit; + m_hwndEdit.GetWindowRect(&rcEdit); + if (!::EqualRect(&rcEdit, &m_rcEdit)) + { + // if so, hide + HideDropDown(); + + m_rcEdit = rcEdit; // update rectangle + m_bResized = FALSE; // clear flag + } + + return 0; +} + +// WM_VSCROLL +// This message is sent when a scroll event occurs. +LRESULT CAutoComplete::OnVScroll(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled) +{ + TRACE("CAutoComplete::OnVScroll(%p)\n", this); + WORD code = LOWORD(wParam); + switch (code) + { + case SB_THUMBPOSITION: case SB_THUMBTRACK: + { + // get the scrolling info + INT nPos = HIWORD(wParam); + SCROLLINFO si = { sizeof(si), SIF_ALL }; + m_hwndList.GetScrollInfo(SB_VERT, &si); + + // scroll the list-view by CListView::EnsureVisible + INT cItems = m_hwndList.GetItemCount(); + // iItem : cItems == (nPos - si.nMin) : (si.nMax - si.nMin). + INT iItem = cItems * (nPos - si.nMin) / (si.nMax - si.nMin); + if (nPos > si.nPos) + { + iItem += m_hwndList.GetVisibleCount(); + if (iItem >= cItems) + iItem = cItems - 1; + } + m_hwndList.EnsureVisible(iItem, FALSE); + + // update scrolling position of m_hwndScrollBar + si.fMask = SIF_POS; + m_hwndList.GetScrollInfo(SB_VERT, &si); + m_hwndScrollBar.SetScrollInfo(SB_VERT, &si, FALSE); + break; + } + default: + { + // pass it to m_hwndList + m_hwndList.SendMessageW(WM_VSCROLL, wParam, lParam); + UpdateScrollBar(); + break; + } + } + return 0; +} diff --git a/dll/win32/browseui/CAutoComplete.h b/dll/win32/browseui/CAutoComplete.h index 7430307eef3..eb979baf314 100644 --- a/dll/win32/browseui/CAutoComplete.h +++ b/dll/win32/browseui/CAutoComplete.h @@ -19,65 +19,286 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ +#pragma once
-#ifndef _AUTOCOMPLETE_H_ -#define _AUTOCOMPLETE_H_ +#include "atltypes.h" +#include "rosctrls.h"
-class CAutoComplete : - public CComCoClass<CAutoComplete, &CLSID_AutoComplete>, - public CComObjectRootEx<CComMultiThreadModelNoCS>, - public IAutoComplete2, - public IAutoCompleteDropDown, - public IEnumString +class CACEditCtrl; +class CACListView; +class CACScrollBar; +class CACSizeBox; +class CAutoComplete; + +////////////////////////////////////////////////////////////////////////////// +// CACEditCtrl --- auto-completion textbox + +class CACEditCtrl + : public CWindowImpl<CACEditCtrl, CWindow, CControlWinTraits> +{ +public: + CAutoComplete* m_pDropDown; + static LPCWSTR GetWndClassName() { return WC_EDITW; } + + CACEditCtrl(); + VOID HookWordBreakProc(BOOL bHook); + + // message map + BEGIN_MSG_MAP(CACEditCtrl) + MESSAGE_HANDLER(WM_CHAR, OnChar) + MESSAGE_HANDLER(WM_CLEAR, OnCutPasteClear) + MESSAGE_HANDLER(WM_CUT, OnCutPasteClear) + MESSAGE_HANDLER(WM_DESTROY, OnDestroy) + MESSAGE_HANDLER(WM_GETDLGCODE, OnGetDlgCode) + MESSAGE_HANDLER(WM_KEYDOWN, OnKeyDown) + MESSAGE_HANDLER(WM_KILLFOCUS, OnKillFocus) + MESSAGE_HANDLER(WM_PASTE, OnCutPasteClear) + MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus) + MESSAGE_HANDLER(WM_SETTEXT, OnSetText) + END_MSG_MAP() + +protected: + // protected variables + EDITWORDBREAKPROCW m_fnOldWordBreakProc; + // message handlers + LRESULT OnChar(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnCutPasteClear(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnGetDlgCode(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnKeyDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnKillFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnSetFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnSetText(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); +}; + +////////////////////////////////////////////////////////////////////////////// +// CACListView --- auto-completion list control + +class CACListView : public CWindowImpl<CACListView, CListView> +{ +public: + CAutoComplete* m_pDropDown; + INT m_cyItem; + static LPCWSTR GetWndClassName() { return WC_LISTVIEW; } + + CACListView(); + HWND Create(HWND hwndParent); + VOID SetFont(HFONT hFont); + + INT GetVisibleCount(); + CStringW GetItemText(INT iItem); + INT ItemFromPoint(INT x, INT y); + + INT GetCurSel(); + VOID SetCurSel(INT iItem); + VOID SelectHere(INT x, INT y); + +protected: + // message map + BEGIN_MSG_MAP(CACListView) + MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown) + MESSAGE_HANDLER(WM_LBUTTONUP, OnButtonUp) + MESSAGE_HANDLER(WM_MBUTTONDOWN, OnMRButtonDown) + MESSAGE_HANDLER(WM_MBUTTONUP, OnButtonUp) + MESSAGE_HANDLER(WM_MOUSEWHEEL, OnMouseWheel) + MESSAGE_HANDLER(WM_NCHITTEST, OnNCHitTest) + MESSAGE_HANDLER(WM_RBUTTONDOWN, OnMRButtonDown) + MESSAGE_HANDLER(WM_RBUTTONUP, OnButtonUp) + END_MSG_MAP() + // message handlers + LRESULT OnButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnMRButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnMouseWheel(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnNCHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); +}; + +////////////////////////////////////////////////////////////////////////////// +// CACScrollBar --- auto-completion scrollbar control + +class CACScrollBar : public CWindowImpl<CACScrollBar> { -private: - BOOL m_enabled; - BOOL m_initialized; - HWND m_hwndEdit; - HWND m_hwndListBox; - WNDPROC m_wpOrigEditProc; - WNDPROC m_wpOrigLBoxProc; - LPWSTR m_txtbackup; // HeapAlloc'ed - LPWSTR m_quickComplete; // HeapAlloc'ed - CComPtr<IEnumString> m_enumstr; - AUTOCOMPLETEOPTIONS m_options; public: + CAutoComplete* m_pDropDown; + static LPCWSTR GetWndClassName() { return WC_SCROLLBARW; }
+ CACScrollBar(); + HWND Create(HWND hwndParent); + +protected: + // message map + BEGIN_MSG_MAP(CACScrollBar) + END_MSG_MAP() +}; + +////////////////////////////////////////////////////////////////////////////// +// CACSizeBox --- auto-completion size-box control + +class CACSizeBox : public CWindowImpl<CACSizeBox> +{ +public: + CAutoComplete* m_pDropDown; + static LPCWSTR GetWndClassName() { return WC_SCROLLBARW; } + + CACSizeBox(); + HWND Create(HWND hwndParent); + VOID SetStatus(BOOL bDowner, BOOL bLongList); + +protected: + // protected variables + BOOL m_bDowner; + BOOL m_bLongList; + // message map + BEGIN_MSG_MAP(CACSizeBox) + MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkGnd) + MESSAGE_HANDLER(WM_NCHITTEST, OnNCHitTest) + MESSAGE_HANDLER(WM_PAINT, OnPaint) + END_MSG_MAP() + // message handlers + LRESULT OnEraseBkGnd(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnNCHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); +}; + +////////////////////////////////////////////////////////////////////////////// +// CAutoComplete --- auto-completion drop-down window + +#define WC_DROPDOWNW L"Auto-Suggest Dropdown" // the window class name + +class CAutoComplete + : public CComCoClass<CAutoComplete, &CLSID_AutoComplete> + , public CComObjectRootEx<CComMultiThreadModelNoCS> + , public CWindowImpl<CAutoComplete> + , public IAutoComplete2 + , public IAutoCompleteDropDown + , public IEnumString +{ +public: + DECLARE_WND_CLASS_EX(WC_DROPDOWNW, CS_DROPSHADOW | CS_SAVEBITS, COLOR_3DFACE) + static LPCWSTR GetWndClassName() { return WC_DROPDOWNW; } + BOOL m_bInSetText; // this flag avoids subsequent action in WM_SETTEXT + BOOL m_bInSelectItem; // this flag avoids subsequent action in LVN_ITEMCHANGED + + // public methods CAutoComplete(); - ~CAutoComplete(); + HWND CreateDropDown(); + virtual ~CAutoComplete();
- static LRESULT APIENTRY ACEditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - static LRESULT APIENTRY ACLBoxSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + BOOL CanAutoSuggest(); + BOOL CanAutoAppend(); + BOOL UseTab(); + BOOL IsComboBoxDropped(); + INT GetItemCount(); + CStringW GetItemText(INT iItem);
- void CreateListbox(); + CStringW GetEditText(); + VOID SetEditText(LPCWSTR pszText); + CStringW GetStemText(); + VOID SetEditSel(INT ich0, INT ich1);
- // IAutoComplete2 - virtual HRESULT WINAPI Enable(BOOL fEnable); - virtual HRESULT WINAPI Init(HWND hwndEdit, IUnknown *punkACL, LPCOLESTR pwzsRegKeyPath, LPCOLESTR pwszQuickComplete); - virtual HRESULT WINAPI GetOptions(DWORD *pdwFlag); - virtual HRESULT WINAPI SetOptions(DWORD dwFlag); + VOID ShowDropDown(); + VOID HideDropDown(); + VOID SelectItem(INT iItem); + VOID DoAutoAppend(); + VOID DoBackWord(); + VOID UpdateScrollBar();
- // IAutoCompleteDropDown - virtual HRESULT STDMETHODCALLTYPE GetDropDownStatus(DWORD *pdwFlags, LPWSTR *ppwszString); - virtual HRESULT STDMETHODCALLTYPE ResetEnumerator(); + LRESULT OnEditChar(WPARAM wParam, LPARAM lParam); + BOOL OnEditKeyDown(WPARAM wParam, LPARAM lParam); + VOID OnEditUpdate(BOOL bAppendOK); + VOID OnListSelChange(); + BOOL OnListUpDown(UINT vk);
+ // IAutoComplete methods + STDMETHODIMP Enable(BOOL fEnable) override; + STDMETHODIMP Init(HWND hwndEdit, IUnknown *punkACL, LPCOLESTR pwszRegKeyPath, + LPCOLESTR pwszQuickComplete) override; + // IAutoComplete2 methods + STDMETHODIMP GetOptions(DWORD *pdwFlag) override; + STDMETHODIMP SetOptions(DWORD dwFlag) override; + // IAutoCompleteDropDown methods + STDMETHODIMP GetDropDownStatus(DWORD *pdwFlags, LPWSTR *ppwszString) override; + STDMETHODIMP ResetEnumerator() override; // IEnumString methods - virtual HRESULT STDMETHODCALLTYPE Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched); - virtual HRESULT STDMETHODCALLTYPE Skip(ULONG celt); - virtual HRESULT STDMETHODCALLTYPE Reset(); - virtual HRESULT STDMETHODCALLTYPE Clone(IEnumString **ppenum); - -DECLARE_REGISTRY_RESOURCEID(IDR_AUTOCOMPLETE) -DECLARE_NOT_AGGREGATABLE(CAutoComplete) - -DECLARE_PROTECT_FINAL_CONSTRUCT() - -BEGIN_COM_MAP(CAutoComplete) - COM_INTERFACE_ENTRY_IID(IID_IAutoComplete, IAutoComplete) - COM_INTERFACE_ENTRY_IID(IID_IAutoComplete2, IAutoComplete2) - COM_INTERFACE_ENTRY_IID(IID_IAutoCompleteDropDown, IAutoCompleteDropDown) - COM_INTERFACE_ENTRY_IID(IID_IEnumString, IEnumString) -END_COM_MAP() -}; + STDMETHODIMP Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched) override; + STDMETHODIMP Skip(ULONG celt) override; + STDMETHODIMP Reset() override; + STDMETHODIMP Clone(IEnumString **ppOut) override; + +protected: + // The following variables are POD (plain old data): + BOOL m_bDowner; // downer or upper? (below textbox or above textbox) + DWORD m_dwOptions; // for IAutoComplete2::SetOptions + DWORD m_bEnabled; // the auto-composition is enabled? + HWND m_hwndCombo; // the combobox if any + HFONT m_hFont; // the font + BOOL m_bResized; // re-sized by size-box? + RECT m_rcEdit; // in screen coordinates, to watch the position + // The following variables are non-POD: + CStringW m_strText; // internal text (used in selecting item and reverting text) + CStringW m_strStemText; // dirname + '\' + CStringW m_strQuickComplete; // used for [Ctrl]+[Enter] + CACEditCtrl m_hwndEdit; // subclassed to watch + CACListView m_hwndList; // this listview is virtual + CACScrollBar m_hwndScrollBar; // scroll bar contol + CACSizeBox m_hwndSizeBox; // the size grip + CComPtr<IEnumString> m_pEnum; // used for enumeration + CComPtr<IACList> m_pACList; // for IACList::Expand to update the list + CSimpleArray<CStringW> m_innerList; // internal list + CSimpleArray<CStringW> m_outerList; // owner data for virtual listview + // protected methods + VOID UpdateDropDownState(); + VOID CalcRects(BOOL bDowner, RECT& rcListView, RECT& rcScrollBar, RECT& rcSizeBox); + VOID LoadQuickComplete(LPCWSTR pwszRegKeyPath, LPCWSTR pwszQuickComplete); + CStringW GetQuickEdit(LPCWSTR pszText); + VOID RepositionDropDown(); + INT ReLoadInnerList(); + INT UpdateInnerList(); + INT UpdateOuterList(); + VOID UpdateCompletion(BOOL bAppendOK); + // message map + BEGIN_MSG_MAP(CAutoComplete) + MESSAGE_HANDLER(WM_CREATE, OnCreate) + MESSAGE_HANDLER(WM_DESTROY, OnDestroy) + MESSAGE_HANDLER(WM_DRAWITEM, OnDrawItem) + MESSAGE_HANDLER(WM_EXITSIZEMOVE, OnExitSizeMove) + MESSAGE_HANDLER(WM_GETMINMAXINFO, OnGetMinMaxInfo) + MESSAGE_HANDLER(WM_MEASUREITEM, OnMeasureItem) + MESSAGE_HANDLER(WM_MOUSEACTIVATE, OnMouseActivate) + MESSAGE_HANDLER(WM_NCACTIVATE, OnNCActivate) + MESSAGE_HANDLER(WM_NCLBUTTONDOWN, OnNCLButtonDown) + MESSAGE_HANDLER(WM_NOTIFY, OnNotify) + MESSAGE_HANDLER(WM_NCHITTEST, OnNCHitTest) + MESSAGE_HANDLER(WM_SIZE, OnSize) + MESSAGE_HANDLER(WM_SHOWWINDOW, OnShowWindow) + MESSAGE_HANDLER(WM_TIMER, OnTimer) + MESSAGE_HANDLER(WM_VSCROLL, OnVScroll) + END_MSG_MAP() + // message handlers + LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnDrawItem(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnExitSizeMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnGetMinMaxInfo(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnMeasureItem(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnMouseActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnNCActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnNCLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnNCHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnShowWindow(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled); + LRESULT OnVScroll(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
-#endif /* _AUTOCOMPLETE_H_ */ + DECLARE_REGISTRY_RESOURCEID(IDR_AUTOCOMPLETE) + DECLARE_NOT_AGGREGATABLE(CAutoComplete) + DECLARE_PROTECT_FINAL_CONSTRUCT() + + BEGIN_COM_MAP(CAutoComplete) + COM_INTERFACE_ENTRY_IID(IID_IAutoComplete, IAutoComplete) + COM_INTERFACE_ENTRY_IID(IID_IAutoComplete2, IAutoComplete2) + COM_INTERFACE_ENTRY_IID(IID_IAutoCompleteDropDown, IAutoCompleteDropDown) + COM_INTERFACE_ENTRY_IID(IID_IEnumString, IEnumString) + END_COM_MAP() +};