org.petero.droidfish.activities.EditBoard.java Source code

Java tutorial

Introduction

Here is the source code for org.petero.droidfish.activities.EditBoard.java

Source

/*
DroidFish - An Android chess program.
Copyright (C) 2011-2013  Peter sterlund, peterosterlund2@gmail.com
    
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.petero.droidfish.activities;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import org.petero.droidfish.ChessBoard;
import org.petero.droidfish.DroidFish;
import org.petero.droidfish.R;
import org.petero.droidfish.ChessBoard.SquareDecoration;
import org.petero.droidfish.Util;
import org.petero.droidfish.Util.MaterialDiff;
import org.petero.droidfish.gamelogic.ChessParseError;
import org.petero.droidfish.gamelogic.Move;
import org.petero.droidfish.gamelogic.Pair;
import org.petero.droidfish.gamelogic.Piece;
import org.petero.droidfish.gamelogic.Position;
import org.petero.droidfish.gamelogic.TextIO;
import org.petero.droidfish.tb.Probe;
import org.petero.droidfish.tb.ProbeResult;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Typeface;
import android.os.Bundle;
import android.os.Handler;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.support.v4.view.MotionEventCompat;
import android.text.ClipboardManager;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.view.View.OnTouchListener;
import android.view.Window;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

@SuppressLint("ClickableViewAccessibility")
public class EditBoard extends Activity {
    private ChessBoardEdit cb;
    private TextView status;
    private Button okButton;
    private Button cancelButton;

    private boolean egtbHints;
    private boolean autoScrollTitle;
    private TextView whiteFigText;
    private TextView blackFigText;
    private Typeface figNotation;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);

        figNotation = Typeface.createFromAsset(getAssets(), "fonts/DroidFishChessNotationDark.otf");

        SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
        egtbHints = settings.getBoolean("tbHintsEdit", false);
        autoScrollTitle = settings.getBoolean("autoScrollTitle", true);

        initUI();

        Util.setFullScreenMode(this, settings);

        Intent i = getIntent();
        Position pos = null;
        try {
            pos = TextIO.readFEN(i.getAction());
        } catch (ChessParseError e) {
            pos = e.pos;
        }
        if (pos != null)
            cb.setPosition(pos);
        checkValidAndUpdateMaterialDiff();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        ChessBoardEdit oldCB = cb;
        String statusStr = status.getText().toString();
        initUI();
        cb.cursorX = oldCB.cursorX;
        cb.cursorY = oldCB.cursorY;
        cb.cursorVisible = oldCB.cursorVisible;
        cb.setPosition(oldCB.pos);
        setSelection(oldCB.selectedSquare);
        cb.userSelectedSquare = oldCB.userSelectedSquare;
        status.setText(statusStr);
        checkValidAndUpdateMaterialDiff();
    }

    private final void initUI() {
        setContentView(R.layout.editboard);
        Util.overrideFonts(findViewById(android.R.id.content));

        View firstTitleLine = findViewById(R.id.first_title_line);
        View secondTitleLine = findViewById(R.id.second_title_line);
        cb = (ChessBoardEdit) findViewById(R.id.eb_chessboard);
        status = (TextView) findViewById(R.id.eb_status);
        okButton = (Button) findViewById(R.id.eb_ok);
        cancelButton = (Button) findViewById(R.id.eb_cancel);

        TextView whiteTitleText = (TextView) findViewById(R.id.white_clock);
        whiteTitleText.setVisibility(View.GONE);
        TextView blackTitleText = (TextView) findViewById(R.id.black_clock);
        blackTitleText.setVisibility(View.GONE);
        TextView engineTitleText = (TextView) findViewById(R.id.title_text);
        engineTitleText.setVisibility(View.GONE);
        whiteFigText = (TextView) findViewById(R.id.white_pieces);
        whiteFigText.setTypeface(figNotation);
        whiteFigText.setSelected(true);
        whiteFigText.setTextColor(whiteTitleText.getTextColors());
        blackFigText = (TextView) findViewById(R.id.black_pieces);
        blackFigText.setTypeface(figNotation);
        blackFigText.setSelected(true);
        blackFigText.setTextColor(blackTitleText.getTextColors());
        TextView summaryTitleText = (TextView) findViewById(R.id.title_text_summary);
        summaryTitleText.setText(R.string.edit_board);

        TextUtils.TruncateAt where = autoScrollTitle ? TextUtils.TruncateAt.MARQUEE : TextUtils.TruncateAt.END;
        engineTitleText.setEllipsize(where);
        whiteFigText.setEllipsize(where);
        blackFigText.setEllipsize(where);

        firstTitleLine.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                openOptionsMenu();
            }
        });
        secondTitleLine.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                openOptionsMenu();
            }
        });

        okButton.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                sendBackResult();
            }
        });
        cancelButton.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                setResult(RESULT_CANCELED);
                finish();
            }
        });

        status.setFocusable(false);
        cb.setFocusable(true);
        cb.requestFocus();
        cb.setClickable(true);
        cb.setOnTouchListener(new OnTouchListener() {
            private boolean pending = false;
            private int sq0 = -1;
            private Handler handler = new Handler();
            private Runnable runnable = new Runnable() {
                public void run() {
                    pending = false;
                    handler.removeCallbacks(runnable);
                    ((Vibrator) getSystemService(Context.VIBRATOR_SERVICE)).vibrate(20);
                    showDialog(EDIT_DIALOG);
                }
            };

            public boolean onTouch(View v, MotionEvent event) {
                int action = MotionEventCompat.getActionMasked(event);
                switch (action) {
                case MotionEvent.ACTION_DOWN:
                    handler.postDelayed(runnable, ViewConfiguration.getLongPressTimeout());
                    sq0 = cb.eventToSquare(event);
                    pending = true;
                    break;
                case MotionEvent.ACTION_UP:
                    if (pending) {
                        pending = false;
                        handler.removeCallbacks(runnable);
                        int sq = cb.eventToSquare(event);
                        if (sq == sq0) {
                            Move m = cb.mousePressed(sq);
                            if (m != null)
                                doMove(m);
                            setEgtbHints(cb.getSelectedSquare());
                        }
                    }
                    break;
                case MotionEvent.ACTION_CANCEL:
                    pending = false;
                    handler.removeCallbacks(runnable);
                    break;
                }
                return true;
            }
        });
        cb.setOnTrackballListener(new ChessBoard.OnTrackballListener() {
            public void onTrackballEvent(MotionEvent event) {
                Move m = cb.handleTrackballEvent(event);
                if (m != null)
                    doMove(m);
                setEgtbHints(cb.getSelectedSquare());
            }
        });
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        showDialog(EDIT_DIALOG);
        return false;
    }

    private final void setSelection(int sq) {
        cb.setSelection(sq);
        setEgtbHints(sq);
    }

    private final void setEgtbHints(int sq) {
        if (!egtbHints || (sq < 0)) {
            cb.setSquareDecorations(null);
            return;
        }

        Probe gtbProbe = Probe.getInstance();
        ArrayList<Pair<Integer, ProbeResult>> x = gtbProbe.relocatePieceProbe(cb.pos, sq);
        if (x == null) {
            cb.setSquareDecorations(null);
            return;
        }

        ArrayList<SquareDecoration> sd = new ArrayList<SquareDecoration>();
        for (Pair<Integer, ProbeResult> p : x)
            sd.add(new SquareDecoration(p.first, p.second));
        cb.setSquareDecorations(sd);
    }

    private void doMove(Move m) {
        if (m.to < 0) {
            if ((m.from < 0) || (cb.pos.getPiece(m.from) == Piece.EMPTY)) {
                setSelection(m.to);
                return;
            }
        }
        Position pos = new Position(cb.pos);
        int piece = Piece.EMPTY;
        if (m.from >= 0) {
            piece = pos.getPiece(m.from);
        } else {
            piece = -(m.from + 2);
        }
        if (m.to >= 0) {
            int oPiece = Piece.swapColor(piece);
            if ((m.from < 0) && (pos.getPiece(m.to) == oPiece))
                pos.setPiece(m.to, Piece.EMPTY);
            else if ((m.from < 0) && (pos.getPiece(m.to) == piece))
                pos.setPiece(m.to, oPiece);
            else
                pos.setPiece(m.to, piece);
        }
        if (m.from >= 0)
            pos.setPiece(m.from, Piece.EMPTY);
        cb.setPosition(pos);
        if (m.from >= 0)
            setSelection(-1);
        else
            setSelection(m.from);
        checkValidAndUpdateMaterialDiff();
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            sendBackResult();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }

    private final void sendBackResult() {
        if (checkValidAndUpdateMaterialDiff()) {
            setPosFields();
            String fen = TextIO.toFEN(cb.pos);
            setResult(RESULT_OK, (new Intent()).setAction(fen));
        } else {
            setResult(RESULT_CANCELED);
        }
        finish();
    }

    private final void setPosFields() {
        setEPFile(getEPFile()); // To handle sideToMove change
        TextIO.fixupEPSquare(cb.pos);
        TextIO.removeBogusCastleFlags(cb.pos);
    }

    private final int getEPFile() {
        int epSquare = cb.pos.getEpSquare();
        if (epSquare < 0)
            return 8;
        return Position.getX(epSquare);
    }

    private final void setEPFile(int epFile) {
        int epSquare = -1;
        if ((epFile >= 0) && (epFile < 8)) {
            int epRank = cb.pos.whiteMove ? 5 : 2;
            epSquare = Position.getSquare(epFile, epRank);
        }
        cb.pos.setEpSquare(epSquare);
    }

    /** Test if a position is valid and update material diff display. */
    private final boolean checkValidAndUpdateMaterialDiff() {
        try {
            MaterialDiff md = Util.getMaterialDiff(cb.pos);
            whiteFigText.setText(md.white);
            blackFigText.setText(md.black);

            String fen = TextIO.toFEN(cb.pos);
            TextIO.readFEN(fen);
            status.setText("");
            return true;
        } catch (ChessParseError e) {
            status.setText(getParseErrString(e));
        }
        return false;
    }

    private final String getParseErrString(ChessParseError e) {
        if (e.resourceId == -1)
            return e.getMessage();
        else
            return getString(e.resourceId);
    }

    static final int EDIT_DIALOG = 0;
    static final int SIDE_DIALOG = 1;
    static final int CASTLE_DIALOG = 2;
    static final int EP_DIALOG = 3;
    static final int MOVCNT_DIALOG = 4;

    @Override
    protected Dialog onCreateDialog(int id) {
        switch (id) {
        case EDIT_DIALOG: {
            final int SIDE_TO_MOVE = 0;
            final int CLEAR_BOARD = 1;
            final int INITIAL_POS = 2;
            final int CASTLING_FLAGS = 3;
            final int EN_PASSANT_FILE = 4;
            final int MOVE_COUNTERS = 5;
            final int COPY_POSITION = 6;
            final int PASTE_POSITION = 7;
            final int GET_FEN = 8;

            List<CharSequence> lst = new ArrayList<CharSequence>();
            List<Integer> actions = new ArrayList<Integer>();
            lst.add(getString(R.string.side_to_move));
            actions.add(SIDE_TO_MOVE);
            lst.add(getString(R.string.clear_board));
            actions.add(CLEAR_BOARD);
            lst.add(getString(R.string.initial_position));
            actions.add(INITIAL_POS);
            lst.add(getString(R.string.castling_flags));
            actions.add(CASTLING_FLAGS);
            lst.add(getString(R.string.en_passant_file));
            actions.add(EN_PASSANT_FILE);
            lst.add(getString(R.string.move_counters));
            actions.add(MOVE_COUNTERS);
            lst.add(getString(R.string.copy_position));
            actions.add(COPY_POSITION);
            lst.add(getString(R.string.paste_position));
            actions.add(PASTE_POSITION);
            if (DroidFish.hasFenProvider(getPackageManager())) {
                lst.add(getString(R.string.get_fen));
                actions.add(GET_FEN);
            }
            final List<Integer> finalActions = actions;
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle(R.string.edit_board);
            builder.setItems(lst.toArray(new CharSequence[lst.size()]), new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int item) {
                    switch (finalActions.get(item)) {
                    case SIDE_TO_MOVE:
                        showDialog(SIDE_DIALOG);
                        setSelection(-1);
                        checkValidAndUpdateMaterialDiff();
                        break;
                    case CLEAR_BOARD: {
                        Position pos = new Position();
                        cb.setPosition(pos);
                        setSelection(-1);
                        checkValidAndUpdateMaterialDiff();
                        break;
                    }
                    case INITIAL_POS: {
                        try {
                            Position pos = TextIO.readFEN(TextIO.startPosFEN);
                            cb.setPosition(pos);
                            setSelection(-1);
                            checkValidAndUpdateMaterialDiff();
                        } catch (ChessParseError e) {
                        }
                        break;
                    }
                    case CASTLING_FLAGS:
                        removeDialog(CASTLE_DIALOG);
                        showDialog(CASTLE_DIALOG);
                        setSelection(-1);
                        checkValidAndUpdateMaterialDiff();
                        break;
                    case EN_PASSANT_FILE:
                        removeDialog(EP_DIALOG);
                        showDialog(EP_DIALOG);
                        setSelection(-1);
                        checkValidAndUpdateMaterialDiff();
                        break;
                    case MOVE_COUNTERS:
                        removeDialog(MOVCNT_DIALOG);
                        showDialog(MOVCNT_DIALOG);
                        setSelection(-1);
                        checkValidAndUpdateMaterialDiff();
                        break;
                    case COPY_POSITION: {
                        setPosFields();
                        String fen = TextIO.toFEN(cb.pos) + "\n";
                        ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
                        clipboard.setText(fen);
                        setSelection(-1);
                        break;
                    }
                    case PASTE_POSITION: {
                        ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
                        if (clipboard.hasText()) {
                            String fen = clipboard.getText().toString();
                            setFEN(fen);
                        }
                        break;
                    }
                    case GET_FEN:
                        Intent i = new Intent(Intent.ACTION_GET_CONTENT);
                        i.setType("application/x-chess-fen");
                        try {
                            startActivityForResult(i, RESULT_GET_FEN);
                        } catch (ActivityNotFoundException e) {
                            Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG).show();
                        }
                    }
                }
            });
            AlertDialog alert = builder.create();
            return alert;
        }
        case SIDE_DIALOG: {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle(R.string.select_side_to_move_first);
            final int selectedItem = (cb.pos.whiteMove) ? 0 : 1;
            builder.setSingleChoiceItems(new String[] { getString(R.string.white), getString(R.string.black) },
                    selectedItem, new Dialog.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                            if (id == 0) { // white to move
                                cb.pos.setWhiteMove(true);
                                checkValidAndUpdateMaterialDiff();
                                dialog.cancel();
                            } else {
                                cb.pos.setWhiteMove(false);
                                checkValidAndUpdateMaterialDiff();
                                dialog.cancel();
                            }
                        }
                    });
            AlertDialog alert = builder.create();
            return alert;
        }
        case CASTLE_DIALOG: {
            final CharSequence[] items = { getString(R.string.white_king_castle),
                    getString(R.string.white_queen_castle), getString(R.string.black_king_castle),
                    getString(R.string.black_queen_castle) };
            boolean[] checkedItems = { cb.pos.h1Castle(), cb.pos.a1Castle(), cb.pos.h8Castle(), cb.pos.a8Castle() };
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle(R.string.castling_flags);
            builder.setMultiChoiceItems(items, checkedItems, new DialogInterface.OnMultiChoiceClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which, boolean isChecked) {
                    Position pos = new Position(cb.pos);
                    boolean a1Castle = pos.a1Castle();
                    boolean h1Castle = pos.h1Castle();
                    boolean a8Castle = pos.a8Castle();
                    boolean h8Castle = pos.h8Castle();
                    switch (which) {
                    case 0:
                        h1Castle = isChecked;
                        break;
                    case 1:
                        a1Castle = isChecked;
                        break;
                    case 2:
                        h8Castle = isChecked;
                        break;
                    case 3:
                        a8Castle = isChecked;
                        break;
                    }
                    int castleMask = 0;
                    if (a1Castle)
                        castleMask |= 1 << Position.A1_CASTLE;
                    if (h1Castle)
                        castleMask |= 1 << Position.H1_CASTLE;
                    if (a8Castle)
                        castleMask |= 1 << Position.A8_CASTLE;
                    if (h8Castle)
                        castleMask |= 1 << Position.H8_CASTLE;
                    pos.setCastleMask(castleMask);
                    cb.setPosition(pos);
                    checkValidAndUpdateMaterialDiff();
                }
            });
            AlertDialog alert = builder.create();
            return alert;
        }
        case EP_DIALOG: {
            final CharSequence[] items = { "A", "B", "C", "D", "E", "F", "G", "H", getString(R.string.none) };
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle(R.string.select_en_passant_file);
            builder.setSingleChoiceItems(items, getEPFile(), new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int item) {
                    setEPFile(item);
                    dialog.cancel();
                }
            });
            AlertDialog alert = builder.create();
            return alert;
        }
        case MOVCNT_DIALOG: {
            View content = View.inflate(this, R.layout.edit_move_counters, null);
            final AlertDialog.Builder builder = new AlertDialog.Builder(this);

            builder.setView(content);
            builder.setTitle(R.string.edit_move_counters);
            final EditText halfMoveClock = (EditText) content.findViewById(R.id.ed_cnt_halfmove);
            final EditText fullMoveCounter = (EditText) content.findViewById(R.id.ed_cnt_fullmove);
            halfMoveClock.setText(String.format(Locale.US, "%d", cb.pos.halfMoveClock));
            fullMoveCounter.setText(String.format(Locale.US, "%d", cb.pos.fullMoveCounter));
            final Runnable setCounters = new Runnable() {
                public void run() {
                    try {
                        int halfClock = Integer.parseInt(halfMoveClock.getText().toString());
                        int fullCount = Integer.parseInt(fullMoveCounter.getText().toString());
                        cb.pos.halfMoveClock = halfClock;
                        cb.pos.fullMoveCounter = fullCount;
                    } catch (NumberFormatException nfe) {
                        Toast.makeText(getApplicationContext(), R.string.invalid_number_format, Toast.LENGTH_SHORT)
                                .show();
                    }
                }
            };
            builder.setPositiveButton("Ok", new Dialog.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    setCounters.run();
                }
            });
            builder.setNegativeButton("Cancel", null);

            final Dialog dialog = builder.create();

            fullMoveCounter.setOnKeyListener(new OnKeyListener() {
                public boolean onKey(View v, int keyCode, KeyEvent event) {
                    if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
                        setCounters.run();
                        dialog.cancel();
                        return true;
                    }
                    return false;
                }
            });
            return dialog;
        }
        }
        return null;
    }

    private final void setFEN(String fen) {
        if (fen == null)
            return;
        try {
            Position pos = TextIO.readFEN(fen);
            cb.setPosition(pos);
        } catch (ChessParseError e) {
            if (e.pos != null)
                cb.setPosition(e.pos);
            Toast.makeText(getApplicationContext(), getParseErrString(e), Toast.LENGTH_SHORT).show();
        }
        setSelection(-1);
        checkValidAndUpdateMaterialDiff();
    }

    static private final int RESULT_GET_FEN = 0;
    static private final int RESULT_LOAD_FEN = 1;

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
        case RESULT_GET_FEN:
            if (resultCode == RESULT_OK) {
                String fen = data.getStringExtra(Intent.EXTRA_TEXT);
                if (fen == null) {
                    String pathName = DroidFish.getFilePathFromUri(data.getData());
                    Intent i = new Intent(EditBoard.this, LoadFEN.class);
                    i.setAction("org.petero.droidfish.loadFen");
                    i.putExtra("org.petero.droidfish.pathname", pathName);
                    startActivityForResult(i, RESULT_LOAD_FEN);
                }
                setFEN(fen);
            }
            break;
        case RESULT_LOAD_FEN:
            if (resultCode == RESULT_OK) {
                String fen = data.getAction();
                setFEN(fen);
            }
            break;
        }
    }
}