forked from GitHub/gf-core
Visualization for parse trees on Android
This commit is contained in:
@@ -12,16 +12,18 @@
|
||||
android:paddingRight="5dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/lexical_desc"
|
||||
android:id="@+id/alternative_desc"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toRightOf="@id/arrow"
|
||||
android:paddingLeft="10dp"
|
||||
android:textSize="25sp"/>
|
||||
|
||||
<WebView
|
||||
android:id="@+id/lexical_inflection"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/lexical_desc"/>
|
||||
<org.grammaticalframework.ui.android.ParseTreeView
|
||||
android:id="@+id/desc_details"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/alternative_desc"
|
||||
android:textSize="25sp"
|
||||
/>
|
||||
</RelativeLayout>
|
||||
27
src/ui/android/res/layout/lexical_item.xml
Normal file
27
src/ui/android/res/layout/lexical_item.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent" >
|
||||
<ImageView
|
||||
android:id="@+id/arrow"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:contentDescription="@string/open_image"
|
||||
android:src="@drawable/open_arrow"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingRight="5dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/lexical_desc"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toRightOf="@id/arrow"
|
||||
android:paddingLeft="10dp"
|
||||
android:textSize="25sp"/>
|
||||
|
||||
<WebView
|
||||
android:id="@+id/desc_details"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/lexical_desc"/>
|
||||
</RelativeLayout>
|
||||
@@ -103,8 +103,8 @@ public class AlternativesActivity extends ListActivity {
|
||||
ImageView arrow = (ImageView) expandedView.findViewById(R.id.arrow);
|
||||
arrow.setImageResource(R.drawable.open_arrow);
|
||||
|
||||
WebView inflectionView = (WebView) expandedView.findViewById(R.id.lexical_inflection);
|
||||
((RelativeLayout) expandedView).removeView(inflectionView);
|
||||
View view = (View) expandedView.findViewById(R.id.desc_details);
|
||||
((RelativeLayout) expandedView).removeView(view);
|
||||
|
||||
expandedView = null;
|
||||
}
|
||||
@@ -117,10 +117,10 @@ public class AlternativesActivity extends ListActivity {
|
||||
ImageView arrow = (ImageView) view.findViewById(R.id.arrow);
|
||||
arrow.setImageResource(R.drawable.close_arrow);
|
||||
|
||||
WebView inflectionView = (WebView) view.findViewById(R.id.lexical_inflection);
|
||||
WebView inflectionView = (WebView) view.findViewById(R.id.desc_details);
|
||||
if (inflectionView == null) {
|
||||
inflectionView = new WebView(this);
|
||||
inflectionView.setId(R.id.lexical_inflection);
|
||||
inflectionView.setId(R.id.desc_details);
|
||||
RelativeLayout.LayoutParams params =
|
||||
new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||
params.addRule(RelativeLayout.BELOW, R.id.lexical_desc);
|
||||
@@ -136,18 +136,18 @@ public class AlternativesActivity extends ListActivity {
|
||||
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);
|
||||
ParseTreeView parseView = (ParseTreeView) view.findViewById(R.id.desc_details);
|
||||
if (parseView == null) {
|
||||
parseView = new ParseTreeView(this);
|
||||
parseView.setId(R.id.desc_details);
|
||||
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);
|
||||
params.addRule(RelativeLayout.BELOW, R.id.alternative_desc);
|
||||
((RelativeLayout) view).addView(parseView, params);
|
||||
}
|
||||
|
||||
String content = String.format("[%.4f] %s", ep.getProb(), ep.getExpr());
|
||||
inflectionView.loadData(content, "text/plain; charset=UTF-8", null);
|
||||
Object[] brackets = mTranslator.bracketedLinearize(ep.getExpr());
|
||||
parseView.setBrackets(brackets);
|
||||
|
||||
expandedView = view;
|
||||
}
|
||||
@@ -160,16 +160,16 @@ public class AlternativesActivity extends ListActivity {
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
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);
|
||||
|
||||
if (item instanceof MorphoAnalysis) {
|
||||
if (convertView == null) {
|
||||
LayoutInflater inflater = (LayoutInflater)
|
||||
getContext().getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
|
||||
convertView = inflater.inflate(R.layout.lexical_item, null);
|
||||
}
|
||||
|
||||
TextView descView = (TextView)
|
||||
convertView.findViewById(R.id.lexical_desc);
|
||||
|
||||
final String lemma = ((MorphoAnalysis) item).getLemma();
|
||||
|
||||
String phrase = mTranslator.generateLexiconEntry(lemma);
|
||||
@@ -188,55 +188,62 @@ public class AlternativesActivity extends ListActivity {
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (item instanceof ExprProb) {
|
||||
final ExprProb ep = (ExprProb) item;
|
||||
|
||||
String phrase = mTranslator.linearize(ep.getExpr());
|
||||
} else if (item instanceof ExprProb) {
|
||||
final ExprProb ep = (ExprProb) item;
|
||||
|
||||
if (convertView == null) {
|
||||
LayoutInflater inflater = (LayoutInflater)
|
||||
getContext().getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
|
||||
convertView = inflater.inflate(R.layout.alternative_item, null);
|
||||
}
|
||||
|
||||
// parse by words, marked by %, darkest red color
|
||||
if (phrase.charAt(0) == '%') {
|
||||
descView.setBackgroundDrawable(getResources().getDrawable(R.drawable.second_person_worst_utterance_bg));
|
||||
phrase = phrase.substring(2);
|
||||
}
|
||||
TextView descView = (TextView)
|
||||
convertView.findViewById(R.id.alternative_desc);
|
||||
|
||||
// parse by chunks, marked by *, red color
|
||||
else if (phrase.charAt(0) == '*') {
|
||||
descView.setBackgroundDrawable(getResources().getDrawable(R.drawable.second_person_chunk_utterance_bg));
|
||||
phrase = phrase.substring(2);
|
||||
}
|
||||
String phrase = mTranslator.linearize(ep.getExpr());
|
||||
|
||||
// parse error or unknown translations (in []) present, darkest red color
|
||||
else if (phrase.contains("parse error:") || phrase.contains("[")) {
|
||||
descView.setBackgroundDrawable(getResources().getDrawable(R.drawable.second_person_worst_utterance_bg));
|
||||
}
|
||||
// parse by words, marked by %, darkest red color
|
||||
if (phrase.charAt(0) == '%') {
|
||||
descView.setBackgroundDrawable(getResources().getDrawable(R.drawable.second_person_worst_utterance_bg));
|
||||
phrase = phrase.substring(2);
|
||||
}
|
||||
|
||||
// parse by domain grammar, marked by +, green color
|
||||
else if (phrase.charAt(0) == '+') {
|
||||
descView.setBackgroundDrawable(getResources().getDrawable(R.drawable.second_person_best_utterance_bg));
|
||||
phrase = phrase.substring(2);
|
||||
}
|
||||
|
||||
else {
|
||||
descView.setBackgroundDrawable(getResources().getDrawable(R.drawable.second_person_utterance_bg));
|
||||
}
|
||||
// parse by chunks, marked by *, red color
|
||||
else if (phrase.charAt(0) == '*') {
|
||||
descView.setBackgroundDrawable(getResources().getDrawable(R.drawable.second_person_chunk_utterance_bg));
|
||||
phrase = phrase.substring(2);
|
||||
}
|
||||
|
||||
descView.setText(phrase);
|
||||
// parse error or unknown translations (in []) present, darkest red color
|
||||
else if (phrase.contains("parse error:") || phrase.contains("[")) {
|
||||
descView.setBackgroundDrawable(getResources().getDrawable(R.drawable.second_person_worst_utterance_bg));
|
||||
}
|
||||
|
||||
convertView.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (expandedView == view)
|
||||
collapse();
|
||||
else if (expandedView == null)
|
||||
expandExpr(view, ep);
|
||||
else {
|
||||
collapse();
|
||||
expandExpr(view, ep);
|
||||
}
|
||||
// parse by domain grammar, marked by +, green color
|
||||
else if (phrase.charAt(0) == '+') {
|
||||
descView.setBackgroundDrawable(getResources().getDrawable(R.drawable.second_person_best_utterance_bg));
|
||||
phrase = phrase.substring(2);
|
||||
}
|
||||
|
||||
else {
|
||||
descView.setBackgroundDrawable(getResources().getDrawable(R.drawable.second_person_utterance_bg));
|
||||
}
|
||||
|
||||
descView.setText(phrase);
|
||||
|
||||
convertView.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (expandedView == view)
|
||||
collapse();
|
||||
else if (expandedView == null)
|
||||
expandExpr(view, ep);
|
||||
else {
|
||||
collapse();
|
||||
expandExpr(view, ep);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return convertView;
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
package org.grammaticalframework.ui.android;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Pair;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
import org.grammaticalframework.pgf.Bracket;
|
||||
|
||||
|
||||
public class ParseTreeView extends View {
|
||||
private static final float SISTER_SKIP = 25;
|
||||
private static final float PARENT_SKIP = 0.5f;
|
||||
private static final float ABOVE_LINE_SKIP = 0.1f;
|
||||
private static final float BELOW_LINE_SKIP = 0.1f;
|
||||
|
||||
private Paint paint;
|
||||
private Object[] brackets;
|
||||
|
||||
public ParseTreeView(Context context) {
|
||||
super(context);
|
||||
initialize();
|
||||
}
|
||||
|
||||
public ParseTreeView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize();
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
paint = new Paint();
|
||||
paint.setTextSize(60);
|
||||
brackets = null;
|
||||
}
|
||||
|
||||
public Object[] getBrackets() {
|
||||
return brackets;
|
||||
}
|
||||
|
||||
public void setBrackets(Object[] brackets) {
|
||||
this.brackets = brackets;
|
||||
}
|
||||
|
||||
static class MeasureResult {
|
||||
float width = 0.0f;
|
||||
float height = 0.0f;
|
||||
float nodeTab = 0.0f;
|
||||
float nodeCenter = 0.0f;
|
||||
float childTab = 0.0f;
|
||||
float localWidth = 0.0f;
|
||||
float localHeight = 0.0f;
|
||||
}
|
||||
|
||||
private MeasureResult mr = new MeasureResult();
|
||||
private SparseArray<PointF> coords = new SparseArray<PointF>();
|
||||
|
||||
private void measureTree(Object o, PointF zeroPoint) {
|
||||
if (o instanceof Bracket) {
|
||||
Bracket bracket = (Bracket) o;
|
||||
|
||||
Rect bounds = new Rect();
|
||||
paint.getTextBounds(bracket.cat,0,bracket.cat.length(),bounds);
|
||||
float localWidth = bounds.width();
|
||||
float localHeight = bounds.height();
|
||||
float layerHeight = localHeight * (1.0f + BELOW_LINE_SKIP + ABOVE_LINE_SKIP + PARENT_SKIP);
|
||||
|
||||
PointF local = coords.get(bracket.fid);
|
||||
if (local == null) {
|
||||
if (zeroPoint != null)
|
||||
coords.put(bracket.fid, zeroPoint);
|
||||
} else {
|
||||
localWidth = 0;
|
||||
}
|
||||
|
||||
float subWidth = 0.0f;
|
||||
float subHeight = 0.0f;
|
||||
float nodeCenter = 0.0f;
|
||||
for (int i = 0; i < bracket.children.length; i++) {
|
||||
measureTree(bracket.children[i], zeroPoint);
|
||||
|
||||
if (i == 0) {
|
||||
nodeCenter += (subWidth + mr.nodeCenter) / 2.0;
|
||||
}
|
||||
if (i == bracket.children.length - 1) {
|
||||
nodeCenter += (subWidth + mr.nodeCenter) / 2.0;
|
||||
}
|
||||
|
||||
subWidth += mr.width;
|
||||
if (i < bracket.children.length - 1) {
|
||||
subWidth += SISTER_SKIP;
|
||||
}
|
||||
|
||||
if (subHeight < mr.height)
|
||||
subHeight = mr.height;
|
||||
}
|
||||
float localLeft = localWidth / 2.0f;
|
||||
float subLeft = nodeCenter;
|
||||
float totalLeft = Math.max(localLeft, subLeft);
|
||||
float localRight = localWidth / 2.0f;
|
||||
float subRight = subWidth - nodeCenter;
|
||||
float totalRight = Math.max(localRight, subRight);
|
||||
mr.width = totalLeft + totalRight;
|
||||
mr.height = layerHeight + subHeight;
|
||||
mr.childTab = totalLeft - subLeft;
|
||||
mr.nodeTab = totalLeft - localLeft;
|
||||
mr.nodeCenter = nodeCenter + mr.childTab;
|
||||
mr.localWidth = localWidth;
|
||||
mr.localHeight = localHeight;
|
||||
} else {
|
||||
String word = o.toString();
|
||||
|
||||
Rect bounds = new Rect();
|
||||
paint.getTextBounds(word,0,word.length(),bounds);
|
||||
mr.width = bounds.width();
|
||||
mr.height = bounds.height();
|
||||
mr.nodeTab = 0.0f;
|
||||
mr.nodeCenter = bounds.width() / 2.0f;
|
||||
mr.childTab = 0.0f;
|
||||
mr.localWidth = bounds.width();
|
||||
mr.localHeight = bounds.height();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
if (brackets == null) {
|
||||
setMeasuredDimension(0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
float width = 0.0f;
|
||||
float height = 0.0f;
|
||||
PointF zeroPoint = new PointF();
|
||||
for (int i = 0; i < brackets.length; i++) {
|
||||
measureTree(brackets[i], zeroPoint);
|
||||
|
||||
width += mr.width;
|
||||
if (i < brackets.length - 1) {
|
||||
width += SISTER_SKIP;
|
||||
}
|
||||
|
||||
if (height < mr.height)
|
||||
height = mr.height;
|
||||
}
|
||||
|
||||
width += 10;
|
||||
height += 10;
|
||||
|
||||
int w = getPaddingLeft() + (int) width + getPaddingRight();
|
||||
int h = getPaddingTop() + (int) height + getPaddingBottom();
|
||||
|
||||
setMeasuredDimension(w, h);
|
||||
}
|
||||
|
||||
private void drawTree(Canvas canvas,
|
||||
float x, float y, float bottom, PointF parentPoint,
|
||||
Object o) {
|
||||
if (o instanceof Bracket) {
|
||||
Bracket bracket = (Bracket) o;
|
||||
|
||||
PointF lineStart = coords.get(bracket.fid);
|
||||
if (lineStart == null) {
|
||||
lineStart = new PointF(x + mr.nodeCenter, y + mr.localHeight * (1.0f + BELOW_LINE_SKIP));
|
||||
coords.put(bracket.fid, lineStart);
|
||||
|
||||
if (parentPoint != null) {
|
||||
float lineEndX = x + mr.nodeCenter;
|
||||
float lineEndY = y;
|
||||
canvas.drawLine(parentPoint.x, parentPoint.y, lineEndX, lineEndY, paint);
|
||||
}
|
||||
|
||||
canvas.drawText(bracket.cat, x+mr.nodeTab, y+mr.localHeight, paint);
|
||||
}
|
||||
|
||||
float layerMultiplier = (1.0f + BELOW_LINE_SKIP + ABOVE_LINE_SKIP + PARENT_SKIP);
|
||||
float layerHeight = mr.localHeight * layerMultiplier;
|
||||
float childStartX = x + mr.childTab;
|
||||
float childStartY = y + layerHeight;
|
||||
for (int i = 0; i < bracket.children.length; i++) {
|
||||
Object child = bracket.children[i];
|
||||
measureTree(child, null);
|
||||
float w = mr.width;
|
||||
drawTree(canvas, childStartX, childStartY, bottom, lineStart, child);
|
||||
childStartX += w + SISTER_SKIP;
|
||||
}
|
||||
} else {
|
||||
float lineEndX = x + mr.nodeCenter;
|
||||
float lineEndY = bottom - mr.height;
|
||||
canvas.drawLine(parentPoint.x, parentPoint.y, lineEndX, lineEndY, paint);
|
||||
canvas.drawText(o.toString(), x, bottom, paint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw (Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
if (brackets == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
coords.clear();
|
||||
|
||||
float startX = getPaddingLeft();
|
||||
for (int i = 0; i < brackets.length; i++) {
|
||||
Object child = brackets[i];
|
||||
|
||||
measureTree(child, null);
|
||||
float w = mr.width;
|
||||
drawTree(canvas, startX, getPaddingTop(), getPaddingTop()+mr.height, null, child);
|
||||
startX += w + SISTER_SKIP;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -340,6 +340,11 @@ public class Translator {
|
||||
return targetLang.linearize(expr);
|
||||
}
|
||||
|
||||
public Object[] bracketedLinearize(Expr expr) {
|
||||
Concr targetLang = getTargetConcr();
|
||||
return targetLang.bracketedLinearize(expr);
|
||||
}
|
||||
|
||||
public String generateLexiconEntry(String lemma) {
|
||||
Concr sourceLang = getSourceConcr();
|
||||
Concr targetLang = getTargetConcr();
|
||||
|
||||
Reference in New Issue
Block a user