Allow copying java EditText to STK editbox

This commit is contained in:
Benau 2019-05-25 00:40:37 +08:00
parent 27f0f8c961
commit e6d5346e5e
8 changed files with 358 additions and 37 deletions

View File

@ -166,7 +166,8 @@ LOCAL_CFLAGS := -I../lib/angelscript/include \
-DNDEBUG \
-DANDROID_PACKAGE_NAME=\"$(PACKAGE_NAME)\" \
-DANDROID_APP_DIR_NAME=\"$(APP_DIR_NAME)\" \
-DSUPERTUXKART_VERSION=\"$(PROJECT_VERSION)\"
-DSUPERTUXKART_VERSION=\"$(PROJECT_VERSION)\" \
-DANDROID_PACKAGE_CALLBACK_NAME=$(PACKAGE_CALLBACK_NAME)
LOCAL_CPPFLAGS := -std=gnu++0x
LOCAL_STATIC_LIBRARIES := irrlicht bullet enet freetype ifaddrs angelscript \

View File

@ -471,7 +471,22 @@ sed -i "s/targetSdkVersion=\".*\"/targetSdkVersion=\"$TARGET_SDK_VERSION\"/g" \
sed -i "s/package=\".*\"/package=\"$PACKAGE_NAME\"/g" \
"$DIRNAME/AndroidManifest.xml"
sed -i "s/package .*/package $PACKAGE_NAME;/g" \
sed -i "s/package org.supertuxkart.*/package $PACKAGE_NAME;/g" \
"$DIRNAME/src/main/java/STKEditText.java"
sed -i "s/import org.supertuxkart.*/import $PACKAGE_NAME.STKInputConnection;/g" \
"$DIRNAME/src/main/java/STKEditText.java"
sed -i "s/package org.supertuxkart.*/package $PACKAGE_NAME;/g" \
"$DIRNAME/src/main/java/STKInputConnection.java"
sed -i "s/import org.supertuxkart.*.STKEditText;/import $PACKAGE_NAME.STKEditText;/g" \
"$DIRNAME/src/main/java/STKInputConnection.java"
sed -i "s/package org.supertuxkart.*/package $PACKAGE_NAME;/g" \
"$DIRNAME/src/main/java/SuperTuxKartActivity.java"
sed -i "s/import org.supertuxkart.*/import $PACKAGE_NAME.STKEditText;/g" \
"$DIRNAME/src/main/java/SuperTuxKartActivity.java"
sed -i "s/versionName=\".*\"/versionName=\"$PROJECT_VERSION\"/g" \

View File

@ -0,0 +1,78 @@
package org.supertuxkart.stk_dbg;
import org.supertuxkart.stk_dbg.STKInputConnection;
import android.content.Context;
import android.text.InputType;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.EditText;
// We need to extend EditText instead of view to allow copying to our STK
// editbox
public class STKEditText extends EditText
{
private int m_composing_start;
private int m_composing_end;
// ------------------------------------------------------------------------
private native static void editText2STKEditbox(String full_text, int start,
int end, int composing_start,
int composing_end);
// ------------------------------------------------------------------------
public STKEditText(Context context)
{
super(context);
setFocusableInTouchMode(true);
m_composing_start = 0;
m_composing_end = 0;
}
// ------------------------------------------------------------------------
@Override
public InputConnection onCreateInputConnection(EditorInfo out_attrs)
{
STKInputConnection sic =
new STKInputConnection(super.onCreateInputConnection(out_attrs), this);
out_attrs.actionLabel = null;
out_attrs.inputType = InputType.TYPE_CLASS_TEXT;
out_attrs.imeOptions = EditorInfo.IME_ACTION_NEXT |
EditorInfo.IME_FLAG_NO_FULLSCREEN |
EditorInfo.IME_FLAG_NO_EXTRACT_UI;
return sic;
}
// ------------------------------------------------------------------------
@Override
public boolean onCheckIsTextEditor() { return true; }
// ------------------------------------------------------------------------
public void resetWhenFocus()
{
clearComposingText();
getText().clear();
m_composing_start = m_composing_end = 0;
}
// ------------------------------------------------------------------------
public void setComposingRegion(int start, int end)
{
// From doc of InputConnectionWrapper, it says:
// Editor authors, be ready to accept a start that is greater than end.
if (start != end && start > end)
{
m_composing_end = start;
m_composing_start = end;
}
else
{
m_composing_start = start;
m_composing_end = end;
}
}
// ------------------------------------------------------------------------
public void updateSTKEditBox()
{
if (!isFocused())
return;
editText2STKEditbox(getText().toString(), getSelectionStart(),
getSelectionEnd(), m_composing_start, m_composing_end);
}
}

