From 2ffdda9fb0043ef2e8dee96b68d792c661c54bbc Mon Sep 17 00:00:00 2001 From: krasimir Date: Tue, 1 Sep 2015 08:00:56 +0000 Subject: [PATCH] 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 --- src/ui/android/AndroidManifest.xml | 16 +- .../drawable-hdpi/ic_search_black_24dp.png | Bin 0 -> 390 bytes .../drawable-mdpi/ic_search_black_24dp.png | Bin 0 -> 249 bytes .../drawable-xhdpi/ic_search_black_24dp.png | Bin 0 -> 464 bytes .../drawable-xxhdpi/ic_search_black_24dp.png | Bin 0 -> 684 bytes .../drawable-xxxhdpi/ic_search_black_24dp.png | Bin 0 -> 868 bytes src/ui/android/res/layout/activity_help.xml | 2 +- .../res/layout/activity_semantic_graph.xml | 61 +++++ src/ui/android/res/values/strings.xml | 4 + src/ui/android/res/xml/searchable.xml | 8 + .../ui/android/LexiconSuggestionProvider.java | 52 ++++ .../ui/android/RotationGestureDetector.java | 109 ++++++++ .../ui/android/SemanticGraph.java | 235 ++++++++++++++++++ .../ui/android/SemanticGraphActivity.java | 102 ++++++++ .../ui/android/SemanticGraphView.java | 144 +++++++++++ 15 files changed, 731 insertions(+), 2 deletions(-) create mode 100644 src/ui/android/res/drawable-hdpi/ic_search_black_24dp.png create mode 100644 src/ui/android/res/drawable-mdpi/ic_search_black_24dp.png create mode 100644 src/ui/android/res/drawable-xhdpi/ic_search_black_24dp.png create mode 100644 src/ui/android/res/drawable-xxhdpi/ic_search_black_24dp.png create mode 100644 src/ui/android/res/drawable-xxxhdpi/ic_search_black_24dp.png create mode 100644 src/ui/android/res/layout/activity_semantic_graph.xml create mode 100644 src/ui/android/res/xml/searchable.xml create mode 100644 src/ui/android/src/org/grammaticalframework/ui/android/LexiconSuggestionProvider.java create mode 100644 src/ui/android/src/org/grammaticalframework/ui/android/RotationGestureDetector.java create mode 100644 src/ui/android/src/org/grammaticalframework/ui/android/SemanticGraph.java create mode 100644 src/ui/android/src/org/grammaticalframework/ui/android/SemanticGraphActivity.java create mode 100644 src/ui/android/src/org/grammaticalframework/ui/android/SemanticGraphView.java diff --git a/src/ui/android/AndroidManifest.xml b/src/ui/android/AndroidManifest.xml index 63602ddb1..37f1efeca 100644 --- a/src/ui/android/AndroidManifest.xml +++ b/src/ui/android/AndroidManifest.xml @@ -27,6 +27,16 @@ + + + + + + + @@ -34,6 +44,10 @@ + + + - diff --git a/src/ui/android/res/drawable-hdpi/ic_search_black_24dp.png b/src/ui/android/res/drawable-hdpi/ic_search_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..c593e7ad854f5c28ca21f59916bef001daf91141 GIT binary patch literal 390 zcmeAS@N?(olHy`uVBq!ia0y~yU{C>J4i*Lm25-&)VFm_9Ur!gukP61P7uK)R3KTj1 zalMg&+WV7AY~4rI1!fk_j*WNZX*yY?&NnAdz~|U!rt9`kjrKJs%v9q@GLmpPV!0vW zrN!ZUR)=)!$|S$#pY1>U=xwIzEuN1rFCKX4xu0{V;No^>MpwCz1g|*-WflEb|6G<| zzeYmPAa%8QdQ`;a>mOWML;CotciIMSzSDc{&%@$f)i?{Kt^LXsJ?^uRLv+^a8(uZ#Qc-WQ{s_aj?GWnop=Bi2W4YN)>m?m>_*Cc(z zB8w9l>Z*4=ZXI`8(63+oK6sO|e8i8Ve#ewwaXiiNJ;UHFm;UqNA(nn6zR+DcThx^L zw~DDhIwJgXQ>@Naj`v=DJSSd%KeUf)R!?N{qf_%#YhynKt$wy6C8#2H>S?FdX*X9t xTfhF-?7dmbLhVx4UlLDU|Ech&{k?sROrQ3PX;1yKkb!}L!PC{xWt~$(69As2tpWf5 literal 0 HcmV?d00001 diff --git a/src/ui/android/res/drawable-mdpi/ic_search_black_24dp.png b/src/ui/android/res/drawable-mdpi/ic_search_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..6b16343235c2729720113158598c891fd041949f GIT binary patch literal 249 zcmeAS@N?(olHy`uVBq!ia0y~yV2}V|4i*Lm2CurW#S9D#2RvOILo5W(9yIhjl)%7t zLHvnKy>j}59WxRhtyL)K=X=BQ#3RH#kxlKya}O(#*T<@DcJN2DhqKSW_ct*^Q%OlF z%eh?V=*1AjfSHz^QOo%YzdTBNqoQV&b2Q|3OiqbK$D+59e_j-bGUxg~T6EYk+}rLn;{GUOw+79VpW9 zF@NzSwa6u}b3UctZqaG#Sn$PCq?Y%;^jFb48nwKEORT!KU8?2$!7wY`gURyjqk7NJ>hlg=^mw9mX2O~6t3|`Z;6<7 zJ#BXMky!!i0lwi+QtD4WzOa3VgVF3lKiP|Y99xbFPr9_YE@~qm=e36|*7qcgpU!Pz zloeq0YBvbGIeqX W$M5KRoyNexz~JfX=d#Wzp$P!Vj?<(7 literal 0 HcmV?d00001 diff --git a/src/ui/android/res/drawable-xxhdpi/ic_search_black_24dp.png b/src/ui/android/res/drawable-xxhdpi/ic_search_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..3ae490ef9be0c727cdea0ff907723362d0ccf021 GIT binary patch literal 684 zcmeAS@N?(olHy`uVBq!ia0y~yVDJE84i*LmhW}5h&oD4B6??ikhEy=Vy?)k9I#J}< z$Nec2GYvDZtW}F`t73FrxVkH@Va5V+g+&S+_u3zXD@Lq+5OtJWfK`0kmDZBT{RVXp z7Dc%Tw0?ZMZqDJ5In(8O5B)j!%hD$0S*`m|`A!$5i5ZK|h-AwYx}FdXy*6Wsi?Zp3 zqg>l})SuHmUGk~L?t|?22Q^FQs((tCEt(D%S%tq;u4O>aM`G(aho|@J$`9w4K2GPo?_LCphMLsLcTDF#d zm!0ilo17NwshNi->??2UOm|soW_Y=^F5=@$_UnxkMZapLcf99auC%+OQ|;^7SI5sU zIC1YnL*u4h<(GbN?hyEy$K%u4D*ESa*B=(PYexT!N))VLt5o07sJ(H6 zT)pJP{YM-$#Fl-SF7)$*U4z$vQRl&)3dBR%~iV4@v$x+xf?W%`55Q z&i}q=^C!MP=&<`Gkz`;>lZk` z!YemnMeP4tnbnt8oNC!~{EGUVS(Tga=pSeDJ1cRFXkFUJ3JMN{sb={WfAyol(L7B_HO)XQNI%~3*l&=25OXqKe-`c335`J<~ wUt?O?nb#5_o7N@=teh#XGtomusGgzs?(g#_7rI+8FfcH9y85}Sb4q9e0J(@r(f|Me literal 0 HcmV?d00001 diff --git a/src/ui/android/res/drawable-xxxhdpi/ic_search_black_24dp.png b/src/ui/android/res/drawable-xxxhdpi/ic_search_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..21be572990b53d8e0e518c620a3257f19bd1864b GIT binary patch literal 868 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4i*Lm29JsRH#0CWYkRslhEy=Vxw+TdIb5RQ z<8jrEt9MLq%5-A)_2Ia6Wp7mBYk`u9(eeEQ$c*a4K-6m(vg#=})n(qrZ-#5B;@Y;TNp1H!uW-2wki=X!L%5f8cl5C4fW^;bm zPhKGHna6v+)l){|(V2pug1bbY@&$$#BpfXGtn$M3R6*a_#8}oq-OH{8?^dX?EC!nYru>Hn}l7y3hw>I68N{F z^mE55$4PNZDxE?$TNHGy$m_hcR^t1)x_@3>VvBYuyP98WJDor2z{FCPyUIKD-u+c) zxqIEh_zY{(3ZFI2&7TDfzZfgZbOpHcnBG*r{A}fJe{RE&$G$SBFE|*4r_Oer)FY&J zM#FQO#>;ZKtsnUd^QN&qs*ZmY&}Q{$e)1C33<#(@GFNXSRxzwbK=c`Ws{K>*KeM9TV zr|LC6haRc>B - \ No newline at end of file + diff --git a/src/ui/android/res/layout/activity_semantic_graph.xml b/src/ui/android/res/layout/activity_semantic_graph.xml new file mode 100644 index 000000000..004e22a7c --- /dev/null +++ b/src/ui/android/res/layout/activity_semantic_graph.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/ui/android/res/values/strings.xml b/src/ui/android/res/values/strings.xml index ca103623e..6ce31d213 100644 --- a/src/ui/android/res/values/strings.xml +++ b/src/ui/android/res/values/strings.xml @@ -7,6 +7,7 @@ Opening Speech Input Keyboard Input + Semantic Graph Help org.grammaticalframework.ui.android.GLOBAL_PREFERENCES @@ -24,4 +25,7 @@ normalKeyboardMode internalKeyboardMode + + Search word: + Search for words in the lexicon diff --git a/src/ui/android/res/xml/searchable.xml b/src/ui/android/res/xml/searchable.xml new file mode 100644 index 000000000..7e7b6a846 --- /dev/null +++ b/src/ui/android/res/xml/searchable.xml @@ -0,0 +1,8 @@ + + + diff --git a/src/ui/android/src/org/grammaticalframework/ui/android/LexiconSuggestionProvider.java b/src/ui/android/src/org/grammaticalframework/ui/android/LexiconSuggestionProvider.java new file mode 100644 index 000000000..7b9813b7d --- /dev/null +++ b/src/ui/android/src/org/grammaticalframework/ui/android/LexiconSuggestionProvider.java @@ -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; + } +} diff --git a/src/ui/android/src/org/grammaticalframework/ui/android/RotationGestureDetector.java b/src/ui/android/src/org/grammaticalframework/ui/android/RotationGestureDetector.java new file mode 100644 index 000000000..d077db1bc --- /dev/null +++ b/src/ui/android/src/org/grammaticalframework/ui/android/RotationGestureDetector.java @@ -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); + } +} diff --git a/src/ui/android/src/org/grammaticalframework/ui/android/SemanticGraph.java b/src/ui/android/src/org/grammaticalframework/ui/android/SemanticGraph.java new file mode 100644 index 000000000..50f83bda0 --- /dev/null +++ b/src/ui/android/src/org/grammaticalframework/ui/android/SemanticGraph.java @@ -0,0 +1,235 @@ +package org.grammaticalframework.ui.android; + +import java.util.*; + +public class SemanticGraph { + private Map nodes; + private List edges; + + private float layoutMinX; + private float layoutMaxX; + private float layoutMinY; + private float layoutMaxY; + + public SemanticGraph() { + nodes = new HashMap(); + edges = new ArrayList(); + + 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 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 prev = new ArrayList(); + 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; + } + } +} diff --git a/src/ui/android/src/org/grammaticalframework/ui/android/SemanticGraphActivity.java b/src/ui/android/src/org/grammaticalframework/ui/android/SemanticGraphActivity.java new file mode 100644 index 000000000..893f023b2 --- /dev/null +++ b/src/ui/android/src/org/grammaticalframework/ui/android/SemanticGraphActivity.java @@ -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() { + @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 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(); + } + } + } +} diff --git a/src/ui/android/src/org/grammaticalframework/ui/android/SemanticGraphView.java b/src/ui/android/src/org/grammaticalframework/ui/android/SemanticGraphView.java new file mode 100644 index 000000000..81785d5ce --- /dev/null +++ b/src/ui/android/src/org/grammaticalframework/ui/android/SemanticGraphView.java @@ -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; + } +}