// // SuperTuxKart - a fun racing game with go-kart // Copyright (C) 2013-2015 SuperTuxKart-Team // // 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 3 // 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 "config/user_config.hpp" #include "graphics/graphics_restrictions.hpp" #include "graphics/irr_driver.hpp" #include "guiengine/engine.hpp" #include "guiengine/screen_keyboard.hpp" #include "guiengine/layout_manager.hpp" #include "guiengine/widget.hpp" #include "guiengine/widgets/button_widget.hpp" #include "guiengine/widgets/CGUIEditBox.hpp" #include "states_screens/state_manager.hpp" #include "utils/log.hpp" #include "utils/string_utils.hpp" #include #include using namespace GUIEngine; // ============================================================================ ScreenKeyboard::KeyboardLayoutProportions ScreenKeyboard::getKeyboardLayoutProportions() const { return { {2, 2, 2, 2, 2, 2, 2, 2, 2, 2}, {1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1}, {2, 2, 2, 2, 2, 2, 2, 2, 2, 2}, {3, 2, 10, 2, 3} }; } // getKeyboardLayoutProportions // ============================================================================ ScreenKeyboard::KeyboardLayout* ScreenKeyboard::getKeyboardLayout(ButtonsType bt) const { static KeyboardLayout layout_lower = {{"q", "w", "e", "r", "t", "y", "u", "i", "o", "p"}, {"Separator", "a", "s", "d", "f", "g", "h", "j", "k", "l", "Separator"}, {"Shift", "z", "x", "c", "v", "b", "n", "m", "?", "Back"}, {"123", ",", "Space", ".", "Enter"}}; static KeyboardLayout layout_upper = {{"Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"}, {"Separator", "A", "S", "D", "F", "G", "H", "J", "K", "L", "Separator"}, {"Shift", "Z", "X", "C", "V", "B", "N", "M", "!", "Back"}, {"123", ",", "Space", ".", "Enter"}}; static KeyboardLayout layout_digits = {{"1", "2", "3", "4", "5", "6", "7", "8", "9", "0"}, {"Separator", "@", "#", "$", "%", "^", "&", "*", "(", ")", "Separator"}, {"Shift", "-", "+", ":", ";", "\"", "\'", "/", "?", "Back"}, {"Text", ",", "Space", ".", "Enter"}}; static KeyboardLayout layout_digits2 = {{"1", "2", "3", "4", "5", "6", "7", "8", "9", "0"}, {"Separator", "@", "[", "]", "{", "}", "~", "`", "\\", "|", "Separator"}, {"Shift", "_", "=", ":", ";", "<", ">", "/", "!", "Back"}, {"Text", ",", "Space", ".", "Enter"}}; KeyboardLayout* keys = NULL; switch (bt) { case BUTTONS_LOWER: keys = &layout_lower; break; case BUTTONS_UPPER: keys = &layout_upper; break; case BUTTONS_DIGITS: keys = &layout_digits; break; case BUTTONS_DIGITS2: keys = &layout_digits2; break; default: break; }; return keys; } // getKeyboardLayout // ============================================================================ ScreenKeyboard* ScreenKeyboard::m_screen_keyboard = NULL; // ---------------------------------------------------------------------------- /** The screen keyboard constructor * \param percent_width A relative value in range of 0.0 to 1.0 that * determines width of the screen that will be used by the keyboard. * \param percent_height A relative value in range of 0.0 to 1.0 that * determines height of the screen that will be used by the keyboard. * \param edit_box The edit box that is assigned to the keyboard. */ ScreenKeyboard::ScreenKeyboard(float percent_width, float percent_height, CGUIEditBox* edit_box) { if (m_screen_keyboard != NULL) { delete m_screen_keyboard; Log::warn("GUIEngine", "Showing a screen keyboard while the previous " "one is still open. Destroying the previous keyboard."); } m_screen_keyboard = this; m_buttons_type = BUTTONS_NONE; m_percent_width = std::min(std::max(percent_width, 0.0f), 1.0f); m_percent_height = std::min(std::max(percent_height, 0.0f), 1.0f); m_irrlicht_window = NULL; m_edit_box = edit_box; m_back_button = NULL; m_repeat_time = 0; m_back_button_pressed = false; m_schedule_close = false; } // ScreenKeyboard // ---------------------------------------------------------------------------- /** The screen keyboard destructor */ ScreenKeyboard::~ScreenKeyboard() { m_screen_keyboard = NULL; GUIEngine::getGUIEnv()->removeFocus(m_irrlicht_window); m_irrlicht_window->remove(); if (input_manager) input_manager->setMode(m_previous_mode); elementsWereDeleted(); } // ~ScreenKeyboard // ---------------------------------------------------------------------------- /** Screen keyboard initialization, needs to be called after new to take into * account for runtime polymorphism */ void ScreenKeyboard::init() { const core::dimension2d& frame_size = irr_driver->getFrameSize(); int margin = 0; int w = int(frame_size.Width * m_percent_width); int h = int(frame_size.Height * m_percent_height); int x = frame_size.Width/2 - w/2; int y = frame_size.Height - h - margin; if (m_edit_box != NULL) { core::rect pos = m_edit_box->getAbsolutePosition(); if (pos.LowerRightCorner.Y + 5 > y) { y = margin; } } m_area = core::rect(x, y, x + w, y + h); m_irrlicht_window = GUIEngine::getGUIEnv()->addWindow(m_area, true); m_irrlicht_window->setDrawTitlebar(false); m_irrlicht_window->getCloseButton()->setVisible(false); m_irrlicht_window->setDraggable(UserConfigParams::m_artist_debug_mode); m_previous_mode=input_manager->getMode(); input_manager->setMode(InputManager::MENU); createButtons(); assignButtons(getDefaultButtonsType()); Widget* button_widget = getWidget("Back"); assert(button_widget != NULL); m_back_button = button_widget->getIrrlichtElement(); } // init // ---------------------------------------------------------------------------- /** Creates all button widgets */ void ScreenKeyboard::createButtons() { const auto& layout_proportions = getKeyboardLayoutProportions(); int rows_num = layout_proportions.size(); int pos_y = 3; const int margin = 2; int height = (m_area.getHeight() - 2 * pos_y) / rows_num - margin; for (int i = 0; i < rows_num; i++) { float pos_x = 3; int total_width = m_area.getWidth() - 2 * (int)pos_x; int total_padding = irr_driver->getDevice()->getLeftPadding() + irr_driver->getDevice()->getRightPadding(); if (total_width - total_padding > 0) total_width -= total_padding; char tmp[100]; sprintf(tmp, "%i", pos_y + (height + margin) * i); std::string pos_y_str = tmp; int total_proportions = 0; for (int value : layout_proportions[i]) { total_proportions += value; } int cols_num = layout_proportions[i].size(); for (int j = 0; j < cols_num; j++) { ButtonWidget* button = new ButtonWidget(); float width = (float)total_width * layout_proportions[i][j] / total_proportions - margin; char width_str[100]; sprintf(width_str, "%i", (int)roundf(width / (SkinConfig::getHorizontalInnerPadding(button->getType(), button)+1.0f))); char height_str[100]; sprintf(height_str, "%i", (int)roundf(height / (SkinConfig::getVerticalInnerPadding(button->getType(), button)+1.0f))); char tmp[100]; sprintf(tmp, "%i", (int)roundf(pos_x)); std::string pos_x_str = tmp; button->setParent(m_irrlicht_window); button->m_properties[PROP_WIDTH] = width_str; button->m_properties[PROP_HEIGHT] = height_str; button->m_properties[PROP_X] = pos_x_str; button->m_properties[PROP_Y] = pos_y_str; m_widgets.push_back(button); m_buttons.push_back(button); pos_x += width + margin; } } LayoutManager::calculateLayout(m_widgets, this); addWidgetsRecursively(m_widgets); assert(m_buttons.size() > 0); m_buttons[0]->setFocusForPlayer(PLAYER_ID_GAME_MASTER); } // createButtons // ---------------------------------------------------------------------------- core::stringw ScreenKeyboard::getKeyName(std::string key_id) { core::stringw key_name; if (key_id == "Enter") { key_name = L"\u21B2"; } else if (key_id == "Shift") { key_name = L"\u21E7"; } else if (key_id == "Back") { key_name = L"\u21E6"; } else if (key_id == "Space") { key_name = L"\u2423"; } else { key_name = StringUtils::utf8ToWide(key_id); } return key_name; } // ---------------------------------------------------------------------------- /** A function that allows to select one of the available buttons layout * \param buttons_type One of the available buttons type */ void ScreenKeyboard::assignButtons(ButtonsType buttons_type) { const auto& layout_proportions = getKeyboardLayoutProportions(); int rows_num = layout_proportions.size(); m_buttons_type = buttons_type; KeyboardLayout* keys = getKeyboardLayout(buttons_type); unsigned int current_button_id = 0; for (int i = 0; i < rows_num; i++) { int cols_num = layout_proportions[i].size(); for (int j = 0; j < cols_num; j++) { std::string key = keys != NULL ? (*keys)[i][j] : "?"; const core::stringw& key_name = getKeyName(key); assert(current_button_id < m_buttons.size()); ButtonWidget* button = m_buttons[current_button_id]; if (key == "Separator") { button->setVisible(false); } else { button->setVisible(true); button->setText(key_name); button->m_properties[PROP_ID] = key; } current_button_id++; } } } // assignButtons // ---------------------------------------------------------------------------- void ScreenKeyboard::onUpdate(float dt) { if (m_back_button->isPressed() || m_back_button_pressed) { const unsigned int repeat_rate = 40; const unsigned int repeat_delay = 400; SEvent event; event.KeyInput.Key = IRR_KEY_BACK; event.KeyInput.Char = 0; event.EventType = EET_KEY_INPUT_EVENT; event.KeyInput.PressedDown = true; event.KeyInput.Control = false; event.KeyInput.Shift = false; if (m_repeat_time == 0) { m_edit_box->OnEvent(event); } while (m_repeat_time > repeat_delay + repeat_rate) { m_edit_box->OnEvent(event); m_repeat_time -= repeat_rate; } m_repeat_time += (unsigned int)(dt * 1000); } if (!m_back_button->isPressed()) { m_back_button_pressed = false; m_repeat_time = 0; } } // ---------------------------------------------------------------------------- /** A function that handles irrlicht events * \param event Irrlicht event * \return Block event if true */ bool ScreenKeyboard::onEvent(const SEvent &event) { if (event.EventType == EET_MOUSE_INPUT_EVENT) { core::position2d point(event.MouseInput.X, event.MouseInput.Y); if (m_edit_box->isPointInside(point)) { m_edit_box->OnEvent(event); return true; } else { bool is_point_inside = m_irrlicht_window->isPointInside(point); if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { if (!is_point_inside) { m_schedule_close = true; return true; } } else if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) { if (!is_point_inside && m_schedule_close) { dismiss(); return true; } else { m_schedule_close = false; return false; } } } } return false; } // ---------------------------------------------------------------------------- /** A function that handles buttons events * \param eventSource Button ID * \return Block event if edit box is assigned */ EventPropagation ScreenKeyboard::processEvent(const std::string& eventSource) { if (m_edit_box == NULL) return EVENT_LET; SEvent event; bool send_event = false; bool close_keyboard = false; std::vector chars; if (eventSource == "Shift") { switch (m_buttons_type) { case BUTTONS_UPPER: assignButtons(BUTTONS_LOWER); break; case BUTTONS_LOWER: assignButtons(BUTTONS_UPPER); break; case BUTTONS_DIGITS: assignButtons(BUTTONS_DIGITS2); break; case BUTTONS_DIGITS2: assignButtons(BUTTONS_DIGITS); break; default: break; } } else if (eventSource == "123") { assignButtons(BUTTONS_DIGITS); } else if (eventSource == "Text") { assignButtons(BUTTONS_LOWER); } else if (eventSource == "Enter") { event.KeyInput.Key = IRR_KEY_RETURN; chars.push_back(0); send_event = true; close_keyboard = true; } else if (eventSource == "Back") { send_event = false; m_back_button_pressed = true; } else if (eventSource == "Space") { event.KeyInput.Key = IRR_KEY_UNKNOWN; chars.push_back(U' '); send_event = true; } else if (eventSource.size() > 0) { event.KeyInput.Key = IRR_KEY_UNKNOWN; // For possible emoji ligatures const std::u32string& s = StringUtils::utf8ToUtf32(eventSource); for (char32_t c : s) chars.push_back(c); send_event = true; } if (send_event) { event.EventType = EET_KEY_INPUT_EVENT; event.KeyInput.PressedDown = true; event.KeyInput.Control = false; event.KeyInput.Shift = false; for (char32_t c : chars) { event.KeyInput.Char = c; m_edit_box->OnEvent(event); } } if (close_keyboard) { dismiss(); } return EVENT_BLOCK; } // processEvent // ---------------------------------------------------------------------------- /** A function that closes the keyboard */ void ScreenKeyboard::dismiss() { delete m_screen_keyboard; m_screen_keyboard = NULL; } // dismiss // ---------------------------------------------------------------------------- /** A function that handles escape pressed event */ bool ScreenKeyboard::onEscapePressed() { dismiss(); return true; } // onEscapePressed // ---------------------------------------------------------------------------- /** A function that determines if (native) screen keyboard should be activated */ bool ScreenKeyboard::shouldUseScreenKeyboard() { bool always_use_screen_keyboard = UserConfigParams::m_screen_keyboard == 2; if (UserConfigParams::m_screen_keyboard == 1) return true; return always_use_screen_keyboard; } // ---------------------------------------------------------------------------- /** Returns true if system screen keyboard is available */ bool ScreenKeyboard::hasSystemScreenKeyboard() { if (GraphicsRestrictions::isDisabled(GraphicsRestrictions::GR_SYSTEM_SCREEN_KEYBOARD)) return false; return irr_driver->getDevice()->hasOnScreenKeyboard(); }