View File

@ -0,0 +1,68 @@
package org.supertuxkart.stk_dbg;
import org.supertuxkart.stk_dbg.STKEditText;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnectionWrapper;
public class STKInputConnection extends InputConnectionWrapper
{
/* The global edittext which will be "copied" to the current focused STK
* box. */
private STKEditText m_stk_edittext;
// ------------------------------------------------------------------------
public STKInputConnection(InputConnection target, STKEditText stk_edittext)
{
super(target, true/*mutable*/);
m_stk_edittext = stk_edittext;
}
// ------------------------------------------------------------------------
@Override
public boolean setComposingText(CharSequence text, int new_cursor_position)
{
boolean ret = super.setComposingText(text, new_cursor_position);
String composing_text = text.toString();
String new_text = m_stk_edittext.getText().toString();
int composing_start = 0;
int composing_end = 0;
// Test last char
if (!composing_text.isEmpty() && !new_text.isEmpty() &&
composing_text.charAt(composing_text.length() - 1) ==
new_text.charAt(new_text.length() - 1))
{
composing_start = new_text.length() - composing_text.length();
composing_end = composing_start + composing_text.length();
}
m_stk_edittext.setComposingRegion(composing_start, composing_end);
m_stk_edittext.updateSTKEditBox();
return ret;
}
// ------------------------------------------------------------------------
@Override
public boolean finishComposingText()
{
m_stk_edittext.setComposingRegion(0, 0);
m_stk_edittext.updateSTKEditBox();
return super.finishComposingText();
}
// ------------------------------------------------------------------------
@Override
public boolean setComposingRegion(int start, int end)
{
m_stk_edittext.setComposingRegion(start, end);
m_stk_edittext.updateSTKEditBox();
return super.setComposingRegion(start, end);
}
// ------------------------------------------------------------------------
@Override
public boolean commitText(CharSequence text, int new_cursor_position)
{
// Usually only a single character, so dismiss composing region
boolean ret = super.commitText(text, new_cursor_position);
m_stk_edittext.setComposingRegion(0, 0);
m_stk_edittext.updateSTKEditBox();
return ret;
}
}

View File

