added code for visualization of the sematic graph in the app. The code is there but the menu item for activating it is not there yet since the functionality is not complete

This commit is contained in:
krasimir
2015-09-01 08:00:56 +00:00
parent e76fb3d9a1
commit 2ffdda9fb0
15 changed files with 731 additions and 2 deletions

View File

@@ -27,6 +27,16 @@
</activity>
<activity android:name="AlternativesActivity"></activity>
<activity android:name="HelpActivity"></activity>
<activity android:name="SemanticGraphActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<meta-data android:name="android.app.searchable"
android:resource="@xml/searchable"/>
<meta-data android:name="android.app.default_searchable"
android:value=".SearchableActivity"/>
</activity>
<service android:name="TranslatorInputMethodService"
android:permission="android.permission.BIND_INPUT_METHOD">
<intent-filter>
@@ -34,6 +44,10 @@
</intent-filter>
<meta-data android:name="android.view.im" android:resource="@xml/method" />
</service>
<provider android:name=".LexiconSuggestionProvider"
android:authorities="org.grammaticalframework.ui.android.LexiconSuggestionProvider">
<path-permission android:pathPrefix="/search_suggest_query"
android:readPermission="android.permission.GLOBAL_SEARCH"/>
</provider>
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 868 B

View File

@@ -8,4 +8,4 @@
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</RelativeLayout>
</RelativeLayout>

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent">
<RelativeLayout
android:layout_height="match_parent"
android:layout_width="match_parent">
<RelativeLayout
android:id="@+id/graph_header"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:padding="8dp"
android:background="#C0C0C0">
<ImageView
android:id="@+id/add_word"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_alignParentRight="true"
android:padding="8dp"
android:src="@drawable/ic_search_black_24dp"/>
<org.grammaticalframework.ui.android.LanguageSelector
android:id="@+id/show_language"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_toLeftOf="@id/add_word"
android:padding="0dp"/>
</RelativeLayout>
<org.grammaticalframework.ui.android.SemanticGraphView
android:id="@+id/semantic_graph"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_below="@id/graph_header"/>
</RelativeLayout>
<LinearLayout
android:id="@+id/progressBarView"
android:gravity="center"
android:visibility="gone"
android:background="#00000000"
android:layout_height="match_parent"
android:layout_width="match_parent">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</FrameLayout>

View File

@@ -7,6 +7,7 @@
<string name="open_image">Opening</string>
<string name="mic_input">Speech Input</string>
<string name="keyboard_input">Keyboard Input</string>
<string name="semantic_graph">Semantic Graph</string>
<string name="help">Help</string>
<string name="global_preferences_key">org.grammaticalframework.ui.android.GLOBAL_PREFERENCES</string>
@@ -24,4 +25,7 @@
<!-- Labels for subtype -->
<string name="normalKeyboardMode">normalKeyboardMode</string>
<string name="internalKeyboardMode">internalKeyboardMode</string>
<string name="search_hint">Search word:</string>
<string name="search_description">Search for words in the lexicon</string>
</resources>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/app_name"
android:hint="@string/search_hint"
android:searchSuggestAuthority="org.grammaticalframework.ui.android.LexiconSuggestionProvider"
android:includeInGlobalSearch="true"
android:searchSettingsDescription="@string/search_description">
</searchable>

View File

@@ -0,0 +1,52 @@
package org.grammaticalframework.ui.android;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.provider.BaseColumns;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.app.SearchManager;
import android.net.Uri;
import android.util.Log;
import android.view.inputmethod.CompletionInfo;
public class LexiconSuggestionProvider extends ContentProvider {
private Translator mTranslator;
public boolean onCreate() {
return true;
}
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
mTranslator = ((GFTranslator) getContext().getApplicationContext()).getTranslator();
String[] columns = new String[] {
BaseColumns._ID,
SearchManager.SUGGEST_COLUMN_TEXT_1,
SearchManager.SUGGEST_COLUMN_QUERY
};
String query = uri.getLastPathSegment();
MatrixCursor cursor = new MatrixCursor(columns, 100);
for (CompletionInfo info : mTranslator.lookupWordPrefix(query)) {
cursor.addRow(new String[] {Long.toString(info.getId()),info.getText().toString(),info.getText().toString()});
}
return cursor;
}
public Uri insert (Uri uri, ContentValues values) {
throw new UnsupportedOperationException();
}
public int update (Uri uri, ContentValues values, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException();
}
public int delete (Uri uri, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException();
}
public String getType (Uri uri) {
return null;
}
}

