mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-16 16:00:30 +02:00
The core and the headless tools no longer use mutexes, condition variables, spinlocks, std::atomic, std::thread or volatile flags. Threads hand off through libretro-common's retro_atomic and rthreads plus a few small pieces on top of them in Common/Thread: Thread, MpscQueue, EventCounter, ParkingLot waits and ReaderGate. Every wait blocks until it's woken; nothing polls on a timer. State that only one thread touches just loses its lock. The PSP filesystem, sceIo and the savedata dialog now belong to the emulation thread: async reads and writes run when they're queued and complete later in emulated time, and the IO and savedata threads are gone. The friend finder only talks to the adhoc server and hands what it gets to the emulation thread. GPU init always runs on the thread that starts the game. The Vulkan render and compile queues, pipeline and render pass caches, delete list and SPIR-V cache, the GL render queue, the software renderer's jit caches, the file loader caches, the web debugger, UPnP, input, savestates, logging and the GE debugger all hand off through queues and atomics. The IR jit's spinning profiler thread goes. A CPU thread with nothing to run now sleeps until work is queued instead of spinning. Headless with --debugger waits for a client while stopped, and ends the run once every client that connected has left. Web server shutdown keeps servicing the CPU queue until the server has finished.
1104 lines
33 KiB
C++
Executable File
1104 lines
33 KiB
C++
Executable File
// 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/ErrorCodes.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 const uint8_t numKeyCols[OSK_KEYBOARD_COUNT] = {12, 12, 13, 13, 12, 12, 12, 12, 12};
|
||
static const uint8_t numKeyRows[OSK_KEYBOARD_COUNT] = {5, 5, 6, 6, 5, 4, 4, 4, 4};
|
||
|
||
// Korean (Hangul) vowel Combination key
|
||
static const uint8_t kor_vowelCom[21] = { 0,8,9,1,8,10,20,8,11,4,13,14,5,13,15,20,13,16,20,18,19 };
|
||
|
||
// Korean (Hangul) last consonant Combination key
|
||
static const uint8_t kor_lconsCom[33] = { 18,0,2,21,3,4,26,3,5,0,7,8,15,7,9,16,7,10,18,7,11,24,7,12,25,7,13,26,7,14,18,16,17 };
|
||
|
||
// Korean (Hangul) last consonant Separation key
|
||
static const uint8_t kor_lconsSpr[33] = { 2,1,9,4,4,12,5,4,18,8,8,0,9,8,6,10,8,7,11,8,9,12,8,16,13,8,17,14,8,18,17,17,9 };
|
||
|
||
static const std::string_view OskKeyboardNames[] =
|
||
{
|
||
"en_US",
|
||
"ja_JP",
|
||
"ko_KR",
|
||
"ru_RU",
|
||
"English Full-width",
|
||
};
|
||
|
||
// 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;
|
||
_string.clear();
|
||
for (u32 addr = em_address.ptr; Memory::IsValidRange(addr, 2); addr += 2) {
|
||
const u16 c = Memory::ReadUnchecked_U16(addr);
|
||
if (c == 0) {
|
||
break;
|
||
}
|
||
const size_t bytes = c < 0x80 ? 1 : (c < 0x800 ? 2 : 3);
|
||
if (_string.size() + bytes > maxLength) {
|
||
break;
|
||
}
|
||
if (c < 0x80) {
|
||
_string.push_back((char)c);
|
||
} else if (c < 0x800) {
|
||
_string.push_back((char)(0xC0 | (c >> 6)));
|
||
_string.push_back((char)(0x80 | (c & 0x3F)));
|
||
} else {
|
||
_string.push_back((char)(0xE0 | (c >> 12)));
|
||
_string.push_back((char)(0x80 | ((c >> 6) & 0x3F)));
|
||
_string.push_back((char)(0x80 | (c & 0x3F)));
|
||
}
|
||
}
|
||
}
|
||
|
||
void GetWideStringFromPSPPointer(std::u16string& _string, const PSPPointer<u16_le>& em_address)
|
||
{
|
||
const size_t maxLength = 2047;
|
||
_string.clear();
|
||
for (u32 addr = em_address.ptr; _string.size() < maxLength && Memory::IsValidRange(addr, 2); addr += 2) {
|
||
const u16 c = Memory::ReadUnchecked_U16(addr);
|
||
if (c == 0) {
|
||
break;
|
||
}
|
||
_string.push_back((char16_t)c);
|
||
}
|
||
}
|
||
|
||
void PSPOskDialog::ConvertUCS2ToUTF8(std::string& _string, const char16_t *input)
|
||
{
|
||
_string.clear();
|
||
int c;
|
||
while ((c = *input++) != 0)
|
||
{
|
||
if (c < 0x80) {
|
||
_string.push_back((char)c);
|
||
} else if (c < 0x800) {
|
||
_string.push_back((char)(0xC0 | (c >> 6)));
|
||
_string.push_back((char)(0x80 | (c & 0x3F)));
|
||
} else {
|
||
_string.push_back((char)(0xE0 | (c >> 12)));
|
||
_string.push_back((char)(0x80 | ((c >> 6) & 0x3F)));
|
||
_string.push_back((char)(0x80 | (c & 0x3F)));
|
||
}
|
||
}
|
||
}
|
||
|
||
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;
|
||
}
|
||
const int check = CheckRequest(oskPtr, { 0x40, 0x44 });
|
||
if (check < 0) {
|
||
ERROR_LOG_REPORT(Log::sceUtility, "sceUtilityOskInitStart: bad request at %08x: %08x", oskPtr, check);
|
||
return check;
|
||
}
|
||
|
||
oskParams = oskPtr;
|
||
// Seems to crash.
|
||
if (!Memory::IsValidRange(oskParams->fields.ptr, sizeof(SceUtilityOskData)))
|
||
{
|
||
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;
|
||
|
||
GetWideStringFromPSPPointer(inputChars, oskParams->fields[0].intext);
|
||
|
||
// Eat any keys pressed before the dialog inited.
|
||
UpdateButtons();
|
||
InitCommon();
|
||
|
||
// A new one, so a callback from an earlier input box can't reach this one.
|
||
native_ = std::make_shared<NativeInput>();
|
||
|
||
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 = OskKeyAt(currentKeyboard, selectedRow, selectedCol);
|
||
|
||
if (inputChars.size() < FieldMaxLength()) {
|
||
string += sw;
|
||
|
||
i_value[0] = GetIndex(KorCons(), 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 = OskKeyAt(currentKeyboard, selectedRow, selectedCol);
|
||
|
||
if(i_level == 0) {
|
||
string += inputChars[i];
|
||
if (inputChars.size() < FieldMaxLength()) {
|
||
string += sw;
|
||
|
||
i_value[0] = GetIndex(KorCons(), sw);
|
||
|
||
if(i_value[0] != -1 && isInput == true)
|
||
i_level = 1;
|
||
} else {
|
||
isCombinated = false;
|
||
}
|
||
} else if(i_level == 1) {
|
||
i_value[1] = GetIndex(KorVowel(), sw);
|
||
|
||
if(i_value[1] == -1) {
|
||
string += inputChars[i];
|
||
if (inputChars.size() < FieldMaxLength()) {
|
||
string += sw;
|
||
|
||
if(isInput == true) {
|
||
i_value[0] = GetIndex(KorCons(), 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(KorVowel(), 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(KorLCons(), sw);
|
||
|
||
if (tmp2 == -1) {
|
||
string += inputChars[i];
|
||
if (inputChars.size() < FieldMaxLength()) {
|
||
string += sw;
|
||
|
||
if (isInput == true) {
|
||
i_value[0] = GetIndex(KorCons(), 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(KorLCons(), 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(KorCons(), sw);
|
||
|
||
if(i_value[0] != -1)
|
||
i_level = 1;
|
||
else
|
||
i_level = 0;
|
||
}
|
||
} else {
|
||
isCombinated = false;
|
||
}
|
||
}
|
||
} else {
|
||
int tmp3 = GetIndex(KorVowel(), sw);
|
||
if (tmp3 == -1) {
|
||
string += inputChars[i];
|
||
if (inputChars.size() < FieldMaxLength()) {
|
||
string += sw;
|
||
|
||
if(isInput == true) {
|
||
i_value[0] = GetIndex(KorCons(), 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(KorCons(), KorLCons()[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(OskKeyAt(currentKeyboard, selectedRow, selectedCol) == L'゛')
|
||
{
|
||
for(u32 i = 0; i < inputChars.size(); i++)
|
||
{
|
||
if(i + 1 == inputChars.size())
|
||
{
|
||
for(u32 j = 0; j < wcslen(JapDiacritics(0)); j+=2)
|
||
{
|
||
if(inputChars[i] == JapDiacritics(0)[j])
|
||
{
|
||
string += JapDiacritics(0)[j + 1];
|
||
isCombinated = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if(isCombinated == false)
|
||
{
|
||
string += inputChars[i];
|
||
}
|
||
}
|
||
else
|
||
{
|
||
string += inputChars[i];
|
||
}
|
||
}
|
||
}
|
||
else if(OskKeyAt(currentKeyboard, selectedRow, selectedCol) == L'゜')
|
||
{
|
||
for(u32 i = 0; i < inputChars.size(); i++)
|
||
{
|
||
if(i + 1 == inputChars.size())
|
||
{
|
||
for(u32 j = 0; j < wcslen(JapDiacritics(1)); j+=2)
|
||
{
|
||
if(inputChars[i] == JapDiacritics(1)[j])
|
||
{
|
||
string += JapDiacritics(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 += OskKeyAt(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 += KorCons()[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()
|
||
{
|
||
const u32 length = oskParams->fields[0].outtextlength;
|
||
const u32 limit = oskParams->fields[0].outtextlimit;
|
||
if (length == 0)
|
||
return 0;
|
||
if (limit > length - 1 || limit == 0)
|
||
return length - 1;
|
||
return limit;
|
||
}
|
||
|
||
// Only writes the text and its terminator, not the rest of the buffer.
|
||
void PSPOskDialog::WriteOutput(SceUtilityOskResult fieldResult) {
|
||
const u32 addr = oskParams->fields[0].outtext.ptr;
|
||
size_t end = oskParams->fields[0].outtextlength;
|
||
if (end > inputChars.size())
|
||
end = inputChars.size() + 1;
|
||
if (end != 0 && !Memory::IsValidRange(addr, (u32)(end * sizeof(u16_le)))) {
|
||
ERROR_LOG_REPORT_ONCE(oskBadOutput, Log::sceUtility, "OSK output %08x (%d chars) isn't in memory", addr, (int)end);
|
||
} else if (end != 0) {
|
||
u16_le *outText = (u16_le *)Memory::GetPointerWriteUnchecked(addr);
|
||
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 = fieldResult;
|
||
}
|
||
|
||
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, 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);
|
||
if (result.size() == (size_t)limit + 1 && drawIndex > 0)
|
||
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, 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, 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, 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] = OskKeyAt(currentKeyboard, row, col);
|
||
|
||
ConvertUCS2ToUTF8(buffer, temp);
|
||
|
||
if (selectedRow == row && col == selectedCol) {
|
||
PPGeDrawText(buffer, 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, 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;
|
||
}
|
||
|
||
std::shared_ptr<NativeInput> native = native_;
|
||
PSPOskNativeStatus status;
|
||
std::string value;
|
||
status = native->Status();
|
||
if (status == PSPOskNativeStatus::IDLE) {
|
||
native->SetStatus(PSPOskNativeStatus::WAITING);
|
||
} else if (status == PSPOskNativeStatus::SUCCESS || status == PSPOskNativeStatus::FAILURE) {
|
||
value = std::move(native->value);
|
||
native->SetStatus(PSPOskNativeStatus::DONE);
|
||
} else if (status == PSPOskNativeStatus::FILLING) {
|
||
// A callback is writing the answer; treat it as still waiting.
|
||
status = PSPOskNativeStatus::WAITING;
|
||
}
|
||
|
||
if (status == PSPOskNativeStatus::IDLE) {
|
||
std::u16string titleText;
|
||
GetWideStringFromPSPPointer(titleText, oskParams->fields[0].desc);
|
||
|
||
std::u16string defaultText;
|
||
GetWideStringFromPSPPointer(defaultText, oskParams->fields[0].intext);
|
||
|
||
// 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,
|
||
[native](std::string_view value, int) {
|
||
// Success callback
|
||
native->Answer(PSPOskNativeStatus::SUCCESS, value);
|
||
},
|
||
[native](int responseValue) {
|
||
// Failure callback
|
||
native->Answer(PSPOskNativeStatus::FAILURE, std::string_view());
|
||
}
|
||
);
|
||
} else if (status == PSPOskNativeStatus::SUCCESS) {
|
||
inputChars = ConvertUTF8ToUCS2(value);
|
||
|
||
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);
|
||
} else if (status == PSPOskNativeStatus::FAILURE) {
|
||
ChangeStatus(SCE_UTILITY_STATUS_FINISHED, 0);
|
||
}
|
||
|
||
// TODO: A cancelled input box leaves the input text, but should it report CANCELLED? Untested.
|
||
WriteOutput(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 (PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)) || 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.bFullScreen)
|
||
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 friendly name from the langvaluesmapping.
|
||
const std::string countryCode(OskKeyboardNames[lang]);
|
||
|
||
const auto &languageMapping = GetLangValuesMapping();
|
||
const auto iter = languageMapping.find(countryCode);
|
||
const char *language = iter == languageMapping.end() ? "" : iter->second.first.c_str();
|
||
|
||
// It seems like this is a "fake" country code for extra keyboard purposes.
|
||
if (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();
|
||
|
||
WriteOutput(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);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
void PSPOskDialog::DoState(PointerWrap &p)
|
||
{
|
||
PSPDialog::DoState(p);
|
||
|
||
auto s = p.Section("PSPOskDialog", 1, 3);
|
||
if (!s)
|
||
return;
|
||
|
||
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);
|
||
}
|
||
if (s >= 3) {
|
||
Do(p, currentKeyboard);
|
||
Do(p, currentKeyboardLanguage);
|
||
Do(p, i_level);
|
||
DoArray(p, i_value, ARRAY_SIZE(i_value));
|
||
} else if (p.mode == p.MODE_READ) {
|
||
// Pick a keyboard the field allows, as Init does, and keep the selection on it.
|
||
currentKeyboardLanguage = OSK_LANGUAGE_ENGLISH;
|
||
currentKeyboard = OSK_KEYBOARD_LATIN_LOWERCASE;
|
||
if (oskParams.IsValid() && Memory::IsValidRange(oskParams->fields.ptr, sizeof(SceUtilityOskData))) {
|
||
FindValidKeyboard(oskParams->fields[0].inputtype, 0, currentKeyboardLanguage, currentKeyboard);
|
||
}
|
||
selectedChar = std::clamp(selectedChar, 0, numKeyCols[currentKeyboard] * numKeyRows[currentKeyboard] - 1);
|
||
i_level = 0;
|
||
}
|
||
if (p.mode == p.MODE_READ) {
|
||
// A box still open can answer into the loaded state (the next Update would only open
|
||
// another). One that finished before mustn't block the next.
|
||
const PSPOskNativeStatus status = native_->Status();
|
||
if (status != PSPOskNativeStatus::WAITING && status != PSPOskNativeStatus::FILLING) {
|
||
native_ = std::make_shared<NativeInput>();
|
||
}
|
||
}
|
||
// Don't need to save state native status or value.
|
||
}
|
||
|
||
pspUtilityDialogCommon *PSPOskDialog::GetCommonParam()
|
||
{
|
||
return &oskParams->base;
|
||
}
|