diff --git a/src/windows/AutoComplete.cpp b/src/windows/AutoComplete.cpp new file mode 100644 index 0000000..84b4010 --- /dev/null +++ b/src/windows/AutoComplete.cpp @@ -0,0 +1,403 @@ +#include "AutoComplete.hpp" +#include "Main.hpp" + +#define T_AUTOCOMPLETE_CLASS TEXT("DMAutoComplete") + +AutoComplete::~AutoComplete() +{ + if (m_hwnd) + { + BOOL b = DestroyWindow(m_hwnd); + assert(b && "Was the window already destroyed?"); + + m_hwnd = NULL; + } + + assert(!m_listHwnd); +} + +void AutoComplete::ShowOrMove(POINT pt) +{ + bool oldReachedMaxX = m_bReachedMaxSizeX; + bool oldReachedMaxY = m_bReachedMaxSizeY; + + if (m_matches.empty()) + { + Hide(); + return; + } + + // Calculate the height of all the entries. + HDC hdc = GetDC(m_hwnd); + HGDIOBJ old = SelectObject(hdc, g_MessageTextFont); + int height = 0; + int width = 0; + const int maxHeight = ScaleByDPI(200); + const int maxWidth = ScaleByDPI(200); + m_bReachedMaxSizeX = false; + m_bReachedMaxSizeY = false; + for (auto& entry : m_matches) + { + RECT rcMeasure{}; + LPTSTR tstr = ConvertCppStringToTString(entry.str); + DrawText(hdc, tstr, -1, &rcMeasure, DT_SINGLELINE | DT_CALCRECT); + free(tstr); + height += rcMeasure.bottom - rcMeasure.top + ScaleByDPI(4); + + int newWidth = rcMeasure.right - rcMeasure.left + ScaleByDPI(4); + width = std::max(width, newWidth); + + if (width > maxWidth) { + m_bReachedMaxSizeX = true; + width = maxWidth; + } + + if (height > maxHeight) { + m_bReachedMaxSizeY = true; + height = maxHeight; + } + } + SelectObject(hdc, old); + ReleaseDC(m_hwnd, hdc); + + // If reachedMaxSize is different, dismiss the old autocomplete window first and recreate: + if (m_bReachedMaxSizeX != oldReachedMaxX || m_bReachedMaxSizeY != oldReachedMaxY) + Hide(); + + constexpr int style = WS_POPUP | WS_BORDER; + width += ScaleByDPI(8); + + width += m_bReachedMaxSizeY ? GetSystemMetrics(SM_CXVSCROLL) : 0; + height += m_bReachedMaxSizeX ? GetSystemMetrics(SM_CYHSCROLL) : 0; + + RECT rc{ pt.x, pt.y - height, pt.x + width, pt.y }; + AdjustWindowRect(&rc, style, FALSE); + + if (!m_hwnd) + { + // Create the window, the list window will be created by it + m_hwnd = CreateWindowEx( + WS_EX_NOACTIVATE, + T_AUTOCOMPLETE_CLASS, + TEXT(""), + style, + rc.left, + rc.top, + rc.right - rc.left, + rc.bottom - rc.top, + GetParent(m_editHwnd), + NULL, + g_hInstance, + this + ); + + ShowWindow(m_hwnd, SW_SHOWNOACTIVATE); + SendMessage(m_hwnd, WM_UPDATECOMPITEMS, 0, 0); + + m_bHadFirstArrowPress = false; + } + else + { + MoveWindow(m_hwnd, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, TRUE); + SendMessage(m_hwnd, WM_UPDATECOMPITEMS, 0, 0); + } +} + +void AutoComplete::Hide() +{ + if (m_hwnd) + { + BOOL b = DestroyWindow(m_hwnd); + assert(b && "This window was already destroyed?"); + m_hwnd = NULL; + } + + assert(!m_listHwnd); + + m_matches.clear(); + m_startAt = 0; + m_bHadFirstArrowPress = false; + m_bReachedMaxSizeX = false; + m_bReachedMaxSizeY = false; +} + +void AutoComplete::Commit() +{ + if (!IsActive()) + return; + + int index = GetSelectionIndex(); + if (index < 0 || index >= int(m_matches.size())) + return; + + const std::string& match = m_matches[index].str; + + // Append this string to the text. + LPTSTR tstr = ConvertCppStringToTString(match); + + // Stolen from https://cboard.cprogramming.com/windows-programming/55742-appending-text-edit-control-post389162.html#post389162 cheers! + int length = GetWindowTextLength(m_editHwnd); + SendMessage(m_editHwnd, EM_SETSEL, m_startAt, length); + SendMessage(m_editHwnd, EM_REPLACESEL, 0, (LPARAM)tstr); + SendMessage(m_editHwnd, WM_VSCROLL, SB_BOTTOM, (LPARAM)NULL); + + free(tstr); + + Hide(); +} + +int AutoComplete::GetSelectionIndex() +{ + if (!IsActive()) return -1; + return (int)SendMessage(m_hwnd, WM_GETCOMPINDEX, 0, 0); +} + +void AutoComplete::_Update(LPCTSTR message, int length) +{ + LPCTSTR initialMessage = message; + if (GetFocus() != m_editHwnd) + return; + + if (length <= 0) { + Hide(); + return; + } + + int autoStart = -1; + + for (int i = length - 1; i >= 0; ) + { + // If a space, break. We've searched the entirety of this word. + TCHAR c = message[i]; + if (c == (TCHAR) ' ') + break; + + if ((c == (TCHAR) '@' || c == (TCHAR) '#' || c == (TCHAR) ':') && + (i == 0 || message[i - 1] == ' ')) + { + // Start auto-completion here. + autoStart = i; + break; + } + + if (i == 0) + break; + --i; + } + + if (autoStart == -1) { + Hide(); + return; + } + + message += autoStart; + + // take the query + char query = (char)*message; message++; + + // and the actual name + std::string stuff = MakeStringFromTString(message); + + m_matches.clear(); + m_lookup(stuff, query, m_matches); + + std::sort(m_matches.begin(), m_matches.end()); + + POINT pt {}; + if (!GetCaretPos(&pt)) + return; + + ClientToScreen(m_editHwnd, &pt); + + m_startAt = int(message - initialMessage); + ShowOrMove(pt); +} + +void AutoComplete::Update(LPCTSTR textInEditControl, int length) +{ + LPTSTR freed = NULL; + + if (!textInEditControl) + { + length = Edit_GetTextLength(m_editHwnd); + + freed = new TCHAR[length + 1]; + Edit_GetText(m_editHwnd, freed, length + 1); + + textInEditControl = freed; + } + + if (!length) + length = int(_tcslen(textInEditControl)); + + _Update(textInEditControl, length); + + if (freed) + delete[] freed; +} + +bool AutoComplete::HandleKeyMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if (!m_listHwnd) + return false; + + if (wParam != VK_UP && wParam != VK_DOWN) + return false; + + SendMessage(m_listHwnd, uMsg, wParam, lParam); + + // HACK: Need to re-do this key press because the list view control drops + // the first one for some reason. + if (!m_bHadFirstArrowPress) + { + m_bHadFirstArrowPress = true; + + if (uMsg == WM_KEYDOWN) { + SendMessage(m_listHwnd, WM_KEYUP, wParam, 3 << 30); + SendMessage(m_listHwnd, uMsg, wParam, lParam); + } + else { + SendMessage(m_listHwnd, WM_KEYDOWN, wParam, 0); + SendMessage(m_listHwnd, uMsg, wParam, lParam); + } + } + + return true; +} + +bool AutoComplete::HandleCharMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if (!m_listHwnd) + return false; + + if (wParam != '\r' && wParam != '\t') + return false; + + Commit(); + return true; +} + +void AutoComplete::SetEdit(HWND hWnd) +{ + m_editHwnd = hWnd; +} + +void AutoComplete::SetFont(HFONT hFont) +{ + m_hFont = hFont; +} + +void AutoComplete::SetLookup(LookUpFunction function) +{ + m_lookup = function; +} + +bool AutoComplete::InitializeClass() +{ + WNDCLASS wc; + ZeroMemory(&wc, sizeof wc); + wc.lpszClassName = T_AUTOCOMPLETE_CLASS; + wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); + wc.style = 0; + wc.hCursor = LoadCursor (0, IDC_ARROW); + wc.lpfnWndProc = AutoComplete::WndProc; + + return RegisterClass(&wc); +} + +LRESULT CALLBACK AutoComplete::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + constexpr int idList = 1; + + AutoComplete* pThis = (AutoComplete*) GetWindowLongPtr(hWnd, GWLP_USERDATA); + + switch (uMsg) + { + case WM_NCCREATE: + { + CREATESTRUCT* cs = (CREATESTRUCT*)lParam; + SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)cs->lpCreateParams); + break; + } + case WM_CREATE: + { + HWND hList = CreateWindow( + WC_LISTVIEW, + NULL, + WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_NOCOLUMNHEADER | LVS_SINGLESEL | LVS_SHOWSELALWAYS, + 0, 0, 1, 1, + hWnd, + (HMENU)idList, + g_hInstance, + NULL + ); + + pThis->m_listHwnd = hList; + if (!hList) + break; + + SetWindowFont(hList, pThis->m_hFont, FALSE); + break; + } + case WM_SIZE: + { + HWND hList = GetDlgItem(hWnd, idList); + if (!hList) + break; + + MoveWindow(hList, 0, 0, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), TRUE); + ListView_DeleteColumn(hList, 0); + + TCHAR colText[] = TEXT("NAME"); + LVCOLUMN lvc; + ZeroMemory(&lvc, sizeof lvc); + lvc.mask = LVCF_TEXT | LVCF_WIDTH; + lvc.pszText = colText; + lvc.cx = GET_X_LPARAM(lParam); + ListView_InsertColumn(hList, 0, &lvc); + break; + } + case WM_GETCOMPINDEX: + { + HWND hList = GetDlgItem(hWnd, idList); + if (!hList) + return -1; + return ListView_GetNextItem(hList, -1, LVNI_SELECTED); + } + case WM_UPDATECOMPITEMS: + { + HWND hList = GetDlgItem(hWnd, idList); + if (!hList) + break; + + ListView_DeleteAllItems(hList); + + int i = 0; + for (auto& entry : pThis->m_matches) + { + LPTSTR str = ConvertCppStringToTString(entry.str); + + LVITEM lvi; + ZeroMemory(&lvi, sizeof lvi); + lvi.mask = LVIF_TEXT; + lvi.pszText = str; + lvi.iItem = i++; + + ListView_InsertItem(hList, &lvi); + free(str); + } + + ListView_SetItemState(hList, 0, LVIS_SELECTED, LVIS_SELECTED); + break; + } + case WM_DESTROY: + { + BOOL b = DestroyWindow(pThis->m_listHwnd); + assert(b && "Was window already destroyed?"); + pThis->m_listHwnd = NULL; + break; + } + } + + return DefWindowProc(hWnd, uMsg, wParam, lParam); +} diff --git a/src/windows/AutoComplete.hpp b/src/windows/AutoComplete.hpp new file mode 100644 index 0000000..5bdd875 --- /dev/null +++ b/src/windows/AutoComplete.hpp @@ -0,0 +1,94 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "WinUtils.hpp" + +struct AutoCompleteMatch +{ + std::string str; + float fuzzy = 0; + AutoCompleteMatch(const std::string& s, float fuz) : str(s), fuzzy(fuz) {} + + bool operator<(const AutoCompleteMatch& oth) const + { + if (fuzzy != oth.fuzzy) + return fuzzy < oth.fuzzy; + + return strcmp(str.c_str(), oth.str.c_str()) < 0; + } +}; + +class AutoComplete +{ +public: + typedef void(*LookUpFunction) (const std::string& lookup, char query, std::vector& matchesFound); + +public: + AutoComplete() {}; + ~AutoComplete(); + + // Updates the autocomplete widget with the text in the edit control. + // If NULL, text is fetched using GetWindowText. + // The pointer passed into here is not freed. + void Update(LPCTSTR textInEditControl = NULL, int length = 0); + + // This is used to change the selection of the autocompletion list while + // focused on the main window's edit control. + bool HandleKeyMessage(UINT uMsg, WPARAM wParam, LPARAM lParam); + + // This is used to apply the auto-completion when pressing ENTER or TAB. + // Returns whether or not the key press was handled. + bool HandleCharMessage(UINT uMsg, WPARAM wParam, LPARAM lParam); + + // Sets the managed edit control. + void SetEdit(HWND hWnd); + + // Sets the font used by the autocomplete window. + void SetFont(HFONT hFont); + + // Sets the lookup function used by the autocomplete window. + void SetLookup(LookUpFunction function); + + // Checks if this is active. + bool IsActive() const + { + return m_hwnd != NULL; + } + +private: + void ShowOrMove(POINT pt); + void Hide(); + void Commit(); + int GetSelectionIndex(); + + // The private function of ::Update(). Here the textInEditControl MUST be valid. + void _Update(LPCTSTR textInEditControl, int length); + + static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +public: + static bool InitializeClass(); + +private: + HWND m_hwnd = NULL; + HWND m_listHwnd = NULL; + + // This is not part of the autocomplete instance itself, instead, AutoComplete + // functions on top of this edit control. + HWND m_editHwnd = NULL; + + HFONT m_hFont = NULL; + + bool m_bReachedMaxSizeX = false; + bool m_bReachedMaxSizeY = false; + bool m_bHadFirstArrowPress = false; + int m_startAt = 0; // place where auto-completion starts. + + std::vector m_matches; + + LookUpFunction m_lookup; +}; diff --git a/src/windows/Main.cpp b/src/windows/Main.cpp index c8c59d7..0f65d78 100644 --- a/src/windows/Main.cpp +++ b/src/windows/Main.cpp @@ -23,6 +23,7 @@ #include "QuickSwitcher.hpp" #include "Frontend_Win32.hpp" #include "ProgressDialog.hpp" +#include "AutoComplete.hpp" #include "../discord/LocalSettings.hpp" #include "../discord/WebsocketClient.hpp" #include "../discord/UpdateChecker.hpp" @@ -990,6 +991,7 @@ LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) GuildHeader::InitializeClass(); GuildLister::InitializeClass(); MemberList ::InitializeClass(); + AutoComplete::InitializeClass(); MessageEditor::InitializeClass(); LoadingMessage::InitializeClass(); diff --git a/src/windows/MessageEditor.cpp b/src/windows/MessageEditor.cpp index 3aa2735..6a5d2f5 100644 --- a/src/windows/MessageEditor.cpp +++ b/src/windows/MessageEditor.cpp @@ -147,148 +147,6 @@ bool MessageEditor::IsUploadingAllowed() return pChan->HasPermission(PERM_ATTACH_FILES); } -void MessageEditor::HideAutocomplete() -{ - if (m_autoComplete_hwnd) - { - BOOL b = DestroyWindow(m_autoComplete_hwnd); - assert(b && "This window was already destroyed?"); - m_autoComplete_hwnd = NULL; - m_autoCompleteList_hwnd = NULL; - } - - m_autocompleteResults.clear(); - m_autoCompleteStart = 0; - m_bHadFirstArrowPress = false; - m_bReachedMaxSizeX = false; - m_bReachedMaxSizeY = false; -} - -void MessageEditor::ShowAutocomplete(const std::vector& autoCompletions, POINT pt) -{ - bool oldReachedMaxX = m_bReachedMaxSizeX; - bool oldReachedMaxY = m_bReachedMaxSizeY; - if (autoCompletions.empty()) - { - HideAutocomplete(); - return; - } - - // Calculate the height of all the entries. - HDC hdc = GetDC(m_hwnd); - HGDIOBJ old = SelectObject(hdc, g_MessageTextFont); - int height = 0; - int width = 0; - const int maxHeight = ScaleByDPI(200); - const int maxWidth = ScaleByDPI(200); - m_bReachedMaxSizeX = false; - m_bReachedMaxSizeY = false; - for (auto& entry : autoCompletions) - { - RECT rcMeasure{}; - LPTSTR tstr = ConvertCppStringToTString(entry); - DrawText(hdc, tstr, -1, &rcMeasure, DT_SINGLELINE | DT_CALCRECT); - free(tstr); - height += rcMeasure.bottom - rcMeasure.top + ScaleByDPI(4); - - int newWidth = rcMeasure.right - rcMeasure.left + ScaleByDPI(4); - width = std::max(width, newWidth); - - if (width > maxWidth) { - m_bReachedMaxSizeX = true; - width = maxWidth; - } - - if (height > maxHeight) { - m_bReachedMaxSizeY = true; - height = maxHeight; - } - } - SelectObject(hdc, old); - ReleaseDC(m_hwnd, hdc); - - // If reachedMaxSize is different, dismiss the old autocomplete window first and recreate: - if (m_bReachedMaxSizeX != oldReachedMaxX || m_bReachedMaxSizeY != oldReachedMaxY) - HideAutocomplete(); - - constexpr int style = WS_POPUP | WS_BORDER; - width += ScaleByDPI(8); - - width += m_bReachedMaxSizeY ? GetSystemMetrics(SM_CXVSCROLL) : 0; - height += m_bReachedMaxSizeX ? GetSystemMetrics(SM_CYHSCROLL) : 0; - - RECT rc{ pt.x, pt.y - height, pt.x + width, pt.y }; - AdjustWindowRect(&rc, style, FALSE); - - m_autocompleteResults = autoCompletions; - - if (!m_autoComplete_hwnd) - { - // Create the window, the list window will be created by it - m_autoComplete_hwnd = CreateWindowEx( - WS_EX_NOACTIVATE, - T_MESSAGE_EDITOR_AUTOCOMPLETE_CLASS, - TEXT(""), - style, - rc.left, - rc.top, - rc.right - rc.left, - rc.bottom - rc.top, - m_parent_hwnd, - NULL, - g_hInstance, - this - ); - - ShowWindow(m_autoComplete_hwnd, SW_SHOWNOACTIVATE); - SendMessage(m_autoComplete_hwnd, WM_UPDATECOMPITEMS, 0, 0); - - m_bHadFirstArrowPress = false; - } - else - { - MoveWindow(m_autoComplete_hwnd, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, TRUE); - SendMessage(m_autoComplete_hwnd, WM_UPDATECOMPITEMS, 0, 0); - } -} - -bool MessageEditor::IsAutocompleteActive() const -{ - return m_autoComplete_hwnd != NULL; -} - -int MessageEditor::GetAutocompleteSelectedIndex() const -{ - if (!IsAutocompleteActive()) return -1; - - return (int) SendMessage(m_autoComplete_hwnd, WM_GETCOMPINDEX, 0, 0); -} - -void MessageEditor::PerformAutocomplete() -{ - if (!IsAutocompleteActive()) - return; - - int index = GetAutocompleteSelectedIndex(); - if (index < 0 || index >= int(m_autocompleteResults.size())) - return; - - const std::string& match = m_autocompleteResults[index]; - - // Append this string to the text. - LPTSTR tstr = ConvertCppStringToTString(match); - - // Stolen from https://cboard.cprogramming.com/windows-programming/55742-appending-text-edit-control-post389162.html#post389162 cheers! - int length = GetWindowTextLength(m_edit_hwnd); - SendMessage(m_edit_hwnd, EM_SETSEL, m_autoCompleteStart, length); - SendMessage(m_edit_hwnd, EM_REPLACESEL, 0, (LPARAM)tstr); - SendMessage(m_edit_hwnd, WM_VSCROLL, SB_BOTTOM, (LPARAM)NULL); - - free(tstr); - - HideAutocomplete(); -} - void MessageEditor::Expand(int Amount) { if (Amount == 0) @@ -494,56 +352,8 @@ void MessageEditor::StopEdit() } } -void MessageEditor::UpdateAutocompleteIfNeeded(LPTSTR message) +void MessageEditor::AutoCompleteLookup(const std::string& word, char query, std::vector& matches) { - LPTSTR initialMessage = message; - if (GetFocus() != m_edit_hwnd) - return; - - std::vector results; - std::vector matches; - - size_t msgLen = _tcslen(message); - if (msgLen > INT_MAX || msgLen == 0) { - HideAutocomplete(); - return; - } - - size_t autoStart = SIZE_MAX; - - for (size_t i = msgLen - 1; i >= 0; ) - { - // If a space, break. We've searched the entirety of this word. - TCHAR c = message[i]; - if (c == (TCHAR) ' ') - break; - - if ((c == (TCHAR) '@' || c == (TCHAR) '#' || c == (TCHAR) ':') && - (i == 0 || message[i - 1] == ' ')) - { - // Start auto-completion here. - autoStart = i; - break; - } - - if (i == 0) - break; - --i; - } - - if (autoStart == SIZE_MAX) { - HideAutocomplete(); - return; - } - - message += autoStart; - - // take the query - char query = (char)*message; message++; - - // and the actual name - std::string stuff = MakeStringFromTString(message); - switch (query) { case '#': // CHANNELS @@ -558,27 +368,13 @@ void MessageEditor::UpdateAutocompleteIfNeeded(LPTSTR message) if (chan.IsCategory() || chan.IsVoice() || chan.IsDM()) continue; - float fzm = CompareFuzzy(chan.m_name, stuff.c_str()); + float fzm = CompareFuzzy(chan.m_name, word.c_str()); if (fzm != 0.0f) matches.push_back(AutoCompleteMatch(chan.m_name, fzm)); } break; } } - - std::sort(matches.begin(), matches.end()); - - for (auto& match : matches) - results.push_back(match.str); - - POINT pt {}; - if (!GetCaretPos(&pt)) - return; - - ClientToScreen(m_edit_hwnd, &pt); - - m_autoCompleteStart = int(message - initialMessage); - ShowAutocomplete(results, pt); } void MessageEditor::OnUpdateText() @@ -611,9 +407,8 @@ void MessageEditor::OnUpdateText() SendMessage(g_Hwnd, WM_UPDATEMESSAGELENGTH, 0, (LPARAM)actualLength); - UpdateAutocompleteIfNeeded(tchr); + m_autoComplete.Update(tchr, length); delete[] tchr; - } LRESULT MessageEditor::EditWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) @@ -638,30 +433,12 @@ LRESULT MessageEditor::EditWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l case WM_KEYUP: case WM_KEYDOWN: { + if (pThis->m_autoComplete.HandleKeyMessage(uMsg, wParam, lParam)) + return 0; + if (wParam == VK_SHIFT) m_shiftHeld = uMsg == WM_KEYDOWN; - if (pThis->m_autoCompleteList_hwnd && (wParam == VK_UP || wParam == VK_DOWN)) { - // forward - SendMessage(pThis->m_autoCompleteList_hwnd, uMsg, wParam, lParam); - - if (!pThis->m_bHadFirstArrowPress) - { - pThis->m_bHadFirstArrowPress = true; - - if (uMsg == WM_KEYDOWN) { - SendMessage(pThis->m_autoCompleteList_hwnd, WM_KEYUP, wParam, 3 << 30); - SendMessage(pThis->m_autoCompleteList_hwnd, uMsg, wParam, lParam); - } - else { - SendMessage(pThis->m_autoCompleteList_hwnd, WM_KEYDOWN, wParam, 0); - SendMessage(pThis->m_autoCompleteList_hwnd, uMsg, wParam, lParam); - } - } - - return 0; - } - break; } case WM_UPDATETEXTSIZE: @@ -694,19 +471,15 @@ LRESULT MessageEditor::EditWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l } case WM_CHAR: { + if (pThis->m_autoComplete.HandleCharMessage(uMsg, wParam, lParam)) + return 0; + if (!GetDiscordInstance()->GetCurrentChannel()) break; if (!GetDiscordInstance()->GetCurrentChannel()->HasPermission(PERM_SEND_MESSAGES)) break; - if ((wParam == '\r' || wParam == '\t') && pThis->IsAutocompleteActive()) - { - // trigger the autocomplete instead - pThis->PerformAutocomplete(); - return 0; - } - if (wParam == '\r' && !m_shiftHeld) { pThis->TryToSendMessage(); @@ -724,96 +497,6 @@ LRESULT MessageEditor::EditWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l return m_editWndProc(hWnd, uMsg, wParam, lParam); } -LRESULT MessageEditor::CompWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - constexpr int idList = 1; - - MessageEditor* pThis = (MessageEditor*) GetWindowLongPtr(hWnd, GWLP_USERDATA); - - switch (uMsg) - { - case WM_NCCREATE: - { - CREATESTRUCT* cs = (CREATESTRUCT*)lParam; - SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)cs->lpCreateParams); - break; - } - case WM_CREATE: - { - HWND hList = CreateWindow( - WC_LISTVIEW, - NULL, - WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_NOCOLUMNHEADER | LVS_SINGLESEL | LVS_SHOWSELALWAYS, - 0, 0, 1, 1, - hWnd, - (HMENU)idList, - g_hInstance, - NULL - ); - - pThis->m_autoCompleteList_hwnd = hList; - if (!hList) - break; - - SetWindowFont(hList, g_MessageTextFont, FALSE); - break; - } - case WM_SIZE: - { - HWND hList = GetDlgItem(hWnd, idList); - if (!hList) - break; - - MoveWindow(hList, 0, 0, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), TRUE); - ListView_DeleteColumn(hList, 0); - - TCHAR colText[] = TEXT("NAME"); - LVCOLUMN lvc; - ZeroMemory(&lvc, sizeof lvc); - lvc.mask = LVCF_TEXT | LVCF_WIDTH; - lvc.pszText = colText; - lvc.cx = GET_X_LPARAM(lParam); - ListView_InsertColumn(hList, 0, &lvc); - break; - } - case WM_GETCOMPINDEX: - { - HWND hList = GetDlgItem(hWnd, idList); - if (!hList) - return -1; - return ListView_GetNextItem(hList, -1, LVNI_SELECTED); - } - case WM_UPDATECOMPITEMS: - { - HWND hList = GetDlgItem(hWnd, idList); - if (!hList) - break; - - ListView_DeleteAllItems(hList); - - int i = 0; - for (auto& entry : pThis->m_autocompleteResults) - { - LPTSTR str = ConvertCppStringToTString(entry); - - LVITEM lvi; - ZeroMemory(&lvi, sizeof lvi); - lvi.mask = LVIF_TEXT; - lvi.pszText = str; - lvi.iItem = i++; - - ListView_InsertItem(hList, &lvi); - free(str); - } - - ListView_SetItemState(hList, 0, LVIS_SELECTED, LVIS_SELECTED); - break; - } - } - - return DefWindowProc(hWnd, uMsg, wParam, lParam); -} - void MessageEditor::Layout() { // Re-position controls @@ -1011,15 +694,6 @@ void MessageEditor::InitializeClass() wc.lpfnWndProc = MessageEditor::WndProc; RegisterClass(&wc); - - ZeroMemory(&wc, sizeof wc); - wc.lpszClassName = T_MESSAGE_EDITOR_AUTOCOMPLETE_CLASS; - wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); - wc.style = 0; - wc.hCursor = LoadCursor (0, IDC_ARROW); - wc.lpfnWndProc = MessageEditor::CompWndProc; - - RegisterClass(&wc); } MessageEditor* MessageEditor::Create(HWND hwnd, LPRECT pRect) @@ -1121,5 +795,9 @@ MessageEditor* MessageEditor::Create(HWND hwnd, LPRECT pRect) SendMessage(newThis->m_mentionName_hwnd, WM_SETFONT, (WPARAM)g_AuthorTextFont, TRUE); SendMessage(newThis->m_mentionCheck_hwnd, WM_SETFONT, (WPARAM)g_SendButtonFont, TRUE); + newThis->m_autoComplete.SetEdit(newThis->m_edit_hwnd); + newThis->m_autoComplete.SetFont(g_SendButtonFont); + newThis->m_autoComplete.SetLookup(&AutoCompleteLookup); + return newThis; } diff --git a/src/windows/MessageEditor.hpp b/src/windows/MessageEditor.hpp index dd7f0c0..13d5382 100644 --- a/src/windows/MessageEditor.hpp +++ b/src/windows/MessageEditor.hpp @@ -4,21 +4,7 @@ #include #include #include "Main.hpp" - -struct AutoCompleteMatch -{ - std::string str; - float fuzzy = 0; - AutoCompleteMatch(const std::string& s, float fuz) : str(s), fuzzy(fuz) {} - - bool operator<(const AutoCompleteMatch& oth) const - { - if (fuzzy != oth.fuzzy) - return fuzzy < oth.fuzzy; - - return strcmp(str.c_str(), oth.str.c_str()) < 0; - } -}; +#include "AutoComplete.hpp" class MessageEditor { @@ -52,10 +38,7 @@ class MessageEditor Snowflake m_replyMessage = 0; // Or edited message ID COLORREF m_userNameColor = CLR_NONE; bool m_bWasUploadingAllowed = false; - bool m_bReachedMaxSizeX = false; - bool m_bReachedMaxSizeY = false; - bool m_bHadFirstArrowPress = false; // HACK: need to repeat the key press because the first ever press is dropped - int m_autoCompleteStart = 0; // place where autocompletion starts + AutoComplete m_autoComplete; static WNDPROC m_editWndProc; static bool m_shiftHeld; @@ -87,18 +70,10 @@ class MessageEditor void UpdateCommonButtonsShown(); bool IsUploadingAllowed(); void OnUpdateText(); - void UpdateAutocompleteIfNeeded(LPTSTR message); - void HideAutocomplete(); - void ShowAutocomplete(const std::vector& autoCompletions, POINT pt); - bool IsAutocompleteActive() const; - int GetAutocompleteSelectedIndex() const; - void AutocompleteUp(); - void AutocompleteDown(); - void PerformAutocomplete(); + static void AutoCompleteLookup(const std::string& keyWord, char query, std::vector& matches); public: static LRESULT CALLBACK EditWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - static LRESULT CALLBACK CompWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); static void InitializeClass(); diff --git a/vs/DiscordMessenger.vcxproj b/vs/DiscordMessenger.vcxproj index a8c79c7..b69e9be 100644 --- a/vs/DiscordMessenger.vcxproj +++ b/vs/DiscordMessenger.vcxproj @@ -356,6 +356,7 @@ + @@ -412,6 +413,7 @@ + diff --git a/vs/DiscordMessenger.vcxproj.filters b/vs/DiscordMessenger.vcxproj.filters index 73fbff2..51a8f9f 100644 --- a/vs/DiscordMessenger.vcxproj.filters +++ b/vs/DiscordMessenger.vcxproj.filters @@ -593,6 +593,9 @@ Header Files\Windows + + Header Files\Windows + @@ -736,5 +739,8 @@ Source Files\Windows + + Source Files\Windows + \ No newline at end of file