From 287a5fa0edf63b6efc53b77eeaa2ac15f116105c Mon Sep 17 00:00:00 2001 From: Ihar Hubchyk Date: Sat, 2 Dec 2023 22:45:20 +0800 Subject: [PATCH 1/7] Add initial support for UTF-8 text input relates to #7933 and #6845 --- VisualStudio/fheroes2/sources.props | 2 + src/engine/char_encoding.cpp | 133 +++++++++++++++++++ src/engine/char_encoding.h | 43 ++++++ src/engine/localevent.cpp | 147 +++++++++++++-------- src/engine/localevent.h | 36 ++++- src/fheroes2/dialog/dialog_selectcount.cpp | 8 +- src/fheroes2/dialog/dialog_selectfile.cpp | 8 +- src/fheroes2/gui/ui_font.cpp | 79 ++++------- src/fheroes2/gui/ui_language.cpp | 40 ++++++ src/fheroes2/gui/ui_language.h | 4 + 10 files changed, 385 insertions(+), 115 deletions(-) create mode 100644 src/engine/char_encoding.cpp create mode 100644 src/engine/char_encoding.h diff --git a/VisualStudio/fheroes2/sources.props b/VisualStudio/fheroes2/sources.props index c55a0306149..6eb5e2694ad 100644 --- a/VisualStudio/fheroes2/sources.props +++ b/VisualStudio/fheroes2/sources.props @@ -3,6 +3,7 @@ + @@ -238,6 +239,7 @@ + diff --git a/src/engine/char_encoding.cpp b/src/engine/char_encoding.cpp new file mode 100644 index 00000000000..f727c7e2604 --- /dev/null +++ b/src/engine/char_encoding.cpp @@ -0,0 +1,133 @@ +/*************************************************************************** + * fheroes2: https://github.com/ihhub/fheroes2 * + * Copyright (C) 2023 * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "char_encoding.h" + +#include +#include + +namespace +{ + // All code page related array indexes are equal to index + 128 value in their code pages. + //For example, a first element in an array is actually value 128 in their code page. + const std::array cp1251CodePoints{ + 0x0402, 0x0403, 0x201A, 0x0453, 0x201E, 0x2026, 0x2020, 0x2021, 0x20AC, 0x2030, 0x0409, 0x2039, 0x040A, 0x040C, 0x040B, 0x040F, 0x0452, 0x2018, 0x2019, 0x201C, + 0x201D, 0x2022, 0x2013, 0x2014, 0x0000, 0x2122, 0x0459, 0x203A, 0x045A, 0x045C, 0x045B, 0x045F, 0x00A0, 0x040E, 0x045E, 0x0408, 0x00A4, 0x0490, 0x00A6, 0x00A7, + 0x0401, 0x00A9, 0x0404, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0407, 0x00B0, 0x00B1, 0x0406, 0x0456, 0x0491, 0x00B5, 0x00B6, 0x00B7, 0x0451, 0x2116, 0x0454, 0x00BB, + 0x0458, 0x0405, 0x0455, 0x0457, 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F, 0x0430, 0x0431, 0x0432, 0x0433, + 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F, 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F }; + + uint8_t codePointToCP1251( const uint32_t value ) + { + if ( value < 0x80 ) { + // This is an ASCII character that is a part of this code page. + return static_cast( value ); + } + + // TODO: optimize the code to avoid looping. + for ( size_t i = 0; i < cp1251CodePoints.size(); ++i ) { + if ( cp1251CodePoints[i] == value ) { + return static_cast( i + 128 ); + } + } + + // This is an invalid character. + return 0; + } +} + +namespace Encoding +{ + bool utf8ToCodePoint( const uint8_t * data, size_t length, uint32_t & codePoint ) + { + if ( data == nullptr || length == 0 ) { + // Why are you trying to decode empty data? + assert( 0 ); + return false; + } + + if ( length > 4 ) { + // Length cannot be longer than 4. + length = 4; + } + + if ( data[0] < 0x80 ) { + // This is an ASCII character. No need further processing. + codePoint = data[0]; + return true; + } + + if ( length < 2 || ( data[1] >> 6 ) != 2 ) { + // This is an invalid character. + return false; + } + + if ( ( data[0] >> 5 ) == 6 ) { + // This is a 2 byte character. + codePoint = ( data[1] & 0x3F ) | ( data[0] & 0x1F ) << 6; + return true; + } + + if ( length < 3 || ( data[2] >> 6 ) != 2 ) { + // This is an invalid character. + return false; + } + + if ( ( data[0] >> 4 ) == 14 ) { + // This is a 3 byte character. + codePoint = ( data[2] & 0x3F ) | ( ( data[1] & 0x3F ) << 6 ) | ( data[0] & 0x0F ) << 12; + return true; + } + + if ( length != 4 || ( data[3] >> 6 ) != 2 ) { + // This is an invalid character. + return false; + } + + if ( ( data[0] >> 3 ) != 30 ) { + // This is an invalid character. + return false; + } + + // This is a 4 byte character. + codePoint = ( data[3] & 0x3F ) | ( ( data[2] & 0x3F ) << 6 ) | ( ( data[1] & 0x3F ) << 12 ) | ( data[0] & 0x07 ) << 18; + return true; + } + + uint8_t getCodePageCharacter( const uint32_t value, const CodePage codePage ) + { + if ( codePage == CodePage::CP1251 ) { + return codePointToCP1251( value ); + } + + if ( value < 0x80 ) { + return static_cast( value ); + } + + return 0; + } + + bool isASCIICharacter( const uint32_t value ) + { + return ( value < 0x80 ); + } +} diff --git a/src/engine/char_encoding.h b/src/engine/char_encoding.h new file mode 100644 index 00000000000..f86e273f2d5 --- /dev/null +++ b/src/engine/char_encoding.h @@ -0,0 +1,43 @@ +/*************************************************************************** + * fheroes2: https://github.com/ihhub/fheroes2 * + * Copyright (C) 2023 * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#pragma once + +#include + +namespace Encoding +{ + enum class CodePage : uint8_t + { + ASCII, + CP1250, + CP1251, + CP1252, + CP1254, + CP1258, + ISO8859_16 + }; + + bool utf8ToCodePoint( const uint8_t * data, size_t length, uint32_t & codePoint ); + + uint8_t getCodePageCharacter( const uint32_t value, const CodePage codePage ); + + bool isASCIICharacter( const uint32_t value ); +} diff --git a/src/engine/localevent.cpp b/src/engine/localevent.cpp index 4d362dbc87f..28af4322fd4 100644 --- a/src/engine/localevent.cpp +++ b/src/engine/localevent.cpp @@ -43,6 +43,7 @@ #include #include "audio.h" +#include "char_encoding.h" #include "image.h" #include "render_processor.h" #include "screen.h" @@ -340,8 +341,12 @@ namespace return modifier; } - char getCharacterFromPressedKey( const fheroes2::Key key, const int32_t mod ) + char getCharacterFromPressedKey( const fheroes2::Key key, int32_t mod ) { + if ( ( mod & fheroes2::KeyModifier::KEY_MODIFIER_SHIFT ) && ( mod & fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) ) { + mod = mod & ~( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ); + } + switch ( key ) { case fheroes2::Key::KEY_1: return ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT & mod ? '!' : '1' ); @@ -609,53 +614,6 @@ namespace fheroes2 return false; } - - size_t InsertKeySym( std::string & res, size_t pos, const Key key, const int32_t mod ) - { - switch ( key ) { - case fheroes2::Key::KEY_BACKSPACE: - if ( !res.empty() && pos ) { - if ( pos >= res.size() ) - res.resize( res.size() - 1 ); - else - res.erase( pos - 1, 1 ); - --pos; - } - break; - case fheroes2::Key::KEY_DELETE: - if ( !res.empty() ) { - if ( pos < res.size() ) - res.erase( pos, 1 ); - } - break; - - case fheroes2::Key::KEY_LEFT: - if ( pos ) - --pos; - break; - case fheroes2::Key::KEY_RIGHT: - if ( pos < res.size() ) - ++pos; - break; - case fheroes2::Key::KEY_HOME: - pos = 0; - break; - case fheroes2::Key::KEY_END: - pos = res.size(); - break; - - default: { - char c = getCharacterFromPressedKey( key, mod ); - - if ( c ) { - res.insert( pos, 1, c ); - ++pos; - } - } - } - - return pos; - } } LocalEvent::LocalEvent() @@ -719,6 +677,16 @@ LocalEvent & LocalEvent::GetClean() return le; } +void LocalEvent::enableTextInput() +{ + SDL_StartTextInput(); +} + +void LocalEvent::disableTextInput() +{ + SDL_StopTextInput(); +} + bool LocalEvent::HandleEvents( const bool sleepAfterEventProcessing, const bool allowExit /* = false */ ) { // Event processing might be computationally heavy. @@ -768,6 +736,7 @@ bool LocalEvent::HandleEvents( const bool sleepAfterEventProcessing, const bool break; case SDL_KEYDOWN: case SDL_KEYUP: + _lastPressedCodePoint = 0; HandleKeyboardEvent( event.key ); break; case SDL_MOUSEMOTION: @@ -829,9 +798,13 @@ bool LocalEvent::HandleEvents( const bool sleepAfterEventProcessing, const bool HandleRenderDeviceResetEvent(); renderRoi = { 0, 0, display.width(), display.height() }; break; - case SDL_TEXTINPUT: - // Keyboard events on Android should be processed here. Use event.text.text to extract text input. + case SDL_TEXTINPUT: { + _lastPressedCodePoint = event.key.keysym.sym; + if ( !Encoding::utf8ToCodePoint( reinterpret_cast( event.text.text ), 32, _lastPressedCodePoint ) ) { + _lastPressedCodePoint = 0; + } break; + } case SDL_TEXTEDITING: // An event when a user pressed a button on a keyboard. Not all buttons are supported. This event should be used mainly on Android devices. break; @@ -1255,25 +1228,25 @@ bool LocalEvent::MousePressRight() const void LocalEvent::HandleKeyboardEvent( const SDL_KeyboardEvent & event ) { - const fheroes2::Key key = getKeyFromSDL( event.keysym.sym ); - if ( key == fheroes2::Key::NONE ) { - return; - } - if ( event.type == SDL_KEYDOWN ) { SetModes( KEY_PRESSED ); SetModes( KEY_HOLD ); - if ( _globalKeyDownEventHook ) { + const fheroes2::Key key = getKeyFromSDL( event.keysym.sym ); + if ( key != fheroes2::Key::NONE && _globalKeyDownEventHook ) { + key_value = key; _globalKeyDownEventHook( key, getKeyModifierFromSDL( event.keysym.mod ) ); } + else { + key_value = fheroes2::Key::NONE; + } } else if ( event.type == SDL_KEYUP ) { ResetModes( KEY_PRESSED ); ResetModes( KEY_HOLD ); - } - key_value = key; + key_value = fheroes2::Key::NONE; + } } void LocalEvent::HandleMouseMotionEvent( const SDL_MouseMotionEvent & motion ) @@ -1576,3 +1549,61 @@ void LocalEvent::setEventProcessingStates() // We do not support custom user events as of now. setEventProcessingState( SDL_USEREVENT, false ); } + +size_t LocalEvent::insertLastPressedSymbol( std::string & res, size_t pos, const Encoding::CodePage codePage ) +{ + switch ( key_value ) { + case fheroes2::Key::KEY_BACKSPACE: + if ( !res.empty() && pos > 0 ) { + if ( pos >= res.size() ) { + res.resize( res.size() - 1 ); + } + else { + res.erase( pos - 1, 1 ); + } + + --pos; + } + return pos; + case fheroes2::Key::KEY_DELETE: + if ( !res.empty() ) { + if ( pos < res.size() ) { + res.erase( pos, 1 ); + } + } + return pos; + case fheroes2::Key::KEY_LEFT: + if ( pos > 0 ) { + --pos; + } + return pos; + case fheroes2::Key::KEY_RIGHT: + if ( pos < res.size() ) { + ++pos; + } + return pos; + case fheroes2::Key::KEY_HOME: + return 0; + case fheroes2::Key::KEY_END: + return res.size(); + default: + break; + } + + if ( Encoding::isASCIICharacter( _lastPressedCodePoint ) ) { + const char character = getCharacterFromPressedKey( key_value, LocalEvent::getCurrentKeyModifiers() ); + if ( character != 0 ) { + res.insert( pos, 1, character ); + ++pos; + } + } + else { + const uint8_t character = Encoding::getCodePageCharacter( _lastPressedCodePoint, codePage ); + if ( character != 0 ) { + res.insert( pos, 1, character ); + ++pos; + } + } + + return pos; +} diff --git a/src/engine/localevent.h b/src/engine/localevent.h index 0512450af68..fd4a3888b43 100644 --- a/src/engine/localevent.h +++ b/src/engine/localevent.h @@ -38,6 +38,11 @@ #include "math_base.h" #include "timing.h" +namespace Encoding +{ + enum class CodePage : uint8_t; +} + namespace fheroes2 { enum class Key : int32_t @@ -174,8 +179,6 @@ namespace fheroes2 const char * KeySymGetName( const Key key ); bool PressIntKey( uint32_t max, uint32_t & result ); - - size_t InsertKeySym( std::string & res, size_t pos, const Key key, const int32_t mod ); } class LocalEvent @@ -198,6 +201,10 @@ class LocalEvent bool HandleEvents( const bool sleepAfterEventProcessing = true, const bool allowExit = false ); + void enableTextInput(); + + void disableTextInput(); + bool MouseMotion() const { return ( modes & MOUSE_MOTION ) == MOUSE_MOTION; @@ -306,6 +313,8 @@ class LocalEvent } } + size_t insertLastPressedSymbol( std::string & res, size_t pos, const Encoding::CodePage codePage ); + private: LocalEvent(); @@ -432,6 +441,29 @@ class LocalEvent std::pair>, std::optional>> _fingerIds; // Is the two-finger gesture currently being processed bool _isTwoFingerGestureInProgress = false; + + uint32_t _lastPressedCodePoint{ 0 }; +}; + +class TextInputEnabler +{ +public: + TextInputEnabler( LocalEvent & eventHandler ) + : _handler( eventHandler ) + { + _handler.enableTextInput(); + } + + TextInputEnabler( const TextInputEnabler & ) = delete; + TextInputEnabler & operator=( const TextInputEnabler & ) = delete; + + ~TextInputEnabler() + { + _handler.disableTextInput(); + } + +private: + LocalEvent & _handler; }; #endif diff --git a/src/fheroes2/dialog/dialog_selectcount.cpp b/src/fheroes2/dialog/dialog_selectcount.cpp index f42791c201e..5bf96e9de95 100644 --- a/src/fheroes2/dialog/dialog_selectcount.cpp +++ b/src/fheroes2/dialog/dialog_selectcount.cpp @@ -31,6 +31,7 @@ #include #include "agg_image.h" +#include "char_encoding.h" #include "cursor.h" #include "dialog.h" #include "game_delays.h" @@ -47,6 +48,7 @@ #include "ui_button.h" #include "ui_dialog.h" #include "ui_keyboard.h" +#include "ui_language.h" #include "ui_text.h" #include "ui_tool.h" @@ -314,6 +316,10 @@ bool Dialog::InputString( const std::string & header, std::string & res, const s const bool isInGameKeyboardRequired = System::isVirtualKeyboardSupported(); + const TextInputEnabler textInputEnabler( le ); + + const Encoding::CodePage codePage = fheroes2::getCodePage( fheroes2::getCurrentLanguage() ); + while ( le.HandleEvents( Game::isDelayNeeded( { Game::DelayType::CURSOR_BLINK_DELAY } ) ) ) { bool redraw = false; @@ -340,7 +346,7 @@ bool Dialog::InputString( const std::string & header, std::string & res, const s } else if ( le.KeyPress() ) { if ( charLimit == 0 || charLimit > res.size() || le.KeyValue() == fheroes2::Key::KEY_BACKSPACE ) { - charInsertPos = InsertKeySym( res, charInsertPos, le.KeyValue(), LocalEvent::getCurrentKeyModifiers() ); + charInsertPos = le.insertLastPressedSymbol( res, charInsertPos, codePage ); redraw = true; } } diff --git a/src/fheroes2/dialog/dialog_selectfile.cpp b/src/fheroes2/dialog/dialog_selectfile.cpp index 376fd89a0e0..d63a53e9149 100644 --- a/src/fheroes2/dialog/dialog_selectfile.cpp +++ b/src/fheroes2/dialog/dialog_selectfile.cpp @@ -34,6 +34,7 @@ #include #include "agg_image.h" +#include "char_encoding.h" #include "cursor.h" #include "dialog.h" #include "dir.h" @@ -54,6 +55,7 @@ #include "ui_button.h" #include "ui_dialog.h" #include "ui_keyboard.h" +#include "ui_language.h" #include "ui_scrollbar.h" #include "ui_text.h" #include "ui_tool.h" @@ -408,8 +410,12 @@ namespace bool isCursorVisible = true; + const Encoding::CodePage codePage = fheroes2::getCodePage( fheroes2::getCurrentLanguage() ); + LocalEvent & le = LocalEvent::Get(); + const TextInputEnabler textInputEnabler( le ); + while ( le.HandleEvents( !isEditing || Game::isDelayNeeded( { Game::DelayType::CURSOR_BLINK_DELAY } ) ) && result.empty() ) { le.MousePressLeft( buttonOk.area() ) && buttonOk.isEnabled() ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); le.MousePressLeft( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); @@ -464,7 +470,7 @@ namespace } else if ( !listboxEvent && le.KeyPress() && ( !isTextLimit || fheroes2::Key::KEY_BACKSPACE == le.KeyValue() || fheroes2::Key::KEY_DELETE == le.KeyValue() ) ) { - charInsertPos = InsertKeySym( filename, charInsertPos, le.KeyValue(), LocalEvent::getCurrentKeyModifiers() ); + charInsertPos = le.insertLastPressedSymbol( filename, charInsertPos, codePage ); if ( filename.empty() ) { buttonOk.disable(); } diff --git a/src/fheroes2/gui/ui_font.cpp b/src/fheroes2/gui/ui_font.cpp index 6f7393c6678..df903e2ea67 100644 --- a/src/fheroes2/gui/ui_font.cpp +++ b/src/fheroes2/gui/ui_font.cpp @@ -5674,49 +5674,38 @@ namespace fheroes2 { void generateAlphabet( const SupportedLanguage language, std::vector> & icnVsSprite ) { - switch ( language ) { - case SupportedLanguage::Czech: - case SupportedLanguage::Hungarian: - case SupportedLanguage::Polish: - case SupportedLanguage::Slovak: + const Encoding::CodePage codePage = getCodePage( language ); + + switch ( codePage ) { + case Encoding::CodePage::CP1250: generateCP1250Alphabet( icnVsSprite ); break; - case SupportedLanguage::Belarusian: - case SupportedLanguage::Bulgarian: - case SupportedLanguage::Russian: - case SupportedLanguage::Ukrainian: + case Encoding::CodePage::CP1251: generateCP1251Alphabet( icnVsSprite ); break; - case SupportedLanguage::Danish: - case SupportedLanguage::Dutch: - case SupportedLanguage::German: - case SupportedLanguage::Italian: - case SupportedLanguage::Norwegian: - case SupportedLanguage::Portuguese: - case SupportedLanguage::Spanish: - case SupportedLanguage::Swedish: - generateCP1252Alphabet( icnVsSprite ); - break; - case SupportedLanguage::French: + case Encoding::CodePage::CP1252: generateCP1252Alphabet( icnVsSprite ); - // This serves to make the font compatible with the original French custom encoding. - generateFrenchAlphabet( icnVsSprite ); break; - case SupportedLanguage::Turkish: + case Encoding::CodePage::CP1254: generateCP1254Alphabet( icnVsSprite ); break; - case SupportedLanguage::Vietnamese: + case Encoding::CodePage::CP1258: generateCP1258Alphabet( icnVsSprite ); break; - case SupportedLanguage::Romanian: + case Encoding::CodePage::ISO8859_16: generateISO8859_16Alphabet( icnVsSprite ); break; default: - // Add new language generation code! + // Add new a code font generation! assert( 0 ); break; } + if ( language == SupportedLanguage::French ) { + // This serves to make the font compatible with the original French custom encoding. + generateFrenchAlphabet( icnVsSprite ); + } + icnVsSprite[ICN::YELLOW_FONT].clear(); icnVsSprite[ICN::YELLOW_SMALLFONT].clear(); icnVsSprite[ICN::GRAY_FONT].clear(); @@ -5767,42 +5756,26 @@ namespace fheroes2 { generateGoodButtonFontBaseShape( icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED] ); - switch ( language ) { - case SupportedLanguage::English: + const Encoding::CodePage codePage = getCodePage( language ); + + switch ( codePage ) { + case Encoding::CodePage::ASCII: generateBaseButtonFont( icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED], icnVsSprite[ICN::BUTTON_GOOD_FONT_PRESSED], icnVsSprite[ICN::BUTTON_EVIL_FONT_RELEASED], icnVsSprite[ICN::BUTTON_EVIL_FONT_PRESSED] ); return; - case SupportedLanguage::Czech: - case SupportedLanguage::Hungarian: - case SupportedLanguage::Polish: - case SupportedLanguage::Slovak: + case Encoding::CodePage::CP1250: generateCP1250GoodButtonFont( icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED] ); break; - case SupportedLanguage::Belarusian: - case SupportedLanguage::Bulgarian: - case SupportedLanguage::Russian: - case SupportedLanguage::Ukrainian: + case Encoding::CodePage::CP1251: generateCP1251GoodButtonFont( icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED] ); break; - case SupportedLanguage::Danish: - case SupportedLanguage::Dutch: - case SupportedLanguage::French: - case SupportedLanguage::German: - case SupportedLanguage::Italian: - case SupportedLanguage::Norwegian: - case SupportedLanguage::Portuguese: - case SupportedLanguage::Spanish: - case SupportedLanguage::Swedish: + case Encoding::CodePage::CP1252: generateCP1252GoodButtonFont( icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED] ); break; - case SupportedLanguage::Turkish: - // generateGoodCP1254ButtonFont( icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED] ); - break; - case SupportedLanguage::Vietnamese: - // generateGoodCP1258ButtonFont( icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED] ); - break; - case SupportedLanguage::Romanian: - // generateGoodISO8859_16ButtonFont( icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED] ); + case Encoding::CodePage::CP1254: + case Encoding::CodePage::CP1258: + case Encoding::CodePage::ISO8859_16: + // TODO: no buttons are generated yet for these code pages. break; default: // Add new language generation code! diff --git a/src/fheroes2/gui/ui_language.cpp b/src/fheroes2/gui/ui_language.cpp index 6d6f279e6a5..f1286730545 100644 --- a/src/fheroes2/gui/ui_language.cpp +++ b/src/fheroes2/gui/ui_language.cpp @@ -71,6 +71,46 @@ namespace namespace fheroes2 { + Encoding::CodePage getCodePage( const SupportedLanguage language ) + { + switch ( language ) { + case SupportedLanguage::English: + return Encoding::CodePage::ASCII; + case SupportedLanguage::Czech: + case SupportedLanguage::Hungarian: + case SupportedLanguage::Polish: + case SupportedLanguage::Slovak: + return Encoding::CodePage::CP1250; + case SupportedLanguage::Belarusian: + case SupportedLanguage::Bulgarian: + case SupportedLanguage::Russian: + case SupportedLanguage::Ukrainian: + return Encoding::CodePage::CP1251; + case SupportedLanguage::Danish: + case SupportedLanguage::Dutch: + case SupportedLanguage::French: + case SupportedLanguage::German: + case SupportedLanguage::Italian: + case SupportedLanguage::Norwegian: + case SupportedLanguage::Portuguese: + case SupportedLanguage::Spanish: + case SupportedLanguage::Swedish: + return Encoding::CodePage::CP1252; + case SupportedLanguage::Turkish: + return Encoding::CodePage::CP1254; + case SupportedLanguage::Vietnamese: + return Encoding::CodePage::CP1258; + case SupportedLanguage::Romanian: + return Encoding::CodePage::ISO8859_16; + default: + // Add new language generation code! + assert( 0 ); + break; + } + + return Encoding::CodePage::ASCII; + } + LanguageSwitcher::LanguageSwitcher( const SupportedLanguage language ) : _currentLanguage( Settings::Get().getGameLanguage() ) { diff --git a/src/fheroes2/gui/ui_language.h b/src/fheroes2/gui/ui_language.h index 347523404f6..b75698455a6 100644 --- a/src/fheroes2/gui/ui_language.h +++ b/src/fheroes2/gui/ui_language.h @@ -23,6 +23,8 @@ #include #include +#include "char_encoding.h" + namespace fheroes2 { enum class SupportedLanguage : int @@ -52,6 +54,8 @@ namespace fheroes2 Vietnamese }; + Encoding::CodePage getCodePage( const SupportedLanguage language ); + class LanguageSwitcher { public: From cbdeaebf18a8cb44f4e6e9be43f4811ed5b3b426 Mon Sep 17 00:00:00 2001 From: Ihar Hubchyk Date: Sat, 2 Dec 2023 22:47:12 +0800 Subject: [PATCH 2/7] Fix code styling --- src/engine/char_encoding.cpp | 18 ++++++++++-------- src/engine/localevent.cpp | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/engine/char_encoding.cpp b/src/engine/char_encoding.cpp index f727c7e2604..97e2196e0b6 100644 --- a/src/engine/char_encoding.cpp +++ b/src/engine/char_encoding.cpp @@ -27,14 +27,16 @@ namespace { // All code page related array indexes are equal to index + 128 value in their code pages. //For example, a first element in an array is actually value 128 in their code page. - const std::array cp1251CodePoints{ - 0x0402, 0x0403, 0x201A, 0x0453, 0x201E, 0x2026, 0x2020, 0x2021, 0x20AC, 0x2030, 0x0409, 0x2039, 0x040A, 0x040C, 0x040B, 0x040F, 0x0452, 0x2018, 0x2019, 0x201C, - 0x201D, 0x2022, 0x2013, 0x2014, 0x0000, 0x2122, 0x0459, 0x203A, 0x045A, 0x045C, 0x045B, 0x045F, 0x00A0, 0x040E, 0x045E, 0x0408, 0x00A4, 0x0490, 0x00A6, 0x00A7, - 0x0401, 0x00A9, 0x0404, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0407, 0x00B0, 0x00B1, 0x0406, 0x0456, 0x0491, 0x00B5, 0x00B6, 0x00B7, 0x0451, 0x2116, 0x0454, 0x00BB, - 0x0458, 0x0405, 0x0455, 0x0457, 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F, - 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F, 0x0430, 0x0431, 0x0432, 0x0433, - 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F, 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, - 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F }; + const std::array cp1251CodePoints{ 0x0402, 0x0403, 0x201A, 0x0453, 0x201E, 0x2026, 0x2020, 0x2021, 0x20AC, 0x2030, 0x0409, 0x2039, 0x040A, + 0x040C, 0x040B, 0x040F, 0x0452, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x0000, 0x2122, + 0x0459, 0x203A, 0x045A, 0x045C, 0x045B, 0x045F, 0x00A0, 0x040E, 0x045E, 0x0408, 0x00A4, 0x0490, 0x00A6, + 0x00A7, 0x0401, 0x00A9, 0x0404, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0407, 0x00B0, 0x00B1, 0x0406, 0x0456, + 0x0491, 0x00B5, 0x00B6, 0x00B7, 0x0451, 0x2116, 0x0454, 0x00BB, 0x0458, 0x0405, 0x0455, 0x0457, 0x0410, + 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, + 0x041E, 0x041F, 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A, + 0x042B, 0x042C, 0x042D, 0x042E, 0x042F, 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F, 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, + 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F }; uint8_t codePointToCP1251( const uint32_t value ) { diff --git a/src/engine/localevent.cpp b/src/engine/localevent.cpp index 28af4322fd4..afc41aa3262 100644 --- a/src/engine/localevent.cpp +++ b/src/engine/localevent.cpp @@ -800,7 +800,7 @@ bool LocalEvent::HandleEvents( const bool sleepAfterEventProcessing, const bool break; case SDL_TEXTINPUT: { _lastPressedCodePoint = event.key.keysym.sym; - if ( !Encoding::utf8ToCodePoint( reinterpret_cast( event.text.text ), 32, _lastPressedCodePoint ) ) { + if ( !Encoding::utf8ToCodePoint( reinterpret_cast( event.text.text ), 32, _lastPressedCodePoint ) ) { _lastPressedCodePoint = 0; } break; From 5c3713b70c44e460f21b4773d7960a1fa31e6852 Mon Sep 17 00:00:00 2001 From: Ihar Hubchyk Date: Sat, 2 Dec 2023 22:48:06 +0800 Subject: [PATCH 3/7] Add a space --- src/engine/char_encoding.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/char_encoding.cpp b/src/engine/char_encoding.cpp index 97e2196e0b6..52b6f07cd71 100644 --- a/src/engine/char_encoding.cpp +++ b/src/engine/char_encoding.cpp @@ -26,7 +26,7 @@ namespace { // All code page related array indexes are equal to index + 128 value in their code pages. - //For example, a first element in an array is actually value 128 in their code page. + // For example, a first element in an array is actually value 128 in their code page. const std::array cp1251CodePoints{ 0x0402, 0x0403, 0x201A, 0x0453, 0x201E, 0x2026, 0x2020, 0x2021, 0x20AC, 0x2030, 0x0409, 0x2039, 0x040A, 0x040C, 0x040B, 0x040F, 0x0452, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x0000, 0x2122, 0x0459, 0x203A, 0x045A, 0x045C, 0x045B, 0x045F, 0x00A0, 0x040E, 0x045E, 0x0408, 0x00A4, 0x0490, 0x00A6, From 8693766b9db94085fc755c41184d91aca6e937ce Mon Sep 17 00:00:00 2001 From: Ihar Hubchyk Date: Sat, 2 Dec 2023 22:54:07 +0800 Subject: [PATCH 4/7] Fix issues --- src/engine/char_encoding.h | 1 + src/engine/localevent.cpp | 2 +- src/engine/localevent.h | 20 ++++++++++---------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/engine/char_encoding.h b/src/engine/char_encoding.h index f86e273f2d5..617d62b311a 100644 --- a/src/engine/char_encoding.h +++ b/src/engine/char_encoding.h @@ -20,6 +20,7 @@ #pragma once +#include #include namespace Encoding diff --git a/src/engine/localevent.cpp b/src/engine/localevent.cpp index afc41aa3262..e39593bc9fb 100644 --- a/src/engine/localevent.cpp +++ b/src/engine/localevent.cpp @@ -1600,7 +1600,7 @@ size_t LocalEvent::insertLastPressedSymbol( std::string & res, size_t pos, const else { const uint8_t character = Encoding::getCodePageCharacter( _lastPressedCodePoint, codePage ); if ( character != 0 ) { - res.insert( pos, 1, character ); + res.insert( pos, 1, static_cast( character ) ); ++pos; } } diff --git a/src/engine/localevent.h b/src/engine/localevent.h index fd4a3888b43..44d8f99677a 100644 --- a/src/engine/localevent.h +++ b/src/engine/localevent.h @@ -187,6 +187,16 @@ class LocalEvent static LocalEvent & Get(); static LocalEvent & GetClean(); // reset all previous event statuses and return a reference for events + static int32_t getCurrentKeyModifiers(); + + static void OpenTouchpad(); + + static void setEventProcessingStates(); + + static void enableTextInput(); + + static void disableTextInput(); + void setGlobalMouseMotionEventHook( std::function hook ) { _globalMouseMotionEventHook = std::move( hook ); @@ -197,14 +207,8 @@ class LocalEvent _globalKeyDownEventHook = std::move( hook ); } - static void setEventProcessingStates(); - bool HandleEvents( const bool sleepAfterEventProcessing = true, const bool allowExit = false ); - void enableTextInput(); - - void disableTextInput(); - bool MouseMotion() const { return ( modes & MOUSE_MOTION ) == MOUSE_MOTION; @@ -299,13 +303,9 @@ class LocalEvent return key_value; } - static int32_t getCurrentKeyModifiers(); - void OpenController(); void CloseController(); - static void OpenTouchpad(); - void SetControllerPointerSpeed( const int newSpeed ) { if ( newSpeed > 0 ) { From 561f5a3290a4855f983e3e8bd719c71ea87725d1 Mon Sep 17 00:00:00 2001 From: Ihar Hubchyk Date: Sat, 2 Dec 2023 23:02:37 +0800 Subject: [PATCH 5/7] Address more tool complaints --- src/engine/localevent.h | 10 +++------- src/fheroes2/dialog/dialog_selectcount.cpp | 2 +- src/fheroes2/dialog/dialog_selectfile.cpp | 2 +- src/fheroes2/gui/ui_font.cpp | 1 + 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/engine/localevent.h b/src/engine/localevent.h index 44d8f99677a..20dc7525bef 100644 --- a/src/engine/localevent.h +++ b/src/engine/localevent.h @@ -448,10 +448,9 @@ class LocalEvent class TextInputEnabler { public: - TextInputEnabler( LocalEvent & eventHandler ) - : _handler( eventHandler ) + TextInputEnabler() { - _handler.enableTextInput(); + LocalEvent::enableTextInput(); } TextInputEnabler( const TextInputEnabler & ) = delete; @@ -459,11 +458,8 @@ class TextInputEnabler ~TextInputEnabler() { - _handler.disableTextInput(); + LocalEvent::disableTextInput(); } - -private: - LocalEvent & _handler; }; #endif diff --git a/src/fheroes2/dialog/dialog_selectcount.cpp b/src/fheroes2/dialog/dialog_selectcount.cpp index 5bf96e9de95..c16928430a7 100644 --- a/src/fheroes2/dialog/dialog_selectcount.cpp +++ b/src/fheroes2/dialog/dialog_selectcount.cpp @@ -316,7 +316,7 @@ bool Dialog::InputString( const std::string & header, std::string & res, const s const bool isInGameKeyboardRequired = System::isVirtualKeyboardSupported(); - const TextInputEnabler textInputEnabler( le ); + const TextInputEnabler textInputEnabler; const Encoding::CodePage codePage = fheroes2::getCodePage( fheroes2::getCurrentLanguage() ); diff --git a/src/fheroes2/dialog/dialog_selectfile.cpp b/src/fheroes2/dialog/dialog_selectfile.cpp index d63a53e9149..308a40ffb55 100644 --- a/src/fheroes2/dialog/dialog_selectfile.cpp +++ b/src/fheroes2/dialog/dialog_selectfile.cpp @@ -414,7 +414,7 @@ namespace LocalEvent & le = LocalEvent::Get(); - const TextInputEnabler textInputEnabler( le ); + const TextInputEnabler textInputEnabler; while ( le.HandleEvents( !isEditing || Game::isDelayNeeded( { Game::DelayType::CURSOR_BLINK_DELAY } ) ) && result.empty() ) { le.MousePressLeft( buttonOk.area() ) && buttonOk.isEnabled() ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); diff --git a/src/fheroes2/gui/ui_font.cpp b/src/fheroes2/gui/ui_font.cpp index df903e2ea67..842656f7bba 100644 --- a/src/fheroes2/gui/ui_font.cpp +++ b/src/fheroes2/gui/ui_font.cpp @@ -26,6 +26,7 @@ #include #include +#include "char_encoding.h" #include "icn.h" #include "image.h" #include "math_base.h" From f740d8611d1d41db688bae9fab16bd7f9ae2f6d0 Mon Sep 17 00:00:00 2001 From: Ihar Hubchyk Date: Sat, 2 Dec 2023 23:21:07 +0800 Subject: [PATCH 6/7] Make SonarQube happier --- src/engine/localevent.cpp | 2 +- src/engine/localevent.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/localevent.cpp b/src/engine/localevent.cpp index e39593bc9fb..fc53dd97571 100644 --- a/src/engine/localevent.cpp +++ b/src/engine/localevent.cpp @@ -1550,7 +1550,7 @@ void LocalEvent::setEventProcessingStates() setEventProcessingState( SDL_USEREVENT, false ); } -size_t LocalEvent::insertLastPressedSymbol( std::string & res, size_t pos, const Encoding::CodePage codePage ) +size_t LocalEvent::insertLastPressedSymbol( std::string & res, size_t pos, const Encoding::CodePage codePage ) const { switch ( key_value ) { case fheroes2::Key::KEY_BACKSPACE: diff --git a/src/engine/localevent.h b/src/engine/localevent.h index 20dc7525bef..e8202a5ad74 100644 --- a/src/engine/localevent.h +++ b/src/engine/localevent.h @@ -313,7 +313,7 @@ class LocalEvent } } - size_t insertLastPressedSymbol( std::string & res, size_t pos, const Encoding::CodePage codePage ); + size_t insertLastPressedSymbol( std::string & res, size_t pos, const Encoding::CodePage codePage ) const; private: LocalEvent(); From bb9a878ae7ef89394d654c0b8c36b6cf4e1dbb00 Mon Sep 17 00:00:00 2001 From: Ihar Hubchyk Date: Mon, 4 Dec 2023 16:43:08 +0800 Subject: [PATCH 7/7] Add some checks --- src/engine/localevent.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/engine/localevent.cpp b/src/engine/localevent.cpp index fc53dd97571..6068ba6c0b6 100644 --- a/src/engine/localevent.cpp +++ b/src/engine/localevent.cpp @@ -684,6 +684,9 @@ void LocalEvent::enableTextInput() void LocalEvent::disableTextInput() { + // If this assertion blows up then you are trying to disable text input twice! + assert( SDL_IsTextInputActive() ); + SDL_StopTextInput(); } @@ -801,6 +804,7 @@ bool LocalEvent::HandleEvents( const bool sleepAfterEventProcessing, const bool case SDL_TEXTINPUT: { _lastPressedCodePoint = event.key.keysym.sym; if ( !Encoding::utf8ToCodePoint( reinterpret_cast( event.text.text ), 32, _lastPressedCodePoint ) ) { + // Not a valid UTF-8 character. Maybe it is an unsupported OS. _lastPressedCodePoint = 0; } break;