Index: binaries/data/mods/mod/gui/gui.rnc =================================================================== --- binaries/data/mods/mod/gui/gui.rnc +++ binaries/data/mods/mod/gui/gui.rnc @@ -68,6 +68,7 @@ attribute maxwidth { xsd:decimal }? & attribute multiline { bool }?& attribute offset { pos }?& + attribute readonly { bool }?& attribute scrollbar { bool }?& attribute scrollbar_style { text }?& attribute scroll_bottom { bool }?& Index: binaries/data/mods/mod/gui/gui.rng =================================================================== --- binaries/data/mods/mod/gui/gui.rng +++ binaries/data/mods/mod/gui/gui.rng @@ -277,6 +277,11 @@ + + + + + Index: binaries/data/mods/public/gui/replaymenu/replay_menu.js =================================================================== --- binaries/data/mods/public/gui/replaymenu/replay_menu.js +++ binaries/data/mods/public/gui/replaymenu/replay_menu.js @@ -271,7 +271,7 @@ Engine.GetGUIObjectByName("sgVictory").caption = translateVictoryCondition(replay.attribs.settings.GameType); Engine.GetGUIObjectByName("sgNbPlayers").caption = sprintf(translate("Players: %(numberOfPlayers)s"), { "numberOfPlayers": replay.attribs.settings.PlayerData.length }); - Engine.GetGUIObjectByName("replayFilename").caption = escapeText(Engine.GetReplayDirectoryName(replay.directory)); + Engine.GetGUIObjectByName("replayFilename").caption = Engine.GetReplayDirectoryName(replay.directory); let metadata = Engine.GetReplayMetadata(replay.directory); Engine.GetGUIObjectByName("sgPlayersNames").caption = Index: binaries/data/mods/public/gui/replaymenu/replay_menu.xml =================================================================== --- binaries/data/mods/public/gui/replaymenu/replay_menu.xml +++ binaries/data/mods/public/gui/replaymenu/replay_menu.xml @@ -106,7 +106,7 @@ - + Index: source/gui/CInput.h =================================================================== --- source/gui/CInput.h +++ source/gui/CInput.h @@ -65,6 +65,16 @@ virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev); /** + * Handle events manually to catch keys which change the text. + */ + virtual void ManuallyMutableHandleEvent(const int szChar); + + /** + * Handle events manually to catch keys which don't change the text. + */ + virtual void ManuallyImmutableHandleEvent(const int szChar); + + /** * Handle hotkey events (called by ManuallyHandleEvent) */ virtual InReaction ManuallyHandleHotkeyEvent(const SDL_Event_* ev); @@ -174,6 +184,9 @@ /// If the cursor should be drawn or not. bool m_CursorVisState; + + /// If true it allows to user only select the text by mouse and copy by hotkey. + bool m_Readonly; }; #endif // INCLUDED_CINPUT Index: source/gui/CInput.cpp =================================================================== --- source/gui/CInput.cpp +++ source/gui/CInput.cpp @@ -44,7 +44,7 @@ CInput::CInput() : m_iBufferPos(-1), m_iBufferPos_Tail(-1), m_SelectingText(false), m_HorizontalScroll(0.f), m_PrevTime(0.0), m_CursorVisState(true), m_CursorBlinkRate(0.5), m_ComposingText(false), - m_iComposedLength(0), m_iComposedPos(0), m_iInsertPos(0) + m_iComposedLength(0), m_iComposedPos(0), m_iInsertPos(0), m_Readonly(false) { AddSetting(GUIST_int, "buffer_position"); AddSetting(GUIST_float, "buffer_zone"); @@ -55,6 +55,7 @@ AddSetting(GUIST_bool, "mask"); AddSetting(GUIST_int, "max_length"); AddSetting(GUIST_bool, "multiline"); + AddSetting(GUIST_bool, "readonly"); AddSetting(GUIST_bool, "scrollbar"); AddSetting(GUIST_CStr, "scrollbar_style"); AddSetting(GUIST_CGUISpriteInstance, "sprite"); @@ -96,6 +97,10 @@ { ENSURE(m_iBufferPos != -1); + // Readonly mode allows to use mouse and keyboard to select and copy the text only + if (m_Readonly && ev->ev.type != SDL_HOTKEYDOWN && ev->ev.type != SDL_KEYDOWN) + return IN_PASS; + if (ev->ev.type == SDL_HOTKEYDOWN) { if (m_ComposingText) @@ -186,345 +191,369 @@ { if (m_ComposingText) return IN_HANDLED; - // 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 = 0; if (ev->ev.type == SDL_KEYDOWN) szChar = ev->ev.key.keysym.sym; - wchar_t cooked = 0; - switch (szChar) - { - case SDLK_TAB: // '\t' - /* Auto Complete */ - // We just send the tab event to JS and let it figure out autocomplete. - SendEvent(GUIM_TAB, "tab"); - break; + ManuallyImmutableHandleEvent(szChar); + if (!m_Readonly) + ManuallyMutableHandleEvent(szChar); - case SDLK_BACKSPACE: // '\b' - m_WantedX = 0.0f; + UpdateBufferPositionSetting(); + return IN_HANDLED; + } - if (SelectingText()) - DeleteCurSelection(); - else - { - m_iBufferPos_Tail = -1; + return IN_PASS; +} - 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); +void CInput::ManuallyMutableHandleEvent(const int szChar) +{ + // 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; + wchar_t cooked = 0; - --m_iBufferPos; + switch (szChar) + { + case SDLK_TAB: // '\t' + /* Auto Complete */ + // We just send the tab event to JS and let it figure out autocomplete. + SendEvent(GUIM_TAB, "tab"); + break; - UpdateText(m_iBufferPos, m_iBufferPos+1, m_iBufferPos); + case SDLK_BACKSPACE: // '\b' + m_WantedX = 0.0f; - } + if (SelectingText()) + DeleteCurSelection(); + else + { + m_iBufferPos_Tail = -1; - UpdateAutoScroll(); - break; + if (pCaption->empty() || m_iBufferPos == 0) + break; - case SDLK_DELETE: - m_WantedX = 0.0f; - // If selection: - if (SelectingText()) - DeleteCurSelection(); + if (m_iBufferPos == (int)pCaption->length()) + *pCaption = pCaption->Left((long)pCaption->length() - 1); else - { - if (pCaption->empty() || m_iBufferPos == (int)pCaption->length()) - break; + *pCaption = pCaption->Left(m_iBufferPos - 1) + + pCaption->Right((long)pCaption->length() - m_iBufferPos); - *pCaption = pCaption->Left(m_iBufferPos) + - pCaption->Right((long)pCaption->length()-(m_iBufferPos+1)); + --m_iBufferPos; - UpdateText(m_iBufferPos, m_iBufferPos+1, m_iBufferPos); - } + UpdateText(m_iBufferPos, m_iBufferPos + 1, m_iBufferPos); - UpdateAutoScroll(); - break; + } - case SDLK_HOME: - // If there's not a selection, we should create one now - 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; - } + UpdateAutoScroll(); + break; - m_iBufferPos = 0; - m_WantedX = 0.0f; + case SDLK_DELETE: + m_WantedX = 0.0f; + // If selection: + if (SelectingText()) + DeleteCurSelection(); + else + { + if (pCaption->empty() || m_iBufferPos == (int)pCaption->length()) + break; - UpdateAutoScroll(); - break; + *pCaption = pCaption->Left(m_iBufferPos) + + pCaption->Right((long)pCaption->length() - (m_iBufferPos + 1)); - case SDLK_END: - // If there's not a selection, we should create one now - 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; - } + UpdateText(m_iBufferPos, m_iBufferPos + 1, m_iBufferPos); + } - m_iBufferPos = (long)pCaption->length(); - m_WantedX = 0.0f; + UpdateAutoScroll(); + break; - UpdateAutoScroll(); + case SDLK_KP_ENTER: + case SDLK_RETURN: + // 'Return' should do a Press event for single liners (e.g. submitting forms) + // otherwise a '\n' character will be added. + { + bool multiline; + GUI::GetSetting(this, "multiline", multiline); + if (!multiline) + { + SendEvent(GUIM_PRESSED, "press"); break; + } - /** - * Conventions for Left/Right when text is selected: - * - * References: - * - * Visual Studio - * Visual Studio has the 'newer' approach, used by newer versions of - * things, and in newer applications. A left press will always place - * the pointer on the left edge of the selection, and then of course - * remove the selection. Right will do the exact same thing. - * If you have the pointer on the right edge and press right, it will - * in other words just remove the selection. - * - * Windows (eg. Notepad) - * A left press always takes the pointer a step to the left and - * removes the selection as if it were never there in the first place. - * Right of course does the same thing but to the right. - * - * I chose the Visual Studio convention. Used also in Word, gtk 2.0, MSN - * Messenger. - */ - case SDLK_LEFT: - m_WantedX = 0.f; - - if (shiftKeyPressed || !SelectingText()) - { - if (!shiftKeyPressed) - m_iBufferPos_Tail = -1; - else if (!SelectingText()) - m_iBufferPos_Tail = m_iBufferPos; + cooked = '\n'; // Change to '\n' and do default: + // NOTE: Fall-through + } + default: // Insert a character + { + // In SDL2, we no longer get Unicode wchars via SDL_Keysym + // we use text input events instead and they provide UTF-8 chars + if (cooked == 0) + return; + + // check max length + int max_length; + GUI::GetSetting(this, "max_length", max_length); + if (max_length != 0 && (int)pCaption->length() >= max_length) + break; - if (m_iBufferPos > 0) - --m_iBufferPos; - } - else - { - if (m_iBufferPos_Tail < m_iBufferPos) - m_iBufferPos = m_iBufferPos_Tail; + m_WantedX = 0.0f; - m_iBufferPos_Tail = -1; - } + if (SelectingText()) + DeleteCurSelection(); + m_iBufferPos_Tail = -1; - UpdateAutoScroll(); - break; + if (m_iBufferPos == (int)pCaption->length()) + *pCaption += cooked; + else + *pCaption = pCaption->Left(m_iBufferPos) + cooked + + pCaption->Right((long)pCaption->length() - m_iBufferPos); - case SDLK_RIGHT: - m_WantedX = 0.0f; + UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos + 1); - if (shiftKeyPressed || !SelectingText()) - { - if (!shiftKeyPressed) - m_iBufferPos_Tail = -1; - else if (!SelectingText()) - m_iBufferPos_Tail = m_iBufferPos; + ++m_iBufferPos; - if (m_iBufferPos < (int)pCaption->length()) - ++m_iBufferPos; - } - else - { - if (m_iBufferPos_Tail > m_iBufferPos) - m_iBufferPos = m_iBufferPos_Tail; + UpdateAutoScroll(); + break; + } + } +} - m_iBufferPos_Tail = -1; - } - UpdateAutoScroll(); - break; +void CInput::ManuallyImmutableHandleEvent(const int szChar) +{ + // 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]; - /** - * Conventions for Up/Down when text is selected: - * - * References: - * - * Visual Studio - * Visual Studio has a very strange approach, down takes you below the - * selection to the next row, and up to the one prior to the whole - * selection. The weird part is that it is always aligned as the - * 'pointer'. I decided this is to much work for something that is - * a bit arbitrary - * - * Windows (eg. Notepad) - * Just like with left/right, the selection is destroyed and it moves - * just as if there never were a selection. - * - * I chose the Notepad convention even though I use the VS convention with - * left/right. - */ - case SDLK_UP: + switch (szChar) + { + case SDLK_HOME: + // If there's not a selection, we should create one now + if (!shiftKeyPressed) { - if (!shiftKeyPressed) - m_iBufferPos_Tail = -1; - else if (!SelectingText()) - m_iBufferPos_Tail = m_iBufferPos; + // 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; + } - std::list::iterator current = m_CharacterPositions.begin(); - while (current != m_CharacterPositions.end()) - { - if (m_iBufferPos >= current->m_ListStart && - m_iBufferPos <= current->m_ListStart+(int)current->m_ListOfX.size()) - break; + m_iBufferPos = 0; + m_WantedX = 0.0f; - ++current; - } + UpdateAutoScroll(); + break; - float pos_x; + case SDLK_END: + // If there's not a selection, we should create one now + 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-current->m_ListStart == 0) - pos_x = 0.f; - else - pos_x = current->m_ListOfX[m_iBufferPos-current->m_ListStart-1]; + m_iBufferPos = (long)pCaption->length(); + m_WantedX = 0.0f; - if (m_WantedX > pos_x) - pos_x = m_WantedX; + UpdateAutoScroll(); + break; - // Now change row: - if (current != m_CharacterPositions.begin()) - { - --current; + /** + * Conventions for Left/Right when text is selected: + * + * References: + * + * Visual Studio + * Visual Studio has the 'newer' approach, used by newer versions of + * things, and in newer applications. A left press will always place + * the pointer on the left edge of the selection, and then of course + * remove the selection. Right will do the exact same thing. + * If you have the pointer on the right edge and press right, it will + * in other words just remove the selection. + * + * Windows (eg. Notepad) + * A left press always takes the pointer a step to the left and + * removes the selection as if it were never there in the first place. + * Right of course does the same thing but to the right. + * + * I chose the Visual Studio convention. Used also in Word, gtk 2.0, MSN + * Messenger. + */ + case SDLK_LEFT: + m_WantedX = 0.f; - // Find X-position: - m_iBufferPos = current->m_ListStart + GetXTextPosition(current, pos_x, m_WantedX); - } - // else we can't move up + if (shiftKeyPressed || !SelectingText()) + { + if (!shiftKeyPressed) + m_iBufferPos_Tail = -1; + else if (!SelectingText()) + m_iBufferPos_Tail = m_iBufferPos; - UpdateAutoScroll(); - break; + if (m_iBufferPos > 0) + --m_iBufferPos; + } + else + { + if (m_iBufferPos_Tail < m_iBufferPos) + m_iBufferPos = m_iBufferPos_Tail; + + m_iBufferPos_Tail = -1; } - case SDLK_DOWN: + UpdateAutoScroll(); + break; + + case SDLK_RIGHT: + m_WantedX = 0.0f; + + if (shiftKeyPressed || !SelectingText()) { if (!shiftKeyPressed) m_iBufferPos_Tail = -1; else if (!SelectingText()) m_iBufferPos_Tail = m_iBufferPos; - std::list::iterator current = m_CharacterPositions.begin(); - while (current != m_CharacterPositions.end()) - { - if (m_iBufferPos >= current->m_ListStart && - m_iBufferPos <= current->m_ListStart+(int)current->m_ListOfX.size()) - break; + if (m_iBufferPos < (int)pCaption->length()) + ++m_iBufferPos; + } + else + { + if (m_iBufferPos_Tail > m_iBufferPos) + m_iBufferPos = m_iBufferPos_Tail; - ++current; - } + m_iBufferPos_Tail = -1; + } - float pos_x; + UpdateAutoScroll(); + break; - if (m_iBufferPos-current->m_ListStart == 0) - pos_x = 0.f; - else - pos_x = current->m_ListOfX[m_iBufferPos-current->m_ListStart-1]; + /** + * Conventions for Up/Down when text is selected: + * + * References: + * + * Visual Studio + * Visual Studio has a very strange approach, down takes you below the + * selection to the next row, and up to the one prior to the whole + * selection. The weird part is that it is always aligned as the + * 'pointer'. I decided this is to much work for something that is + * a bit arbitrary + * + * Windows (eg. Notepad) + * Just like with left/right, the selection is destroyed and it moves + * just as if there never were a selection. + * + * I chose the Notepad convention even though I use the VS convention with + * left/right. + */ + case SDLK_UP: + { + if (!shiftKeyPressed) + m_iBufferPos_Tail = -1; + else if (!SelectingText()) + m_iBufferPos_Tail = m_iBufferPos; - if (m_WantedX > pos_x) - pos_x = m_WantedX; + std::list::iterator current = m_CharacterPositions.begin(); + while (current != m_CharacterPositions.end()) + { + if (m_iBufferPos >= current->m_ListStart && + m_iBufferPos <= current->m_ListStart + (int)current->m_ListOfX.size()) + break; - // Now change row: - // Add first, so we can check if it's .end() ++current; - if (current != m_CharacterPositions.end()) - { - // Find X-position: - m_iBufferPos = current->m_ListStart + GetXTextPosition(current, pos_x, m_WantedX); - } - // else we can't move up - - UpdateAutoScroll(); - break; } - case SDLK_PAGEUP: - GetScrollBar(0).ScrollMinusPlenty(); - UpdateAutoScroll(); - break; + float pos_x; - case SDLK_PAGEDOWN: - GetScrollBar(0).ScrollPlusPlenty(); - UpdateAutoScroll(); - break; - /* END: Message History Lookup */ + if (m_iBufferPos - current->m_ListStart == 0) + pos_x = 0.f; + else + pos_x = current->m_ListOfX[m_iBufferPos - current->m_ListStart - 1]; + + if (m_WantedX > pos_x) + pos_x = m_WantedX; - case SDLK_KP_ENTER: - case SDLK_RETURN: - // 'Return' should do a Press event for single liners (e.g. submitting forms) - // otherwise a '\n' character will be added. + // Now change row: + if (current != m_CharacterPositions.begin()) { - bool multiline; - GUI::GetSetting(this, "multiline", multiline); - if (!multiline) - { - SendEvent(GUIM_PRESSED, "press"); - break; - } + --current; - cooked = '\n'; // Change to '\n' and do default: - // NOTE: Fall-through + // Find X-position: + m_iBufferPos = current->m_ListStart + GetXTextPosition(current, pos_x, m_WantedX); } - default: // Insert a character - { - // In SDL2, we no longer get Unicode wchars via SDL_Keysym - // we use text input events instead and they provide UTF-8 chars - if (ev->ev.type == SDL_KEYDOWN && cooked == 0) - return IN_HANDLED; - - // check max length - int max_length; - GUI::GetSetting(this, "max_length", max_length); - if (max_length != 0 && (int)pCaption->length() >= max_length) - break; + // else we can't move up - m_WantedX = 0.0f; + UpdateAutoScroll(); + break; + } - if (SelectingText()) - DeleteCurSelection(); + case SDLK_DOWN: + { + if (!shiftKeyPressed) m_iBufferPos_Tail = -1; + else if (!SelectingText()) + m_iBufferPos_Tail = m_iBufferPos; - if (m_iBufferPos == (int)pCaption->length()) - *pCaption += cooked; - else - *pCaption = pCaption->Left(m_iBufferPos) + cooked + - pCaption->Right((long) pCaption->length()-m_iBufferPos); + std::list::iterator current = m_CharacterPositions.begin(); + while (current != m_CharacterPositions.end()) + { + if (m_iBufferPos >= current->m_ListStart && + m_iBufferPos <= current->m_ListStart + (int)current->m_ListOfX.size()) + break; - UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1); + ++current; + } - ++m_iBufferPos; + float pos_x; - UpdateAutoScroll(); - break; - } + if (m_iBufferPos - current->m_ListStart == 0) + pos_x = 0.f; + else + pos_x = current->m_ListOfX[m_iBufferPos - current->m_ListStart - 1]; + + if (m_WantedX > pos_x) + pos_x = m_WantedX; + + // Now change row: + // Add first, so we can check if it's .end() + ++current; + if (current != m_CharacterPositions.end()) + { + // Find X-position: + m_iBufferPos = current->m_ListStart + GetXTextPosition(current, pos_x, m_WantedX); } + // else we can't move up - UpdateBufferPositionSetting(); - return IN_HANDLED; + UpdateAutoScroll(); + break; } - return IN_PASS; + case SDLK_PAGEUP: + GetScrollBar(0).ScrollMinusPlenty(); + UpdateAutoScroll(); + break; + + case SDLK_PAGEDOWN: + GetScrollBar(0).ScrollPlusPlenty(); + UpdateAutoScroll(); + break; + /* END: Message History Lookup */ + + default: + break; + } } @@ -534,6 +563,9 @@ bool shiftKeyPressed = g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT]; std::string hotkey = static_cast(ev->ev.user.data1); + if (m_Readonly && hotkey != "copy" && hotkey != "text.move.left" && hotkey != "text.move.right") + return IN_PASS; + if (hotkey == "paste") { m_WantedX = 0.0f; @@ -847,6 +879,9 @@ UpdateText(); } UpdateAutoScroll(); + + if (Message.value == CStr("readonly")) + GUI::GetSetting(this, "readonly", m_Readonly); break; } @@ -1031,6 +1066,8 @@ UpdateText(); UpdateAutoScroll(); + + GUI::GetSetting(this, "readonly", m_Readonly); break; }