@ -1,20 +1,27 @@
package org.supertuxkart.stk_dbg;
import org.supertuxkart.stk_dbg.STKEditText;
import android.app.NativeActivity;
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.inputmethod.InputMethodManager;
import android.view.KeyEvent;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.View;
import android.widget.FrameLayout;
public class SuperTuxKartActivity extends NativeActivity
{
private native void saveFromJavaChars(String chars);
private native void saveKeyboardHeight(int height);
private STKEditText m_stk_edittext;
// ------------------------------------------------------------------------
private native void saveKeyboardHeight(int height);
// ------------------------------------------------------------------------
private void hideNavBar(View decor_view)
{
if (Build.VERSION.SDK_INT < 19)
@ -27,12 +34,14 @@ public class SuperTuxKartActivity extends NativeActivity
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
}
// ------------------------------------------------------------------------
@Override
public void onCreate(Bundle instance)
{
super.onCreate(instance);
System.loadLibrary("main");
m_stk_edittext = null;
final View root = getWindow().getDecorView().findViewById(
android.R.id.content);
root.getViewTreeObserver().addOnGlobalLayoutListener(new
@ -61,26 +70,7 @@ public class SuperTuxKartActivity extends NativeActivity
}
});
}
@Override
public boolean dispatchKeyEvent(KeyEvent event)
{
// ACTION_MULTIPLE deprecated in API level Q, it says if the key code
// is KEYCODE_UNKNOWN, then this is a sequence of characters as
// returned by getCharacters()
if (event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN &&
event.getAction() == KeyEvent.ACTION_MULTIPLE)
{
String chars = event.getCharacters();
if (chars != null)
{
saveFromJavaChars(chars);
return true;
}
}
return super.dispatchKeyEvent(event);
}
// ------------------------------------------------------------------------
@Override
public void onWindowFocusChanged(boolean has_focus)
{
@ -88,20 +78,88 @@ public class SuperTuxKartActivity extends NativeActivity
if (has_focus)
hideNavBar(getWindow().getDecorView());
}
// ------------------------------------------------------------------------
@Override
public boolean dispatchKeyEvent(KeyEvent event)
{
// Called when user change cursor / select all text in native android
// keyboard
boolean ret = super.dispatchKeyEvent(event);
if (m_stk_edittext != null)
m_stk_edittext.updateSTKEditBox();
return ret;
}
// ------------------------------------------------------------------------
public void showKeyboard()
{
InputMethodManager imm = (InputMethodManager)
getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(getWindow().getDecorView(),
InputMethodManager.SHOW_FORCED);
}
public void hideKeyboard()
final Context context = this;
// Need to run in ui thread as it access the view m_stk_edittext
runOnUiThread(new Runnable()
{
@Override
public void run()
{
InputMethodManager imm = (InputMethodManager)
getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(
getWindow().getDecorView().getWindowToken(), 0);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT);
if (m_stk_edittext == null)
{
m_stk_edittext = new STKEditText(context);
// For some copy-and-paste text are not done by commitText
// in STKInputConnection, so we need an extra watcher
m_stk_edittext.addTextChangedListener(new TextWatcher()
{
@Override
public void onTextChanged(CharSequence s,
int start, int before,
int count) {}
@Override
public void beforeTextChanged(CharSequence s,
int start, int count,
int after) {}
@Override
public void afterTextChanged(Editable edit)
{
if (m_stk_edittext != null)
m_stk_edittext.updateSTKEditBox();
}
});
addContentView(m_stk_edittext, params);
}
else
m_stk_edittext.setLayoutParams(params);
m_stk_edittext.resetWhenFocus();
m_stk_edittext.setVisibility(View.VISIBLE);
m_stk_edittext.requestFocus();
imm.showSoftInput(m_stk_edittext,
InputMethodManager.SHOW_FORCED);
}
});
}
// ------------------------------------------------------------------------
public void hideKeyboard()
{
runOnUiThread(new Runnable()
{
@Override
public void run()
{
if (m_stk_edittext == null)
return;
m_stk_edittext.clearFocus();
m_stk_edittext.setVisibility(View.GONE);
InputMethodManager imm = (InputMethodManager)
getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(m_stk_edittext.getWindowToken(),
0);
}
});
}
}

View File

