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 000000000..c593e7ad8
Binary files /dev/null and b/src/ui/android/res/drawable-hdpi/ic_search_black_24dp.png differ
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 000000000..6b1634323
Binary files /dev/null and b/src/ui/android/res/drawable-mdpi/ic_search_black_24dp.png differ
diff --git a/src/ui/android/res/drawable-xhdpi/ic_search_black_24dp.png b/src/ui/android/res/drawable-xhdpi/ic_search_black_24dp.png
new file mode 100644
index 000000000..638190268
Binary files /dev/null and b/src/ui/android/res/drawable-xhdpi/ic_search_black_24dp.png differ
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 000000000..3ae490ef9
Binary files /dev/null and b/src/ui/android/res/drawable-xxhdpi/ic_search_black_24dp.png differ
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 000000000..21be57299
Binary files /dev/null and b/src/ui/android/res/drawable-xxxhdpi/ic_search_black_24dp.png differ
diff --git a/src/ui/android/res/layout/activity_help.xml b/src/ui/android/res/layout/activity_help.xml
index 645f061af..3cb88a569 100644
--- a/src/ui/android/res/layout/activity_help.xml
+++ b/src/ui/android/res/layout/activity_help.xml
@@ -8,4 +8,4 @@
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
-
\ 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;
+ }
+}