ppsspp/Core/Dialog/PSPOskDialog.cpp
2025-01-04 11:35:54 +01:00

1123 lines
32 KiB
C++
Executable file
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (c) 2012- PPSSPP Project.
// 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, version 2.0 or later versions.
// 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 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include "ppsspp_config.h"
#include <cctype>
#include <cmath>
#include <algorithm>
#include "Common/Data/Text/I18n.h"
#include "Common/Math/math_util.h"
#include "Common/Data/Encoding/Utf8.h"
#include "Common/Serialize/SerializeFuncs.h"
#include "Common/System/Request.h"
#include "Common/Serialize/Serializer.h"
#include "Core/Dialog/PSPOskDialog.h"
#include "Core/Util/PPGeDraw.h"
#include "Core/HLE/sceCtrl.h"
#include "Core/HLE/sceUtility.h"
#include "Core/HW/Display.h"
#include "Core/Config.h"
#include "Core/Reporting.h"
// These are rough, it seems to take a long time to init, and probably depends on threads.
// TODO: This takes like 700ms on a PSP but that's annoyingly long.
const static int OSK_INIT_DELAY_US = 300000;
const static int OSK_SHUTDOWN_DELAY_US = 40000;
static std::map<std::string, std::pair<std::string, int>, std::less<>> languageMapping;
const uint8_t numKeyCols[OSK_KEYBOARD_COUNT] = {12, 12, 13, 13, 12, 12, 12, 12, 12};
const uint8_t numKeyRows[OSK_KEYBOARD_COUNT] = {4, 4, 6, 6, 5, 4, 4, 4, 4};
// Japanese (Kana) diacritics
extern const wchar_t diacritics[2][103];
// Korean (Hangul) consonant
extern const wchar_t kor_cons[20];
// Korean (Hangul) vowels, Some vowels are not used, they will be spaces
extern const wchar_t kor_vowel[22];
// Korean (Hangul) vowel Combination key
extern const uint8_t kor_vowelCom[21];
// Korean (Hangul) last consonant(diacritics)
extern const wchar_t kor_lcons[28];
// Korean (Hangul) last consonant Combination key
extern const uint8_t kor_lconsCom[33];
// Korean (Hangul) last consonant Separation key
extern const uint8_t kor_lconsSpr[33];
extern const char16_t oskKeys[OSK_KEYBOARD_COUNT][6][14];
// This isn't a complete representation of these flags, it just helps ensure we show the right keyboards.
static const int allowedInputFlagsMap[OSK_KEYBOARD_COUNT] = {
PSP_UTILITY_OSK_INPUTTYPE_LATIN_LOWERCASE | PSP_UTILITY_OSK_INPUTTYPE_LATIN_UPPERCASE | PSP_UTILITY_OSK_INPUTTYPE_LATIN_SYMBOL | PSP_UTILITY_OSK_INPUTTYPE_LATIN_DIGIT,
PSP_UTILITY_OSK_INPUTTYPE_LATIN_LOWERCASE | PSP_UTILITY_OSK_INPUTTYPE_LATIN_UPPERCASE | PSP_UTILITY_OSK_INPUTTYPE_LATIN_SYMBOL,
PSP_UTILITY_OSK_INPUTTYPE_JAPANESE_HIRAGANA,
PSP_UTILITY_OSK_INPUTTYPE_JAPANESE_KATAKANA | PSP_UTILITY_OSK_INPUTTYPE_JAPANESE_HALF_KATAKANA,
PSP_UTILITY_OSK_INPUTTYPE_KOREAN,
PSP_UTILITY_OSK_INPUTTYPE_RUSSIAN_LOWERCASE | PSP_UTILITY_OSK_INPUTTYPE_RUSSIAN_UPPERCASE,
PSP_UTILITY_OSK_INPUTTYPE_RUSSIAN_LOWERCASE | PSP_UTILITY_OSK_INPUTTYPE_RUSSIAN_UPPERCASE,
PSP_UTILITY_OSK_INPUTTYPE_JAPANESE_LOWERCASE | PSP_UTILITY_OSK_INPUTTYPE_JAPANESE_UPPERCASE | PSP_UTILITY_OSK_INPUTTYPE_JAPANESE_SYMBOL | PSP_UTILITY_OSK_INPUTTYPE_JAPANESE_DIGIT,
PSP_UTILITY_OSK_INPUTTYPE_JAPANESE_LOWERCASE | PSP_UTILITY_OSK_INPUTTYPE_JAPANESE_UPPERCASE | PSP_UTILITY_OSK_INPUTTYPE_JAPANESE_SYMBOL,
};
static const int defaultInputFlagsMap[OSK_KEYBOARD_COUNT] = {
PSP_UTILITY_OSK_INPUTTYPE_LATIN_LOWERCASE | PSP_UTILITY_OSK_INPUTTYPE_LATIN_SYMBOL | PSP_UTILITY_OSK_INPUTTYPE_LATIN_DIGIT,
PSP_UTILITY_OSK_INPUTTYPE_LATIN_UPPERCASE | PSP_UTILITY_OSK_INPUTTYPE_LATIN_SYMBOL,
PSP_UTILITY_OSK_INPUTTYPE_JAPANESE_HIRAGANA,
PSP_UTILITY_OSK_INPUTTYPE_JAPANESE_KATAKANA | PSP_UTILITY_OSK_INPUTTYPE_JAPANESE_HALF_KATAKANA,
PSP_UTILITY_OSK_INPUTTYPE_KOREAN,
PSP_UTILITY_OSK_INPUTTYPE_RUSSIAN_LOWERCASE,
PSP_UTILITY_OSK_INPUTTYPE_RUSSIAN_UPPERCASE,
PSP_UTILITY_OSK_INPUTTYPE_JAPANESE_LOWERCASE | PSP_UTILITY_OSK_INPUTTYPE_JAPANESE_SYMBOL | PSP_UTILITY_OSK_INPUTTYPE_JAPANESE_DIGIT,
PSP_UTILITY_OSK_INPUTTYPE_JAPANESE_UPPERCASE | PSP_UTILITY_OSK_INPUTTYPE_JAPANESE_SYMBOL,
};
PSPOskDialog::PSPOskDialog(UtilityDialogType type) : PSPDialog(type) {
// This can break all kinds of stuff, changing the decimal point in sprintf for example.
// Not sure what the intended effect is so commented out for now.
// setlocale(LC_ALL, "");
}
PSPOskDialog::~PSPOskDialog() {
}
void PSPOskDialog::ConvertUCS2ToUTF8(std::string& _string, const PSPPointer<u16_le>& em_address)
{
if (!em_address.IsValid())
{
_string.clear();
return;
}
const size_t maxLength = 2047;
char stringBuffer[maxLength + 1];
char *string = stringBuffer;
u16_le *input = &em_address[0];
int c;
while ((c = *input++) != 0 && string < stringBuffer + maxLength)
{
if (c < 0x80)
*string++ = c;
else if (c < 0x800) {
*string++ = 0xC0 | (c >> 6);
*string++ = 0x80 | (c & 0x3F);
} else {
*string++ = 0xE0 | (c >> 12);
*string++ = 0x80 | ((c >> 6) & 0x3F);
*string++ = 0x80 | (c & 0x3F);
}
}
*string++ = '\0';
_string = stringBuffer;
}
void GetWideStringFromPSPPointer(std::u16string& _string, const PSPPointer<u16_le>& em_address)
{
if (!em_address.IsValid())
{
_string.clear();
return;
}
const size_t maxLength = 2047;
char16_t stringBuffer[maxLength + 1];
char16_t *string = stringBuffer;
u16_le *input = &em_address[0];
int c;
while ((c = *input++) != 0 && string < stringBuffer + maxLength)
*string++ = c;
*string++ = '\0';
_string = stringBuffer;
}
void PSPOskDialog::ConvertUCS2ToUTF8(std::string& _string, const char16_t *input)
{
char stringBuffer[2048];
char *string = stringBuffer;
int c;
while ((c = *input++) != 0)
{
if (c < 0x80)
*string++ = c;
else if (c < 0x800) {
*string++ = 0xC0 | (c >> 6);
*string++ = 0x80 | (c & 0x3F);
} else {
*string++ = 0xE0 | (c >> 12);
*string++ = 0x80 | ((c >> 6) & 0x3F);
*string++ = 0x80 | (c & 0x3F);
}
}
*string++ = '\0';
_string = stringBuffer;
}
static void FindValidKeyboard(s32 inputType, int direction, OskKeyboardLanguage &lang, OskKeyboardDisplay &disp) {
OskKeyboardLanguage origLang = lang;
OskKeyboardDisplay origDisp = disp;
if (inputType == 0) {
return;
}
// We use direction = 0 for default, but we actually move "forward".
const int *matchMap = allowedInputFlagsMap;
if (direction == 0) {
direction = 1;
matchMap = defaultInputFlagsMap;
}
// TODO: Limit by allowed keyboards properly... this is just an approximation.
int tries = OSK_LANGUAGE_COUNT * 2;
while (!(inputType & matchMap[disp]) && tries > 0) {
if ((--tries % 2) == 0) {
lang = (OskKeyboardLanguage)((OSK_LANGUAGE_COUNT + lang + direction) % OSK_LANGUAGE_COUNT);
disp = OskKeyboardCases[lang][LOWERCASE];
} else {
disp = OskKeyboardCases[lang][UPPERCASE];
}
}
if (tries == 0) {
// In case of error, let's just fall back to allowing all.
lang = origLang;
disp = origDisp;
}
}
static bool IsKeyboardShiftValid(s32 inputType, OskKeyboardLanguage lang, OskKeyboardDisplay disp) {
// Swap disp and check if it's valid.
if (disp == OskKeyboardCases[lang][UPPERCASE])
disp = OskKeyboardCases[lang][LOWERCASE];
else
disp = OskKeyboardCases[lang][UPPERCASE];
return inputType == 0 || (inputType & allowedInputFlagsMap[disp]) != 0;
}
int PSPOskDialog::Init(u32 oskPtr) {
// Ignore if already running
if (GetStatus() != SCE_UTILITY_STATUS_NONE) {
ERROR_LOG_REPORT(Log::sceUtility, "sceUtilityOskInitStart: invalid status");
return SCE_ERROR_UTILITY_INVALID_STATUS;
}
// Seems like this should crash?
if (!Memory::IsValidAddress(oskPtr)) {
ERROR_LOG_REPORT(Log::sceUtility, "sceUtilityOskInitStart: invalid params (%08x)", oskPtr);
return -1;
}
oskParams = oskPtr;
if (oskParams->base.size != sizeof(SceUtilityOskParams))
{
ERROR_LOG_REPORT(Log::sceUtility, "sceUtilityOskInitStart: invalid size %d", oskParams->base.size);
return SCE_ERROR_UTILITY_INVALID_PARAM_SIZE;
}
// Also seems to crash.
if (!oskParams->fields.IsValid())
{
ERROR_LOG_REPORT(Log::sceUtility, "sceUtilityOskInitStart: invalid field data (%08x)", oskParams->fields.ptr);
return -1;
}
if (oskParams->unk_60 != 0)
WARN_LOG_REPORT(Log::sceUtility, "sceUtilityOskInitStart: unknown param is non-zero (%08x)", oskParams->unk_60);
if (oskParams->fieldCount != 1)
WARN_LOG_REPORT(Log::sceUtility, "sceUtilityOskInitStart: unsupported field count %d", oskParams->fieldCount);
ChangeStatusInit(OSK_INIT_DELAY_US);
selectedChar = 0;
currentKeyboardLanguage = OSK_LANGUAGE_ENGLISH;
currentKeyboard = OSK_KEYBOARD_LATIN_LOWERCASE;
FindValidKeyboard(oskParams->fields[0].inputtype, 0, currentKeyboardLanguage, currentKeyboard);
ConvertUCS2ToUTF8(oskDesc, oskParams->fields[0].desc);
ConvertUCS2ToUTF8(oskIntext, oskParams->fields[0].intext);
ConvertUCS2ToUTF8(oskOuttext, oskParams->fields[0].outtext);
i_level = 0;
inputChars.clear();
if (oskParams->fields[0].intext.IsValid()) {
auto src = oskParams->fields[0].intext;
int c;
while ((c = *src++) != 0)
inputChars += c;
}
languageMapping = g_Config.GetLangValuesMapping();
// Eat any keys pressed before the dialog inited.
UpdateButtons();
InitCommon();
std::lock_guard<std::mutex> guard(nativeMutex_);
nativeStatus_ = PSPOskNativeStatus::IDLE;
StartFade(true);
return 0;
}
std::u16string PSPOskDialog::CombinationKorean(bool isInput)
{
std::u16string string;
isCombinated = true;
int selectedRow = selectedChar / numKeyCols[currentKeyboard];
int selectedCol = selectedChar % numKeyCols[currentKeyboard];
if (inputChars.size() == 0) {
wchar_t sw = oskKeys[currentKeyboard][selectedRow][selectedCol];
if (inputChars.size() < FieldMaxLength()) {
string += sw;
i_value[0] = GetIndex(kor_cons, sw);
if(i_value[0] != -1 && isInput == true)
i_level = 1;
} else {
isCombinated = false;
}
} else {
for(u32 i = 0; i < inputChars.size(); i++) {
if(i + 1 == inputChars.size()) {
wchar_t sw = oskKeys[currentKeyboard][selectedRow][selectedCol];
if(i_level == 0) {
string += inputChars[i];
if (inputChars.size() < FieldMaxLength()) {
string += sw;
i_value[0] = GetIndex(kor_cons, sw);
if(i_value[0] != -1 && isInput == true)
i_level = 1;
} else {
isCombinated = false;
}
} else if(i_level == 1) {
i_value[1] = GetIndex(kor_vowel, sw);
if(i_value[1] == -1) {
string += inputChars[i];
if (inputChars.size() < FieldMaxLength()) {
string += sw;
if(isInput == true) {
i_value[0] = GetIndex(kor_cons, sw);
if(i_value[0] != -1)
i_level = 1;
else
i_level = 0;
}
} else {
isCombinated = false;
}
} else {
u16 code = 0xAC00 + i_value[0] * 0x24C + i_value[1] * 0x1C;
string += code;
if(isInput == true) {
i_level = 2;
}
}
} else if(i_level == 2) {
int tmp = GetIndex(kor_vowel, sw);
if(tmp != -1) {
int tmp2 = -1;
for(size_t j = 0; j < sizeof(kor_vowelCom) / 4; j+=3) {
if(kor_vowelCom[j] == tmp && kor_vowelCom[j + 1] == i_value[1]) {
tmp2 = kor_vowelCom[j + 2];
break;
}
}
if(tmp2 != -1) {
if(isInput == true) {
i_value[1] = tmp2;
}
u16 code = 0xAC00 + i_value[0] * 0x24C + tmp2 * 0x1C;
string += code;
} else {
string += inputChars[i];
if (inputChars.size() < FieldMaxLength()) {
string += sw;
if(isInput == true) {
i_level = 0;
}
} else {
isCombinated = false;
}
}
} else {
int tmp2 = GetIndex(kor_lcons, sw);
if (tmp2 == -1) {
string += inputChars[i];
if (inputChars.size() < FieldMaxLength()) {
string += sw;
if (isInput == true) {
i_value[0] = GetIndex(kor_cons, sw);
if(i_value[0] != -1)
i_level = 1;
else
i_level = 0;
}
} else {
isCombinated = false;
}
} else {
u16 code = 0xAC00 + i_value[0] * 0x24C + i_value[1] * 0x1C + tmp2 + 1;
string += code;
if (isInput == true) {
i_level = 3;
i_value[2] = tmp2;
}
}
}
} else if(i_level == 3) {
int tmp = GetIndex(kor_lcons, sw);
if(tmp != -1) {
int tmp2 = -1;
for(size_t j = 0; j < sizeof(kor_lconsCom) / 4; j+=3) {
if(kor_lconsCom[j] == tmp && kor_lconsCom[j + 1] == i_value[2]) {
tmp2 = kor_lconsCom[j + 2];
break;
}
}
if(tmp2 != -1) {
if(isInput == true) {
i_value[2] = tmp2;
}
u16 code = 0xAC00 + i_value[0] * 0x24C + tmp2 * 0x1C + i_value[2] + 1;
string += code;
} else {
string += inputChars[i];
if (inputChars.size() < FieldMaxLength()) {
string += sw;
if(isInput == true) {
i_value[0] = GetIndex(kor_cons, sw);
if(i_value[0] != -1)
i_level = 1;
else
i_level = 0;
}
} else {
isCombinated = false;
}
}
} else {
int tmp3 = GetIndex(kor_vowel, sw);
if (tmp3 == -1) {
string += inputChars[i];
if (inputChars.size() < FieldMaxLength()) {
string += sw;
if(isInput == true) {
i_value[0] = GetIndex(kor_cons, sw);
if(i_value[0] != -1)
i_level = 1;
else
i_level = 0;
}
} else {
isCombinated = false;
}
} else {
if (inputChars.size() < FieldMaxLength()) {
int tmp2 = -1;
for(size_t j = 0; j < sizeof(kor_lconsSpr) / 4; j+=3) {
if(kor_lconsSpr[j] == i_value[2]) {
tmp2 = (int)j;
break;
}
}
if(tmp2 != -1) {
u16 code = 0xAC00 + i_value[0] * 0x24C + i_value[1] * 0x1C + kor_lconsSpr[tmp2 + 1];
string += code;
code = 0xAC00 + kor_lconsSpr[tmp2 + 2] * 0x24C + tmp3 * 0x1C;
string += code;
if(isInput == true) {
i_value[0] = kor_lconsSpr[tmp2 + 2];
i_value[1] = tmp3;
i_level = 2;
}
} else {
int tmp4 = GetIndex(kor_cons, kor_lcons[i_value[2]]);
if (tmp4 != -1) {
u16 code = 0xAC00 + i_value[0] * 0x24C + i_value[1] * 0x1C;
string += code;
code = 0xAC00 + tmp4 * 0x24C + tmp3 * 0x1C;
string += code;
if(isInput == true) {
i_value[0] = tmp4;
i_value[1] = tmp3;
i_level = 2;
}
} else {
string += inputChars[i];
string += sw;
if(isInput == true) {
i_level = 0;
}
}
}
} else {
string += inputChars[i];
isCombinated = false;
}
}
}
}
} else {
string += inputChars[i];
}
}
}
return string;
}
std::u16string PSPOskDialog::CombinationString(bool isInput)
{
std::u16string string;
isCombinated = false;
int selectedRow = selectedChar / numKeyCols[currentKeyboard];
int selectedCol = selectedChar % numKeyCols[currentKeyboard];
if(currentKeyboard == OSK_KEYBOARD_KOREAN)
{
string = CombinationKorean(isInput);
}
else
{
if(isInput == true)
{
i_level = 0;
}
if(oskKeys[currentKeyboard][selectedRow][selectedCol] == L'')
{
for(u32 i = 0; i < inputChars.size(); i++)
{
if(i + 1 == inputChars.size())
{
for(u32 j = 0; j < wcslen(diacritics[0]); j+=2)
{
if(inputChars[i] == diacritics[0][j])
{
string += diacritics[0][j + 1];
isCombinated = true;
break;
}
}
if(isCombinated == false)
{
string += inputChars[i];
}
}
else
{
string += inputChars[i];
}
}
}
else if(oskKeys[currentKeyboard][selectedRow][selectedCol] == L'')
{
for(u32 i = 0; i < inputChars.size(); i++)
{
if(i + 1 == inputChars.size())
{
for(u32 j = 0; j < wcslen(diacritics[1]); j+=2)
{
if(inputChars[i] == diacritics[1][j])
{
string += diacritics[1][j + 1];
isCombinated = true;
break;
}
}
if(isCombinated == false)
{
string += inputChars[i];
}
}
else
{
string += inputChars[i];
}
}
}
else
{
for(u32 i = 0; i < inputChars.size(); i++)
{
string += inputChars[i];
}
if (string.size() < FieldMaxLength())
{
string += oskKeys[currentKeyboard][selectedRow][selectedCol];
}
isCombinated = true;
}
}
return string;
}
void PSPOskDialog::RemoveKorean()
{
if(i_level == 1)
{
i_level = 0;
}
else if(i_level == 2)
{
int tmp = -1;
for(size_t i = 2; i < sizeof(kor_vowelCom) / 4; i+=3)
{
if(kor_vowelCom[i] == i_value[1])
{
tmp = kor_vowelCom[i - 1];
break;
}
}
if(tmp != -1)
{
i_value[1] = tmp;
u16 code = 0xAC00 + i_value[0] * 0x24C + i_value[1] * 0x1C;
inputChars += code;
}
else
{
i_level = 1;
inputChars += kor_cons[i_value[0]];
}
}
else if(i_level == 3)
{
int tmp = -1;
for(size_t i = 2; i < sizeof(kor_lconsCom) / 4; i+=3)
{
if(kor_lconsCom[i] == i_value[2])
{
tmp = kor_lconsCom[i - 1];
break;
}
}
if(tmp != -1)
{
i_value[2] = tmp;
u16 code = 0xAC00 + i_value[0] * 0x24C + i_value[1] * 0x1C + i_value[2] + 1;
inputChars += code;
}
else
{
i_level = 2;
u16 code = 0xAC00 + i_value[0] * 0x24C + i_value[1] * 0x1C;
inputChars += code;
}
}
}
int PSPOskDialog::GetIndex(const wchar_t* src, wchar_t ch)
{
for(int i = 0, end = (int)wcslen(src); i < end; i++)
{
if(src[i] == ch)
{
return i;
}
}
return -1;
}
u32 PSPOskDialog::FieldMaxLength()
{
if ((oskParams->fields[0].outtextlimit > oskParams->fields[0].outtextlength - 1) || oskParams->fields[0].outtextlimit == 0)
return oskParams->fields[0].outtextlength - 1;
return oskParams->fields[0].outtextlimit;
}
void PSPOskDialog::RenderKeyboard()
{
// Sanity check that a valid keyboard is selected.
if ((int)currentKeyboard < 0 || (int)currentKeyboard >= OSK_KEYBOARD_COUNT) {
return;
}
int selectedRow = selectedChar / numKeyCols[currentKeyboard];
int selectedCol = selectedChar % numKeyCols[currentKeyboard];
char16_t temp[2];
temp[1] = '\0';
std::string buffer;
static const u32 FIELDDRAWMAX = 16;
u32 limit = FieldMaxLength();
u32 drawLimit = std::min(FIELDDRAWMAX, limit); // Field drew length limit.
const float keyboardLeftSide = (480.0f - (24.0f * numKeyCols[currentKeyboard])) / 2.0f;
const float characterWidth = 12.0f;
float previewLeftSide = (480.0f - (12.0f * drawLimit)) / 2.0f;
float title = (480.0f - (0.5f * drawLimit)) / 2.0f;
PPGeStyle descStyle = FadedStyle(PPGeAlign::BOX_CENTER, 0.5f);
PPGeDrawText(oskDesc.c_str(), title, 20, descStyle);
PPGeStyle textStyle = FadedStyle(PPGeAlign::BOX_HCENTER, 0.5f);
PPGeStyle keyStyle = FadedStyle(PPGeAlign::BOX_HCENTER, 0.6f);
PPGeStyle selectedKeyStyle = FadedStyle(PPGeAlign::BOX_HCENTER, 0.6f);
selectedKeyStyle.color = CalcFadedColor(0xFF3060FF);
std::u16string result;
result = CombinationString(false);
u32 drawIndex = (u32)(result.size() > drawLimit ? result.size() - drawLimit : 0);
drawIndex = result.size() == limit + 1 ? drawIndex - 1 : drawIndex; // When the length reached limit, the last character don't fade in and out.
for (u32 i = 0; i < drawLimit; ++i, ++drawIndex)
{
if (drawIndex + 1 < result.size())
{
temp[0] = result[drawIndex];
ConvertUCS2ToUTF8(buffer, temp);
PPGeDrawText(buffer.c_str(), previewLeftSide + (i * characterWidth), 40.0f, textStyle);
}
else
{
if (drawIndex + 1 == result.size())
{
temp[0] = result[drawIndex];
if(isCombinated == true)
{
float animStep = (float)(__DisplayGetNumVblanks() % 40) / 20.0f;
// Fade in and out the next character so they know it's not part of the string yet.
u32 alpha = (0.5f - (cosf(animStep * M_PI) / 2.0f)) * 128 + 127;
PPGeStyle animStyle = textStyle;
animStyle.color = CalcFadedColor((alpha << 24) | 0x00FFFFFF);
ConvertUCS2ToUTF8(buffer, temp);
PPGeDrawText(buffer.c_str(), previewLeftSide + (i * characterWidth), 40.0f, animStyle);
// Also draw the underline for the same reason.
PPGeDrawText("_", previewLeftSide + (i * characterWidth), 40.0f, textStyle);
}
else
{
ConvertUCS2ToUTF8(buffer, temp);
PPGeDrawText(buffer.c_str(), previewLeftSide + (i * characterWidth), 40.0f, textStyle);
}
}
else
{
PPGeDrawText("_", previewLeftSide + (i * characterWidth), 40.0f, textStyle);
}
}
}
for (int row = 0; row < numKeyRows[currentKeyboard]; ++row)
{
for (int col = 0; col < numKeyCols[currentKeyboard]; ++col)
{
temp[0] = oskKeys[currentKeyboard][row][col];
ConvertUCS2ToUTF8(buffer, temp);
if (selectedRow == row && col == selectedCol) {
PPGeDrawText(buffer.c_str(), keyboardLeftSide + (25.0f * col) + characterWidth / 2.0, 70.0f + (25.0f * row), selectedKeyStyle);
PPGeDrawText("_", keyboardLeftSide + (25.0f * col) + characterWidth / 2.0, 70.0f + (25.0f * row), keyStyle);
} else {
PPGeDrawText(buffer.c_str(), keyboardLeftSide + (25.0f * col) + characterWidth / 2.0, 70.0f + (25.0f * row), keyStyle);
}
}
}
}
// TODO: Why does this have a 2 button press lag/delay when
// re-opening the dialog box? I don't get it.
int PSPOskDialog::NativeKeyboard() {
if (GetStatus() != SCE_UTILITY_STATUS_RUNNING) {
return SCE_ERROR_UTILITY_INVALID_STATUS;
}
#if defined(USING_WIN_UI) || defined(USING_QT_UI) || PPSSPP_PLATFORM(ANDROID)
bool beginInputBox = false;
if (nativeStatus_ == PSPOskNativeStatus::IDLE) {
std::lock_guard<std::mutex> guard(nativeMutex_);
if (nativeStatus_ == PSPOskNativeStatus::IDLE) {
nativeStatus_ = PSPOskNativeStatus::WAITING;
beginInputBox = true;
}
}
if (beginInputBox) {
std::u16string titleText;
GetWideStringFromPSPPointer(titleText, oskParams->fields[0].desc);
std::u16string defaultText;
GetWideStringFromPSPPointer(defaultText, oskParams->fields[0].intext);
if (defaultText.empty())
defaultText.assign(u"VALUE");
// There's already ConvertUCS2ToUTF8 in this file. Should we use that instead of the global ones?
System_InputBoxGetString(NON_EPHEMERAL_TOKEN, ::ConvertUCS2ToUTF8(titleText), ::ConvertUCS2ToUTF8(defaultText), false,
[&](const std::string &value, int) {
// Success callback
std::lock_guard<std::mutex> guard(nativeMutex_);
if (nativeStatus_ != PSPOskNativeStatus::WAITING) {
return;
}
nativeValue_ = value;
nativeStatus_ = PSPOskNativeStatus::SUCCESS;
},
[&]() {
// Failure callback
std::lock_guard<std::mutex> guard(nativeMutex_);
if (nativeStatus_ != PSPOskNativeStatus::WAITING) {
return;
}
nativeValue_ = "";
nativeStatus_ = PSPOskNativeStatus::FAILURE;
}
);
} else if (nativeStatus_ == PSPOskNativeStatus::SUCCESS) {
inputChars = ConvertUTF8ToUCS2(nativeValue_);
nativeValue_.clear();
u32 maxLength = FieldMaxLength();
if (inputChars.length() > maxLength) {
ERROR_LOG(Log::sceUtility, "NativeKeyboard: input text too long(%d characters/glyphs max), truncating to game-requested length.", maxLength);
inputChars.erase(maxLength, std::string::npos);
}
ChangeStatus(SCE_UTILITY_STATUS_FINISHED, 0);
nativeStatus_ = PSPOskNativeStatus::DONE;
} else if (nativeStatus_ == PSPOskNativeStatus::FAILURE) {
ChangeStatus(SCE_UTILITY_STATUS_FINISHED, 0);
nativeStatus_ = PSPOskNativeStatus::DONE;
}
#endif
u16_le *outText = oskParams->fields[0].outtext;
size_t end = oskParams->fields[0].outtextlength;
if (end > inputChars.size())
end = inputChars.size() + 1;
// Only write the bytes of the output and the null terminator, don't write the rest.
for (size_t i = 0; i < end; ++i) {
u16 value = 0;
if (i < FieldMaxLength() && i < inputChars.size())
value = inputChars[i];
outText[i] = value;
}
oskParams->base.result = 0;
oskParams->fields[0].result = PSP_UTILITY_OSK_RESULT_CHANGED;
return 0;
}
int PSPOskDialog::Update(int animSpeed) {
if (GetStatus() != SCE_UTILITY_STATUS_RUNNING) {
return SCE_ERROR_UTILITY_INVALID_STATUS;
}
int cancelButton = GetCancelButton();
int confirmButton = GetConfirmButton();
static int cancelBtnFramesHeld = 0;
static int confirmBtnFramesHeld = 0;
static int leftBtnFramesHeld = 0;
static int upBtnFramesHeld = 0;
static int downBtnFramesHeld = 0;
static int rightBtnFramesHeld = 0;
const int framesHeldThreshold = 10;
const int framesHeldRepeatRate = 5;
UpdateButtons();
UpdateCommon();
int selectedRow = selectedChar / numKeyCols[currentKeyboard];
int selectedExtra = selectedChar % numKeyCols[currentKeyboard];
#if defined(USING_WIN_UI) || defined(USING_QT_UI) || PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(SWITCH)
// Windows: Fall back to the OSK/continue normally if we're in fullscreen.
// The dialog box doesn't work right if in fullscreen.
if (System_GetPropertyBool(SYSPROP_HAS_KEYBOARD)) {
if (g_Config.bBypassOSKWithKeyboard && !g_Config.UseFullScreen())
return NativeKeyboard();
}
#endif
UpdateFade(animSpeed);
StartDraw();
PPGeDrawRect(0, 0, 480, 272, CalcFadedColor(0x63636363));
RenderKeyboard();
auto di = GetI18NCategory(I18NCat::DIALOG);
PPGeStyle actionStyle = FadedStyle(PPGeAlign::BOX_LEFT, 0.5f);
PPGeStyle guideStyle = FadedStyle(PPGeAlign::BOX_LEFT, 0.6f);
PPGeDrawImage(ImageID("I_SQUARE"), 365, 222, 16, 16, guideStyle);
PPGeDrawText(di->T("Space"), 390, 222, actionStyle);
if (GetConfirmButton() != CTRL_CIRCLE) {
PPGeDrawImage(ImageID("I_CROSS"), 45, 222, 16, 16, guideStyle);
PPGeDrawImage(ImageID("I_CIRCLE"), 45, 247, 16, 16, guideStyle);
} else {
PPGeDrawImage(ImageID("I_CIRCLE"), 45, 222, 16, 16, guideStyle);
PPGeDrawImage(ImageID("I_CROSS"), 45, 247, 16, 16, guideStyle);
}
PPGeDrawText(di->T("Select"), 75, 222, actionStyle);
PPGeDrawText(di->T("Delete"), 75, 247, actionStyle);
PPGeDrawText("Start", 135, 220, guideStyle);
PPGeDrawText(di->T("Finish"), 185, 222, actionStyle);
auto lookupLangName = [&](int direction) {
// First, find the valid one...
OskKeyboardLanguage lang = (OskKeyboardLanguage)((OSK_LANGUAGE_COUNT + currentKeyboardLanguage + direction) % OSK_LANGUAGE_COUNT);
OskKeyboardDisplay disp = OskKeyboardCases[lang][LOWERCASE];
FindValidKeyboard(oskParams->fields[0].inputtype, direction, lang, disp);
if (lang == currentKeyboardLanguage) {
return (const char *)nullptr;
}
// Now, let's grab the name.
const char *countryCode = OskKeyboardNames[lang].c_str();
const char *language = languageMapping[countryCode].first.c_str();
// It seems like this is a "fake" country code for extra keyboard purposes.
if (!strcmp(countryCode, "English Full-width"))
language = "English Full-width";
return language;
};
if (OskKeyboardNames[currentKeyboardLanguage] != "ko_KR" && IsKeyboardShiftValid(oskParams->fields[0].inputtype, currentKeyboardLanguage, currentKeyboard)) {
PPGeDrawText("Select", 135, 245, guideStyle);
PPGeDrawText(di->T("Shift"), 185, 247, actionStyle);
}
const char *prevLang = lookupLangName(-1);
if (prevLang) {
PPGeDrawText("L", 235, 220, guideStyle);
PPGeDrawText(prevLang, 255, 222, actionStyle);
}
const char *nextLang = lookupLangName(1);
if (nextLang) {
PPGeDrawText("R", 235, 245, guideStyle);
PPGeDrawText(nextLang, 255, 247, actionStyle);
}
if (IsButtonPressed(CTRL_UP) || IsButtonHeld(CTRL_UP, upBtnFramesHeld, framesHeldThreshold, framesHeldRepeatRate)) {
selectedChar -= numKeyCols[currentKeyboard];
} else if (IsButtonPressed(CTRL_DOWN) || IsButtonHeld(CTRL_DOWN, downBtnFramesHeld, framesHeldThreshold, framesHeldRepeatRate)) {
selectedChar += numKeyCols[currentKeyboard];
} else if (IsButtonPressed(CTRL_LEFT) || IsButtonHeld(CTRL_LEFT, leftBtnFramesHeld, framesHeldThreshold, framesHeldRepeatRate)) {
selectedChar--;
if (((selectedChar + numKeyCols[currentKeyboard]) % numKeyCols[currentKeyboard]) == numKeyCols[currentKeyboard] - 1)
selectedChar += numKeyCols[currentKeyboard];
} else if (IsButtonPressed(CTRL_RIGHT) || IsButtonHeld(CTRL_RIGHT, rightBtnFramesHeld, framesHeldThreshold, framesHeldRepeatRate)) {
selectedChar++;
if ((selectedChar % numKeyCols[currentKeyboard]) == 0)
selectedChar -= numKeyCols[currentKeyboard];
}
selectedChar = (selectedChar + (numKeyCols[currentKeyboard] * numKeyRows[currentKeyboard])) % (numKeyCols[currentKeyboard] * numKeyRows[currentKeyboard]);
if (IsButtonPressed(confirmButton) || IsButtonHeld(confirmButton, confirmBtnFramesHeld, framesHeldThreshold, framesHeldRepeatRate)) {
inputChars = CombinationString(true);
} else if (IsButtonPressed(CTRL_SELECT)) {
// Select now swaps case.
if (IsKeyboardShiftValid(oskParams->fields[0].inputtype, currentKeyboardLanguage, currentKeyboard)) {
if (currentKeyboard == OskKeyboardCases[currentKeyboardLanguage][UPPERCASE])
currentKeyboard = OskKeyboardCases[currentKeyboardLanguage][LOWERCASE];
else
currentKeyboard = OskKeyboardCases[currentKeyboardLanguage][UPPERCASE];
}
if (selectedRow >= numKeyRows[currentKeyboard]) {
selectedRow = numKeyRows[currentKeyboard] - 1;
}
if (selectedExtra >= numKeyCols[currentKeyboard]) {
selectedExtra = numKeyCols[currentKeyboard] - 1;
}
selectedChar = selectedRow * numKeyCols[currentKeyboard] + selectedExtra;
} else if (IsButtonPressed(CTRL_RTRIGGER)) {
// RTRIGGER now cycles languages forward.
currentKeyboardLanguage = (OskKeyboardLanguage)((currentKeyboardLanguage + 1) % OSK_LANGUAGE_COUNT);
currentKeyboard = OskKeyboardCases[currentKeyboardLanguage][LOWERCASE];
FindValidKeyboard(oskParams->fields[0].inputtype, 1, currentKeyboardLanguage, currentKeyboard);
if (selectedRow >= numKeyRows[currentKeyboard]) {
selectedRow = numKeyRows[currentKeyboard] - 1;
}
if (selectedExtra >= numKeyCols[currentKeyboard]) {
selectedExtra = numKeyCols[currentKeyboard] - 1;
}
selectedChar = selectedRow * numKeyCols[currentKeyboard] + selectedExtra;
} else if (IsButtonPressed(CTRL_LTRIGGER)) {
// LTRIGGER now cycles languages backward.
if (currentKeyboardLanguage - 1 >= 0)
currentKeyboardLanguage = (OskKeyboardLanguage)((currentKeyboardLanguage - 1) % OSK_LANGUAGE_COUNT);
else
currentKeyboardLanguage = (OskKeyboardLanguage)(OSK_LANGUAGE_COUNT - 1);
currentKeyboard = OskKeyboardCases[currentKeyboardLanguage][LOWERCASE];
FindValidKeyboard(oskParams->fields[0].inputtype, -1, currentKeyboardLanguage, currentKeyboard);
if (selectedRow >= numKeyRows[currentKeyboard]) {
selectedRow = numKeyRows[currentKeyboard] - 1;
}
if (selectedExtra >= numKeyCols[currentKeyboard]) {
selectedExtra = numKeyCols[currentKeyboard] - 1;
}
selectedChar = selectedRow * numKeyCols[currentKeyboard] + selectedExtra;
} else if (IsButtonPressed(cancelButton) || IsButtonHeld(cancelButton, cancelBtnFramesHeld, framesHeldThreshold, framesHeldRepeatRate)) {
if (inputChars.size() > 0) {
inputChars.resize(inputChars.size() - 1);
if (i_level != 0) {
RemoveKorean();
}
}
} else if (IsButtonPressed(CTRL_START)) {
StartFade(false);
} else if (IsButtonPressed(CTRL_SQUARE) && inputChars.size() < FieldMaxLength()) {
// Use a regular space if the current keyboard isn't Japanese nor full-width English
if (currentKeyboardLanguage != OSK_LANGUAGE_JAPANESE && currentKeyboardLanguage != OSK_LANGUAGE_ENGLISH_FW)
inputChars += u" ";
else
inputChars += u" ";
}
EndDraw();
u16_le *outText = oskParams->fields[0].outtext;
size_t end = oskParams->fields[0].outtextlength;
// Only write the bytes of the output and the null terminator, don't write the rest.
if (end > inputChars.size())
end = inputChars.size() + 1;
for (size_t i = 0; i < end; ++i)
{
u16 value = 0;
if (i < FieldMaxLength() && i < inputChars.size())
value = inputChars[i];
outText[i] = value;
}
oskParams->base.result = 0;
oskParams->fields[0].result = PSP_UTILITY_OSK_RESULT_CHANGED;
return 0;
}
int PSPOskDialog::Shutdown(bool force)
{
if (GetStatus() != SCE_UTILITY_STATUS_FINISHED && !force)
return SCE_ERROR_UTILITY_INVALID_STATUS;
PSPDialog::Shutdown(force);
if (!force) {
ChangeStatusShutdown(OSK_SHUTDOWN_DELAY_US);
}
nativeStatus_ = PSPOskNativeStatus::IDLE;
return 0;
}
void PSPOskDialog::DoState(PointerWrap &p)
{
PSPDialog::DoState(p);
auto s = p.Section("PSPOskDialog", 1, 2);
if (!s)
return;
// TODO: Should we save currentKeyboard/currentKeyboardLanguage?
Do(p, oskParams);
Do(p, oskDesc);
Do(p, oskIntext);
Do(p, oskOuttext);
Do(p, selectedChar);
if (s >= 2) {
Do(p, inputChars);
} else {
// Discard the wstring.
std::wstring wstr;
Do(p, wstr);
}
// Don't need to save state native status or value.
}
pspUtilityDialogCommon *PSPOskDialog::GetCommonParam()
{
return &oskParams->base;
}