View File

@@ -0,0 +1,109 @@
package org.grammaticalframework.ui.android;
import android.view.MotionEvent;
public class RotationGestureDetector {
private static final int INVALID_POINTER_ID = -1;
private float fX, fY, sX, sY, focalX, focalY;
private int ptrID1, ptrID2;
private float mAngle;
private boolean firstTouch;
private OnRotationGestureListener mListener;
public RotationGestureDetector(OnRotationGestureListener listener) {
mListener = listener;
ptrID1 = INVALID_POINTER_ID;
ptrID2 = INVALID_POINTER_ID;
}
public float getAngle() {
return mAngle;
}
public boolean onTouchEvent(MotionEvent event){
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
sX = event.getX();
sY = event.getY();
ptrID1 = event.getPointerId(0);
mAngle = 0;
firstTouch = true;
break;
case MotionEvent.ACTION_POINTER_DOWN:
fX = event.getX();
fY = event.getY();
focalX = getMidpoint(fX, sX);
focalY = getMidpoint(fY, sY);
ptrID2 = event.getPointerId(event.getActionIndex());
mAngle = 0;
firstTouch = true;
break;
case MotionEvent.ACTION_MOVE:
if(ptrID1 != INVALID_POINTER_ID && ptrID2 != INVALID_POINTER_ID) {
float nfX, nfY, nsX, nsY;
nsX = event.getX(event.findPointerIndex(ptrID1));
nsY = event.getY(event.findPointerIndex(ptrID1));
nfX = event.getX(event.findPointerIndex(ptrID2));
nfY = event.getY(event.findPointerIndex(ptrID2));
if (firstTouch) {
mAngle = 0;
firstTouch = false;
} else {
mAngle = angleBetweenLines(fX, fY, sX, sY, nfX, nfY, nsX, nsY);
}
if (mListener != null) {
mListener.OnRotation(this);
}
fX = nfX;
fY = nfY;
sX = nsX;
sY = nsY;
}
break;
case MotionEvent.ACTION_UP:
ptrID1 = INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_POINTER_UP:
ptrID2 = INVALID_POINTER_ID;
break;
}
return true;
}
private float getMidpoint(float a, float b) {
return (a + b) / 2;
}
private float findAngleDelta(float angle1, float angle2)
{
angle2 = angle2 % 360.0f;
angle1 = angle1 % 360.0f;
float dist = angle1 - angle2;
if (dist < -180.0f)
{
dist += 360.0f;
}
else if (dist > 180.0f)
{
dist -= 360.0f;
}
return dist;
}
private float angleBetweenLines(float fx1, float fy1, float fx2, float fy2, float sx1, float sy1, float sx2, float sy2)
{
float angle1 = (float) Math.atan2((fy1 - fy2), (fx1 - fx2));
float angle2 = (float) Math.atan2((sy1 - sy2), (sx1 - sx2));
return findAngleDelta((float)Math.toDegrees(angle1),(float)Math.toDegrees(angle2));
}
public static interface OnRotationGestureListener {
public boolean OnRotation(RotationGestureDetector detector);
}
}

View File

