diff --git a/android-project/src/org/libsdl/app/SDLActivity.java b/android-project/src/org/libsdl/app/SDLActivity.java index 685719967..1b60de2dd 100644 --- a/android-project/src/org/libsdl/app/SDLActivity.java +++ b/android-project/src/org/libsdl/app/SDLActivity.java @@ -9,7 +9,11 @@ import javax.microedition.khronos.egl.*; import android.app.*; import android.content.*; import android.view.*; +import android.view.inputmethod.BaseInputConnection; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; +import android.widget.AbsoluteLayout; import android.os.*; import android.util.Log; import android.graphics.*; @@ -33,6 +37,8 @@ public class SDLActivity extends Activity { // Main components private static SDLActivity mSingleton; private static SDLSurface mSurface; + private static View mTextEdit; + private static ViewGroup mLayout; // This is what SDL runs in. It invokes SDL_main(), eventually private static Thread mSDLThread; @@ -70,7 +76,12 @@ public class SDLActivity extends Activity { // Set up the surface mSurface = new SDLSurface(getApplication()); - setContentView(mSurface); + + mLayout = new AbsoluteLayout(this); + mLayout.addView(mSurface); + + setContentView(mLayout); + SurfaceHolder holder = mSurface.getHolder(); } @@ -109,6 +120,7 @@ public class SDLActivity extends Activity { // Messages from the SDLMain thread static final int COMMAND_CHANGE_TITLE = 1; static final int COMMAND_KEYBOARD_SHOW = 2; + static final int COMMAND_TEXTEDIT_HIDE = 3; // Handler for the messages Handler commandHandler = new Handler() { @@ -134,6 +146,14 @@ public class SDLActivity extends Activity { } } break; + case COMMAND_TEXTEDIT_HIDE: + if (mTextEdit != null) { + mTextEdit.setVisibility(View.GONE); + + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0); + } + break; } } }; @@ -201,6 +221,50 @@ public class SDLActivity extends Activity { } } } + + static class ShowTextInputHandler implements Runnable { + /* + * This is used to regulate the pan&scan method to have some offset from + * the bottom edge of the input region and the top edge of an input + * method (soft keyboard) + */ + static final int HEIGHT_PADDING = 15; + + public int x, y, w, h; + + public ShowTextInputHandler(int x, int y, int w, int h) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + } + + public void run() { + AbsoluteLayout.LayoutParams params = new AbsoluteLayout.LayoutParams( + w, h + HEIGHT_PADDING, x, y); + + if (mTextEdit == null) { + mTextEdit = new DummyEdit(getContext()); + + mLayout.addView(mTextEdit, params); + } else { + mTextEdit.setLayoutParams(params); + } + + mTextEdit.setVisibility(View.VISIBLE); + mTextEdit.requestFocus(); + + InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(mTextEdit, 0); + } + + } + + public static void showTextInput(int x, int y, int w, int h) { + // Transfer the task to the main thread as a Runnable + mSingleton.commandHandler.post(new ShowTextInputHandler(x, y, w, h)); + } + // EGL functions public static boolean initEGL(int majorVersion, int minorVersion) { @@ -623,5 +687,104 @@ class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, event.values[2] / SensorManager.GRAVITY_EARTH); } } + +} + +/* This is a fake invisible editor view that receives the input and defines the + * pan&scan region + */ +class DummyEdit extends View implements View.OnKeyListener { + InputConnection ic; + + public DummyEdit(Context context) { + super(context); + setFocusableInTouchMode(true); + setFocusable(true); + setOnKeyListener(this); + } + + @Override + public boolean onCheckIsTextEditor() { + return true; + } + + public boolean onKey(View v, int keyCode, KeyEvent event) { + + // This handles the hardware keyboard input + if (event.isPrintingKey()) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1); + } + return true; + } + + if (event.getAction() == KeyEvent.ACTION_DOWN) { + SDLActivity.onNativeKeyDown(keyCode); + return true; + } else if (event.getAction() == KeyEvent.ACTION_UP) { + SDLActivity.onNativeKeyUp(keyCode); + return true; + } + + return false; + } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + ic = new SDLInputConnection(this, true); + + outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI + | EditorInfo.IME_FLAG_NO_FULLSCREEN; + + return ic; + } +} + +class SDLInputConnection extends BaseInputConnection { + + public SDLInputConnection(View targetView, boolean fullEditor) { + super(targetView, fullEditor); + + } + + @Override + public boolean sendKeyEvent(KeyEvent event) { + + /* + * This handles the keycodes from soft keyboard (and IME-translated + * input from hardkeyboard) + */ + int keyCode = event.getKeyCode(); + if (event.getAction() == KeyEvent.ACTION_DOWN) { + + SDLActivity.onNativeKeyDown(keyCode); + return true; + } else if (event.getAction() == KeyEvent.ACTION_UP) { + + SDLActivity.onNativeKeyUp(keyCode); + return true; + } + return super.sendKeyEvent(event); + } + + @Override + public boolean commitText(CharSequence text, int newCursorPosition) { + + nativeCommitText(text.toString(), newCursorPosition); + + return super.commitText(text, newCursorPosition); + } + + @Override + public boolean setComposingText(CharSequence text, int newCursorPosition) { + + nativeSetComposingText(text.toString(), newCursorPosition); + + return super.setComposingText(text, newCursorPosition); + } + + public native void nativeCommitText(String text, int newCursorPosition); + + public native void nativeSetComposingText(String text, int newCursorPosition); } diff --git a/src/core/android/SDL_android.cpp b/src/core/android/SDL_android.cpp index a97e075ca..b01735dc3 100644 --- a/src/core/android/SDL_android.cpp +++ b/src/core/android/SDL_android.cpp @@ -218,6 +218,30 @@ extern "C" void Java_org_libsdl_app_SDLActivity_nativeRunAudioThread( Android_RunAudioThread(); } +extern "C" void Java_org_libsdl_app_SDLInputConnection_nativeCommitText( + JNIEnv* env, jclass cls, + jstring text, jint newCursorPosition) +{ + const char *utftext = env->GetStringUTFChars(text, NULL); + + SDL_SendKeyboardText(utftext); + + env->ReleaseStringUTFChars(text, utftext); +} + +extern "C" void Java_org_libsdl_app_SDLInputConnection_nativeSetComposingText( + JNIEnv* env, jclass cls, + jstring text, jint newCursorPosition) +{ + const char *utftext = env->GetStringUTFChars(text, NULL); + + SDL_SendEditingText(utftext, 0, 0); + + env->ReleaseStringUTFChars(text, utftext); +} + + + /******************************************************************************* Functions called by SDL into Java @@ -918,6 +942,42 @@ extern "C" int Android_JNI_SendMessage(int command, int param) return 0; } +extern "C" int Android_JNI_ShowTextInput(SDL_Rect *inputRect) +{ + JNIEnv *env = Android_JNI_GetEnv(); + if (!env) { + return -1; + } + + jmethodID mid = env->GetStaticMethodID(mActivityClass, "showTextInput", "(IIII)V"); + if (!mid) { + return -1; + } + env->CallStaticVoidMethod( mActivityClass, mid, + inputRect->x, + inputRect->y, + inputRect->w, + inputRect->h ); + return 0; +} + +/*extern "C" int Android_JNI_HideTextInput() +{ + + + JNIEnv *env = Android_JNI_GetEnv(); + if (!env) { + return -1; + } + + jmethodID mid = env->GetStaticMethodID(mActivityClass, "hideTextInput", "()V"); + if (!mid) { + return -1; + } + env->CallStaticVoidMethod(mActivityClass, mid); + return 0; +}*/ + #endif /* __ANDROID__ */ /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/core/android/SDL_android.h b/src/core/android/SDL_android.h index e87bddc67..637e465bc 100644 --- a/src/core/android/SDL_android.h +++ b/src/core/android/SDL_android.h @@ -27,11 +27,14 @@ extern "C" { /* *INDENT-ON* */ #endif +#include "SDL_rect.h" + /* Interface from the SDL library into the Android Java activity */ extern SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion); extern void Android_JNI_SwapWindow(); extern void Android_JNI_SetActivityTitle(const char *title); extern SDL_bool Android_JNI_GetAccelerometerValues(float values[3]); +extern int Android_JNI_ShowTextInput(SDL_Rect *inputRect); // Audio support extern int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames); diff --git a/src/video/android/SDL_androidkeyboard.c b/src/video/android/SDL_androidkeyboard.c index 112b2dc88..a69f836a9 100644 --- a/src/video/android/SDL_androidkeyboard.c +++ b/src/video/android/SDL_androidkeyboard.c @@ -321,6 +321,27 @@ Android_IsScreenKeyboardShown(_THIS, SDL_Window * window) return SDL_FALSE; } +void +Android_StartTextInput(_THIS) +{ + SDL_VideoData *videodata = (SDL_VideoData *)_this->driverdata; + Android_JNI_ShowTextInput(&videodata->textRect); +} + +#define COMMAND_TEXTEDIT_HIDE 3 +void +Android_StopTextInput(_THIS) +{ + Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0); +} + +void +Android_SetTextInputRect(_THIS, SDL_Rect *rect) +{ + SDL_VideoData *videodata = (SDL_VideoData *)_this->driverdata; + videodata->textRect = *rect; +} + #endif /* SDL_VIDEO_DRIVER_ANDROID */ /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/video/android/SDL_androidkeyboard.h b/src/video/android/SDL_androidkeyboard.h index c1f3b5263..be3d82833 100644 --- a/src/video/android/SDL_androidkeyboard.h +++ b/src/video/android/SDL_androidkeyboard.h @@ -32,4 +32,8 @@ extern int Android_HideScreenKeyboard(_THIS, SDL_Window * window); extern int Android_ToggleScreenKeyboard(_THIS, SDL_Window * window); extern SDL_bool Android_IsScreenKeyboardShown(_THIS, SDL_Window * window); +extern void Android_StartTextInput(_THIS); +extern void Android_StopTextInput(_THIS); +extern void Android_SetTextInputRect(_THIS, SDL_Rect *rect); + /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/video/android/SDL_androidvideo.c b/src/video/android/SDL_androidvideo.c index 05639ee63..a171094ca 100644 --- a/src/video/android/SDL_androidvideo.c +++ b/src/video/android/SDL_androidvideo.c @@ -87,17 +87,24 @@ Android_CreateDevice(int devindex) { printf("Creating video device\n"); SDL_VideoDevice *device; + SDL_VideoData *data; /* Initialize all variables that we clean on shutdown */ device = (SDL_VideoDevice *) SDL_calloc(1, sizeof(SDL_VideoDevice)); if (!device) { SDL_OutOfMemory(); - if (device) { - SDL_free(device); - } - return (0); + return NULL; } + data = (SDL_VideoData*) SDL_calloc(1, sizeof(SDL_VideoData)); + if (!data) { + SDL_OutOfMemory(); + SDL_free(device); + return NULL; + } + + device->driverdata = data; + /* Set the function pointers */ device->VideoInit = Android_VideoInit; device->VideoQuit = Android_VideoQuit; @@ -132,6 +139,11 @@ Android_CreateDevice(int devindex) device->GetClipboardText = Android_GetClipboardText; device->HasClipboardText = Android_HasClipboardText; + /* Text input */ + device->StartTextInput = Android_StartTextInput; + device->StopTextInput = Android_StopTextInput; + device->SetTextInputRect = Android_SetTextInputRect; + return device; } diff --git a/src/video/android/SDL_androidvideo.h b/src/video/android/SDL_androidvideo.h index 90905d36d..3add56ad0 100644 --- a/src/video/android/SDL_androidvideo.h +++ b/src/video/android/SDL_androidvideo.h @@ -24,6 +24,7 @@ #define _SDL_androidvideo_h #include "SDL_mutex.h" +#include "SDL_rect.h" #include "../SDL_sysvideo.h" /* Called by the JNI layer when the screen changes size or format */ @@ -31,6 +32,11 @@ extern void Android_SetScreenResolution(int width, int height, Uint32 format); /* Private display data */ +typedef struct SDL_VideoData +{ + SDL_Rect textRect; +} SDL_VideoData; + extern int Android_ScreenWidth; extern int Android_ScreenHeight; extern Uint32 Android_ScreenFormat;