cz.urbangaming.galgs.GAlg.java Source code

Java tutorial

Introduction

Here is the source code for cz.urbangaming.galgs.GAlg.java

Source

package cz.urbangaming.galgs;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.ruboto.JRubyAdapter;

import android.annotation.SuppressLint;
import android.app.ActionBar;
import android.app.ActionBar.OnNavigationListener;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.pm.ConfigurationInfo;
import android.content.res.AssetManager;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.os.Environment;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.view.MotionEventCompat;
import android.util.Log;
import android.view.ActionProvider;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import cz.urbangaming.galgs.utils.Point2D;

/**
 * 
 * @author Michal Karm Babacek
 * @license GNU GPL 3.0
 * 
 */
public class GAlg extends FragmentActivity implements OnNavigationListener {
    public static final String DEBUG_TAG = "KARM";

    private PointsRenderer pointsRenderer = null;

    // Ruby integration
    public static final String GALGS_CLASS_DIR = Environment.getExternalStorageDirectory().toString()
            + File.separator + "scripts";
    public static final String GALGS_CLASS_FILE = "galg_algorithms.rb";

    // Menus begin
    public static final int WORK_MODE = 10;
    public static final int WORK_MODE_EDIT = 20;
    public static final int WORK_MODE_ADD = 30;
    public static final int WORK_MODE_DELETE = 40;

    public static final int SELECT_ALGORITHM = 50;
    public static final int CONVEX_HULL_GW = 60;
    public static final int CONVEX_HULL_GS = 61;
    public static final int LINKED_POINTS = 62;
    public static final int LINKED_POINTS_RUBY = 666;
    public static final int RED_STAR = 668;

    public static final int SWEEP_TRIANGULATION = 63;
    public static final int NAIVE_TRIANGULATION = 64;
    public static final int KD_TREE = 65;
    public static final int RELOAD_RUBY_SCRIPT = 162;

    public static final int REMOVE_ALL_POINTS = 70;
    public static final int ADD_RANDOM_POINTS = 80;

    private ArrayAdapter<String> aAdpt = null;
    private ActionProvider javaAlgsActionProvider = null;
    private ActionProvider rubyAlgsActionProvider = null;
    private File galgsRubyClassesDirectory = null;

    // Menus end

    private int currentWorkMode = WORK_MODE_ADD;

    // /////// Some settings: Move it outside... /////////

    // TODO: THIS IS SO EPICLY WRONG! I must calculate it accordingly to display's density...
    public static final float POINT_SIZE = 7f;
    public static final int FINGER_ACCURACY = Math.round(POINT_SIZE) * 3;
    // No, it's not very convenient to have points too close to boundaries
    public static final int BORDER_POINT_POSITION = Math.round(POINT_SIZE) * 3;
    public static final int HOW_MANY_POINTS_GENERATE = Math.round(POINT_SIZE) * 3;

    // Ruby dynamically generated options
    @SuppressLint("UseSparseArrays")
    private Map<Integer, String> rubyMethods = new HashMap<Integer, String>();

    public static final Pattern pattern = Pattern.compile("[ \\t]*def[ \\t]*galgs_([^(]*)\\(.*");
    public static final int MAX_METHOD_NAME_LENGTH = 30;

    @Override
    public boolean onNavigationItemSelected(int itemPosition, long itemId) {
        if (aAdpt != null && itemPosition == aAdpt.getPosition(getResources().getString(R.string.workmode_add))) {
            currentWorkMode = WORK_MODE_ADD;
            return true;
        } else if (aAdpt != null
                && itemPosition == aAdpt.getPosition(getResources().getString(R.string.workmode_delete))) {
            currentWorkMode = WORK_MODE_DELETE;
            return true;
        } else if (aAdpt != null
                && itemPosition == aAdpt.getPosition(getResources().getString(R.string.workmode_edit))) {
            currentWorkMode = WORK_MODE_EDIT;
            return true;
        } else {
            return false;
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

        ActionBar actionBar = getActionBar();
        actionBar.setDisplayHomeAsUpEnabled(false);
        actionBar.setDisplayShowTitleEnabled(false);
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);

        ArrayList<String> itemList = new ArrayList<String>();
        //TODO:Make static, use String constants strings.xml
        itemList.add(getResources().getString(R.string.workmode_add));
        itemList.add(getResources().getString(R.string.workmode_edit));
        itemList.add(getResources().getString(R.string.workmode_delete));
        this.aAdpt = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, android.R.id.text1,
                itemList);
        actionBar.setListNavigationCallbacks(aAdpt, this);
        mGLSurfaceView = new GLSurfaceView(this);

        if (detectOpenGLES20()) {
            // Tell the surface view we want to create an OpenGL ES 2.0-compatible
            // context, and set an OpenGL ES 2.0-compatible renderer.
            mGLSurfaceView.setEGLContextClientVersion(2);
            pointsRenderer = new PointsRenderer(this);
            mGLSurfaceView.setRenderer(pointsRenderer);
        } else {
            // TODO: Handle as an unrecoverable error and leave the activity somehow...
        }