@@ -0,0 +1,235 @@
package org.grammaticalframework.ui.android;
import java.util.*;
public class SemanticGraph {
private Map<String,Node> nodes;
private List<Edge> edges;
private float layoutMinX;
private float layoutMaxX;
private float layoutMinY;
private float layoutMaxY;
public SemanticGraph() {
nodes = new HashMap<String,Node>();
edges = new ArrayList<Edge>();
layoutMinX = 0;
layoutMaxX = 0;
layoutMinY = 0;
layoutMaxY = 0;
}
public Node addNode(String lemma) {
Node n = nodes.get(lemma);
if (n == null) {
n = new Node(lemma, new String[(int) (10*Math.random())]);
}
nodes.put(lemma,n);
return n;
}
public Node getNode(String lemma) {
return nodes.get(lemma);
}
public Collection<Node> getNodes() {
return Collections.unmodifiableCollection(nodes.values());
}
public Edge addEdge(Node node1, Node node2) {
Edge edge = new Edge(node1, node2);
edges.add(edge);
return edge;
}
private static final int LAYOUT_ITERATIONS = 500;
private static final float LAYOUT_K = 2;
private static final float LAYOUT_C = 0.01f;
private static final float LAYOUT_MAX_VERTEX_MOVEMENT = 0.5f;
private static final float LAYOUT_MAX_REPULSIVE_FORCE_DISTANCE = 6;
public void layout() {
layoutPrepare();
for (int i = 0; i < LAYOUT_ITERATIONS; i++) {
layoutIteration();
}
layoutCalcBounds();
}
public float getLayoutMinX() {
return layoutMinX;
}
public float getLayoutMaxX() {
return layoutMaxX;
}
public float getLayoutMinY() {
return layoutMinY;
}
public float getLayoutMaxY() {
return layoutMaxY;
}
private void layoutPrepare() {
for (Node node : nodes.values()) {
node.layoutForceX = 0;
node.layoutForceY = 0;
}
}
private void layoutIteration() {
List<Node> prev = new ArrayList<Node>();
for(Node node1 : this.nodes.values()) {
for (Node node2 : prev) {
layoutRepulsive(node1, node2);
}
prev.add(node1);
}
// Forces on nodes due to edge attractions
for (Edge edge : edges) {
layoutAttractive(edge);
}
// Move by the given force
for (Node node : nodes.values()) {
float xmove = LAYOUT_C * node.layoutForceX;
float ymove = LAYOUT_C * node.layoutForceY;
float max = LAYOUT_MAX_VERTEX_MOVEMENT;
if (xmove > max) xmove = max;
if (xmove < -max) xmove = -max;
if (ymove > max) ymove = max;
if (ymove < -max) ymove = -max;
node.layoutPosX += xmove;
node.layoutPosY += ymove;
node.layoutForceX = 0;
node.layoutForceY = 0;
}
}
private void layoutRepulsive(Node node1, Node node2) {
float dx = node2.layoutPosX - node1.layoutPosX;
float dy = node2.layoutPosY - node1.layoutPosY;
float d2 = dx * dx + dy * dy;
if (d2 < 0.01) {
dx = (float) (0.1 * Math.random() + 0.1);
dy = (float) (0.1 * Math.random() + 0.1);
d2 = dx * dx + dy * dy;
}
float d = (float) Math.sqrt(d2);
if (d < LAYOUT_MAX_REPULSIVE_FORCE_DISTANCE) {
float repulsiveForce = LAYOUT_K * LAYOUT_K / d;
node2.layoutForceX += repulsiveForce * dx / d;
node2.layoutForceY += repulsiveForce * dy / d;
node1.layoutForceX -= repulsiveForce * dx / d;
node1.layoutForceY -= repulsiveForce * dy / d;
}
}
private void layoutAttractive(Edge edge) {
Node node1 = edge.source;
Node node2 = edge.target;
float dx = node2.layoutPosX - node1.layoutPosX;
float dy = node2.layoutPosY - node1.layoutPosY;
float d2 = dx * dx + dy * dy;
if (d2 < 0.01) {
dx = (float) (0.1 * Math.random() + 0.1);
dy = (float) (0.1 * Math.random() + 0.1);
d2 = dx * dx + dy * dy;
}
float d = (float) Math.sqrt(d2);
if (d > LAYOUT_MAX_REPULSIVE_FORCE_DISTANCE) {
d = LAYOUT_MAX_REPULSIVE_FORCE_DISTANCE;
d2 = d * d;
}
float attractiveForce = (d2 - LAYOUT_K * LAYOUT_K) / LAYOUT_K;
attractiveForce *= Math.log(edge.attraction) * 0.5 + 1;
node2.layoutForceX -= attractiveForce * dx / d;
node2.layoutForceY -= attractiveForce * dy / d;
node1.layoutForceX += attractiveForce * dx / d;
node1.layoutForceY += attractiveForce * dy / d;
}
private void layoutCalcBounds() {
float minx = Float.POSITIVE_INFINITY,
maxx = Float.NEGATIVE_INFINITY,
miny = Float.POSITIVE_INFINITY,
maxy = Float.NEGATIVE_INFINITY;
for (Node node : nodes.values()) {
float x = node.layoutPosX;
float y = node.layoutPosY;
if (x > maxx) maxx = x;
if (x < minx) minx = x;
if (y > maxy) maxy = y;
if (y < miny) miny = y;
}
layoutMinX = minx;
layoutMaxX = maxx;
layoutMinY = miny;
layoutMaxY = maxy;
}
public static class Node {
private String lemma;
private String[] senses;
private float layoutPosX;
private float layoutPosY;
private float layoutForceX;
private float layoutForceY;
private Node(String lemma, String[] senses) {
this.lemma = lemma;
this.senses = senses;
layoutPosX = 0;
layoutPosY = 0;
layoutForceX = 0;
layoutForceY = 0;
}
public String getLemma() {
return lemma;
}
public int getSenseCount() {
return senses.length;
}
public String getSenseId(int i) {
return senses[i];
}
public float getLayoutX() {
return layoutPosX;
}
public float getLayoutY() {
return layoutPosY;
}
}
public static class Edge {
private Node source;
private Node target;
private float attraction;
private Edge(Node source, Node target) {
this.source = source;
this.target = target;
this.attraction = 1;
}
}
}

