From ab1856046ffca7187d7d806a454729d9fb1bbecc Mon Sep 17 00:00:00 2001 From: "kr.angelov" Date: Wed, 13 Nov 2013 10:45:20 +0000 Subject: [PATCH] now the Android App has a translation keyboard which allows the translations to be done from inside another application --- src/ui/android/AndroidManifest.xml | 7 + .../android/res/drawable-hdpi/btn_close.png | Bin 0 -> 2220 bytes .../res/drawable-hdpi/sym_keyboard_delete.png | Bin 0 -> 885 bytes .../res/drawable-hdpi/sym_keyboard_return.png | Bin 0 -> 536 bytes .../res/drawable-hdpi/sym_keyboard_search.png | Bin 0 -> 1623 bytes .../res/drawable-hdpi/sym_keyboard_shift.png | Bin 0 -> 1247 bytes .../res/drawable-hdpi/sym_keyboard_space.png | Bin 0 -> 859 bytes .../res/drawable-mdpi/sym_keyboard_delete.png | Bin 0 -> 465 bytes .../res/drawable-mdpi/sym_keyboard_done.png | Bin 0 -> 771 bytes .../res/drawable-mdpi/sym_keyboard_return.png | Bin 0 -> 337 bytes .../res/drawable-mdpi/sym_keyboard_search.png | Bin 0 -> 1029 bytes .../res/drawable-mdpi/sym_keyboard_shift.png | Bin 0 -> 1029 bytes .../res/drawable-mdpi/sym_keyboard_space.png | Bin 0 -> 436 bytes src/ui/android/res/layout/input.xml | 9 + .../res/layout/keyboard_languages_options.xml | 20 + src/ui/android/res/values/dimens.xml | 3 + src/ui/android/res/values/strings.xml | 11 + src/ui/android/res/xml/cyrillic.xml | 76 +++ src/ui/android/res/xml/method.xml | 6 + src/ui/android/res/xml/popup_keyboard.xml | 5 + src/ui/android/res/xml/qwerty.xml | 74 +++ src/ui/android/res/xml/symbols.xml | 73 +++ src/ui/android/res/xml/symbols_shift.xml | 71 +++ .../ui/android/ConversationView.java | 4 + .../ui/android/Language.java | 9 +- .../ui/android/LexicalEntryActivity.java | 2 +- .../ui/android/MainActivity.java | 14 +- .../ui/android/Translator.java | 11 +- .../android/TranslatorInputMethodService.java | 581 ++++++++++++++++++ .../ui/android/TranslatorKeyboard.java | 103 ++++ .../ui/android/TranslatorKeyboardView.java | 113 ++++ 31 files changed, 1181 insertions(+), 11 deletions(-) create mode 100644 src/ui/android/res/drawable-hdpi/btn_close.png create mode 100644 src/ui/android/res/drawable-hdpi/sym_keyboard_delete.png create mode 100644 src/ui/android/res/drawable-hdpi/sym_keyboard_return.png create mode 100644 src/ui/android/res/drawable-hdpi/sym_keyboard_search.png create mode 100644 src/ui/android/res/drawable-hdpi/sym_keyboard_shift.png create mode 100644 src/ui/android/res/drawable-hdpi/sym_keyboard_space.png create mode 100644 src/ui/android/res/drawable-mdpi/sym_keyboard_delete.png create mode 100644 src/ui/android/res/drawable-mdpi/sym_keyboard_done.png create mode 100644 src/ui/android/res/drawable-mdpi/sym_keyboard_return.png create mode 100644 src/ui/android/res/drawable-mdpi/sym_keyboard_search.png create mode 100644 src/ui/android/res/drawable-mdpi/sym_keyboard_shift.png create mode 100644 src/ui/android/res/drawable-mdpi/sym_keyboard_space.png create mode 100644 src/ui/android/res/layout/input.xml create mode 100644 src/ui/android/res/layout/keyboard_languages_options.xml create mode 100644 src/ui/android/res/xml/cyrillic.xml create mode 100644 src/ui/android/res/xml/method.xml create mode 100644 src/ui/android/res/xml/popup_keyboard.xml create mode 100644 src/ui/android/res/xml/qwerty.xml create mode 100644 src/ui/android/res/xml/symbols.xml create mode 100644 src/ui/android/res/xml/symbols_shift.xml create mode 100644 src/ui/android/src/org/grammaticalframework/ui/android/TranslatorInputMethodService.java create mode 100644 src/ui/android/src/org/grammaticalframework/ui/android/TranslatorKeyboard.java create mode 100644 src/ui/android/src/org/grammaticalframework/ui/android/TranslatorKeyboardView.java 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 0000000000000000000000000000000000000000..47f11e5bf67fcb0284261ff30310d58a325d4329 GIT binary patch literal 2220 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 A1^@s6 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5a5670c320ffe799ebc46b7834c4be80b68edbed GIT binary patch literal 536 zcmeAS@N?(olHy`uVBq!ia0y~yV9;V-L1 z;Fyx1l&avFo0y&&l$w}QS$HzlhJk@`m#2$kNX4zBKmY&R9}Zyf;c;f^u`0Q;a50~3 z>5CQ4?y^>|E(EsoaUF2#TXufF{pCZtS`Kemj_5a3unYWu^z`Y{GYd@^W;)nNcFbJ( z=dr^Zwj=Ru!B@B$KeDK}H#MjVi1ThUS;coGC*nvi=dwVlrH(NTGbIYWoUicoyEZSY z=wf=5?69ZA!1{`4$8%@r%gGz8L`3dN7p62gJ`RXgxW{y4PV-K#scetlI_P*UkMW=8 z?AT|`P^z=Fk%jN${v}pZ;*((Ri3rVGGAe0foM!iuLUg?M;`? zabdaqU=E(g*dmI zk6So9f?vEj(C-k#cI4%fGFLH&;xubUZU@EsMke;AqVIVMas6>B6N79@9Zp~5vdMC| z%R2esg`KNguKAq&A);7*gl*G0|8}>t3*KLEzLdx!@LrmWSzv#I1xJTJZ|g*%0KrGh zN6guGJ3Brw6|iRh7`$YH+?Lv9%P&mix$wbT+g8TwTMx{Q2|e^yH)@y^5-;q`kAlR^OdHd-m?70UCKce0=v> zSLsg3onRYsn^nedxoJ{bebVvyY?bTQtavRR^73wwwQ0C?{rdOKE=rY>UpN&66%ti`%xTzl@7}%m3)6DD!RpJ|3}h}A zS5{WaOYpGSa(1bUyUlh zK0bF+++$XTlnJa5cqNg(%=VR&b1ZMhO3?W7Z=^8VAOPdxeg`SSaU zYHDgKl9Q7~<;or9TpYM%9q01pZajP9#EJKN_-dz3nX;#$|M zzC8Vbb1%=KjH9evHx+IN+;2McN=R5(|3Xif46o0Tb7Iq`PMym8sCL^0r6(Qi+1c6u zH%yI-ixXn3EQ`DK==t+(mw#Jt)UrMe?Dk&*x z`!@gH#5Z#8RuWGpc`dz_bY3Chwb6?H<;ExOHt`GIj9Ig4)vunfjdlzFxmLA1pEsQM z=NO}IR=V}pef#z?3ci)SaJ1pa=QYk3!h9^FebUmN?f(Am?(LM?FWg1cf*B2QfT+-dtDQPxG_?@P&_NkL&@*M2)0@oZk&)!SP;Imc$sK~YPo zi#K-d+I7CwM|P%*xA*DqC$tW*efQ_`bo21=@XOB57CvqEBPcldv+|8Sd_tNJX7c$) zA2DIpWM*czpLuHYx9{IgCwTm0T=SQW%kF-AlAbP1hkGPrLK;LwwtHi>7){jRMI3OCk- zg@s*nG|4qt8Xpz4%Xj8iV-Z)^KBi;mWacGaG&nU~lyfEb{66DgN{OORw6t;);=k9E}d-v{LO&y&-6V^#Hy|j4n zW~YcRA5XEYf)axbhu@4AA&*Q~*Q4%MbN#aU?#PMU=)Z2TSuriiaN$;NgXk89$Dewq zpMGlO{UG4_g$o~41sUdKe)MEJrk@y-)Oq#l)uQ0W_Pt*MwWeM-j8NQbz3~9cg95Qh zuU^02zk!7>T~A5rQRSWW>ar62=bFFy7bPb@F5MfKZ^3(@ z%{z1W?TY%yO$RcLOu6w(C}0ow&*^nKS8R8kG=BVj#ep{w6}yhfzjIp@%$BVonqKnP k^9(Blz6t#4-@wQqYA{>t&iC1p3=9kmp00i_>zopr0JoS9TmS$7 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..275769618a23b642d3ff7305de827a49e5b72ce0 GIT binary patch literal 1247 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..cbe2b152f32330ea352d1954b822c6601860be7d GIT binary patch literal 337 zcmeAS@N?(olHy`uVBq!ia0y~yV31*8U{K>=V_;yo{^oZ)0|SF(iEBhjaDG}zd16s2 zgJVj5QmTSyZen_BP-%hAr-fh{`~)M&uq&m%slttiq^$qeNh%Z zx_|!sEM^ec&;IDY!yfs_zKLS4(ibLub~wf0d1B)9>E9W2o3Au1QefQ`!PxRrsNJDk zsNqVjf}KCF_sotBId)UD8IEk(ERZpic~a_@vtkaX9VYIZC$_SawLSIsLdTAYzZyQZ zoGCe=-Z_b%Gs2BifaTd?eho)1-^MK)av0ATeX(HSv|F$#RlH$`K=z`>T}pykhD#Yz zj7^jYy>Q*JY{?6gxLs7Glyq9f4~3j<4-mHs(yZvz&df;FythdtD^tld r*zj@2{iG|00=eCFmsm1z7%;d6=2W-(lw4q7U|{fc^>bP0l+XkKl0tu1 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..127755d6b597c85c00eb05f760dfd48230352dd1 GIT binary patch literal 1029 zcmeAS@N?(olHy`uVBq!ia0y~yU{GOTU{K;%X8O-xS>N=;0uEIgTN!@$72+|$J|q~g|_h**!{M2T-_9@pEiE;%Kz*)(f& zVCMfbVosgAGgaeQq;GgFD}NfR>2jrMqIm3!P$jLDMujEeoQ~!~u{Ynb9As+^TYQE4 zvRARK{vP&ycH6hTbmXdem^kNq&GVY`-~ZX%FJE>fX=6|QNgm1nGjavJ(y|swcP&zh z?r@9{5PhWo&v&)PQ>n1a6=4rQ?sTwIp8Z$*fY62$`%n7>?C0>DcG9J%fm5Sw_g!{b zK6lNqjDXNmOA)TUjsLknNdDl`G1&O!)(j^xexaGN2N)0By?OIzNm*H$p4v^JoSd9D zx*hk9?3nDpIpMINPvyUqmKAQ>n7?&(b*-K}dGf>vA?2jl*jWD$KBlfhCoWAESehvx z^mz8XdH*#0{a83ZDv9y1$v?iCJSVMLuH}Vlb;IVxix;mydXkNuJw9^nwXi#@+4Y$A zO`A4NuB%T%vr9|2qr3ia%L`SpD@S-I`jnS{=l-G3%(p)g#G znpap|{kt;v&z(DWZ1PpLt29h~ckExcZe8e_&S%e_$^FRQxtS|cYQ=58i6R|Uu3y>r zPZnR$%@^2}b?D)TmKFNBxw+>b>}Luv$!hS{oBn#A&OE>6pB3NDWh^W7n%b*XDp$&v zIdO%4l=ox@A(un5CRR+i$P$+@&Jzsw=?p29afVzsL3>cb}6_UySM*y-}F^Zjo==OuEyJUl#)Kgl}C?hu;H zxkNxQP^oCA*f*wj3*O_5lbBa;Sbz1HwQRu>xet|{V$0X5r`B7a3c9kb@MC)M*^6@N zt|EUsJ%0ZEXDyMucw?1-*V7%lC;Ks*&DNdJA#%F0(3nG2)%EW7n=StoS|8fSR`z0kM^=F6OnX?PmEr0t}Ve0YO`F#1exNCYMFMpfu=<(-L r=nsuery?fvroMhZJ4mJh`hKCF@W-u@?NS3%plmzFem6RtIr7}3C zrF(gM{uIVy_xFu+rrY)X1@q9V~>_# zv(j3zV3GZz*$!PDlg{K#`J~jc;LRoO{Gdyf%N<$0N(6TY1qB5DUbs$4L^*19rQ*%? zYtI%}#Z~*~B(Hm$qstm!apUK{_jBIYp8IY0{(EhDlg{Z`Q&XbOa&0zsTHQB6B0E5! zr6S(zL42oR_)-^b4a1Y-rx=PDWY2$P{=xU-vFg-QQ!;0EoP1dl_VxSs`v*!TC-A2f zUJ)|qx?S{xThcax)$d1*o&LM&*4Eb57w&nhJXRFFx}3|2Y57VP3nM$be<6H^(@$UR z{df03SVF8$&`JfB53|LY+1c3H+dEpWzn;5t_3GDg39DDGT=*rFX?F5UlSn5QHDLp$ zY7JX1$<h$UJ3(g;Vd@DrY z)PoJ-0VW+BmakvE>JnLR=JLi}T}^G8$HW&G?bxP#+A)Dgu_$T5hTCtyMP1^y%*@TL z?btW@fLULRn!91Ucb$5HsPN@wQkYRsf(U|ta$h9?>`;SNr%2%x^SU@>t@}}D_6eoojt$GEZpp5 zhRHLn-c6e}P1l@7Hcy)T}e(-cKRqRy%;QrdY+% zU%t!~UZ0q$>~cQ!^3f|-u0+p1drf$gtch~3lJKQv-4l*jU3&5LSI(T)LoREtW<7P` z`@PcQ(4|>h{Ns+Xv9Za0uv_kbW&16Ti%I6UZ{MyyonR2rba26miIGRN9>~kd)g93; zH+Y!UDcUJ4)LWsA6cEDeqzEU)hx>+ snZN9o{y$#tp8daWjg`}W772#)ml@fJ4mJh`hKCF@W-u@?NS3%plmzFem6RtIr7}3C zW?~Z=YKxl4Q0q zL4hp^87-)1d-7daj`dSFth?`f|5vH~(Lc6q>PxsUNft?8Qhxcf-J$t! zj(3?$R7zx-`;+fczapL&oHd>E=iAu{I;V>=gV|g-=$bY zu9Vj12!)2uy;R_9rJgI>$P~oWAA7)Bf@{fQK_`=ED!!L*t9a(|t(>RPWhXCi@9Y9C z + + 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); + } + } +}