From 492e49da384fde458fa38470e02f2eb274156fad Mon Sep 17 00:00:00 2001 From: janwas Date: Thu, 23 Jun 2011 10:12:43 +0000 Subject: [PATCH] add support for selection and deletion of entire words in text controls (ctrl+backspace/delete/left/right) based on patch by Chakakhan/kenny (thanks!) also refactored wclipboard closes #511 This was SVN commit r9646. --- binaries/data/config/default.cfg | 7 + source/gui/CInput.cpp | 513 +++++++++++++++++++----- source/gui/CInput.h | 5 + source/lib/sysdep/clipboard.h | 18 +- source/lib/sysdep/os/win/wclipboard.cpp | 109 ++--- 5 files changed, 494 insertions(+), 158 deletions(-) diff --git a/binaries/data/config/default.cfg b/binaries/data/config/default.cfg index f9db3f2fd5..fc3523547c 100644 --- a/binaries/data/config/default.cfg +++ b/binaries/data/config/default.cfg @@ -129,6 +129,7 @@ hotkey.camera.down = S, DownArrow ; Scroll or rotate dow hotkey.console.toggle = BackQuote, F9 ; Open/close console hotkey.console.copy = "Ctrl+C" ; Copy from console to clipboard hotkey.console.paste = "Ctrl+V" ; Paste clipboard to console +hotkey.console.cut = "Ctrl+X" ; Cut the selected text and copy it to the clipboard ; > ENTITY SELECTION hotkey.selection.add = Shift ; Add units to selection @@ -186,6 +187,12 @@ hotkey.menu.toggle = "F10" ; Toggle in-game menu ; > HOTKEYS ONLY hotkey.chat = Return ; Toggle chat window +; > GUI TEXTBOX HOTKEYS +hotkey.text.delete.word.left = "Ctrl+Backspace" ; Used in text input boxes to delete word to the left of cursor +hotkey.text.delete.word.right = "Ctrl+Del" ; Used in text input boxes to delete word to the right of cursor +hotkey.text.move.word.left = "Ctrl+LeftArrow" ; Move cursor to start of word to the left of cursor +hotkey.text.move.word.right = "Ctrl+RightArrow" ; Move cursor to start of word to the left of cursor + ; > PROFILER hotkey.profile.toggle = "F11" ; Enable/disable real-time profiler hotkey.profile.save = "Shift+F11" ; Save current profiler data to logs/profile.txt diff --git a/source/gui/CInput.cpp b/source/gui/CInput.cpp index 0aeaaa2b73..86505725a1 100644 --- a/source/gui/CInput.cpp +++ b/source/gui/CInput.cpp @@ -34,6 +34,7 @@ CInput #include "ps/CLogger.h" #include "ps/Globals.h" +#include //------------------------------------------------------------------- // Constructor / Destructor @@ -70,49 +71,30 @@ InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev) { ENSURE(m_iBufferPos != -1); - // Since the GUI framework doesn't handle to set settings - // in Unicode (CStrW), we'll simply retrieve the actual - // pointer and edit that. - CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting; - if (ev->ev.type == SDL_HOTKEYDOWN) { - std::string hotkey = static_cast(ev->ev.user.data1); - if (hotkey == "console.paste") - { - wchar_t* text = sys_clipboard_get(); - if (text) - { - if (m_iBufferPos == (int)pCaption->length()) - *pCaption += text; - else - *pCaption = pCaption->Left(m_iBufferPos) + text + - pCaption->Right((long) pCaption->length()-m_iBufferPos); - - UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1); - - m_iBufferPos += (int)wcslen(text); - - sys_clipboard_free(text); - } - - return IN_HANDLED; - } + return(ManuallyHandleHotkeyEvent(ev)); } else if (ev->ev.type == SDL_KEYDOWN) { + // Since the GUI framework doesn't handle to set settings + // in Unicode (CStrW), we'll simply retrieve the actual + // pointer and edit that. + CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting; + bool shiftKeyPressed = g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT]; + int szChar = ev->ev.key.keysym.sym; wchar_t cooked = (wchar_t)ev->ev.key.keysym.unicode; switch (szChar) { - case '\t': + case SDLK_TAB: // '\t' /* Auto Complete */ // TODO Gee: (2004-09-07) What to do with tab? break; - case '\b': - m_WantedX=0.f; + case SDLK_BACKSPACE: // '\b' + m_WantedX=0.0f; if (SelectingText()) DeleteCurSelection(); @@ -120,26 +102,29 @@ InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev) { m_iBufferPos_Tail = -1; - if (pCaption->empty() || - m_iBufferPos == 0) + if (pCaption->empty() || m_iBufferPos == 0) + { break; - - if (m_iBufferPos == (int)pCaption->length()) - *pCaption = pCaption->Left( (long) pCaption->length()-1); + } else - *pCaption = pCaption->Left( m_iBufferPos-1 ) + - pCaption->Right( (long) pCaption->length()-m_iBufferPos ); + { + if (m_iBufferPos == (int)pCaption->length()) + *pCaption = pCaption->Left( (long) pCaption->length()-1); + else + *pCaption = pCaption->Left( m_iBufferPos-1 ) + + pCaption->Right( (long) pCaption->length()-m_iBufferPos ); - --m_iBufferPos; + --m_iBufferPos; - UpdateText(m_iBufferPos, m_iBufferPos+1, m_iBufferPos); + UpdateText(m_iBufferPos, m_iBufferPos+1, m_iBufferPos); + } } UpdateAutoScroll(); break; case SDLK_DELETE: - m_WantedX=0.f; + m_WantedX=0.0f; // If selection: if (SelectingText()) { @@ -147,14 +132,17 @@ InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev) } else { - if (pCaption->empty() || - m_iBufferPos == (int)pCaption->length()) + if (pCaption->empty() || m_iBufferPos == (int)pCaption->length()) + { break; + } + else + { + *pCaption = pCaption->Left( m_iBufferPos ) + + pCaption->Right( (long) pCaption->length()-(m_iBufferPos+1) ); - *pCaption = pCaption->Left( m_iBufferPos ) + - pCaption->Right( (long) pCaption->length()-(m_iBufferPos+1) ); - - UpdateText(m_iBufferPos, m_iBufferPos+1, m_iBufferPos); + UpdateText(m_iBufferPos, m_iBufferPos+1, m_iBufferPos); + } } UpdateAutoScroll(); @@ -162,7 +150,7 @@ InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev) case SDLK_HOME: // If there's not a selection, we should create one now - if (!g_keys[SDLK_RSHIFT] && !g_keys[SDLK_LSHIFT]) + if (!shiftKeyPressed) { // Make sure a selection isn't created. m_iBufferPos_Tail = -1; @@ -174,14 +162,14 @@ InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev) } m_iBufferPos = 0; - m_WantedX=0.f; + m_WantedX=0.0f; UpdateAutoScroll(); break; case SDLK_END: // If there's not a selection, we should create one now - if (!g_keys[SDLK_RSHIFT] && !g_keys[SDLK_LSHIFT]) + if (!shiftKeyPressed) { // Make sure a selection isn't created. m_iBufferPos_Tail = -1; @@ -193,7 +181,7 @@ InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev) } m_iBufferPos = (long) pCaption->length(); - m_WantedX=0.f; + m_WantedX=0.0f; UpdateAutoScroll(); break; @@ -221,25 +209,20 @@ InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev) **/ case SDLK_LEFT: - // reset m_WantedX, very important m_WantedX=0.f; - if (g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT] || - !SelectingText()) + if (shiftKeyPressed || !SelectingText()) { - // If there's not a selection, we should create one now - if (!SelectingText() && !g_keys[SDLK_RSHIFT] && !g_keys[SDLK_LSHIFT]) + if (!shiftKeyPressed) { - // Make sure a selection isn't created. m_iBufferPos_Tail = -1; } else if (!SelectingText()) { - // Place tail at the current point: m_iBufferPos_Tail = m_iBufferPos; } - if (m_iBufferPos) + if (m_iBufferPos > 0) --m_iBufferPos; } else @@ -254,25 +237,20 @@ InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev) break; case SDLK_RIGHT: - m_WantedX=0.f; + m_WantedX=0.0f; - if (g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT] || - !SelectingText()) + if (shiftKeyPressed || !SelectingText()) { - // If there's not a selection, we should create one now - if (!SelectingText() && !g_keys[SDLK_RSHIFT] && !g_keys[SDLK_LSHIFT]) + if (!shiftKeyPressed) { - // Make sure a selection isn't created. m_iBufferPos_Tail = -1; } else if (!SelectingText()) { - // Place tail at the current point: m_iBufferPos_Tail = m_iBufferPos; } - - if (m_iBufferPos != (int)pCaption->length()) + if (m_iBufferPos < (int)pCaption->length()) ++m_iBufferPos; } else @@ -308,15 +286,12 @@ InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev) **/ case SDLK_UP: { - // If there's not a selection, we should create one now - if (!g_keys[SDLK_RSHIFT] && !g_keys[SDLK_LSHIFT]) + if (!shiftKeyPressed) { - // Make sure a selection isn't created. m_iBufferPos_Tail = -1; } else if (!SelectingText()) { - // Place tail at the current point: m_iBufferPos_Tail = m_iBufferPos; } @@ -356,15 +331,12 @@ InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev) case SDLK_DOWN: { - // If there's not a selection, we should create one now - if (!g_keys[SDLK_RSHIFT] && !g_keys[SDLK_LSHIFT]) + if (!shiftKeyPressed) { - // Make sure a selection isn't created. m_iBufferPos_Tail = -1; } else if (!SelectingText()) { - // Place tail at the current point: m_iBufferPos_Tail = m_iBufferPos; } @@ -428,7 +400,6 @@ InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev) } default: //Insert a character { - // If there's a selection, delete if first. if (cooked == 0) return IN_PASS; // Important, because we didn't use any key @@ -438,7 +409,7 @@ InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev) if (max_length != 0 && (int)pCaption->length() >= max_length) break; - m_WantedX=0.f; + m_WantedX=0.0f; if (SelectingText()) DeleteCurSelection(); @@ -465,6 +436,251 @@ InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev) return IN_PASS; } + +InReaction CInput::ManuallyHandleHotkeyEvent(const SDL_Event_* ev) +{ + CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting; + bool shiftKeyPressed = g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT]; + + std::string hotkey = static_cast(ev->ev.user.data1); + if (hotkey == "console.paste") + { + m_WantedX=0.0f; + + wchar_t* text = sys_clipboard_get(); + if (text) + { + if (m_iBufferPos == (int)pCaption->length()) + *pCaption += text; + else + *pCaption = pCaption->Left(m_iBufferPos) + text + + pCaption->Right((long) pCaption->length()-m_iBufferPos); + + UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1); + + m_iBufferPos += (int)wcslen(text); + + sys_clipboard_free(text); + } + + return IN_HANDLED; + } + else if (hotkey == "console.copy" || hotkey == "console.cut") + { + m_WantedX=0.0f; + + if (SelectingText()) + { + int virtualFrom; + int virtualTo; + + if (m_iBufferPos_Tail >= m_iBufferPos) + { + virtualFrom = m_iBufferPos; + virtualTo = m_iBufferPos_Tail; + } + else + { + virtualFrom = m_iBufferPos_Tail; + virtualTo = m_iBufferPos; + } + + CStrW text = (pCaption->Left(virtualTo)).Right(virtualTo - virtualFrom); + + sys_clipboard_set(&text[0]); + + if (hotkey == "console.cut") + { + DeleteCurSelection(); + } + } + + return IN_HANDLED; + } + else if (hotkey == "text.delete.word.left") + { + m_WantedX=0.0f; + + if (SelectingText()) + { + DeleteCurSelection(); + } + if (!pCaption->empty() && !m_iBufferPos == 0) + { + m_iBufferPos_Tail = m_iBufferPos; + CStrW searchString = pCaption->Left( m_iBufferPos ); + + // If we are starting in whitespace, adjust position until we get a non whitespace + while (m_iBufferPos > 0) + { + if (!iswspace(searchString[m_iBufferPos - 1])) + break; + + m_iBufferPos--; + } + + // If we end up on a puctuation char we just delete it (treat punct like a word) + if (iswpunct(searchString[m_iBufferPos - 1])) + m_iBufferPos--; + else + { + // Now we are on a non white space character, adjust position to char after next whitespace char is found + while (m_iBufferPos > 0) + { + if (iswspace(searchString[m_iBufferPos - 1]) || iswpunct(searchString[m_iBufferPos - 1])) + break; + + m_iBufferPos--; + } + } + + DeleteCurSelection(); + } + return IN_HANDLED; + } + else if (hotkey == "text.delete.word.right") + { + m_WantedX=0.0f; + + if (SelectingText()) + { + DeleteCurSelection(); + } + if (!pCaption->empty() && m_iBufferPos < (int)pCaption->length()) + { + // Delete the word to the right of the cursor + m_iBufferPos_Tail = m_iBufferPos; + + // Delete chars to the right unit we hit whitespace + while (++m_iBufferPos < (int)pCaption->length()) + { + if (iswspace((*pCaption)[m_iBufferPos]) || iswpunct((*pCaption)[m_iBufferPos])) + break; + } + + // Eliminate any whitespace behind the word we just deleted + while (m_iBufferPos < (int)pCaption->length()) + { + if (!iswspace((*pCaption)[m_iBufferPos])) + break; + + m_iBufferPos++; + } + DeleteCurSelection(); + } + return IN_HANDLED; + } + else if (hotkey == "text.move.word.left") + { + m_WantedX=0.0f; + + if (shiftKeyPressed || !SelectingText()) + { + if (!shiftKeyPressed) + { + m_iBufferPos_Tail = -1; + } + else if (!SelectingText()) + { + m_iBufferPos_Tail = m_iBufferPos; + } + + if (!pCaption->empty() && !m_iBufferPos == 0) + { + CStrW searchString = pCaption->Left( m_iBufferPos ); + + // If we are starting in whitespace, adjust position until we get a non whitespace + while (m_iBufferPos > 0) + { + if (!iswspace(searchString[m_iBufferPos - 1])) + break; + + m_iBufferPos--; + } + + // If we end up on a puctuation char we just select it (treat punct like a word) + if (iswpunct(searchString[m_iBufferPos - 1])) + m_iBufferPos--; + else + { + // Now we are on a non white space character, adjust position to char after next whitespace char is found + while (m_iBufferPos > 0) + { + if (iswspace(searchString[m_iBufferPos - 1]) || iswpunct(searchString[m_iBufferPos - 1])) + break; + + m_iBufferPos--; + } + } + } + } + else + { + if (m_iBufferPos_Tail < m_iBufferPos) + m_iBufferPos = m_iBufferPos_Tail; + + m_iBufferPos_Tail = -1; + } + + UpdateAutoScroll(); + + return IN_HANDLED; + } + else if (hotkey == "text.move.word.right") + { + m_WantedX=0.0f; + + if (shiftKeyPressed || !SelectingText()) + { + if (!shiftKeyPressed) + { + m_iBufferPos_Tail = -1; + } + else if (!SelectingText()) + { + m_iBufferPos_Tail = m_iBufferPos; + } + + if (!pCaption->empty() && m_iBufferPos < (int)pCaption->length()) + { + CStrW searchString = *pCaption; + + // Select chars to the right until we hit whitespace + while (++m_iBufferPos < (int)pCaption->length()) + { + if (iswspace((*pCaption)[m_iBufferPos]) || iswpunct((*pCaption)[m_iBufferPos])) + break; + } + + // Also select any whitespace following the word we just selected + while (m_iBufferPos < (int)pCaption->length()) + { + if (!iswspace((*pCaption)[m_iBufferPos])) + break; + + m_iBufferPos++; + } + } + } + else + { + if (m_iBufferPos_Tail > m_iBufferPos) + m_iBufferPos = m_iBufferPos_Tail; + + m_iBufferPos_Tail = -1; + } + + UpdateAutoScroll(); + + return IN_HANDLED; + } + else + { + return IN_PASS; + } +} + + void CInput::HandleMessage(SGUIMessage &Message) { // TODO Gee: @@ -480,7 +696,7 @@ void CInput::HandleMessage(SGUIMessage &Message) // Update scroll-bar // TODO Gee: (2004-09-01) Is this really updated each time it should? if (scrollbar && - (Message.value == CStr("size") || + (Message.value == CStr("size") || Message.value == CStr("z") || Message.value == CStr("absolute"))) { @@ -565,6 +781,101 @@ void CInput::HandleMessage(SGUIMessage &Message) }break; + case GUIM_MOUSE_DBLCLICK_LEFT: + { + CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting; + m_iBufferPos = m_iBufferPos_Tail = GetMouseHoveringTextPosition(); + + // See if we are clicking over whitespace + if (iswspace((*pCaption)[m_iBufferPos])) + { + // see if we are in a section of whitespace greater than one character + if ((m_iBufferPos + 1 < (int) pCaption->length() && iswspace((*pCaption)[m_iBufferPos + 1])) || + (m_iBufferPos - 1 > 0 && iswspace((*pCaption)[m_iBufferPos - 1]))) + { + // + // We are clicking in an area with more than one whitespace character + // so we select both the word to the left and then the word to the right + // + // [1] First the left + // skip the whitespace + while (m_iBufferPos > 0) + { + if (!iswspace((*pCaption)[m_iBufferPos - 1])) + break; + + m_iBufferPos--; + } + // now go until we hit white space or punctuation + while (m_iBufferPos > 0) + { + if (iswspace((*pCaption)[m_iBufferPos - 1])) + break; + + m_iBufferPos--; + + if (iswpunct((*pCaption)[m_iBufferPos])) + break; + } + + // [2] Then the right + // go right until we are not in whitespace + while (++m_iBufferPos_Tail < (int)pCaption->length()) + { + if (!iswspace((*pCaption)[m_iBufferPos_Tail])) + break; + } + // now go to the right until we hit whitespace or punctuation + while (++m_iBufferPos_Tail < (int)pCaption->length()) + { + if (iswspace((*pCaption)[m_iBufferPos_Tail]) || iswpunct((*pCaption)[m_iBufferPos_Tail])) + break; + } + } + else + { + // single whitespace so select word to the right + while (++m_iBufferPos_Tail < (int)pCaption->length()) + { + if (!iswspace((*pCaption)[m_iBufferPos_Tail])) + break; + } + + // Don't include the leading whitespace + m_iBufferPos = m_iBufferPos_Tail; + + // now go to the right until we hit whitespace or punctuation + while (++m_iBufferPos_Tail < (int)pCaption->length()) + { + if (iswspace((*pCaption)[m_iBufferPos_Tail]) || iswpunct((*pCaption)[m_iBufferPos_Tail])) + break; + } + } + } + else + { + // clicked on non-whitespace so select current word + // go until we hit white space or punctuation + while (m_iBufferPos > 0) + { + if (iswspace((*pCaption)[m_iBufferPos - 1])) + break; + + m_iBufferPos--; + + if (iswpunct((*pCaption)[m_iBufferPos])) + break; + } + // go to the right until we hit whitespace or punctuation + while (++m_iBufferPos_Tail < (int)pCaption->length()) + { + if (iswspace((*pCaption)[m_iBufferPos_Tail]) || iswpunct((*pCaption)[m_iBufferPos_Tail])) + break; + } + } + } + break; + case GUIM_MOUSE_RELEASE_LEFT: if (m_SelectingText) { @@ -921,7 +1232,7 @@ void CInput::Draw() } if (i < (int)it->m_ListOfX.size()) - x_pointer += (float)font.GetCharacterWidth((*pCaption)[it->m_ListStart + i]); + x_pointer += (float)font.GetCharacterWidth((*pCaption)[it->m_ListStart + i]); } if (done) @@ -953,7 +1264,7 @@ void CInput::Draw() { if (multiline) { - if (buffered_y + buffer_zone > m_CachedActualSize.GetHeight()) + if (buffered_y + buffer_zone > m_CachedActualSize.GetHeight()) break; } @@ -961,7 +1272,7 @@ void CInput::Draw() // Text must always be drawn in integer values. So we have to convert scroll if (multiline) - glTranslatef(0.f, -(float)(int)scroll, 0.f); + glTranslatef(0.f, -(float)(int)scroll, 0.f); else glTranslatef(-(float)(int)m_HorizontalScroll, 0.f, 0.f); @@ -1169,7 +1480,7 @@ void CInput::UpdateText(int from, int to_before, int to_after) if (destroy_row_to_used == false) { - destroy_row_to = m_CharacterPositions.end(); + destroy_row_to = m_CharacterPositions.end(); check_point_row_start = -1; destroy_row_from_used = true; @@ -1180,7 +1491,7 @@ void CInput::UpdateText(int from, int to_before, int to_after) from = destroy_row_from->m_ListStart; if (destroy_row_to != m_CharacterPositions.end()) - to = destroy_row_to->m_ListStart; // notice it will iterate [from, to), so it will never reach to. + to = destroy_row_to->m_ListStart; // notice it will iterate [from, to), so it will never reach to. else to = (int)caption.length(); @@ -1428,7 +1739,7 @@ int CInput::GetMouseHoveringTextPosition() return 0; // Return position - int RetPosition; + int retPosition; float buffer_zone; bool multiline; @@ -1493,20 +1804,19 @@ int CInput::GetMouseHoveringTextPosition() } //m_iBufferPos = m_CharacterPositions.get.m_ListStart; - RetPosition = current->m_ListStart; + retPosition = current->m_ListStart; // Okay, now loop through the glyphs to find the appropriate X position float dummy; - RetPosition += GetXTextPosition(current, mouse.x, dummy); + retPosition += GetXTextPosition(current, mouse.x, dummy); - return RetPosition; + return retPosition; } // Does not process horizontal scrolling, 'x' must be modified before inputted. int CInput::GetXTextPosition(const std::list::iterator ¤t, const float &x, float &wanted) { - int Ret=0; - + int ret=0; float previous=0.f; int i=0; @@ -1517,9 +1827,9 @@ int CInput::GetXTextPosition(const std::list::iterator ¤t, const flo if (*it >= x) { if (x - previous >= *it - x) - Ret += i+1; + ret += i+1; else - Ret += i; + ret += i; break; } @@ -1529,39 +1839,40 @@ int CInput::GetXTextPosition(const std::list::iterator ¤t, const flo // character of that line. if (i == (int)current->m_ListOfX.size()) { - Ret += i; + ret += i; wanted = x; } else wanted = 0.f; - return Ret; + return ret; } void CInput::DeleteCurSelection() { CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting; - int VirtualFrom, VirtualTo; + int virtualFrom; + int virtualTo; if (m_iBufferPos_Tail >= m_iBufferPos) { - VirtualFrom = m_iBufferPos; - VirtualTo = m_iBufferPos_Tail; + virtualFrom = m_iBufferPos; + virtualTo = m_iBufferPos_Tail; } else { - VirtualFrom = m_iBufferPos_Tail; - VirtualTo = m_iBufferPos; + virtualFrom = m_iBufferPos_Tail; + virtualTo = m_iBufferPos; } - *pCaption = pCaption->Left( VirtualFrom ) + - pCaption->Right( (long) pCaption->length()-(VirtualTo) ); + *pCaption = pCaption->Left( virtualFrom ) + + pCaption->Right( (long) pCaption->length() - (virtualTo) ); - UpdateText(VirtualFrom, VirtualTo, VirtualFrom); + UpdateText(virtualFrom, virtualTo, virtualFrom); // Remove selection m_iBufferPos_Tail = -1; - m_iBufferPos = VirtualFrom; + m_iBufferPos = virtualFrom; } bool CInput::SelectingText() const diff --git a/source/gui/CInput.h b/source/gui/CInput.h index eafcd7b73b..3ab96ef5aa 100644 --- a/source/gui/CInput.h +++ b/source/gui/CInput.h @@ -96,6 +96,11 @@ protected: */ virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev); + /** + * Handle hotkey events (called by ManuallyHandleEvent) + */ + virtual InReaction ManuallyHandleHotkeyEvent(const SDL_Event_* ev); + /** * @see IGUIObject#UpdateCachedSize() */ diff --git a/source/lib/sysdep/clipboard.h b/source/lib/sysdep/clipboard.h index 8a7485827a..5332b505fe 100644 --- a/source/lib/sysdep/clipboard.h +++ b/source/lib/sysdep/clipboard.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2010 Wildfire Games +/* Copyright (c) 2011 Wildfire Games * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -20,15 +20,19 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#ifndef INCLUDED_SYSDEP_CLIPBOARD +#define INCLUDED_SYSDEP_CLIPBOARD + // "copy" text into the clipboard. replaces previous contents. extern Status sys_clipboard_set(const wchar_t* text); -// allow "pasting" from clipboard. returns the current contents if they -// can be represented as text, otherwise 0. -// when it is no longer needed, the returned pointer must be freed via -// sys_clipboard_free. (NB: not necessary if zero, but doesn't hurt) +// allow "pasting" from clipboard. +// @return current clipboard text or 0 if not representable as text. +// callers are responsible for passing this pointer to sys_clipboard_free. extern wchar_t* sys_clipboard_get(); -// frees memory used by , which must have been returned by -// sys_clipboard_get. see note above. +// free memory returned by sys_clipboard_get. +// @param copy is ignored if 0. extern Status sys_clipboard_free(wchar_t* copy); + +#endif // #ifndef INCLUDED_SYSDEP_CLIPBOARD diff --git a/source/lib/sysdep/os/win/wclipboard.cpp b/source/lib/sysdep/os/win/wclipboard.cpp index 862c314ddd..e4c617d7cb 100644 --- a/source/lib/sysdep/os/win/wclipboard.cpp +++ b/source/lib/sysdep/os/win/wclipboard.cpp @@ -26,89 +26,98 @@ #include "lib/sysdep/os/win/win.h" #include "lib/sysdep/os/win/wutil.h" -// caller is responsible for freeing *hMem. -static Status SetClipboardText(const wchar_t* text, HGLOBAL* hMem) + +// caller is responsible for freeing hMem. +static Status SetClipboardText(const wchar_t* text, HGLOBAL& hMem) { const size_t numChars = wcslen(text); - *hMem = GlobalAlloc(GMEM_MOVEABLE, (numChars+1) * sizeof(wchar_t)); - if(!*hMem) + hMem = GlobalAlloc(GMEM_MOVEABLE|GMEM_ZEROINIT, (numChars + 1) * sizeof(wchar_t)); + if(!hMem) WARN_RETURN(ERR::NO_MEM); - wchar_t* lockedText = (wchar_t*)GlobalLock(*hMem); + wchar_t* lockedText = (wchar_t*)GlobalLock(hMem); if(!lockedText) WARN_RETURN(ERR::NO_MEM); wcscpy_s(lockedText, numChars+1, text); - GlobalUnlock(*hMem); + GlobalUnlock(hMem); - HANDLE hData = SetClipboardData(CF_UNICODETEXT, *hMem); + HANDLE hData = SetClipboardData(CF_UNICODETEXT, hMem); if(!hData) // failed WARN_RETURN(ERR::FAIL); return INFO::OK; } -// "copy" text into the clipboard. replaces previous contents. -Status sys_clipboard_set(const wchar_t* text) + +// @return INFO::OK iff text has been assigned a pointer (which the +// caller must free via sys_clipboard_free) to the clipboard text. +static Status GetClipboardText(wchar_t*& text) { - // note: MSDN claims that the window handle must not be 0; - // that does actually work on WinXP, but we'll play it safe. - if(!OpenClipboard(wutil_AppWindow())) - WARN_RETURN(ERR::FAIL); - EmptyClipboard(); + // NB: Windows NT/2000+ auto convert CF_UNICODETEXT <-> CF_TEXT. - HGLOBAL hMem; - Status ret = SetClipboardText(text, &hMem); + if(!IsClipboardFormatAvailable(CF_UNICODETEXT)) + return INFO::CANNOT_HANDLE; - CloseClipboard(); - - // note: MSDN's SetClipboardData documentation says hMem must not be - // freed until after CloseClipboard. however, GlobalFree still fails - // after the successful completion of both. we'll leave it in to avoid - // memory leaks, but ignore its return value. - (void)GlobalFree(hMem); - - return ret; -} - - -static wchar_t* CopyClipboardContents() -{ - // Windows NT/2000+ auto convert UNICODETEXT <-> TEXT HGLOBAL hMem = GetClipboardData(CF_UNICODETEXT); if(!hMem) - return 0; + WARN_RETURN(ERR::FAIL); const wchar_t* lockedText = (const wchar_t*)GlobalLock(hMem); if(!lockedText) - return 0; + WARN_RETURN(ERR::NO_MEM); - const size_t numChars = GlobalSize(hMem)/sizeof(wchar_t) - 1; - wchar_t* text = new wchar_t[numChars+1]; - wcscpy_s(text, numChars+1, lockedText); + const size_t size = GlobalSize(hMem); + text = (wchar_t*)malloc(size); + if(!text) + WARN_RETURN(ERR::NO_MEM); + wcscpy_s(text, size/sizeof(wchar_t), lockedText); - GlobalUnlock(hMem); + (void)GlobalUnlock(hMem); - return text; + return INFO::OK; } -// allow "pasting" from clipboard. returns the current contents if they -// can be represented as text, otherwise 0. -// when it is no longer needed, the returned pointer must be freed via -// sys_clipboard_free. (NB: not necessary if zero, but doesn't hurt) -wchar_t* sys_clipboard_get() + +// OpenClipboard parameter. +// NB: using wutil_AppWindow() causes GlobalLock to fail. +static const HWND hWndNewOwner = 0; // MSDN: associate with "current task" + +Status sys_clipboard_set(const wchar_t* text) { - if(!OpenClipboard(wutil_AppWindow())) - return 0; - wchar_t* const ret = CopyClipboardContents(); - CloseClipboard(); + if(!OpenClipboard(hWndNewOwner)) + WARN_RETURN(ERR::FAIL); + + WARN_IF_FALSE(EmptyClipboard()); + + // NB: to enable copy/pasting something other than text, add + // message handlers for WM_RENDERFORMAT and WM_RENDERALLFORMATS. + HGLOBAL hMem; + Status ret = SetClipboardText(text, hMem); + + WARN_IF_FALSE(CloseClipboard()); // must happen before GlobalFree + + ENSURE(GlobalFree(hMem) == 0); // (0 indicates success) + return ret; } -// frees memory used by , which must have been returned by -// sys_clipboard_get. see note above. +wchar_t* sys_clipboard_get() +{ + if(!OpenClipboard(hWndNewOwner)) + return 0; + + wchar_t* text; + Status ret = GetClipboardText(text); + + WARN_IF_FALSE(CloseClipboard()); + + return (ret == INFO::OK)? text : 0; +} + + Status sys_clipboard_free(wchar_t* text) { - delete[] text; + free(text); return INFO::OK; }