View File

@@ -0,0 +1,102 @@
package org.grammaticalframework.ui.android;
import java.util.*;
import android.app.Activity;
import android.app.SearchManager;
import android.os.Bundle;
import android.os.AsyncTask;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.Toast;
import android.content.Intent;
import org.grammaticalframework.pgf.MorphoAnalysis;
import org.grammaticalframework.ui.android.LanguageSelector.OnLanguageSelectedListener;
public class SemanticGraphActivity extends Activity {
private Translator mTranslator;
private LanguageSelector mLanguageView;
private View mProgressBarView = null;
private ImageView mAddWordButton;
private SemanticGraphView mGraphView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_semantic_graph);
mTranslator = ((GFTranslator) getApplicationContext()).getTranslator();
mLanguageView = (LanguageSelector) findViewById(R.id.show_language);
mLanguageView.setLanguages(mTranslator.getAvailableLanguages());
mLanguageView.setOnLanguageSelectedListener(new OnLanguageSelectedListener() {
@Override
public void onLanguageSelected(final Language language) {
new AsyncTask<Void,Void,Void>() {
@Override
protected void onPreExecute() {
showProgressBar();
}
@Override
protected Void doInBackground(Void... params) {
mTranslator.setSourceLanguage(language);
mTranslator.isTargetLanguageLoaded();
return null;
}
@Override
protected void onPostExecute(Void result) {
hideProgressBar();
}
}.execute();
}
});
mAddWordButton = (ImageView) findViewById(R.id.add_word);
mGraphView = (SemanticGraphView) findViewById(R.id.semantic_graph);
mAddWordButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onSearchRequested();
}
});
mProgressBarView = findViewById(R.id.progressBarView);
}
@Override
protected void onResume() {
super.onResume();
mLanguageView.setSelectedLanguage(mTranslator.getSourceLanguage());
}
private void showProgressBar() {
mProgressBarView.setVisibility(View.VISIBLE);
}
private void hideProgressBar() {
mProgressBarView.setVisibility(View.GONE);
}
@Override
protected void onNewIntent (Intent intent) {
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
String query = intent.getStringExtra(SearchManager.QUERY);
List<MorphoAnalysis> list = mTranslator.lookupMorpho(query);
if (list == null || list.size() == 0) {
Toast toast = Toast.makeText(this, "\""+query+"\" doesn't match", Toast.LENGTH_SHORT);
toast.show();
} else {
mGraphView.getGraph().addNode(query);
mGraphView.refresh();
}
}
}
}

