/*
DroidFish - An Android chess program.
Copyright (C) 2011 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 org.petero.droidfish.ChessBoard;
import org.petero.droidfish.R;
import org.petero.droidfish.ChessBoard.SquareDecoration;
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.gtb.Probe;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.ClipboardManager;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.view.View.OnLongClickListener;
import android.view.View.OnTouchListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
public class EditBoard extends Activity {
private ChessBoardEdit cb;
private TextView status;
private Button okButton;
private Button cancelButton;
boolean egtbHints;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initUI();
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
egtbHints = settings.getBoolean("tbHintsEdit", false);
Intent i = getIntent();
Position pos;
try {
pos = TextIO.readFEN(i.getAction());
cb.setPosition(pos);
} catch (ChessParseError e) {
}
}
@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);
status.setText(statusStr);
}
private final void initUI() {
setContentView(R.layout.editboard);
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);
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);
final GestureDetector gd = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
public boolean onDown(MotionEvent e) {
return false;
}
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
cb.cancelLongPress();
return true;
}
public boolean onSingleTapUp(MotionEvent e) {
cb.cancelLongPress();
handleClick(e);
return true;
}
public boolean onDoubleTapEvent(MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_UP)
handleClick(e);
return true;
}
private final void handleClick(MotionEvent e) {
int sq = cb.eventToSquare(e);
Move m = cb.mousePressed(sq);
if (m != null)
doMove(m);
setEgtbHints(cb.getSelectedSquare());
}
});
cb.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
return gd.onTouchEvent(event);
}
});
cb.setOnTrackballListener(new ChessBoard.OnTrackballListener() {
public void onTrackballEvent(MotionEvent event) {
Move m = cb.handleTrackballEvent(event);
if (m != null)
doMove(m);
setEgtbHints(cb.getSelectedSquare());
}
});
cb.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
showDialog(EDIT_DIALOG);
return true;
}
});
}
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, Integer>> x = gtbProbe.relocatePieceProbe(cb.pos, sq);
if (x == null) {
cb.setSquareDecorations(null);
return;
}
ArrayList<SquareDecoration> sd = new ArrayList<SquareDecoration>();
for (Pair<Integer,Integer> 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);
checkValid();
}
@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 (checkValid()) {
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. */
private final boolean checkValid() {
try {
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 CharSequence[] items = {
getString(R.string.side_to_move),
getString(R.string.clear_board), getString(R.string.initial_position),
getString(R.string.castling_flags), getString(R.string.en_passant_file),
getString(R.string.move_counters),
getString(R.string.copy_position), getString(R.string.paste_position)
};
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.edit_board);
builder.setItems(items, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
switch (item) {
case 0: // Edit side to move
showDialog(SIDE_DIALOG);
setSelection(-1);
checkValid();
break;
case 1: { // Clear board
Position pos = new Position();
cb.setPosition(pos);
setSelection(-1);
checkValid();
break;
}
case 2: { // Set initial position
try {
Position pos = TextIO.readFEN(TextIO.startPosFEN);
cb.setPosition(pos);
setSelection(-1);
checkValid();
} catch (ChessParseError e) {
}
break;
}
case 3: // Edit castling flags
removeDialog(CASTLE_DIALOG);
showDialog(CASTLE_DIALOG);
setSelection(-1);
checkValid();
break;
case 4: // Edit en passant file
removeDialog(EP_DIALOG);
showDialog(EP_DIALOG);
setSelection(-1);
checkValid();
break;
case 5: // Edit move counters
removeDialog(MOVCNT_DIALOG);
showDialog(MOVCNT_DIALOG);
setSelection(-1);
checkValid();
break;
case 6: { // Copy position
setPosFields();
String fen = TextIO.toFEN(cb.pos) + "\n";
ClipboardManager clipboard = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE);
clipboard.setText(fen);
setSelection(-1);
break;
}
case 7: { // Paste position
ClipboardManager clipboard = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE);
if (clipboard.hasText()) {
String fen = clipboard.getText().toString();
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);
checkValid();
}
break;
}
}
}
});
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);
checkValid();
dialog.cancel();
} else {
cb.pos.setWhiteMove(false);
checkValid();
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);
checkValid();
}
});
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("%d", cb.pos.halfMoveClock));
fullMoveCounter.setText(String.format("%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;
}
}