        // External files preparation

        InputStream in = null;
        OutputStream out = null;
        try {
            Log.d(DEBUG_TAG, "Media rady: " + isExternalStorageWritable());

            AssetManager assetManager = getAssets();
            in = assetManager.open(GALGS_CLASS_FILE);
            if (in != null) {
                galgsRubyClassesDirectory = new File(GALGS_CLASS_DIR);
                galgsRubyClassesDirectory.mkdir();
                if (!galgsRubyClassesDirectory.isDirectory()) {
                    Log.d(DEBUG_TAG, "Hmm, " + galgsRubyClassesDirectory + " does not exist, trying mkdirs...");
                    galgsRubyClassesDirectory.mkdirs();
                }
                File outputFile = new File(galgsRubyClassesDirectory, GALGS_CLASS_FILE);
                if (outputFile.exists()) {
                    // Load from what user might have edited
                    outputFile = new File(galgsRubyClassesDirectory, GALGS_CLASS_FILE + ".orig");
                }
                out = new FileOutputStream(outputFile);
                copyFile(in, out);
                in.close();
                in = null;
                out.flush();
                out.close();
                out = null;
            } else {
                Log.e("IO HELL", "Asset " + GALGS_CLASS_FILE + " not found...");
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        // Stops the thing from trashing the context on pause/resume.
        mGLSurfaceView.setPreserveEGLContextOnPause(true);
        setContentView(mGLSurfaceView);

    }

    private void loadRubyMethodsToMenu(BufferedReader bufferedReader) throws IOException, InterruptedException {
        String line = null;
        rubyMethods.clear();
        Log.d(DEBUG_TAG, "I'm in loadRubyMethodsToMenu nad  bufferedReader.read():" + bufferedReader.read());

        while ((line = bufferedReader.readLine()) != null) {
            Matcher matcher = pattern.matcher(line);
            if (matcher.matches() && matcher.group(1) != null
                    && matcher.group(1).length() <= MAX_METHOD_NAME_LENGTH) {
                Log.d(DEBUG_TAG, "Adding method " + matcher.group(1));
                rubyMethods.put(matcher.group(1).hashCode(), matcher.group(1));
            } else {
                Log.d(DEBUG_TAG, "Line [" + line + "] does not contain the expected method...");
            }
        }
        bufferedReader.close();
    }

    private void copyFile(InputStream in, OutputStream out) throws IOException {
        byte[] buffer = new byte[1024];
        int read;
        Log.d(DEBUG_TAG, "copyFile: in.available():" + in.available());
        while ((read = in.read(buffer)) != -1) {
            Log.d(DEBUG_TAG, "copyFile: buffer:" + new String(buffer));
            out.write(buffer, 0, read);
        }
    }

    /* Checks if external storage is available for read and write */
    public boolean isExternalStorageWritable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state)) {
            return true;
        }
        return false;
    }

    public boolean onCreateOptionsMenu(Menu menu) {
        //TODO: OMG, put this in a separate, non blocking task!!!
        Log.d(DEBUG_TAG, "CALLED onCreateOptionsMenu!");
        try {
            File rbClassFile = new File(galgsRubyClassesDirectory, GALGS_CLASS_FILE);
            Log.d(DEBUG_TAG, "rbClassFile.getAbsolutePath() " + rbClassFile.getAbsolutePath());
            Log.d(DEBUG_TAG, "rbClassFile.exists() " + rbClassFile.exists());
            Log.d(DEBUG_TAG, "rbClassFile.length() " + rbClassFile.length());
            Log.d(DEBUG_TAG, "rbClassFile.canRead() " + rbClassFile.canRead());
            Log.d(DEBUG_TAG, "rbClassFile.canWrite() " + rbClassFile.canWrite());
            FileReader rbClassFileReader = new FileReader(rbClassFile);
            Log.d(DEBUG_TAG, "rbClassFile " + rbClassFileReader.getEncoding());

            loadRubyMethodsToMenu(new BufferedReader(rbClassFileReader));
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        //JavaAlgs Action provider
        javaAlgsActionProvider = new JavaAlgsActionProvider(this);
        //RubyAlgs Action provider
        Log.d(DEBUG_TAG, "Ruby methods just before creatingProvider:" + rubyMethods);

        rubyAlgsActionProvider = new RubyAlgsActionProvider(this, rubyMethods);

        // Inflate the menu items for use in the action bar
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.galg, menu);
        menu.getItem(0).setActionProvider(javaAlgsActionProvider);
        menu.getItem(1).setActionProvider(rubyAlgsActionProvider);

        return super.onCreateOptionsMenu(menu);

    }

    public boolean onOptionsItemSelected(MenuItem item) {
        boolean itemHandled = true;
        switch (item.getItemId()) {
        case R.id.remove_all_points:
            pointsRenderer.clearScene();
            break;
        case R.id.generate_random_points:
            pointsRenderer.addRandomPoints();
            break;
        case CONVEX_HULL_GW:
            doTheJob(CONVEX_HULL_GW);
            break;
        case CONVEX_HULL_GS:
            doTheJob(CONVEX_HULL_GS);
            break;
        case LINKED_POINTS:
            pointsRenderer.linkPoints(LINKED_POINTS);
            break;
        case SWEEP_TRIANGULATION:
            doTheJob(SWEEP_TRIANGULATION);
            break;
        case NAIVE_TRIANGULATION:
            doTheJob(NAIVE_TRIANGULATION);
            break;
        case KD_TREE:
            doTheJob(KD_TREE);
            break;
        case RELOAD_RUBY_SCRIPT:
            invalidateOptionsMenu();
            break;
        default:
            if (rubyMethods.containsKey(item.getItemId())) {
                if (!JRubyAdapter.isInitialized()) {
                    Toast.makeText(this, getResources().getString(R.string.init_jruby_thing), Toast.LENGTH_LONG)
                            .show();
                    JRubyAdapter.setUpJRuby(this);
                }
                doTheJob(item.getItemId());
            } else {
                itemHandled = false;
            }
            break;
        }
        return itemHandled;
    }

    //WRONG!!! What about onPause and onResume?
    // What about user clicking the button several times in a row?
    // Must re-think this bit... 
    protected void doTheJob(final int algorithm) {
        Thread worker = new Thread(new Runnable() {
            @Override
            public void run() {
                long time = 0L;
                time = System.currentTimeMillis();
                pointsRenderer.renderAlgorithm(algorithm);
                perflog(System.currentTimeMillis() - time);
            }
        });
        worker.start();
    }

    private void perflog(long info) {
        Log.d(DEBUG_TAG, "Computed in " + String.valueOf(info));
        MyDialogFragment dialog = new MyDialogFragment("Computed in " + String.valueOf(info) + " ms");
        dialog.show(this.getSupportFragmentManager(), "Notice");
    }

    // TODO:
    // replace this stuff with a simple         Toast.makeText(mContext, item.getTitle(), Toast.LENGTH_SHORT).show();
    private class MyDialogFragment extends DialogFragment {
        String message = null;

        public MyDialogFragment(String message) {
            super();
            this.message = message;
        }

        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            // Use the Builder class for convenient dialog construction
            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
            builder.setMessage(message);
            // Create the AlertDialog object and return it
            return builder.create();
        }
    }

    private boolean detectOpenGLES20() {
        ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        ConfigurationInfo info = am.getDeviceConfigurationInfo();
        return (info.reqGlEsVersion >= 0x20000);
    }

    @Override
    protected void onResume() {
        // Ideally a application should implement onResume() and onPause()
        // to take appropriate action when the activity looses focus
        super.onResume();
        mGLSurfaceView.onResume();
    }

    @Override
    protected void onPause() {
        // Ideally a game should implement onResume() and onPause()
        // to take appropriate action when the activity looses focus
        super.onPause();
        mGLSurfaceView.onPause();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = MotionEventCompat.getActionMasked(event);
        switch (action) {
        case (MotionEvent.ACTION_DOWN):
            Point2D point2d = new Point2D(event.getRawX(), event.getRawY());
            Log.d(DEBUG_TAG, "Action was DOWN " + point2d.toString());
            switch (currentWorkMode) {
            case WORK_MODE_ADD:
                pointsRenderer.addVertex(point2d);
                break;
            case WORK_MODE_DELETE:
                pointsRenderer.removeVertex(point2d);
                break;
            case WORK_MODE_EDIT:
                pointsRenderer.selectVertex(point2d);
                break;
            default:
                break;
            }
            return true;
        case (MotionEvent.ACTION_MOVE):
            if (currentWorkMode == WORK_MODE_EDIT) {
                pointsRenderer.moveSelectedVertexTo(event.getRawX(), event.getRawY());
            }
            return true;
        case (MotionEvent.ACTION_UP):
            Log.d(DEBUG_TAG, "Action was UP");
            if (currentWorkMode == WORK_MODE_EDIT) {
                pointsRenderer.deselectVertex();
            }
            return true;
        case (MotionEvent.ACTION_CANCEL):
            Log.d(DEBUG_TAG, "Action was CANCEL");
            return true;
        case (MotionEvent.ACTION_OUTSIDE):
            Log.d(DEBUG_TAG, "Movement occurred outside bounds of current screen element");
            return true;
        default:
            return super.onTouchEvent(event);
        }
    }

    private GLSurfaceView mGLSurfaceView;

    public Map<Integer, String> getRubyMethods() {
        return rubyMethods;
    }
}