View File

@@ -0,0 +1,144 @@
package org.grammaticalframework.ui.android;
import android.view.View;
import android.view.GestureDetector;
import android.view.ScaleGestureDetector;
import android.view.MotionEvent;
import android.graphics.Paint;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
public class SemanticGraphView extends View implements GestureDetector.OnGestureListener, ScaleGestureDetector.OnScaleGestureListener, RotationGestureDetector.OnRotationGestureListener {
private SemanticGraph mGraph = new SemanticGraph();
private float mStartX = 0;
private float mStartY = 0;
private float mFocusX = 0;
private float mFocusY = 0;
private float mScale = 1;
private float mAngle = 0;
private Paint mPaint;
private GestureDetector mGD;
private ScaleGestureDetector mSGD;
private RotationGestureDetector mRGD;
private static final float TEXT_PAD = 10;
private static final float SENSE_POINT_RADIUS = 5;
public SemanticGraphView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setTextSize(60);
mGD = new GestureDetector(this);
mSGD = new ScaleGestureDetector(context,this);
mRGD = new RotationGestureDetector(this);
}
public SemanticGraph getGraph() {
return mGraph;
}
public void refresh() {
mGraph.layout();
invalidate();
}
@Override
protected void onDraw (Canvas canvas) {
super.onDraw(canvas);
canvas.scale(mScale,mScale,mFocusX,mFocusY);
canvas.translate(mStartX, mStartY);
canvas.rotate(mAngle);
Rect bounds = new Rect();
float dx = mGraph.getLayoutMinX();
float sx = getWidth()/(mGraph.getLayoutMaxX()-mGraph.getLayoutMinX());
float dy = mGraph.getLayoutMinY();
float sy = getHeight()/(mGraph.getLayoutMaxY()-mGraph.getLayoutMinY());
for (SemanticGraph.Node node : mGraph.getNodes()) {
mPaint.getTextBounds(node.getLemma().toCharArray(), 0, node.getLemma().length(), bounds);
float left = (node.getLayoutX()-dx)*sx - TEXT_PAD;
float base = (node.getLayoutY()-dy)*sy;
float top = base - bounds.height() - TEXT_PAD;
float right = left + bounds.right + TEXT_PAD;
float bottom = base + bounds.bottom + TEXT_PAD;
float sqrt2 = (float) Math.sqrt(2);
canvas.drawText(node.getLemma(), left + TEXT_PAD, base, mPaint);
float pi = (float) Math.PI;
for (int i = 0; i < node.getSenseCount(); i++) {
float phi = i * 2*pi / node.getSenseCount();
float cx = ((left+right) + (right-left)*sqrt2*((float) Math.sin(phi)))/2;
float cy = ((top+bottom) + (bottom-top)*sqrt2*((float) Math.cos(phi)))/2;
canvas.drawCircle(cx,cy,SENSE_POINT_RADIUS,mPaint);
}
}
}
public boolean onTouchEvent(MotionEvent ev) {
mGD.onTouchEvent(ev);
mSGD.onTouchEvent(ev);
mRGD.onTouchEvent(ev);
return true;
}
public boolean onDown(MotionEvent e) {
return true;
}
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return true;
}
public void onLongPress(MotionEvent e) {
}
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
mStartX -= distanceX;
mStartY -= distanceY;
invalidate();
return true;
}
public void onShowPress(MotionEvent e) {
}
public boolean onSingleTapUp(MotionEvent e) {
return true;
}
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
}
public boolean onScale(ScaleGestureDetector detector) {
mScale *= detector.getScaleFactor();
mFocusX = detector.getFocusX();
mFocusY = detector.getFocusY();
invalidate();
return true;
}
public void onScaleEnd(ScaleGestureDetector detector) {
}
public boolean OnRotation(RotationGestureDetector detector) {
mAngle -= detector.getAngle();
invalidate();
return true;
}
}