stk-code_catmod/src/guiengine/screen_keyboard.cpp

548 lines
17 KiB
C++

//
// 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 <algorithm>
#include <string>
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<u32>& 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<s32> pos = m_edit_box->getAbsolutePosition();
if (pos.LowerRightCorner.Y + 5 > y)
{
y = margin;
}
}
m_area = core::rect<s32>(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<ButtonWidget>("Back");
assert(button_widget != NULL);
m_back_button = button_widget->getIrrlichtElement<IGUIButton>();
} // 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<s32> 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<char32_t> 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();
}