diff --git a/src/ui/android/AndroidManifest.xml b/src/ui/android/AndroidManifest.xml index 0752ab2c9..7f9bbe99e 100644 --- a/src/ui/android/AndroidManifest.xml +++ b/src/ui/android/AndroidManifest.xml @@ -25,7 +25,7 @@ - + diff --git a/src/ui/android/res/layout/first_person_utterance.xml b/src/ui/android/res/layout/first_person_utterance.xml index f7d96c467..be3b0eb08 100644 --- a/src/ui/android/res/layout/first_person_utterance.xml +++ b/src/ui/android/res/layout/first_person_utterance.xml @@ -1,29 +1,12 @@ - - - - - - - \ No newline at end of file + diff --git a/src/ui/android/res/layout/lexical_item.xml b/src/ui/android/res/layout/lexical_item.xml deleted file mode 100644 index 49f090a26..000000000 --- a/src/ui/android/res/layout/lexical_item.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/ui/android/src/org/grammaticalframework/ui/android/LexicalEntryActivity.java b/src/ui/android/src/org/grammaticalframework/ui/android/AlternativesActivity.java similarity index 62% rename from src/ui/android/src/org/grammaticalframework/ui/android/LexicalEntryActivity.java rename to src/ui/android/src/org/grammaticalframework/ui/android/AlternativesActivity.java index ac1957cca..ca956b58f 100644 --- a/src/ui/android/src/org/grammaticalframework/ui/android/LexicalEntryActivity.java +++ b/src/ui/android/src/org/grammaticalframework/ui/android/AlternativesActivity.java @@ -1,6 +1,5 @@ package org.grammaticalframework.ui.android; -import java.util.ArrayList; import java.util.List; import android.app.Activity; @@ -8,17 +7,13 @@ import android.app.ListActivity; import android.content.Context; import android.os.AsyncTask; import android.os.Bundle; -import android.util.Log; -import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; -import android.view.ViewParent; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.webkit.WebView; import android.widget.ArrayAdapter; -import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; @@ -26,7 +21,7 @@ import android.widget.TextView; import org.grammaticalframework.pgf.*; import org.grammaticalframework.ui.android.LanguageSelector.OnLanguageSelectedListener; -public class LexicalEntryActivity extends ListActivity { +public class AlternativesActivity extends ListActivity { private Translator mTranslator; private LanguageSelector mShowLanguageView; @@ -94,17 +89,10 @@ public class LexicalEntryActivity extends ListActivity { private void updateTranslations() { @SuppressWarnings("unchecked") - List list = (List) + List list = (List) getIntent().getExtras().getSerializable("analyses"); - List data = new ArrayList(); - for (MorphoAnalysis a : list) { - if (!data.contains(a.getLemma())) { - data.add(a.getLemma()); - } - } - - setListAdapter(new LexicalAdapter(this, data)); + setListAdapter(new AlternativesAdapter(this, list)); expandedView = null; } @@ -144,39 +132,86 @@ public class LexicalEntryActivity extends ListActivity { expandedView = view; } - private class LexicalAdapter extends ArrayAdapter { - public LexicalAdapter(Context context, List data) { + private void expandExpr(View view, Expr expr) { + ImageView arrow = (ImageView) view.findViewById(R.id.arrow); + arrow.setImageResource(R.drawable.close_arrow); + + WebView inflectionView = (WebView) view.findViewById(R.id.lexical_inflection); + if (inflectionView == null) { + inflectionView = new WebView(this); + inflectionView.setId(R.id.lexical_inflection); + RelativeLayout.LayoutParams params = + new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + params.addRule(RelativeLayout.BELOW, R.id.lexical_desc); + ((RelativeLayout) view).addView(inflectionView, params); + } + + inflectionView.loadData(expr.toString(), "text/plain; charset=UTF-8", null); + + expandedView = view; + } + + private class AlternativesAdapter extends ArrayAdapter { + public AlternativesAdapter(Context context, List data) { super(context, android.R.layout.simple_list_item_1, data); } public View getView(int position, View convertView, ViewGroup parent) { - final String lemma = getItem(position); - - LayoutInflater inflater = (LayoutInflater) getContext() - .getSystemService(Activity.LAYOUT_INFLATER_SERVICE); - if (convertView == null) { - convertView = inflater.inflate(R.layout.lexical_item, null); + Object item = getItem(position); + + if (convertView == null) { + LayoutInflater inflater = (LayoutInflater) + getContext().getSystemService(Activity.LAYOUT_INFLATER_SERVICE); + convertView = inflater.inflate(R.layout.alternative_item, null); } - TextView descView = - (TextView) convertView.findViewById(R.id.lexical_desc); + TextView descView = (TextView) + convertView.findViewById(R.id.lexical_desc); - String phrase = mTranslator.generateLexiconEntry(lemma); - descView.setText(phrase); + if (item instanceof MorphoAnalysis) { + final String lemma = ((MorphoAnalysis) item).getLemma(); + + String phrase = mTranslator.generateLexiconEntry(lemma); + descView.setText(phrase); + + convertView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + if (expandedView == view) + collapse(); + else if (expandedView == null) + expand(view, lemma); + else { + collapse(); + expand(view, lemma); + } + } + }); + } else { + if (item instanceof ExprProb) { + final Expr expr = ((ExprProb) item).getExpr(); + + String phrase = mTranslator.linearize(expr); + if (phrase.startsWith("% ") || phrase.startsWith("* ") || phrase.startsWith("+ ")) + phrase = phrase.substring(2); - convertView.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - if (expandedView == view) - collapse(); - else if (expandedView == null) - expand(view, lemma); - else { - collapse(); - expand(view, lemma); - } + descView.setText(phrase); + + convertView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + if (expandedView == view) + collapse(); + else if (expandedView == null) + expandExpr(view, expr); + else { + collapse(); + expandExpr(view, expr); + } + } + }); } - }); + } return convertView; } 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 8cc105322..f7f02584a 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.content.Intent; import android.os.Bundle; import android.util.AttributeSet; import android.view.LayoutInflater; @@ -21,7 +22,7 @@ public class ConversationView extends ScrollView { private ViewGroup mContent; - private OnWordSelectedListener mWordListener; + private OnAlternativesListener mAlternativesListener; private ASR.Listener mSpeechListener; public ConversationView(Context context, AttributeSet attrs, int defStyle) { @@ -53,7 +54,7 @@ public class ConversationView extends ScrollView { inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0); } v.setFocusable(false); - mLastUtterance = (View) v.getParent(); + mLastUtterance = v; if (mSpeechListener != null) mSpeechListener.onSpeechInput(text.toString().trim()); return true; @@ -71,18 +72,17 @@ public class ConversationView extends ScrollView { }; private EditorListener mEditorListener = new EditorListener(); - private View mLastUtterance = null; + private TextView mLastUtterance = null; public void addFirstPersonUtterance(CharSequence text, boolean focused) { - View view = + EditText edittext = (EditText) mInflater.inflate(R.layout.first_person_utterance, mContent, false); - EditText edittext = (EditText) view.findViewById(R.id.input_text); edittext.setText(text); edittext.setOnEditorActionListener(mEditorListener); edittext.setOnClickListener(mEditorListener); Bundle extras = edittext.getInputExtras(true); extras.putBoolean("show_language_toggle", false); - mContent.addView(view); + mContent.addView(edittext); if (focused) { edittext.requestFocus(); @@ -98,16 +98,16 @@ public class ConversationView extends ScrollView { } }); - mLastUtterance = view; + mLastUtterance = edittext; } @SuppressWarnings("deprecation") - public CharSequence addSecondPersonUtterance(CharSequence text) { + public CharSequence addSecondPersonUtterance(final CharSequence source, CharSequence target, final Object alternatives) { TextView view; if (mLastUtterance != null && mLastUtterance.getTag() != null) view = (TextView) mLastUtterance.getTag(); else { - view = (TextView) + view = (TextView) mInflater.inflate(R.layout.second_person_utterance, mContent, false); mContent.addView(view); post(new Runnable() { @@ -119,71 +119,55 @@ public class ConversationView extends ScrollView { mLastUtterance.setTag(view); } - // parse by words, marked by %, darkest red colour - if (text.charAt(0) == '%') { + view.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mAlternativesListener != null) + mAlternativesListener.onAlternativesSelected(source, alternatives); + } + }); + + // parse by words, marked by %, darkest red color + if (target.charAt(0) == '%') { view.setBackgroundDrawable(getResources().getDrawable(R.drawable.second_person_worst_utterance_bg)); - text = text.subSequence(2, text.length()) ; + target = target.subSequence(2, target.length()) ; } - // parse error or unknown translations (in []) present, darkest red colour - else if (text.toString().contains("parse error:") || text.toString().contains("[")) { + // parse error or unknown translations (in []) present, darkest red color + else if (target.toString().contains("parse error:") || target.toString().contains("[")) { view.setBackgroundDrawable(getResources().getDrawable(R.drawable.second_person_worst_utterance_bg)); } - // parse by chunks, marked by *, red colour - else if (text.charAt(0) == '*') { + // parse by chunks, marked by *, red color + else if (target.charAt(0) == '*') { view.setBackgroundDrawable(getResources().getDrawable(R.drawable.second_person_chunk_utterance_bg)); - text = text.subSequence(2, text.length()) ; + target = target.subSequence(2, target.length()) ; } - // parse by domain grammar, marked by +, green colour - else if (text.charAt(0) == '+') { + // parse by domain grammar, marked by +, green color + else if (target.charAt(0) == '+') { view.setBackgroundDrawable(getResources().getDrawable(R.drawable.second_person_best_utterance_bg)); - text = text.subSequence(2, text.length()) ; + target = target.subSequence(2, target.length()) ; } - view.setText(text); - return text ; + view.setText(target); + return target; } - public void updateLastUtterance(CharSequence text, Object lexicon) { - if (mLastUtterance == null) - return; - - EditText textview = (EditText) mLastUtterance.findViewById(R.id.input_text); - if (textview == null) - return; - - textview.setText(text); - - if (lexicon != null && mWordListener != null) { - ImageView showWordButton = (ImageView) mLastUtterance.findViewById(R.id.show_word); - showWordButton.setVisibility(VISIBLE); - - final Object lexicon2 = lexicon; - showWordButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (mWordListener != null) { - TextView textview = (TextView) - ((View) v.getParent()).findViewById(R.id.input_text); - CharSequence text = textview.getText(); - mWordListener.onWordSelected(text, lexicon2); - } - } - }); - } + public void updateLastUtterance(CharSequence text) { + if (mLastUtterance != null) + mLastUtterance.setText(text); } - public void setOnWordSelectedListener(OnWordSelectedListener listener) { - mWordListener = listener; + public void setOnAlternativesListener(OnAlternativesListener listener) { + mAlternativesListener = listener; } public void setSpeechInputListener(ASR.Listener listener) { mSpeechListener = listener; } - public interface OnWordSelectedListener { - public void onWordSelected(CharSequence word, Object lexicon); + public interface OnAlternativesListener { + public void onAlternativesSelected(CharSequence word, Object lexicon); } } 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 404b0ee11..fb84fa6cb 100644 --- a/src/ui/android/src/org/grammaticalframework/ui/android/MainActivity.java +++ b/src/ui/android/src/org/grammaticalframework/ui/android/MainActivity.java @@ -10,6 +10,7 @@ import android.os.AsyncTask; import android.os.Bundle; import android.speech.SpeechRecognizer; import android.util.Log; +import android.util.Pair; import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; @@ -27,7 +28,8 @@ import android.widget.TextView; import org.grammaticalframework.ui.android.ASR.State; import org.grammaticalframework.ui.android.LanguageSelector.OnLanguageSelectedListener; -import org.grammaticalframework.ui.android.ConversationView.OnWordSelectedListener; +import org.grammaticalframework.ui.android.ConversationView.OnAlternativesListener; +import org.grammaticalframework.pgf.ExprProb; import org.grammaticalframework.pgf.MorphoAnalysis; public class MainActivity extends Activity { @@ -115,11 +117,11 @@ public class MainActivity extends Activity { mSpeechListener = new SpeechInputListener(); - mConversationView.setOnWordSelectedListener(new OnWordSelectedListener() { + mConversationView.setOnAlternativesListener(new OnAlternativesListener() { @Override - public void onWordSelected(CharSequence word, Object lexicon) { - Intent myIntent = new Intent(MainActivity.this, LexicalEntryActivity.class); - myIntent.putExtra("source", word); + public void onAlternativesSelected(CharSequence input, Object lexicon) { + Intent myIntent = new Intent(MainActivity.this, AlternativesActivity.class); + myIntent.putExtra("source", input); myIntent.putExtra("analyses", (Serializable) lexicon); MainActivity.this.startActivity(myIntent); } @@ -282,40 +284,38 @@ public class MainActivity extends Activity { } private void handlePartialSpeechInput(String input) { - mConversationView.updateLastUtterance(input, null); + mConversationView.updateLastUtterance(input); } private void handleSpeechInput(final String input) { - List list = mTranslator.lookupMorpho(input); - if (list.size() == 0) - list = null; + final List list = mTranslator.lookupMorpho(input); - mConversationView.updateLastUtterance(input, list); - new AsyncTask() { + mConversationView.updateLastUtterance(input); + new AsyncTask>>() { @Override protected void onPreExecute() { showProgressBar(); } @Override - protected String doInBackground(Void... params) { + protected Pair> doInBackground(Void... params) { return mTranslator.translate(input); } @Override - protected void onPostExecute(String result) { - outputText(result); + protected void onPostExecute(Pair> res) { + String text = res.first; + List alts = res.second; + if (DBG) Log.d(TAG, "Speaking: " + res.first); + CharSequence text2 = + mConversationView.addSecondPersonUtterance(input, text, (list.size() == 0) ? alts : list); + mTts.speak(getTargetLanguageCode(), text2.toString()); + hideProgressBar(); } }.execute(); } - private void outputText(String text) { - if (DBG) Log.d(TAG, "Speaking: " + text); - CharSequence text2 = mConversationView.addSecondPersonUtterance(text); - mTts.speak(getTargetLanguageCode(), text2.toString()); - } - private class SpeechInputListener implements ASR.Listener { @Override 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 705796494..06bcc006f 100644 --- a/src/ui/android/src/org/grammaticalframework/ui/android/Translator.java +++ b/src/ui/android/src/org/grammaticalframework/ui/android/Translator.java @@ -3,10 +3,12 @@ package org.grammaticalframework.ui.android; import android.content.Context; import android.content.SharedPreferences; import android.util.Log; +import android.util.Pair; import android.view.inputmethod.CompletionInfo; import org.grammaticalframework.pgf.Concr; import org.grammaticalframework.pgf.Expr; +import org.grammaticalframework.pgf.ExprProb; import org.grammaticalframework.pgf.FullFormEntry; import org.grammaticalframework.pgf.MorphoAnalysis; import org.grammaticalframework.pgf.PGF; @@ -15,6 +17,7 @@ import org.grammaticalframework.pgf.ParseError; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; @@ -75,6 +78,8 @@ public class Translator { private static final String SOURCE_LANG_KEY = "source_lang"; private static final String TARGET_LANG_KEY = "target_lang"; + private static final int NUM_ALT_TRANSLATIONS = 10; + private SharedPreferences mSharedPref; private Language getPrefLang(String key, int def) { @@ -258,67 +263,79 @@ public class Translator { return out; } - public String translateWord(String input) { + private String translateWord(String input) { - String output = "[" + input + "]" ; // if all else fails, return the word itself in brackets - Concr sourceLang = getSourceConcr() ; - Concr targetLang = getTargetConcr() ; + String output = "[" + input + "]" ; // if all else fails, return the word itself in brackets + Concr sourceLang = getSourceConcr() ; + Concr targetLang = getTargetConcr() ; - String lowerinput = input.toLowerCase() ; // also consider lower-cased versions of the word + String lowerinput = input.toLowerCase() ; // also consider lower-cased versions of the word try { - Expr expr = sourceLang.parseBest("Chunk", input) ; // try parse as chunk + Expr expr = sourceLang.parseBest("Chunk", input) ; // try parse as chunk output = targetLang.linearize(expr); return output ; - } catch (ParseError e) { // if this fails + } catch (ParseError e) { // if this fails + List morphos = lookupMorpho(input) ; // lookup morphological analyses - List morphos = lookupMorpho(input) ; // lookup morphological analyses + morphos.addAll(lookupMorpho(lowerinput)) ; // including the analyses of the lower-cased word - morphos.addAll(lookupMorpho(lowerinput)) ; // including the analyses of the lower-cased word - - for (MorphoAnalysis ana : morphos) { - if (targetLang.hasLinearization(ana.getLemma())) { // check that the word has linearization in target - output = targetLang.linearize(Expr.readExpr(ana.getLemma())) ; - break ; // if yes, don't search any more - } - } - return output ; - } + for (MorphoAnalysis ana : morphos) { + if (targetLang.hasLinearization(ana.getLemma())) { // check that the word has linearization in target + output = targetLang.linearize(Expr.readExpr(ana.getLemma())) ; + break ; // if yes, don't search any more + } + } + return output ; + } } - public String parseByLookup(String input) { - String[] words = input.split(" ") ; + private String translateByLookup(String input) { + String[] words = input.split(" ") ; String output = "%" ; - for (String w : words) { - output = output + " " + translateWord(w) ; + output = output + " " + translateWord(w) ; } - return output ; + return output ; } /** * Takes a lot of time. Must not be called on the main thread. */ - public String translate(String input) { + public Pair> translate(String input) { if (getSourceLanguage().getLangCode().equals("cmn-Hans-CN")) { // for Chinese we need to put space after every character input = explode(input); } + String output = null; + List exprs = new ArrayList(); + try { Concr sourceLang = getSourceConcr(); - Expr expr = sourceLang.parseBest(getGrammar().getStartCat(), input); Concr targetLang = getTargetConcr(); - String output = targetLang.linearize(expr); - return output; + + Expr expr = sourceLang.parseBest(getGrammar().getStartCat(), input); + + int count = NUM_ALT_TRANSLATIONS; + for (ExprProb ep : sourceLang.parse(getGrammar().getStartCat(), input)) { + if (count-- <= 0) + break; + exprs.add(ep); + output = targetLang.linearize(expr); + } } catch (ParseError e) { - // Log.e(TAG, "Parse error: " + e); //lookupMorpho - // return "parse error: " + e.getMessage(); - String output = parseByLookup(input) ; - return output ; + output = translateByLookup(input); } + + return new Pair>(output, exprs); + } + + public String linearize(Expr expr) { + Concr targetLang = getTargetConcr(); + return targetLang.linearize(expr); } public String generateLexiconEntry(String lemma) { diff --git a/src/ui/android/src/org/grammaticalframework/ui/android/TranslatorInputMethodService.java b/src/ui/android/src/org/grammaticalframework/ui/android/TranslatorInputMethodService.java index 81b0eff05..a15d030a2 100644 --- a/src/ui/android/src/org/grammaticalframework/ui/android/TranslatorInputMethodService.java +++ b/src/ui/android/src/org/grammaticalframework/ui/android/TranslatorInputMethodService.java @@ -374,7 +374,7 @@ public class TranslatorInputMethodService extends InputMethodService mTranslator.setSourceLanguage(newSource); handleChangeSourceLanguage(newSource); } else if (primaryCode == TranslatorKeyboard.KEYCODE_TARGET_LANGUAGE) { - String translation = mTranslator.translate(getComposingString()); + String translation = mTranslator.translate(getComposingString()).first; getCurrentInputConnection().commitText(translation, 1); return; } else if (primaryCode < TranslatorKeyboard.KEYCODE_TARGET_LANGUAGE &&