diff --git a/src/ui/android/res/drawable-hdpi/ic_drawer.png b/src/ui/android/res/drawable-hdpi/ic_drawer.png new file mode 100644 index 000000000..c59f601ca Binary files /dev/null and b/src/ui/android/res/drawable-hdpi/ic_drawer.png differ diff --git a/src/ui/android/res/drawable-mdpi/ic_drawer.png b/src/ui/android/res/drawable-mdpi/ic_drawer.png new file mode 100644 index 000000000..1ed2c56ee Binary files /dev/null and b/src/ui/android/res/drawable-mdpi/ic_drawer.png differ diff --git a/src/ui/android/res/drawable-xhdpi/ic_drawer.png b/src/ui/android/res/drawable-xhdpi/ic_drawer.png new file mode 100644 index 000000000..a5fa74def Binary files /dev/null and b/src/ui/android/res/drawable-xhdpi/ic_drawer.png differ diff --git a/src/ui/android/res/drawable-xxhdpi/ic_drawer.png b/src/ui/android/res/drawable-xxhdpi/ic_drawer.png new file mode 100644 index 000000000..9c4685d6e Binary files /dev/null and b/src/ui/android/res/drawable-xxhdpi/ic_drawer.png differ diff --git a/src/ui/android/res/layout/activity_lexical_entry.xml b/src/ui/android/res/layout/activity_lexical_entry.xml index 0dc7fc7be..fa49f252b 100644 --- a/src/ui/android/res/layout/activity_lexical_entry.xml +++ b/src/ui/android/res/layout/activity_lexical_entry.xml @@ -4,49 +4,66 @@ android:layout_height="match_parent" android:layout_width="match_parent"> - + + + android:layout_height="match_parent" + android:layout_width="match_parent"> - + android:layout_alignParentLeft="true" + android:layout_alignParentRight="true" + android:padding="8dp" + android:background="#C0C0C0"> + + + + + + + + - - - + - - + android:layout_gravity="start" + android:choiceMode="singleChoice" + android:divider="@android:color/transparent" + android:dividerHeight="0dp" + android:background="#FFFFE0"/> + - \ 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 index e7f551491..1d3835e54 100644 --- a/src/ui/android/res/layout/lexical_item.xml +++ b/src/ui/android/res/layout/lexical_item.xml @@ -18,10 +18,10 @@ android:layout_toRightOf="@id/arrow" android:paddingLeft="10dp" android:textSize="25sp"/> - + - \ No newline at end of file + diff --git a/src/ui/android/res/menu/main.xml b/src/ui/android/res/menu/main.xml index 7f5a4c4f7..b1ba5f268 100644 --- a/src/ui/android/res/menu/main.xml +++ b/src/ui/android/res/menu/main.xml @@ -1,6 +1,8 @@ + diff --git a/src/ui/android/res/values/strings.xml b/src/ui/android/res/values/strings.xml index a4d02295d..3b9f828b2 100644 --- a/src/ui/android/res/values/strings.xml +++ b/src/ui/android/res/values/strings.xml @@ -29,4 +29,8 @@ Search word: Search for words in the lexicon + + Topics + Open topics + Close topics diff --git a/src/ui/android/src/org/grammaticalframework/ui/android/AlternativesActivity.java b/src/ui/android/src/org/grammaticalframework/ui/android/AlternativesActivity.java index 2cb8f9da4..8027441b0 100644 --- a/src/ui/android/src/org/grammaticalframework/ui/android/AlternativesActivity.java +++ b/src/ui/android/src/org/grammaticalframework/ui/android/AlternativesActivity.java @@ -5,19 +5,30 @@ import java.util.*; import android.app.Activity; import android.app.ListActivity; import android.content.Context; +import android.content.res.Configuration; import android.os.AsyncTask; import android.os.Bundle; -import android.view.LayoutInflater; +import android.view.*; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; +import android.view.MenuItem; +import android.view.LayoutInflater; import android.webkit.WebView; +import android.widget.BaseAdapter; +import android.widget.ListAdapter; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.ListView; +import android.widget.AdapterView; import android.util.Log; +import android.support.v4.widget.DrawerLayout; +import android.support.v4.app.ActionBarDrawerToggle; import org.grammaticalframework.pgf.*; import org.grammaticalframework.ui.android.LanguageSelector.OnLanguageSelectedListener; @@ -28,6 +39,9 @@ public class AlternativesActivity extends ListActivity { private LanguageSelector mShowLanguageView; private View mProgressBarView = null; private AlternativesAdapter mAdapter = null; + private DrawerLayout mDrawerLayout; + private ListView mDrawerList; + private ActionBarDrawerToggle mDrawerToggle; /** Called when the activity is first created. */ @Override @@ -64,23 +78,44 @@ public class AlternativesActivity extends ListActivity { }.execute(); } }); - + + mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); + mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, + R.drawable.ic_drawer, + R.string.topics_open, + R.string.topics_close); + mDrawerLayout.setDrawerListener(mDrawerToggle); + + getActionBar().setDisplayHomeAsUpEnabled(true); + getActionBar().setHomeButtonEnabled(true); + TextView descrView = (TextView) findViewById(R.id.lexical_desc); - String authority = getIntent().getData().getAuthority(); - String source = getIntent().getData().getQueryParameter("source"); - List analyses = new ArrayList(); - for (String an : getIntent().getData().getQueryParameters("alternative")) { - analyses.add(Expr.readExpr(an)); - } - descrView.setText(source); + if (getIntent().getData() != null) { + String authority = getIntent().getData().getAuthority(); + String source = getIntent().getData().getQueryParameter("source"); - mAdapter = new AlternativesAdapter(this, authority, analyses); + List analyses = new ArrayList(); + for (String an : getIntent().getData().getQueryParameters("alternative")) { + analyses.add(Expr.readExpr(an)); + } + descrView.setText(source); + + mAdapter = new AlternativesAdapter(this, authority, analyses); + } else { + mDrawerLayout.openDrawer(Gravity.LEFT); + + mAdapter = new AlternativesAdapter(this, Translator.WORDS); + } + + expandedView = null; setListAdapter(mAdapter); - expandedView = null; + + mDrawerList = (ListView) findViewById(R.id.topics_list); + mDrawerList.setAdapter(mAdapter.getTopicsAdapter()); mProgressBarView = findViewById(R.id.progressBarView); - } + } @Override protected void onResume() { @@ -98,7 +133,7 @@ public class AlternativesActivity extends ListActivity { } private View expandedView; - + private void collapse() { if (expandedView == null) return; @@ -182,22 +217,183 @@ public class AlternativesActivity extends ListActivity { expandedView = view; } - private class AlternativesAdapter extends ArrayAdapter { - private String mAuthority; + private class Topic { + public String name; + public Expr expr; + public boolean isChecked; + public boolean isAvailable; + + public Topic(String name, Expr expr) { + this.name = name; + this.expr = expr; + this.isChecked = false; + this.isAvailable = true; + } + } - public AlternativesAdapter(Context context, String authority, List data) { - super(context, android.R.layout.simple_list_item_1, data); - mAuthority = authority; - } + private class AlternativesAdapter extends BaseAdapter implements ListAdapter { + /** + * Contains the list of objects that represent the alternatives + */ + private List mAlternatives; + + private Context mContext; + + // A copy of the original mAlternatives array, initialized from and then used instead as soon as + // a topic filtering is applied. mAlternatives will then only contain the filtered values. + private ArrayList mOriginalAlternatives; + + private LayoutInflater mInflater; + + private String mAuthority; + + /** + * A list of lists of topics. Each element in this list contains + * the list of topics for the correponding item in + * mAlternatives/mOriginalAlternatives + */ + private List> mTopics; + + private Map mTopicMap; + private Topic[] mAllTopics; + private Topic[] mOriginalAllTopics; + + private Topic mOtherTopic; + private Topic mSourceTopic; + + private TopicsAdapter mTopicsAdapter; + + public AlternativesAdapter(Context context, String authority, List alternatives) { + mContext = context; + mAuthority = authority; + mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mAlternatives = alternatives; + + boolean addOther = false; + mTopics = new ArrayList>(); + mTopicMap = new TreeMap(); + for (Expr e : mAlternatives) { + List topics = new ArrayList(); + for (Expr topicExpr : mTranslator.getTopicsOf(e)) { + String name = mTranslator.linearizeSource(topicExpr); + String key = name.toLowerCase(); + Topic topic = mTopicMap.get(key); + if (topic == null) { + topic = new Topic(name, topicExpr); + mTopicMap.put(key,topic); + } + topics.add(topic); + } + mTopics.add(topics); + + if (topics.size() == 0) + addOther = true; + } + + int i = 0; + mAllTopics = new Topic[mTopicMap.size() + (addOther ? 1 : 0)]; + for (Map.Entry entry : mTopicMap.entrySet()) { + mAllTopics[i++] = entry.getValue(); + } + if (addOther) { + Expr topicExpr = Expr.readExpr("other_1_A"); + String name = mTranslator.linearizeSource(topicExpr); + mOtherTopic = new Topic(name, topicExpr); + mAllTopics[i++] = mOtherTopic; + } + + mOriginalAllTopics = mAllTopics; + + mTopicsAdapter = new TopicsAdapter(); + } + + public AlternativesAdapter(Context context, String authority) { + mContext = context; + mAuthority = authority; + mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mAlternatives = null; + + mTopics = null; + + int i = 0; + mTopicMap = new TreeMap(); + for (Expr topicExpr : mTranslator.getTopicsOf(null)) { + String name = mTranslator.linearizeSource(topicExpr); + String key = name.toLowerCase(); + Topic topic = mTopicMap.get(key); + if (topic == null) { + topic = new Topic(name, topicExpr); + mTopicMap.put(key,topic); + } + } + mAllTopics = new Topic[mTopicMap.size()]; + for (Map.Entry entry : mTopicMap.entrySet()) { + mAllTopics[i++] = entry.getValue(); + } + + mOriginalAllTopics = mAllTopics; + + mTopicsAdapter = new TopicsAdapter(); + } + + /** + * Returns the context associated with this array adapter. The context is used + * to create views from the resource passed to the constructor. + * + * @return The Context associated with this adapter. + */ + public Context getContext() { + return mContext; + } + + public String getAuthority() { + return mAuthority; + } + + /** + * {@inheritDoc} + */ + public int getCount() { + if (mAlternatives == null) + return 0; + else + return mAlternatives.size(); + } + + /** + * {@inheritDoc} + */ + public Expr getItem(int position) { + return mAlternatives.get(position); + } + + /** + * Returns the position of the specified item in the array. + * + * @param item The item to retrieve the position of. + * + * @return The position of the specified item. + */ + public int getPosition(Expr item) { + if (mAlternatives == null) + return -1; + else + return mAlternatives.indexOf(item); + } + + /** + * {@inheritDoc} + */ + public long getItemId(int position) { + return position; + } public View getView(int position, View convertView, ViewGroup parent) { final Expr expr = getItem(position); if (mAuthority.equals(Translator.WORDS)) { if (convertView == null) { - LayoutInflater inflater = (LayoutInflater) - getContext().getSystemService(Activity.LAYOUT_INFLATER_SERVICE); - convertView = inflater.inflate(R.layout.lexical_item, null); + convertView = mInflater.inflate(R.layout.lexical_item, null); } TextView descView = (TextView) @@ -221,9 +417,7 @@ public class AlternativesActivity extends ListActivity { }); } else if (mAuthority.equals(Translator.SENTENCES)) { if (convertView == null) { - LayoutInflater inflater = (LayoutInflater) - getContext().getSystemService(Activity.LAYOUT_INFLATER_SERVICE); - convertView = inflater.inflate(R.layout.alternative_item, null); + convertView = mInflater.inflate(R.layout.alternative_item, null); View treeView = (View) convertView.findViewById(R.id.desc_details); ((RelativeLayout) convertView).removeView(treeView); @@ -283,5 +477,192 @@ public class AlternativesActivity extends ListActivity { return convertView; } + + public boolean areAllItemsEnabled() { + return true; + } + + public boolean isEnabled(int position) { + return true; + } + + public TopicsAdapter getTopicsAdapter() { + return mTopicsAdapter; + } + + void filterOnTopics(List selected_topics) { + if (mSourceTopic != null && !selected_topics.contains(mSourceTopic)) + mAlternatives = null; + + if (mAlternatives == null) { + if (selected_topics.size() == 0) + return; + + mSourceTopic = selected_topics.get(0); + mAlternatives = mTranslator.getTopicWords(mSourceTopic.expr); + + mTopics = new ArrayList>(); + for (Expr e : mAlternatives) { + List topics = new ArrayList(); + for (Expr topicExpr : mTranslator.getTopicsOf(e)) { + String name = mTranslator.linearizeSource(topicExpr); + String key = name.toLowerCase(); + topics.add(mTopicMap.get(key)); + } + mTopics.add(topics); + } + } + + if (mOriginalAlternatives == null) { + mOriginalAlternatives = new ArrayList(mAlternatives); + } + + mAlternatives = new ArrayList(); + List> topics = new ArrayList>(); + + for (Topic topic : mOriginalAllTopics) { + topic.isAvailable = false; + } + + int count = 0; + for (int i = 0; i < mOriginalAlternatives.size(); i++) { + boolean match = true; + for (Topic topic : selected_topics) { + if (topic == mOtherTopic) { + if (mTopics.get(i).size() > 0) { + match = false; + break; + } + } else if (!mTopics.get(i).contains(topic)) { + match = false; + break; + } + } + if (match) { + mAlternatives.add(mOriginalAlternatives.get(i)); + if (mTopics.get(i).size() == 0) { + if (!mOtherTopic.isAvailable) + count++; + mOtherTopic.isAvailable = true; + } else { + for (Topic topic : mTopics.get(i)) { + if (!topic.isAvailable) + count++; + topic.isAvailable = true; + } + } + } + } + + int i = 0; + mAllTopics = new Topic[count]; + for (Topic topic : mOriginalAllTopics) { + if (topic.isAvailable) + mAllTopics[i++] = topic; + } + + notifyDataSetChanged(); + } + } + + private class TopicsAdapter extends BaseAdapter implements ListAdapter { + public TopicsAdapter() { + } + + public Context getContext() { + return mAdapter.getContext(); + } + + public int getCount() { + return mAdapter.mAllTopics.length; + } + + public Topic getItem(int position) { + return mAdapter.mAllTopics[position]; + } + + public int getPosition(Topic topic) { + for (int i = 0; i < mAdapter.mAllTopics.length; i++) { + if (mAdapter.mAllTopics[i] == topic) + return i; + } + return -1; + } + + public long getItemId(int position) { + return position; + } + + // Shame on you Google this class should not have been here + // but unfortuantely CheckBox.setChecked doesn't work and here + // we need a workarround. Since the class is there now it is also + // used to implement OnClickListener. + private class TopicCheckBox extends CheckBox implements OnClickListener { + private Topic mTopic; + + public TopicCheckBox(Context context, Topic topic) { + super(context); + mTopic = topic; + setOnClickListener(this); + } + + @Override + public boolean isChecked() { + if (mTopic == null) + return false; + else + return mTopic.isChecked; + } + + @Override + public void onClick(View view) { + mTopic.isChecked = !mTopic.isChecked; + filterOnTopics(); + notifyDataSetChanged(); + } + } + + private void filterOnTopics() { + List selected_topics = new ArrayList(); + for (int i = 0; i < getCount(); i++) { + Topic topic = getItem(i); + if (topic.isChecked) + selected_topics.add(topic); + } + mAdapter.filterOnTopics(selected_topics); + } + + public View getView(int position, View convertView, ViewGroup parent) { + Topic entry = getItem(position); + + CheckBox checkBox = new TopicCheckBox(getContext(), entry); + checkBox.setText(entry.name); + checkBox.setTextSize(25); + checkBox.setTextColor(android.graphics.Color.parseColor("#808080")); + return checkBox; + } + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + // Sync the toggle state after onRestoreInstanceState has occurred. + mDrawerToggle.syncState(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + mDrawerToggle.onConfigurationChanged(newConfig); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Pass the event to ActionBarDrawerToggle, if it returns + // true, then it has handled the app icon touch event + if (mDrawerToggle.onOptionsItemSelected(item)) { + return true; + } + return super.onOptionsItemSelected(item); } } 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 9ef344997..1d1bd481d 100644 --- a/src/ui/android/src/org/grammaticalframework/ui/android/MainActivity.java +++ b/src/ui/android/src/org/grammaticalframework/ui/android/MainActivity.java @@ -191,8 +191,8 @@ public class MainActivity extends Activity { editor.commit(); return true; - case R.id.semantic_graph: { - Intent myIntent = new Intent(MainActivity.this, SemanticGraphActivity.class); + case R.id.topics: { + Intent myIntent = new Intent(MainActivity.this, AlternativesActivity.class); MainActivity.this.startActivity(myIntent); return true; } 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 88884f819..620d42350 100644 --- a/src/ui/android/src/org/grammaticalframework/ui/android/Translator.java +++ b/src/ui/android/src/org/grammaticalframework/ui/android/Translator.java @@ -338,11 +338,26 @@ public class Translator { if (s == null) s = "% "; // make sure that we return something - if (getTargetLanguage().getLangCode().equals("cmn-Hans-CN") || - getTargetLanguage().getLangCode().equals("ja-JP") || - getTargetLanguage().getLangCode().equals("th-TH")) - return implode(s) ; - else return s ; + if (getTargetLanguage().getLangCode().equals("cmn-Hans-CN") || + getTargetLanguage().getLangCode().equals("ja-JP") || + getTargetLanguage().getLangCode().equals("th-TH")) + return implode(s); + else + return s; + } + + public String linearizeSource(Expr expr) { + Concr targetLang = getSourceConcr(); + String s = targetLang.linearize(expr); + if (s == null) + s = "% "; // make sure that we return something + + if (getTargetLanguage().getLangCode().equals("cmn-Hans-CN") || + getTargetLanguage().getLangCode().equals("ja-JP") || + getTargetLanguage().getLangCode().equals("th-TH")) + return implode(s); + else + return s; } public Object[] bracketedLinearize(Expr expr) { @@ -443,15 +458,34 @@ public class Translator { } } - private Expr getTopicWords(Expr lemma) { - StringBuilder sbuilder = new StringBuilder(); + public List getTopicWords(Expr lemma) { + TripleResult res = null; + List words = new ArrayList(); try { - TripleResult res = mSGManager.queryTriple(null, topic_pred, lemma); + res = mSGManager.queryTriple(null, topic_pred, lemma); + while (res.hasNext()) { + words.add(res.getSubject()); + } + } catch (IOException e) { + // nothing + } catch (SGError e) { + // nothing + } finally { + if (res != null) + res.close(); + } + return words; + } + + private Expr getTopicWordsHtml(Expr lemma) { + StringBuilder sbuilder = new StringBuilder(); + TripleResult res = null; + try { + res = mSGManager.queryTriple(null, topic_pred, lemma); Map map = new TreeMap(); while (res.hasNext()) { updateWordsMap(res.getSubject(), map); } - res.close(); StringBuilder builder = new StringBuilder(); buildWordsHtml(map, builder); @@ -460,6 +494,9 @@ public class Translator { // nothing } catch (SGError e) { // nothing + } finally { + if (res != null) + res.close(); } return null; } @@ -483,9 +520,9 @@ public class Translator { Expr e = new Expr("MkDocument", def, new Expr("Inflection"+cat,lemma), - getTopicWords(lemma)); + getTopicWordsHtml(lemma)); String html = - "" + + "" + targetLang.linearize(e) + ""; return html; @@ -495,7 +532,26 @@ public class Translator { return null; } } - + + public List getTopicsOf(Expr lemma) { + TripleResult res = null; + List topics = new ArrayList(); + try { + res = mSGManager.queryTriple(lemma, topic_pred, null); + while (res.hasNext()) { + topics.add(res.getObject()); + } + } catch (IOException e) { + // nothing + } catch (SGError e) { + // nothing + } finally { + if (res != null) + res.close(); + } + return topics; + } + private static String escapeHtml(CharSequence text) { StringBuilder out = new StringBuilder();