Android: Process input prompts more clearly.

This way, the processing is generally inline or explicitly a callback.
There's also less parsing issues since all the parsing is centralized.
This commit is contained in:
Unknown W. Brackets 2020-03-08 19:54:24 -07:00
parent efceb031ce
commit a3e12f4242
9 changed files with 127 additions and 113 deletions

View File

@ -121,7 +121,10 @@ UI::EventReturn ChatMenu::OnSubmit(UI::EventParams &e) {
chatEdit_->SetFocus();
sendChat(chat);
#elif PPSSPP_PLATFORM(ANDROID)
System_SendMessage("inputbox", "Chat:");
auto n = GetI18NCategory("Networking");
System_InputBoxGetString(n->T("Chat"), "", [](bool result, const std::string &value) {
sendChat(value);
});
#endif
return UI::EVENT_DONE;
}

View File

@ -982,7 +982,32 @@ UI::EventReturn GameSettingsScreen::OnJitAffectingSetting(UI::EventParams &e) {
UI::EventReturn GameSettingsScreen::OnChangeMemStickDir(UI::EventParams &e) {
auto sy = GetI18NCategory("System");
System_SendMessage("inputbox", (std::string(sy->T("Memory Stick Folder")) + ":" + g_Config.memStickDirectory).c_str());
System_InputBoxGetString(sy->T("Memory Stick Folder"), g_Config.memStickDirectory, [&](bool result, const std::string &value) {
auto sy = GetI18NCategory("System");
auto di = GetI18NCategory("Dialog");
if (result) {
std::string newPath = value;
size_t pos = newPath.find_last_not_of("/");
// Gotta have at least something but a /, and also needs to start with a /.
if (newPath.empty() || pos == newPath.npos || newPath[0] != '/') {
settingInfo_->Show(sy->T("ChangingMemstickPathInvalid", "That path couldn't be used to save Memory Stick files."), nullptr);
return;
}
if (pos != newPath.size() - 1) {
newPath = newPath.substr(0, pos + 1);
}
pendingMemstickFolder_ = newPath;
std::string promptMessage = sy->T("ChangingMemstickPath", "Save games, save states, and other data will not be copied to this folder.\n\nChange the Memory Stick folder?");
if (!File::Exists(newPath)) {
promptMessage = sy->T("ChangingMemstickPathNotExists", "That folder doesn't exist yet.\n\nSave games, save states, and other data will not be copied to this folder.\n\nCreate a new Memory Stick folder?");
}
// Add the path for clarity and proper confirmation.
promptMessage += "\n\n" + newPath + "/";
screenManager()->push(new PromptScreen(promptMessage, di->T("Yes"), di->T("No"), std::bind(&GameSettingsScreen::CallbackMemstickFolder, this, std::placeholders::_1)));
}
});
return UI::EVENT_DONE;
}
@ -1139,43 +1164,6 @@ void GameSettingsScreen::onFinish(DialogResult result) {
NativeMessageReceived("gpu_clearCache", "");
}
void GameSettingsScreen::sendMessage(const char *message, const char *value) {
UIDialogScreenWithGameBackground::sendMessage(message, value);
auto sy = GetI18NCategory("System");
auto di = GetI18NCategory("Dialog");
if (!strcmp(message, "inputbox_completed")) {
std::vector<std::string> inputboxValue;
SplitString(value, ':', inputboxValue);
#if PPSSPP_PLATFORM(ANDROID)
if (inputboxValue.size() >= 2 && inputboxValue[0] == sy->T("Memory Stick Folder")) {
// Allow colons in the path.
std::string newPath = std::string(value).substr(inputboxValue[0].size() + 1);
size_t pos = newPath.find_last_not_of("/");
// Gotta have at least something but a /, and also needs to start with a /.
if (newPath.empty() || pos == newPath.npos || newPath[0] != '/') {
settingInfo_->Show(sy->T("ChangingMemstickPathInvalid", "That path couldn't be used to save Memory Stick files."), nullptr);
return;
}
if (pos != newPath.size() - 1) {
newPath = newPath.substr(0, pos + 1);
}
pendingMemstickFolder_ = newPath;
std::string promptMessage = sy->T("ChangingMemstickPath", "Save games, save states, and other data will not be copied to this folder.\n\nChange the Memory Stick folder?");
if (!File::Exists(newPath)) {
promptMessage = sy->T("ChangingMemstickPathNotExists", "That folder doesn't exist yet.\n\nSave games, save states, and other data will not be copied to this folder.\n\nCreate a new Memory Stick folder?");
}
// Add the path for clarity and proper confirmation.
promptMessage += "\n\n" + newPath + "/";
screenManager()->push(new PromptScreen(promptMessage, di->T("Yes"), di->T("No"), std::bind(&GameSettingsScreen::CallbackMemstickFolder, this, std::placeholders::_1)));
}
#endif
}
}
#if PPSSPP_PLATFORM(ANDROID)
void GameSettingsScreen::CallbackMemstickFolder(bool yes) {
auto sy = GetI18NCategory("System");
@ -1289,86 +1277,73 @@ UI::EventReturn GameSettingsScreen::OnAudioDevice(UI::EventParams &e) {
}
UI::EventReturn GameSettingsScreen::OnChangeQuickChat0(UI::EventParams &e) {
#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI)
#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || defined(__ANDROID__)
auto n = GetI18NCategory("Networking");
System_InputBoxGetString(n->T("Enter Quick Chat 1"), g_Config.sQuickChat0, [](bool result, const std::string &value) {
if (result) {
g_Config.sQuickChat0 = value;
}
});
#elif defined(__ANDROID__)
System_SendMessage("inputbox", ("quickchat0:" + g_Config.sQuickChat0).c_str());
#endif
return UI::EVENT_DONE;
}
UI::EventReturn GameSettingsScreen::OnChangeQuickChat1(UI::EventParams &e) {
#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI)
#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || defined(__ANDROID__)
auto n = GetI18NCategory("Networking");
System_InputBoxGetString(n->T("Enter Quick Chat 2"), g_Config.sQuickChat1, [](bool result, const std::string &value) {
if (result) {
g_Config.sQuickChat1 = value;
}
});
#elif defined(__ANDROID__)
System_SendMessage("inputbox", ("quickchat1:" + g_Config.sQuickChat1).c_str());
#endif
return UI::EVENT_DONE;
}
UI::EventReturn GameSettingsScreen::OnChangeQuickChat2(UI::EventParams &e) {
#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI)
#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || defined(__ANDROID__)
auto n = GetI18NCategory("Networking");
System_InputBoxGetString(n->T("Enter Quick Chat 3"), g_Config.sQuickChat2, [](bool result, const std::string &value) {
if (result) {
g_Config.sQuickChat2 = value;
}
});
#elif defined(__ANDROID__)
System_SendMessage("inputbox", ("quickchat2:" + g_Config.sQuickChat2).c_str());
#endif
return UI::EVENT_DONE;
}
UI::EventReturn GameSettingsScreen::OnChangeQuickChat3(UI::EventParams &e) {
#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI)
#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || defined(__ANDROID__)
auto n = GetI18NCategory("Networking");
System_InputBoxGetString(n->T("Enter Quick Chat 4"), g_Config.sQuickChat3, [](bool result, const std::string &value) {
if (result) {
g_Config.sQuickChat3 = value;
}
});
#elif defined(__ANDROID__)
System_SendMessage("inputbox", ("quickchat3:" + g_Config.sQuickChat3).c_str());
#endif
return UI::EVENT_DONE;
}
UI::EventReturn GameSettingsScreen::OnChangeQuickChat4(UI::EventParams &e) {
#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI)
#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || defined(__ANDROID__)
auto n = GetI18NCategory("Networking");
System_InputBoxGetString(n->T("Enter Quick Chat 5"), g_Config.sQuickChat4, [](bool result, const std::string &value) {
if (result) {
g_Config.sQuickChat4 = value;
}
});
#elif defined(__ANDROID__)
System_SendMessage("inputbox", ("quickchat4:" + g_Config.sQuickChat4).c_str());
#endif
return UI::EVENT_DONE;
}
UI::EventReturn GameSettingsScreen::OnChangeNickname(UI::EventParams &e) {
#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI)
#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || defined(__ANDROID__)
auto n = GetI18NCategory("Networking");
System_InputBoxGetString(n->T("Enter a new PSP nickname"), g_Config.sNickName, [](bool result, const std::string &value) {
if (result) {
g_Config.sNickName = StripSpaces(value);
}
});
#elif defined(__ANDROID__)
// TODO: The return value is handled in NativeApp::inputbox_completed. This is horrific.
System_SendMessage("inputbox", ("nickname:" + g_Config.sNickName).c_str());
#endif
return UI::EVENT_DONE;
}
@ -1377,7 +1352,12 @@ UI::EventReturn GameSettingsScreen::OnChangeproAdhocServerAddress(UI::EventParam
auto sy = GetI18NCategory("System");
#if defined(__ANDROID__)
System_SendMessage("inputbox", ("IP:" + g_Config.proAdhocServer).c_str());
auto n = GetI18NCategory("Networking");
System_InputBoxGetString(n->T("IP"), g_Config.proAdhocServer, [](bool result, const std::string &value) {
if (result) {
g_Config.proAdhocServer = value;
}
});
#else
screenManager()->push(new HostnameSelectScreen(&g_Config.proAdhocServer, sy->T("proAdhocServer Address:")));
#endif

View File

@ -34,7 +34,6 @@ public:
void update() override;
void onFinish(DialogResult result) override;
void sendMessage(const char *message, const char *value) override;
std::string tag() const override { return "settings"; }
protected:

View File

@ -159,13 +159,19 @@ struct PendingMessage {
std::string value;
};
struct PendingInputBox {
std::function<void(bool, const std::string &)> cb;
bool result;
std::string value;
};
static std::mutex pendingMutex;
static std::vector<PendingMessage> pendingMessages;
static std::vector<PendingInputBox> pendingInputBoxes;
static Draw::DrawContext *g_draw;
static Draw::Pipeline *colorPipeline;
static Draw::Pipeline *texColorPipeline;
static UIContext *uiContext;
static std::vector<std::string> inputboxValue;
#ifdef _WIN32
WindowsAudioBackend *winAudioBackend;
@ -445,6 +451,7 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch
setlocale( LC_ALL, "C" );
std::string user_data_path = savegame_dir;
pendingMessages.clear();
pendingInputBoxes.clear();
#ifdef IOS
user_data_path += "/";
#endif
@ -1093,42 +1100,6 @@ void HandleGlobalMessage(const std::string &msg, const std::string &value) {
if (msg == "inputDeviceConnected") {
KeyMap::NotifyPadConnected(value);
}
if (msg == "inputbox_completed") {
SplitString(value, ':', inputboxValue);
std::string setString = inputboxValue.size() > 1 ? inputboxValue[1] : "";
if (inputboxValue[0] == "IP")
g_Config.proAdhocServer = setString;
else if (inputboxValue[0] == "nickname")
g_Config.sNickName = setString;
else if (inputboxValue[0] == "remoteiso_subdir")
g_Config.sRemoteISOSubdir = setString;
else if (inputboxValue[0] == "remoteiso_server")
g_Config.sLastRemoteISOServer = setString;
if (inputboxValue[0] == "quickchat0")
g_Config.sQuickChat0 = setString;
if (inputboxValue[0] == "quickchat1")
g_Config.sQuickChat1 = setString;
if (inputboxValue[0] == "quickchat2")
g_Config.sQuickChat2 = setString;
if (inputboxValue[0] == "quickchat3")
g_Config.sQuickChat3 = setString;
if (inputboxValue[0] == "quickchat4")
g_Config.sQuickChat4 = setString;
if (inputboxValue[0] == "nickname")
g_Config.sNickName = setString;
if (inputboxValue[0] == "Chat") {
if (inputboxValue.size() > 2)
{
std::string chatString = value;
chatString.erase(0, 5);
sendChat(chatString);
} else {
sendChat(setString);
}
}
inputboxValue.clear();
}
if (msg == "bgImage_updated") {
if (!value.empty()) {
std::string dest = GetSysDirectory(DIRECTORY_SYSTEM) + (endsWithNoCase(value, ".jpg") ? "background.jpg" : "background.png");
@ -1183,15 +1154,21 @@ void NativeUpdate() {
PROFILE_END_FRAME();
std::vector<PendingMessage> toProcess;
std::vector<PendingInputBox> inputToProcess;
{
std::lock_guard<std::mutex> lock(pendingMutex);
toProcess = std::move(pendingMessages);
inputToProcess = std::move(pendingInputBoxes);
pendingMessages.clear();
pendingInputBoxes.clear();
}
for (size_t i = 0; i < toProcess.size(); i++) {
HandleGlobalMessage(toProcess[i].msg, toProcess[i].value);
screenManager->sendMessage(toProcess[i].msg.c_str(), toProcess[i].value.c_str());
for (const auto &item : toProcess) {
HandleGlobalMessage(item.msg, item.value);
screenManager->sendMessage(item.msg.c_str(), item.value.c_str());
}
for (const auto &item : inputToProcess) {
item.cb(item.result, item.value);
}
g_DownloadManager.Update();
@ -1338,6 +1315,15 @@ void NativeMessageReceived(const char *message, const char *value) {
pendingMessages.push_back(pendingMessage);
}
void NativeInputBoxReceived(std::function<void(bool, const std::string &)> cb, bool result, const std::string &value) {
std::lock_guard<std::mutex> lock(pendingMutex);
PendingInputBox pendingMessage;
pendingMessage.cb = cb;
pendingMessage.result = result;
pendingMessage.value = value;
pendingInputBoxes.push_back(pendingMessage);
}
void NativeResized() {
// NativeResized can come from any thread so we just set a flag, then process it later.
ILOG("NativeResized - setting flag");

View File

@ -520,12 +520,22 @@ void RemoteISOSettingsScreen::CreateViews() {
}
UI::EventReturn RemoteISOSettingsScreen::OnClickRemoteServer(UI::EventParams &e) {
System_SendMessage("inputbox", ("remoteiso_server:" + g_Config.sLastRemoteISOServer).c_str());
#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || defined(__ANDROID__)
auto ri = GetI18NCategory("RemoteISO");
System_InputBoxGetString(ri->T("Remote Server"), g_Config.sLastRemoteISOServer, [](bool result, const std::string &value) {
g_Config.sLastRemoteISOServer = value;
});
#endif
return UI::EVENT_DONE;
}
UI::EventReturn RemoteISOSettingsScreen::OnClickRemoteISOSubdir(UI::EventParams &e) {
System_SendMessage("inputbox", ("remoteiso_subdir:" + g_Config.sRemoteISOSubdir).c_str());
#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || defined(__ANDROID__)
auto ri = GetI18NCategory("RemoteISO");
System_InputBoxGetString(ri->T("Remote Subdirectory"), g_Config.sRemoteISOSubdir, [](bool result, const std::string &value) {
g_Config.sRemoteISOSubdir = value;
});
#endif
return UI::EVENT_DONE;
}

View File

@ -123,6 +123,8 @@ static jmethodID postCommand;
static jobject nativeActivity;
static volatile bool exitRenderLoop;
static bool renderLoopRunning;
static int inputBoxSequence = 1;
std::map<int, std::function<void(bool, const std::string &)>> inputBoxCallbacks;
static float dp_xscale = 1.0f;
static float dp_yscale = 1.0f;
@ -572,6 +574,7 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_shutdown(JNIEnv *, jclass) {
ILOG("Not shutting down renderer - not initialized");
}
inputBoxCallbacks.clear();
NativeShutdown();
VFSShutdown();
while (frameCommands.size())
@ -667,6 +670,33 @@ extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_backbufferResize(JNIEnv
}
}
void System_InputBoxGetString(const std::string &title, const std::string &defaultValue, std::function<void(bool, const std::string &)> cb) {
int seq = inputBoxSequence++;
inputBoxCallbacks[seq] = cb;
std::string serialized = StringFromFormat("%d:%s:%s", seq, title.c_str(), defaultValue.c_str());
System_SendMessage("inputbox", serialized.c_str());
}
extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_sendInputBox(JNIEnv *env, jclass, jstring jseqID, jboolean result, jstring jvalue) {
std::string seqID = GetJavaString(env, jseqID);
std::string value = GetJavaString(env, jvalue);
int seq = 0;
if (!TryParse(seqID, &seq)) {
ELOG("Invalid inputbox seqID value: %s", seqID.c_str());
return;
}
auto entry = inputBoxCallbacks.find(seq);
if (entry == inputBoxCallbacks.end()) {
ELOG("Did not find inputbox callback for %s, shutdown?", seqID.c_str());
return;
}
NativeInputBoxReceived(entry->second, result, value);
}
void UpdateRunLoopAndroid(JNIEnv *env) {
NativeUpdate();

View File

@ -1169,8 +1169,8 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C
return bld;
}
// The return value is sent elsewhere. TODO in java, in SendMessage in C++.
public void inputBox(final String title, String defaultText, String defaultAction) {
// The return value is sent to C++ via seqID.
public void inputBox(final String seqID, final String title, String defaultText, String defaultAction) {
final FrameLayout fl = new FrameLayout(this);
final EditText input = new EditText(this);
input.setGravity(Gravity.CENTER);
@ -1202,14 +1202,14 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C
.setPositiveButton(defaultAction, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface d, int which) {
NativeApp.sendMessage("inputbox_completed", title + ":" + input.getText().toString());
NativeApp.sendInputBox(seqID, true, input.getText().toString());
d.dismiss();
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface d, int which) {
NativeApp.sendMessage("inputbox_failed", "");
NativeApp.sendInputBox(seqID, false, "");
d.cancel();
}
})
@ -1311,15 +1311,17 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C
inputMethodManager.toggleSoftInputFromWindow(surfView.getApplicationWindowToken(), InputMethodManager.SHOW_FORCED, 0);
return true;
} else if (command.equals("inputbox")) {
String seqID = "";
String title = "Input";
String defString = "";
String[] param = params.split(":");
if (param[0].length() > 0)
title = param[0];
if (param.length > 1)
defString = param[1];
Log.i(TAG, "Launching inputbox: " + title + " " + defString);
inputBox(title, defString, "OK");
String[] param = params.split(":", 3);
seqID = param[0];
if (param.length > 1 && param[1].length() > 0)
title = param[1];
if (param.length > 2)
defString = param[2];
Log.i(TAG, "Launching inputbox: #" + seqID + " " + title + " " + defString);
inputBox(seqID, title, defString, "OK");
return true;
} else if (command.equals("vibrate")) {
int milliseconds = -1;

View File

@ -48,6 +48,7 @@ public class NativeApp {
public static native boolean accelerometer(float x, float y, float z);
public static native void sendMessage(String msg, String arg);
public static native void sendInputBox(String seqID, boolean result, String value);
public static native String queryConfig(String queryName);

View File

@ -35,6 +35,9 @@ void NativeGetAppInfo(std::string *app_dir_name, std::string *app_nice_name, boo
// Generic host->C++ messaging, used for functionality like system-native popup input boxes.
void NativeMessageReceived(const char *message, const char *value);
// This is used to communicate back and thread requested input box strings.
void NativeInputBoxReceived(std::function<void(bool, const std::string &)> cb, bool result, const std::string &value);
// Easy way for the Java side to ask the C++ side for configuration options, such as
// the rotation lock which must be controlled from Java on Android.
// It is currently not called on non-Android platforms.