https://git.reactos.org/?p=reactos.git;a=commitdiff;h=c19d9df2593d6376f783d…
commit c19d9df2593d6376f783dae78ba67fe53280fa4a
Author: Katayama Hirofumi MZ <katayama.hirofumi.mz(a)gmail.com>
AuthorDate: Wed Mar 10 16:22:57 2021 +0900
Commit: GitHub <noreply(a)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/m…
+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-editw…
+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()
+};