@ -23,6 +23,8 @@
#include "../../../lib/irrlicht/include/IrrCompileConfig.h"
#include "../../../lib/irrlicht/source/Irrlicht/CIrrDeviceLinux.h"
#include <cstdlib>
#ifdef ANDROID
#include "../../../lib/irrlicht/source/Irrlicht/CIrrDeviceAndroid.h"
#endif
@ -69,6 +71,9 @@ CGUIEditBox::CGUIEditBox(const wchar_t* text, bool border,
// FIXME quick hack to enable mark movement with keyboard and mouse for rtl language,
// don't know why it's disabled in the first place, because STK fail
// to input unicode characters before?
m_from_android_edittext = false;
m_composing_start = 0;
m_composing_end = 0;
#ifdef _DEBUG
setDebugName("CGUIEditBox");
@ -261,6 +266,9 @@ bool CGUIEditBox::OnEvent(const SEvent& event)
#ifndef SERVER_ONLY
if (isEnabled())
{
// Ignore key input if we only fromAndroidEditText
if (m_from_android_edittext && event.EventType == EET_KEY_INPUT_EVENT)
return true;
switch(event.EventType)
{
case EET_GUI_EVENT:
@ -287,6 +295,9 @@ bool CGUIEditBox::OnEvent(const SEvent& event)
dl->setTextInputEnabled(false);
}
#endif
m_from_android_edittext = false;
m_composing_start = 0;
m_composing_end = 0;
}
else if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUSED)
{
@ -1073,6 +1084,19 @@ void CGUIEditBox::draw()
// it will return the input pointer if (this->isRTLLanguage()) from Translations::isRTLText
// is false
// draw composing text underline
if (!PasswordBox && focus && m_composing_start != m_composing_end && i == hlineStart)
{
s = txtLine->subString(0, m_composing_start);
s32 underline_begin = font->getDimension(s.c_str()).Width;
core::rect<s32> underline = CurrentTextRect;
underline.UpperLeftCorner.X += underline_begin;
s32 height = underline.LowerRightCorner.Y - underline.UpperLeftCorner.Y;
underline.UpperLeftCorner.Y += s32(std::abs(height) * 0.9f);
underline.LowerRightCorner.Y -= s32(std::abs(height) * 0.08f);
GL32_draw2DRectangle(video::SColor(255, 0, 0, 0), underline);
}
// draw mark and marked text
if (focus && MarkBegin != MarkEnd && i >= hlineStart && i < hlineStart + hlineCount)
{
@ -1780,3 +1804,40 @@ void CGUIEditBox::openScreenKeyboard()
new GUIEngine::ScreenKeyboard(1.0f, 0.40f, this);
}
// Real copying is happening in text_box_widget.cpp with static function
void CGUIEditBox::fromAndroidEditText(const core::stringw& text, int start,
int end, int composing_start,
int composing_end)
{
// When focus of this element is lost, this will be set to false again
m_from_android_edittext = true;
Text = text;
// Prevent invalid start or end
if ((unsigned)end > Text.size())
{
end = (int)Text.size();
start = end;
}
CursorPos = end;
m_composing_start = 0;
m_composing_end = 0;
if (start != end)
setTextMarkers(start, end);
else
{
MarkBegin = 0;
MarkEnd = 0;
}
if (composing_start != composing_end)
{
if (composing_start < 0)
composing_start = 0;
if (composing_end > end)
composing_end = end;
m_composing_start = composing_start;
m_composing_end = composing_end;
}
}

View File

@ -119,6 +119,8 @@ using namespace gui;
virtual irr::gui::IGUIFont* getActiveFont() const { return NULL; }
virtual void setDrawBackground(bool) { }
void fromAndroidEditText(const core::stringw& text, int start, int end,
int composing_start, int composing_end);
void openScreenKeyboard();
s32 getCursorPosInBox() const { return CursorPos; }
s32 getTextCount() const { return (s32)Text.size(); }
@ -174,6 +176,13 @@ using namespace gui;
core::array< s32 > BrokenTextPositions;
core::rect<s32> CurrentTextRect, FrameRect; // temporary values
s32 m_composing_start;
s32 m_composing_end;
/* If true, this editbox will copy text and selection only from
* android edittext, and process only mouse event. */
bool m_from_android_edittext;
};

View File

@ -228,3 +228,34 @@ EventPropagation TextBoxWidget::leftPressed (const int playerID)
return EVENT_LET;
} // leftPressed
// ============================================================================
/* This callback will allow copying android edittext data directly to editbox,
* which will allow composing text to be auto updated. */
#ifdef ANDROID
#include "jni.h"
#if !defined(ANDROID_PACKAGE_CALLBACK_NAME)
#error
#endif
#define MAKE_EDITTEXT_CALLBACK(x) JNIEXPORT void JNICALL Java_ ## x##_STKEditText_editText2STKEditbox(JNIEnv* env, jobject this_obj, jstring text, jint start, jint end, jint composing_start, jint composing_end)
#define ANDROID_EDITTEXT_CALLBACK(PKG_NAME) MAKE_EDITTEXT_CALLBACK(PKG_NAME)
extern "C"
ANDROID_EDITTEXT_CALLBACK(ANDROID_PACKAGE_CALLBACK_NAME)
{
TextBoxWidget* tb = dynamic_cast<TextBoxWidget*>(getFocusForPlayer(0));
if (!tb || text == NULL)
return;
const char* utf8_text = env->GetStringUTFChars(text, NULL);
if (utf8_text == NULL)
return;
core::stringw to_editbox = StringUtils::utf8ToWide(utf8_text);
tb->getIrrlichtElement<MyCGUIEditBox>()->fromAndroidEditText(
to_editbox, start, end, composing_start, composing_end);
env->ReleaseStringUTFChars(text, utf8_text);
}
#endif