diff --git a/src/ui/android/AndroidManifest.xml b/src/ui/android/AndroidManifest.xml
index d9f983e65..47a0073c7 100644
--- a/src/ui/android/AndroidManifest.xml
+++ b/src/ui/android/AndroidManifest.xml
@@ -25,6 +25,13 @@
+
+
+
+
+
+
diff --git a/src/ui/android/res/drawable-hdpi/btn_close.png b/src/ui/android/res/drawable-hdpi/btn_close.png
new file mode 100644
index 000000000..47f11e5bf
Binary files /dev/null and b/src/ui/android/res/drawable-hdpi/btn_close.png differ
diff --git a/src/ui/android/res/drawable-hdpi/sym_keyboard_delete.png b/src/ui/android/res/drawable-hdpi/sym_keyboard_delete.png
new file mode 100644
index 000000000..5139c7179
Binary files /dev/null and b/src/ui/android/res/drawable-hdpi/sym_keyboard_delete.png differ
diff --git a/src/ui/android/res/drawable-hdpi/sym_keyboard_return.png b/src/ui/android/res/drawable-hdpi/sym_keyboard_return.png
new file mode 100644
index 000000000..5a5670c32
Binary files /dev/null and b/src/ui/android/res/drawable-hdpi/sym_keyboard_return.png differ
diff --git a/src/ui/android/res/drawable-hdpi/sym_keyboard_search.png b/src/ui/android/res/drawable-hdpi/sym_keyboard_search.png
new file mode 100644
index 000000000..e72cde3bb
Binary files /dev/null and b/src/ui/android/res/drawable-hdpi/sym_keyboard_search.png differ
diff --git a/src/ui/android/res/drawable-hdpi/sym_keyboard_shift.png b/src/ui/android/res/drawable-hdpi/sym_keyboard_shift.png
new file mode 100644
index 000000000..275769618
Binary files /dev/null and b/src/ui/android/res/drawable-hdpi/sym_keyboard_shift.png differ
diff --git a/src/ui/android/res/drawable-hdpi/sym_keyboard_space.png b/src/ui/android/res/drawable-hdpi/sym_keyboard_space.png
new file mode 100644
index 000000000..cef2daa5d
Binary files /dev/null and b/src/ui/android/res/drawable-hdpi/sym_keyboard_space.png differ
diff --git a/src/ui/android/res/drawable-mdpi/sym_keyboard_delete.png b/src/ui/android/res/drawable-mdpi/sym_keyboard_delete.png
new file mode 100644
index 000000000..6cee59682
Binary files /dev/null and b/src/ui/android/res/drawable-mdpi/sym_keyboard_delete.png differ
diff --git a/src/ui/android/res/drawable-mdpi/sym_keyboard_done.png b/src/ui/android/res/drawable-mdpi/sym_keyboard_done.png
new file mode 100644
index 000000000..c0d6d1394
Binary files /dev/null and b/src/ui/android/res/drawable-mdpi/sym_keyboard_done.png differ
diff --git a/src/ui/android/res/drawable-mdpi/sym_keyboard_return.png b/src/ui/android/res/drawable-mdpi/sym_keyboard_return.png
new file mode 100644
index 000000000..cbe2b152f
Binary files /dev/null and b/src/ui/android/res/drawable-mdpi/sym_keyboard_return.png differ
diff --git a/src/ui/android/res/drawable-mdpi/sym_keyboard_search.png b/src/ui/android/res/drawable-mdpi/sym_keyboard_search.png
new file mode 100644
index 000000000..127755d6b
Binary files /dev/null and b/src/ui/android/res/drawable-mdpi/sym_keyboard_search.png differ
diff --git a/src/ui/android/res/drawable-mdpi/sym_keyboard_shift.png b/src/ui/android/res/drawable-mdpi/sym_keyboard_shift.png
new file mode 100644
index 000000000..d05962846
Binary files /dev/null and b/src/ui/android/res/drawable-mdpi/sym_keyboard_shift.png differ
diff --git a/src/ui/android/res/drawable-mdpi/sym_keyboard_space.png b/src/ui/android/res/drawable-mdpi/sym_keyboard_space.png
new file mode 100644
index 000000000..09b94d9e6
Binary files /dev/null and b/src/ui/android/res/drawable-mdpi/sym_keyboard_space.png differ
diff --git a/src/ui/android/res/layout/input.xml b/src/ui/android/res/layout/input.xml
new file mode 100644
index 000000000..fdef07a53
--- /dev/null
+++ b/src/ui/android/res/layout/input.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/src/ui/android/res/layout/keyboard_languages_options.xml b/src/ui/android/res/layout/keyboard_languages_options.xml
new file mode 100644
index 000000000..0b45b739c
--- /dev/null
+++ b/src/ui/android/res/layout/keyboard_languages_options.xml
@@ -0,0 +1,20 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/ui/android/res/values/dimens.xml b/src/ui/android/res/values/dimens.xml
index 55c1e5908..55088756c 100644
--- a/src/ui/android/res/values/dimens.xml
+++ b/src/ui/android/res/values/dimens.xml
@@ -3,5 +3,8 @@
16dp
16dp
+ 50dip
+ 16sp
+ 6sp
diff --git a/src/ui/android/res/values/strings.xml b/src/ui/android/res/values/strings.xml
index 57e20027d..e7c4e560f 100644
--- a/src/ui/android/res/values/strings.xml
+++ b/src/ui/android/res/values/strings.xml
@@ -9,4 +9,15 @@
Speech Input
Keyboard Input
org.grammaticalframework.ui.android.GLOBAL_PREFERENCES
+
+
+ Done
+ Go
+ Next
+ Previous
+ Send
+
+
+ normalKeyboardMode
+ internalKeyboardMode
diff --git a/src/ui/android/res/xml/cyrillic.xml b/src/ui/android/res/xml/cyrillic.xml
new file mode 100644
index 000000000..2e444507b
--- /dev/null
+++ b/src/ui/android/res/xml/cyrillic.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ui/android/res/xml/method.xml b/src/ui/android/res/xml/method.xml
new file mode 100644
index 000000000..af83761ca
--- /dev/null
+++ b/src/ui/android/res/xml/method.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/src/ui/android/res/xml/popup_keyboard.xml b/src/ui/android/res/xml/popup_keyboard.xml
new file mode 100644
index 000000000..521d3d278
--- /dev/null
+++ b/src/ui/android/res/xml/popup_keyboard.xml
@@ -0,0 +1,5 @@
+
+
+
\ No newline at end of file
diff --git a/src/ui/android/res/xml/qwerty.xml b/src/ui/android/res/xml/qwerty.xml
new file mode 100644
index 000000000..6e0b95975
--- /dev/null
+++ b/src/ui/android/res/xml/qwerty.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ui/android/res/xml/symbols.xml b/src/ui/android/res/xml/symbols.xml
new file mode 100644
index 000000000..72deff01c
--- /dev/null
+++ b/src/ui/android/res/xml/symbols.xml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ui/android/res/xml/symbols_shift.xml b/src/ui/android/res/xml/symbols_shift.xml
new file mode 100644
index 000000000..b55e6f521
--- /dev/null
+++ b/src/ui/android/res/xml/symbols_shift.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ui/android/src/org/grammaticalframework/ui/android/ConversationView.java b/src/ui/android/src/org/grammaticalframework/ui/android/ConversationView.java
index cda719604..0a1004462 100644
--- a/src/ui/android/src/org/grammaticalframework/ui/android/ConversationView.java
+++ b/src/ui/android/src/org/grammaticalframework/ui/android/ConversationView.java
@@ -1,6 +1,7 @@
package org.grammaticalframework.ui.android;
import android.content.Context;
+import android.os.Bundle;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -88,6 +89,9 @@ public class ConversationView extends ScrollView {
return false;
}
});
+ Bundle extras = edittext.getInputExtras(true);
+ extras.putBoolean("show_language_toggle", false);
+
mContent.addView(view);
post(new Runnable() {
public void run() {
diff --git a/src/ui/android/src/org/grammaticalframework/ui/android/Language.java b/src/ui/android/src/org/grammaticalframework/ui/android/Language.java
index 996846e82..6b63bb8f2 100644
--- a/src/ui/android/src/org/grammaticalframework/ui/android/Language.java
+++ b/src/ui/android/src/org/grammaticalframework/ui/android/Language.java
@@ -9,12 +9,15 @@ public class Language implements Serializable {
private final String mLangName;
private final String mConcrete;
private final int mInflResource;
+ private final int mKeyboardResource;
- public Language(String langCode, String langName, String concrete, int inflResource) {
+ public Language(String langCode, String langName, String concrete,
+ int inflResource, int keyboardResource) {
mLangCode = langCode;
mLangName = langName;
mConcrete = concrete;
mInflResource = inflResource;
+ mKeyboardResource = keyboardResource;
}
public String getLangCode() {
@@ -28,6 +31,10 @@ public class Language implements Serializable {
public int getInflectionResource() {
return mInflResource;
}
+
+ public int getKeyboardResource() {
+ return mKeyboardResource;
+ }
String getConcrete() {
return mConcrete;
diff --git a/src/ui/android/src/org/grammaticalframework/ui/android/LexicalEntryActivity.java b/src/ui/android/src/org/grammaticalframework/ui/android/LexicalEntryActivity.java
index 8c8777e2f..8d9a41955 100644
--- a/src/ui/android/src/org/grammaticalframework/ui/android/LexicalEntryActivity.java
+++ b/src/ui/android/src/org/grammaticalframework/ui/android/LexicalEntryActivity.java
@@ -45,7 +45,7 @@ public class LexicalEntryActivity extends ListActivity {
mTranslator = ((GFTranslator) getApplicationContext()).getTranslator();
mShowLanguageView = (LanguageSelector) findViewById(R.id.show_language);
- mShowLanguageView.setLanguages(mTranslator.getAvailableSourceLanguages());
+ mShowLanguageView.setLanguages(mTranslator.getAvailableLanguages());
mShowLanguageView.setOnLanguageSelectedListener(new OnLanguageSelectedListener() {
@Override
public void onLanguageSelected(Language language) {
diff --git a/src/ui/android/src/org/grammaticalframework/ui/android/MainActivity.java b/src/ui/android/src/org/grammaticalframework/ui/android/MainActivity.java
index ab14bbfe2..9cbeb6930 100644
--- a/src/ui/android/src/org/grammaticalframework/ui/android/MainActivity.java
+++ b/src/ui/android/src/org/grammaticalframework/ui/android/MainActivity.java
@@ -101,14 +101,14 @@ public class MainActivity extends Activity {
mTranslator = ((GFTranslator) getApplicationContext()).getTranslator();
- mSourceLanguageView.setLanguages(mTranslator.getAvailableSourceLanguages());
+ mSourceLanguageView.setLanguages(mTranslator.getAvailableLanguages());
mSourceLanguageView.setOnLanguageSelectedListener(new OnLanguageSelectedListener() {
@Override
public void onLanguageSelected(Language language) {
onSourceLanguageSelected(language);
}
});
- mTargetLanguageView.setLanguages(mTranslator.getAvailableTargetLanguages());
+ mTargetLanguageView.setLanguages(mTranslator.getAvailableLanguages());
mTargetLanguageView.setOnLanguageSelectedListener(new OnLanguageSelectedListener() {
@Override
public void onLanguageSelected(Language language) {
@@ -192,10 +192,16 @@ public class MainActivity extends Activity {
void onSourceLanguageSelected(Language language) {
mTranslator.setSourceLanguage(language);
+ if (TranslatorInputMethodService.getInstance() != null) {
+ TranslatorInputMethodService.getInstance().handleChangeSourceLanguage(language);
+ }
}
void onTargetLanguageSelected(Language language) {
mTranslator.setTargetLanguage(language);
+ if (TranslatorInputMethodService.getInstance() != null) {
+ TranslatorInputMethodService.getInstance().handleChangeTargetLanguage(language);
+ }
}
public String getSourceLanguageCode() {
@@ -211,6 +217,10 @@ public class MainActivity extends Activity {
Language newTarget = mTranslator.getSourceLanguage();
mSourceLanguageView.setSelectedLanguage(newSource);
mTargetLanguageView.setSelectedLanguage(newTarget);
+
+ if (TranslatorInputMethodService.getInstance() != null) {
+ TranslatorInputMethodService.getInstance().handleSwitchLanguages();
+ }
}
private void startRecognition() {
diff --git a/src/ui/android/src/org/grammaticalframework/ui/android/Translator.java b/src/ui/android/src/org/grammaticalframework/ui/android/Translator.java
index f3a247844..e8ef4738e 100644
--- a/src/ui/android/src/org/grammaticalframework/ui/android/Translator.java
+++ b/src/ui/android/src/org/grammaticalframework/ui/android/Translator.java
@@ -33,8 +33,9 @@ public class Translator {
new Language("fi-FI", "Finnish", "TranslateFin", 0),
new Language("sv-SE", "Swedish", "TranslateSwe", R.xml.inflection_sv),
*/
- new Language("en-US", "English", "ParseEng", R.xml.inflection_en),
- new Language("bg-BG", "Bulgarian", "ParseBul", R.xml.inflection_bg),
+ new Language("en-US", "English", "ParseEng", R.xml.inflection_en, R.xml.qwerty),
+ new Language("bg-BG", "Bulgarian", "ParseBul", R.xml.inflection_bg, R.xml.cyrillic),
+ new Language("sv-SE", "Swedish", "ParseSwe", R.xml.inflection_sv, R.xml.qwerty),
};
private Language mSourceLanguage;
@@ -78,11 +79,7 @@ public class Translator {
mTargetLanguage = getPrefLang(TARGET_LANG_KEY, 1);
}
- public List getAvailableSourceLanguages() {
- return Arrays.asList(mLanguages);
- }
-
- public List getAvailableTargetLanguages() {
+ public List getAvailableLanguages() {
return Arrays.asList(mLanguages);
}
diff --git a/src/ui/android/src/org/grammaticalframework/ui/android/TranslatorInputMethodService.java b/src/ui/android/src/org/grammaticalframework/ui/android/TranslatorInputMethodService.java
new file mode 100644
index 000000000..29c52c44a
--- /dev/null
+++ b/src/ui/android/src/org/grammaticalframework/ui/android/TranslatorInputMethodService.java
@@ -0,0 +1,581 @@
+package org.grammaticalframework.ui.android;
+
+import android.inputmethodservice.InputMethodService;
+import android.text.InputType;
+import android.text.method.MetaKeyKeyListener;
+import android.util.Log;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class TranslatorInputMethodService extends InputMethodService
+ implements android.inputmethodservice.KeyboardView.OnKeyboardActionListener {
+
+ private TranslatorKeyboardView mInputView;
+ private CompletionsView mCandidateView;
+ private CompletionInfo[] mCompletions;
+
+ private StringBuilder mComposing = new StringBuilder();
+ private boolean mPredictionOn;
+ private boolean mCompletionOn;
+ private boolean mCapsLock;
+ private long mLastShiftTime;
+ private long mMetaState;
+
+ private TranslatorKeyboard mSymbolsKeyboard;
+ private TranslatorKeyboard mSymbolsShiftedKeyboard;
+ private TranslatorKeyboard mLanguageKeyboard;
+
+ private TranslatorKeyboard mCurKeyboard;
+
+ private int mActionId;
+
+ private Translator mTranslator;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ mTranslator = ((GFTranslator) getApplicationContext()).getTranslator();
+
+ mSymbolsKeyboard = null;
+ mSymbolsShiftedKeyboard = null;
+ mLanguageKeyboard = null;
+ }
+
+ @Override
+ public View onCreateInputView() {
+ mInputView = (TranslatorKeyboardView)
+ getLayoutInflater().inflate(R.layout.input, null);
+ mInputView.setOnKeyboardActionListener(this);
+ mInputView.setKeyboard(mCurKeyboard);
+ return mInputView;
+ }
+
+ @Override
+ public View onCreateCandidatesView() {
+ mCandidateView = new CompletionsView(this);
+ mCandidateView.setService(this);
+ return mCandidateView;
+ }
+
+ private int mModeId;
+ private static TranslatorInputMethodService mInstance;
+
+ static TranslatorInputMethodService getInstance() {
+ return mInstance;
+ }
+
+ @Override
+ public void onStartInput(EditorInfo attribute, boolean restarting) {
+ super.onStartInput(attribute, restarting);
+
+ // Reset our state. We want to do this even if restarting, because
+ // the underlying state of the text editor could have changed in any way.
+ mComposing.setLength(0);
+ updateCandidates();
+
+ if (!restarting) {
+ // Clear shift states.
+ mMetaState = 0;
+ }
+
+ mPredictionOn = false;
+ mCompletionOn = false;
+ mCompletions = null;
+
+ int res =
+ mTranslator.getSourceLanguage().getKeyboardResource();
+ mModeId = R.string.normalKeyboardMode;
+ if (attribute.extras != null &&
+ !attribute.extras.getBoolean("show_language_toggle", true)) {
+ mModeId = R.string.internalKeyboardMode;
+ }
+ mLanguageKeyboard = new TranslatorKeyboard(this, res, mModeId);
+ mSymbolsKeyboard = new TranslatorKeyboard(this, R.xml.symbols, mModeId);
+ mSymbolsShiftedKeyboard = new TranslatorKeyboard(this, R.xml.symbols_shift, mModeId);
+
+ // We are now going to initialize our state based on the type of
+ // text being edited.
+ switch (attribute.inputType & InputType.TYPE_MASK_CLASS) {
+ case InputType.TYPE_CLASS_NUMBER:
+ case InputType.TYPE_CLASS_DATETIME:
+ // Numbers and dates default to the symbols keyboard, with
+ // no extra features.
+ mCurKeyboard = mSymbolsKeyboard;
+ break;
+
+ case InputType.TYPE_CLASS_PHONE:
+ // Phones will also default to the symbols keyboard, though
+ // often you will want to have a dedicated phone keyboard.
+ mCurKeyboard = mSymbolsKeyboard;
+ break;
+
+ case InputType.TYPE_CLASS_TEXT:
+ // This is general text editing. We will default to the
+ // normal alphabetic keyboard, and assume that we should
+ // be doing predictive text (showing candidates as the
+ // user types).
+ mCurKeyboard = mLanguageKeyboard;
+ mPredictionOn = true;
+
+ // We now look for a few special variations of text that will
+ // modify our behavior.
+ int variation = attribute.inputType & InputType.TYPE_MASK_VARIATION;
+ if (variation == InputType.TYPE_TEXT_VARIATION_PASSWORD ||
+ variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) {
+ // Do not display predictions / what the user is typing
+ // when they are entering a password.
+ mPredictionOn = false;
+ }
+
+ if (variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
+ || variation == InputType.TYPE_TEXT_VARIATION_URI
+ || variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
+ // Our predictions are not useful for e-mail addresses
+ // or URIs.
+ mPredictionOn = false;
+ }
+
+ if ((attribute.inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) {
+ // If this is an auto-complete text view, then our predictions
+ // will not be shown and instead we will allow the editor
+ // to supply their own. We only show the editor's
+ // candidates when in full-screen mode, otherwise relying
+ // own it displaying its own UI.
+ mPredictionOn = false;
+ mCompletionOn = isFullscreenMode();
+ }
+
+ // We also want to look at the current state of the editor
+ // to decide whether our alphabetic keyboard should start out
+ // shifted.
+ updateShiftKeyState(attribute);
+ break;
+
+ default:
+ // For all unknown input types, default to the alphabetic
+ // keyboard with no special features.
+ mCurKeyboard = mLanguageKeyboard;
+ updateShiftKeyState(attribute);
+ }
+
+ mActionId = attribute.imeOptions & EditorInfo.IME_MASK_ACTION;
+ mCurKeyboard.setImeOptions(getResources(), attribute.imeOptions);
+
+ mInstance = this;
+ }
+
+ @Override
+ public void onFinishInput() {
+ super.onFinishInput();
+
+ // Clear current composing text and candidates.
+ mComposing.setLength(0);
+ updateCandidates();
+
+ // We only hide the candidates window when finishing input on
+ // a particular editor, to avoid popping the underlying application
+ // up and down if the user is entering text into the bottom of
+ // its window.
+ setCandidatesViewShown(false);
+
+ mCurKeyboard = mLanguageKeyboard;
+ if (mInputView != null) {
+ mInputView.closing();
+ }
+
+ mInstance = null;
+ }
+
+ @Override
+ public void onStartInputView(EditorInfo attribute, boolean restarting) {
+ super.onStartInputView(attribute, restarting);
+ // Apply the selected keyboard to the input view.
+ mInputView.setKeyboard(mCurKeyboard);
+ mInputView.closing();
+ }
+
+ @Override
+ public void onUpdateSelection(int oldSelStart, int oldSelEnd,
+ int newSelStart, int newSelEnd,
+ int candidatesStart, int candidatesEnd) {
+ super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
+ candidatesStart, candidatesEnd);
+
+ // If the current selection in the text view changes, we should
+ // clear whatever candidate text we have.
+ if (mComposing.length() > 0 && (newSelStart != candidatesEnd
+ || newSelEnd != candidatesEnd)) {
+ mComposing.setLength(0);
+ updateCandidates();
+ InputConnection ic = getCurrentInputConnection();
+ if (ic != null) {
+ ic.finishComposingText();
+ }
+ }
+ }
+
+ @Override
+ public void onDisplayCompletions(CompletionInfo[] completions) {
+ if (mCompletionOn) {
+ mCompletions = completions;
+ if (completions == null) {
+ setSuggestions(null, false, false);
+ return;
+ }
+
+ List stringList = new ArrayList();
+ for (int i = 0; i < completions.length; i++) {
+ CompletionInfo ci = completions[i];
+ if (ci != null) stringList.add(ci.getText().toString());
+ }
+ setSuggestions(stringList, true, true);
+ }
+ }
+
+ /**
+ * This translates incoming hard key events in to edit operations on an
+ * InputConnection. It is only needed when using the
+ * PROCESS_HARD_KEYS option.
+ */
+ private boolean translateKeyDown(int keyCode, KeyEvent event) {
+ mMetaState = MetaKeyKeyListener.handleKeyDown(mMetaState,
+ keyCode, event);
+ int c = event.getUnicodeChar(MetaKeyKeyListener.getMetaState(mMetaState));
+ mMetaState = MetaKeyKeyListener.adjustMetaAfterKeypress(mMetaState);
+ InputConnection ic = getCurrentInputConnection();
+ if (c == 0 || ic == null) {
+ return false;
+ }
+
+ if ((c & KeyCharacterMap.COMBINING_ACCENT) != 0) {
+ c = c & KeyCharacterMap.COMBINING_ACCENT_MASK;
+ }
+
+ if (mComposing.length() > 0) {
+ char accent = mComposing.charAt(mComposing.length() -1 );
+ int composed = KeyEvent.getDeadChar(accent, c);
+
+ if (composed != 0) {
+ c = composed;
+ mComposing.setLength(mComposing.length()-1);
+ }
+ }
+
+ onKey(c, null);
+
+ return true;
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_BACK:
+ // The InputMethodService already takes care of the back
+ // key for us, to dismiss the input method if it is shown.
+ // However, our keyboard could be showing a pop-up window
+ // that back should dismiss, so we first allow it to do that.
+ if (event.getRepeatCount() == 0 && mInputView != null) {
+ if (mInputView.handleBack()) {
+ return true;
+ }
+ }
+ break;
+
+ case KeyEvent.KEYCODE_DEL:
+ // Special handling of the delete key: if we currently are
+ // composing text for the user, we want to modify that instead
+ // of let the application to the delete itself.
+ if (mComposing.length() > 0) {
+ onKey(TranslatorKeyboard.KEYCODE_DELETE, null);
+ return true;
+ }
+ break;
+
+ case KeyEvent.KEYCODE_ENTER:
+ // Let the underlying text editor always handle these.
+ return false;
+
+ default:
+ if (mPredictionOn && translateKeyDown(keyCode, event)) {
+ return true;
+ }
+ }
+
+ return super.onKeyDown(keyCode, event);
+ }
+
+ /**
+ * Helper function to commit any text being composed in to the editor.
+ */
+ private void commitTyped(InputConnection inputConnection) {
+ if (mComposing.length() > 0) {
+ inputConnection.commitText(mComposing, mComposing.length());
+ mComposing.setLength(0);
+ updateCandidates();
+ }
+ }
+
+ /**
+ * Helper to update the shift state of our keyboard based on the initial
+ * editor state.
+ */
+ private void updateShiftKeyState(EditorInfo attr) {
+ if (attr != null
+ && mInputView != null && mLanguageKeyboard == mInputView.getKeyboard()) {
+ int caps = 0;
+ EditorInfo ei = getCurrentInputEditorInfo();
+ if (ei != null && ei.inputType != InputType.TYPE_NULL) {
+ caps = getCurrentInputConnection().getCursorCapsMode(attr.inputType);
+ }
+ mInputView.setShifted(mCapsLock || caps != 0);
+ }
+ }
+
+ /**
+ * Helper to send a key down / key up pair to the current editor.
+ */
+ private void keyDownUp(int keyEventCode) {
+ getCurrentInputConnection().sendKeyEvent(
+ new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode));
+ getCurrentInputConnection().sendKeyEvent(
+ new KeyEvent(KeyEvent.ACTION_UP, keyEventCode));
+ }
+
+ // Implementation of KeyboardViewListener
+ public void onKey(int primaryCode, int[] keyCodes) {
+ if (primaryCode == TranslatorKeyboard.KEYCODE_DELETE) {
+ handleBackspace();
+ } else if (primaryCode == TranslatorKeyboard.KEYCODE_SHIFT) {
+ handleShift();
+ } else if (primaryCode == TranslatorKeyboard.KEYCODE_SOURCE_LANGUAGE
+ && mInputView != null) {
+ Language newSource = mTranslator.getTargetLanguage();
+ Language newTarget = mTranslator.getSourceLanguage();
+ mTranslator.setSourceLanguage(newSource);
+ mTranslator.setTargetLanguage(newTarget);
+ handleSwitchLanguages();
+ } else if (primaryCode < TranslatorKeyboard.KEYCODE_SOURCE_LANGUAGE &&
+ primaryCode > TranslatorKeyboard.KEYCODE_SOURCE_LANGUAGE-TranslatorKeyboard.MAX_LANGUAGE_KEYCODES) {
+ Language newSource =
+ mTranslator.getAvailableLanguages().get(TranslatorKeyboard.KEYCODE_SOURCE_LANGUAGE-primaryCode-1);
+ mTranslator.setSourceLanguage(newSource);
+ handleChangeSourceLanguage(newSource);
+ } else if (primaryCode == TranslatorKeyboard.KEYCODE_TARGET_LANGUAGE) {
+ String translation = mTranslator.translate(mComposing.toString());
+ getCurrentInputConnection().commitText(translation, 1);
+ return;
+ } else if (primaryCode < TranslatorKeyboard.KEYCODE_TARGET_LANGUAGE &&
+ primaryCode > TranslatorKeyboard.KEYCODE_TARGET_LANGUAGE-TranslatorKeyboard.MAX_LANGUAGE_KEYCODES) {
+ Language newTarget =
+ mTranslator.getAvailableLanguages().get(TranslatorKeyboard.KEYCODE_TARGET_LANGUAGE-primaryCode-1);
+ mTranslator.setTargetLanguage(newTarget);
+ handleChangeTargetLanguage(newTarget);
+ } else if (primaryCode == TranslatorKeyboard.KEYCODE_MODE_CHANGE
+ && mInputView != null) {
+ TranslatorKeyboard current = (TranslatorKeyboard) mInputView.getKeyboard();
+ if (current == mSymbolsKeyboard || current == mSymbolsShiftedKeyboard) {
+ current = mLanguageKeyboard;
+ } else {
+ current = mSymbolsKeyboard;
+ }
+ mInputView.setKeyboard(current);
+ if (current == mSymbolsKeyboard) {
+ current.setShifted(false);
+ }
+ } else if (primaryCode == 10) {
+ getCurrentInputConnection().performEditorAction(mActionId);
+ } else if (primaryCode == ' ' && mComposing.length() == 0) {
+ getCurrentInputConnection().commitText(" ", 1);
+ } else {
+ handleCharacter(primaryCode, keyCodes);
+ }
+ }
+
+ public void onText(CharSequence text) {
+ InputConnection ic = getCurrentInputConnection();
+ if (ic == null) return;
+ ic.beginBatchEdit();
+ if (mComposing.length() > 0) {
+ commitTyped(ic);
+ }
+ ic.commitText(text, 0);
+ ic.endBatchEdit();
+ updateShiftKeyState(getCurrentInputEditorInfo());
+ }
+
+ /**
+ * Update the list of available candidates from the current composing
+ * text. This will need to be filled in by however you are determining
+ * candidates.
+ */
+ private void updateCandidates() {
+ if (!mCompletionOn) {
+ if (mComposing.length() > 0) {
+ ArrayList list = new ArrayList();
+ list.add(mComposing.toString());
+ list.add("alfa");
+ list.add("beta");
+ setSuggestions(list, true, true);
+ Log.d("KEYBOARD", mComposing.toString());
+ } else {
+ setSuggestions(null, false, false);
+ }
+ }
+ }
+
+ public void setSuggestions(List suggestions, boolean completions,
+ boolean typedWordValid) {
+ if (suggestions != null && suggestions.size() > 0) {
+ setCandidatesViewShown(true);
+ } else if (isExtractViewShown()) {
+ setCandidatesViewShown(true);
+ }
+ if (mCandidateView != null) {
+ mCandidateView.setSuggestions(suggestions, completions, typedWordValid);
+ }
+ }
+
+ private void handleBackspace() {
+ final int length = mComposing.length();
+ if (length > 1) {
+ mComposing.delete(length - 1, length);
+ getCurrentInputConnection().setComposingText(mComposing, 1);
+ updateCandidates();
+ } else if (length > 0) {
+ mComposing.setLength(0);
+ getCurrentInputConnection().commitText("", 0);
+ updateCandidates();
+ } else {
+ keyDownUp(KeyEvent.KEYCODE_DEL);
+ }
+ updateShiftKeyState(getCurrentInputEditorInfo());
+ }
+
+ private void handleShift() {
+ if (mInputView == null) {
+ return;
+ }
+
+ TranslatorKeyboard currentKeyboard = (TranslatorKeyboard) mInputView.getKeyboard();
+ if (mLanguageKeyboard == currentKeyboard) {
+ // Alphabet keyboard
+ checkToggleCapsLock();
+ mInputView.setShifted(mCapsLock || !mInputView.isShifted());
+ } else if (currentKeyboard == mSymbolsKeyboard) {
+ mSymbolsKeyboard.setShifted(true);
+ mInputView.setKeyboard(mSymbolsShiftedKeyboard);
+ mSymbolsShiftedKeyboard.setShifted(true);
+ } else if (currentKeyboard == mSymbolsShiftedKeyboard) {
+ mSymbolsShiftedKeyboard.setShifted(false);
+ mInputView.setKeyboard(mSymbolsKeyboard);
+ mSymbolsKeyboard.setShifted(false);
+ }
+ }
+
+ private void handleCharacter(int primaryCode, int[] keyCodes) {
+ if (isInputViewShown()) {
+ if (mInputView.isShifted()) {
+ primaryCode = Character.toUpperCase(primaryCode);
+ }
+ }
+
+ mComposing.append((char) primaryCode);
+ getCurrentInputConnection().setComposingText(mComposing, 1);
+ updateShiftKeyState(getCurrentInputEditorInfo());
+
+ if (mPredictionOn) {
+ updateCandidates();
+ }
+ }
+
+ private void handleClose() {
+ commitTyped(getCurrentInputConnection());
+ requestHideSelf(0);
+ mInputView.closing();
+ }
+
+ void handleChangeSourceLanguage(Language newSource) {
+ mLanguageKeyboard =
+ new TranslatorKeyboard(this, newSource.getKeyboardResource(), mModeId);
+ mSymbolsKeyboard.updateLanguageKeyLabels();
+ mSymbolsShiftedKeyboard.updateLanguageKeyLabels();
+ mInputView.setKeyboard(mLanguageKeyboard);
+ }
+
+ void handleChangeTargetLanguage(Language newTarget) {
+ mLanguageKeyboard.updateLanguageKeyLabels();
+ mSymbolsKeyboard.updateLanguageKeyLabels();
+ mSymbolsShiftedKeyboard.updateLanguageKeyLabels();
+ mInputView.invalidateAllKeys();
+ }
+
+ void handleSwitchLanguages() {
+ Language newSource = mTranslator.getSourceLanguage();
+ mLanguageKeyboard =
+ new TranslatorKeyboard(this, newSource.getKeyboardResource(), mModeId);
+ mInputView.setKeyboard(mLanguageKeyboard);
+ }
+
+ private void checkToggleCapsLock() {
+ long now = System.currentTimeMillis();
+ if (mLastShiftTime + 800 > now) {
+ mCapsLock = !mCapsLock;
+ mLastShiftTime = 0;
+ } else {
+ mLastShiftTime = now;
+ }
+ }
+
+ public void pickDefaultCandidate() {
+ pickSuggestionManually(0);
+ }
+
+ public void pickSuggestionManually(int index) {
+ if (mCompletionOn && mCompletions != null && index >= 0
+ && index < mCompletions.length) {
+ CompletionInfo ci = mCompletions[index];
+ getCurrentInputConnection().commitCompletion(ci);
+ if (mCandidateView != null) {
+ mCandidateView.clear();
+ }
+ updateShiftKeyState(getCurrentInputEditorInfo());
+ } else if (mComposing.length() > 0) {
+ // If we were generating candidate suggestions for the current
+ // text, we would commit one of them here. But for this sample,
+ // we will just commit the current text.
+ commitTyped(getCurrentInputConnection());
+ }
+ }
+
+ public void swipeRight() {
+ if (mCompletionOn) {
+ pickDefaultCandidate();
+ }
+ }
+
+ public void swipeLeft() {
+ handleBackspace();
+ }
+
+ public void swipeDown() {
+ handleClose();
+ }
+
+ public void swipeUp() {
+ }
+
+ public void onPress(int primaryCode) {
+ }
+
+ public void onRelease(int primaryCode) {
+ }
+}
diff --git a/src/ui/android/src/org/grammaticalframework/ui/android/TranslatorKeyboard.java b/src/ui/android/src/org/grammaticalframework/ui/android/TranslatorKeyboard.java
new file mode 100644
index 000000000..cd29d6914
--- /dev/null
+++ b/src/ui/android/src/org/grammaticalframework/ui/android/TranslatorKeyboard.java
@@ -0,0 +1,103 @@
+package org.grammaticalframework.ui.android;
+
+import java.util.Locale;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.view.inputmethod.EditorInfo;
+import android.inputmethodservice.Keyboard;
+
+public class TranslatorKeyboard extends Keyboard {
+
+ private Key mEnterKey;
+ private Key mSourceLanguageKey;
+ private Key mTargetLanguageKey;
+
+ static final int KEYCODE_SOURCE_LANGUAGE = -100;
+ static final int KEYCODE_TARGET_LANGUAGE = -200;
+ static final int MAX_LANGUAGE_KEYCODES = 99;
+
+ private Translator mTranslator;
+
+ public TranslatorKeyboard(Context context, int xmlLayoutResId, int modeId) {
+ super(context, xmlLayoutResId, modeId);
+
+ mTranslator = ((GFTranslator) context.getApplicationContext()).getTranslator();
+ updateLanguageKeyLabels();
+ }
+
+ public void updateLanguageKeyLabels() {
+ if (mSourceLanguageKey != null)
+ mSourceLanguageKey.label = getLanguageKeyLabel(mTranslator.getSourceLanguage());
+
+ if (mTargetLanguageKey != null)
+ mTargetLanguageKey.label = getLanguageKeyLabel(mTranslator.getTargetLanguage());
+ }
+
+ public static String getLanguageKeyLabel(Language lang) {
+ return
+ LocaleUtils.parseJavaLocale(lang.getLangCode(), Locale.getDefault())
+ .getISO3Language();
+ }
+
+ @Override
+ protected Key createKeyFromXml(Resources res, Row parent, int x, int y,
+ XmlResourceParser parser) {
+ Key key = new Key(res, parent, x, y, parser);
+ if (key.codes[0] == 10) {
+ mEnterKey = key;
+ } else if (key.codes[0] == KEYCODE_SOURCE_LANGUAGE) {
+ mSourceLanguageKey = key;
+ } else if (key.codes[0] == KEYCODE_TARGET_LANGUAGE) {
+ mTargetLanguageKey = key;
+ }
+ return key;
+ }
+
+ /**
+ * This looks at the ime options given by the current editor, to set the
+ * appropriate label on the keyboard's enter key (if it has one).
+ */
+ void setImeOptions(Resources res, int options) {
+ if (mEnterKey == null) {
+ return;
+ }
+
+ switch (options&(EditorInfo.IME_MASK_ACTION|EditorInfo.IME_FLAG_NO_ENTER_ACTION)) {
+ case EditorInfo.IME_ACTION_DONE:
+ mEnterKey.iconPreview = null;
+ mEnterKey.icon = null;
+ mEnterKey.label = res.getText(R.string.label_done_key);
+ break;
+ case EditorInfo.IME_ACTION_GO:
+ mEnterKey.iconPreview = null;
+ mEnterKey.icon = null;
+ mEnterKey.label = res.getText(R.string.label_go_key);
+ break;
+ case EditorInfo.IME_ACTION_NEXT:
+ mEnterKey.iconPreview = null;
+ mEnterKey.icon = null;
+ mEnterKey.label = res.getText(R.string.label_next_key);
+ break;
+ case EditorInfo.IME_ACTION_PREVIOUS:
+ mEnterKey.iconPreview = null;
+ mEnterKey.icon = null;
+ mEnterKey.label = res.getText(R.string.label_previous_key);
+ break;
+ case EditorInfo.IME_ACTION_SEARCH:
+ mEnterKey.icon = res.getDrawable(R.drawable.sym_keyboard_search);
+ mEnterKey.label = null;
+ break;
+ case EditorInfo.IME_ACTION_SEND:
+ mEnterKey.iconPreview = null;
+ mEnterKey.icon = null;
+ mEnterKey.label = res.getText(R.string.label_send_key);
+ break;
+ default:
+ mEnterKey.icon = res.getDrawable(R.drawable.sym_keyboard_return);
+ mEnterKey.label = null;
+ break;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ui/android/src/org/grammaticalframework/ui/android/TranslatorKeyboardView.java b/src/ui/android/src/org/grammaticalframework/ui/android/TranslatorKeyboardView.java
new file mode 100644
index 000000000..6bca2ad59
--- /dev/null
+++ b/src/ui/android/src/org/grammaticalframework/ui/android/TranslatorKeyboardView.java
@@ -0,0 +1,113 @@
+package org.grammaticalframework.ui.android;
+
+import java.util.Locale;
+
+import org.grammaticalframework.ui.android.TranslatorKeyboard;
+
+import android.content.Context;
+import android.inputmethodservice.Keyboard.Key;
+import android.inputmethodservice.KeyboardView;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.PopupWindow;
+
+public class TranslatorKeyboardView extends KeyboardView {
+
+ private Translator mTranslator;
+
+ public TranslatorKeyboardView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mTranslator = ((GFTranslator) context.getApplicationContext()).getTranslator();
+ }
+
+ public TranslatorKeyboardView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mTranslator = ((GFTranslator) context.getApplicationContext()).getTranslator();
+ }
+
+ private PopupWindow mLanguagesPopup = null;
+ private Key mLanguagesKey = null;
+
+ private void showLanguageOptions(Key popupKey) {
+ if (mLanguagesPopup == null) {
+ LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ LinearLayout popupContainer = (LinearLayout)
+ inflater.inflate(R.layout.keyboard_languages_options, null);
+
+ int index = 0;
+ for (Language lang : mTranslator.getAvailableLanguages()) {
+ Button item = new Button(getContext());
+ item.setText(TranslatorKeyboard.getLanguageKeyLabel(lang));
+ item.setTag(index);
+ item.setOnClickListener(this);
+ popupContainer.addView(item, index++);
+ }
+
+ popupContainer.measure(
+ MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
+ MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
+
+ mLanguagesPopup = new PopupWindow(getContext());
+ mLanguagesPopup.setWidth(popupContainer.getMeasuredWidth());
+ mLanguagesPopup.setHeight(popupContainer.getMeasuredHeight());
+ mLanguagesPopup.setContentView(popupContainer);
+
+ int[] windowOffset = new int[2];
+ getLocationInWindow(windowOffset);
+ int popupX = popupKey.x + popupKey.width - popupContainer.getMeasuredWidth();
+ int popupY = popupKey.y - popupContainer.getMeasuredHeight();
+ final int x = popupX + popupContainer.getPaddingRight() + windowOffset[0];
+ final int y = popupY + popupContainer.getPaddingBottom() + windowOffset[1];
+ mLanguagesPopup.showAtLocation(this, Gravity.NO_GRAVITY, x, y);
+
+ View closeButton = popupContainer.findViewById(R.id.closeButton);
+ if (closeButton != null) closeButton.setOnClickListener(this);
+ }
+
+ mLanguagesKey = popupKey;
+ }
+
+ private void dismissLanguages() {
+ if (mLanguagesPopup != null) {
+ mLanguagesPopup.dismiss();
+ mLanguagesPopup = null;
+ mLanguagesKey = null;
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ super.onClick(v);
+
+ if (v.getTag() != null) {
+ if (mLanguagesKey.codes[0] == TranslatorKeyboard.KEYCODE_SOURCE_LANGUAGE ||
+ mLanguagesKey.codes[0] == TranslatorKeyboard.KEYCODE_TARGET_LANGUAGE) {
+ int keyCode = mLanguagesKey.codes[0] - ((Integer) v.getTag()) - 1;
+ getOnKeyboardActionListener().onKey(keyCode, new int[] {keyCode});
+ }
+ }
+
+ dismissLanguages();
+ }
+
+ public void closing() {
+ super.closing();
+ dismissLanguages();
+ }
+
+ @Override
+ protected boolean onLongPress(Key key) {
+ if (key.codes[0] == TranslatorKeyboard.KEYCODE_SOURCE_LANGUAGE ||
+ key.codes[0] == TranslatorKeyboard.KEYCODE_TARGET_LANGUAGE) {
+ showLanguageOptions(key);
+ return true;
+ } else {
+ return super.onLongPress(key);
+ }
+ }
+}