Java tutorial
/* MD Chess - An Android chess program. Copyright (C) 2011-2014 Peter sterlund, peterosterlund2@gmail.com Copyright (C) 2012 Leo Mayer 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.mdc.chess; import android.Manifest; import android.annotation.SuppressLint; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.ActivityNotFoundException; import android.content.ClipData; import android.content.ClipDescription; import android.content.ClipboardManager; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.content.res.Resources.NotFoundException; import android.graphics.Color; import android.graphics.Typeface; import android.media.MediaPlayer; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Vibrator; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.design.widget.NavigationView; import android.support.design.widget.TextInputLayout; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v4.view.GravityCompat; import android.support.v4.view.MotionEventCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.text.Html; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; import android.text.style.BackgroundColorSpan; import android.text.style.ForegroundColorSpan; import android.text.style.LeadingMarginSpan; import android.text.style.StyleSpan; import android.util.Log; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; 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.view.ViewConfiguration; import android.view.WindowManager; import android.webkit.WebView; import android.widget.EditText; import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toast; import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.kalab.chess.enginesupport.ChessEngine; import com.kalab.chess.enginesupport.ChessEngineResolver; import org.mdc.chess.ChessBoard.SquareDecoration; import org.mdc.chess.activities.CPUWarning; import org.mdc.chess.activities.EditBoard; import org.mdc.chess.activities.EditOptions; import org.mdc.chess.activities.EditPGNLoad; import org.mdc.chess.activities.EditPGNSave; import org.mdc.chess.activities.LoadFEN; import org.mdc.chess.activities.LoadScid; import org.mdc.chess.activities.Preferences; import org.mdc.chess.book.BookOptions; import org.mdc.chess.engine.EngineUtil; import org.mdc.chess.engine.UCIOptions; import org.mdc.chess.gamelogic.ChessParseError; import org.mdc.chess.gamelogic.GameTree.Node; import org.mdc.chess.gamelogic.MDChessController; import org.mdc.chess.gamelogic.Move; import org.mdc.chess.gamelogic.Pair; import org.mdc.chess.gamelogic.PgnToken; import org.mdc.chess.gamelogic.Piece; import org.mdc.chess.gamelogic.Position; import org.mdc.chess.gamelogic.TextIO; import org.mdc.chess.gamelogic.TimeControlData; import org.mdc.chess.tb.Probe; import org.mdc.chess.tb.ProbeResult; import java.io.File; import java.io.FileFilter; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TreeMap; @SuppressLint("ClickableViewAccessibility") public class MDChess extends AppCompatActivity implements GUIInterface, ActivityCompat.OnRequestPermissionsResultCallback, NavigationView.OnNavigationItemSelectedListener { // FIXME!!! PGN view option: game continuation (for training) // FIXME!!! Remove invalid playerActions in PGN import (should be done in verifyChildren) // FIXME!!! Implement bookmark mechanism for positions in pgn files // FIXME!!! Add support for "Chess Leipzig" font // FIXME!!! Computer clock should stop if phone turned off (computer stops thinking if // unplugged) // FIXME!!! Add support for "no time control" and "hour-glass time control" as defined by the // PGN standard // FIXME!!! Add chess960 support // FIXME!!! Implement "hint" feature // FIXME!!! Show extended book info. (Win percent, number of games, performance rating, etc.) // FIXME!!! Green color for "main move". Red color for "don't play in tournaments" moves. // FIXME!!! ECO opening codes // FIXME!!! Option to display coordinates in border outside chess board. // FIXME!!! Better behavior if engine is terminated. How exactly? // FIXME!!! Handle PGN non-file intents with more than one game. // FIXME!!! Save position to fen/epd file // FIXME!!! Selection dialog for going into variation // FIXME!!! Use two engines in engine/engine games private final static String bookDir = "MD Chess/book"; private final static String pgnDir = "MD Chess/pgn"; private final static String fenDir = "MD Chess/epd"; private final static String engineDir = "MD Chess/uci"; private final static String gtbDefaultDir = "MD Chess/gtb"; private final static String rtbDefaultDir = "MD Chess/rtb"; // Unicode code points for chess pieces private static final String figurinePieceNames = Piece.NOTATION_PAWN + " " + Piece.NOTATION_KNIGHT + " " + Piece.NOTATION_BISHOP + " " + Piece.NOTATION_ROOK + " " + Piece.NOTATION_QUEEN + " " + Piece.NOTATION_KING; static private final int RESULT_EDITBOARD = 0; static private final int RESULT_SETTINGS = 1; static private final int RESULT_LOAD_PGN = 2; static private final int RESULT_LOAD_FEN = 3; static private final int RESULT_SELECT_SCID = 4; static private final int RESULT_OI_PGN_SAVE = 5; static private final int RESULT_OI_PGN_LOAD = 6; static private final int RESULT_OI_FEN_LOAD = 7; static private final int RESULT_GET_FEN = 8; static private final int RESULT_EDITOPTIONS = 9; static private final int SELECT_PGN_FILE_DIALOG = 7; static private final int SELECT_PGN_FILE_SAVE_DIALOG = 8; static private final int SELECT_FEN_FILE_DIALOG = 27; private final static int FT_NONE = 0; private final static int FT_PGN = 1; private final static int FT_SCID = 2; private final static int FT_FEN = 3; private static MDChessController ctrl = null; private final BookOptions bookOptions = new BookOptions(); private final PGNOptions pgnOptions = new PGNOptions(); private final EngineOptions engineOptions = new EngineOptions(); private final Handler handlerTimer = new Handler(); private final Runnable r = new Runnable() { public void run() { ctrl.updateRemainingTime(); } }; private final Handler autoModeTimer = new Handler(); private ChessBoardPlay cb; private boolean mShowThinking; private boolean mShowStats; private int numPV; private boolean mWhiteBasedScores; private boolean mShowBookHints; private int maxNumArrows; private GameMode gameMode; private boolean mPonderMode; private int timeControl; private int movesPerSession; private int timeIncrement; private String playerName; private boolean boardFlipped; private boolean autoSwapSides; private boolean playerNameFlip; private boolean discardVariations; private TextView status; private ScrollView moveListScroll; private MoveListView moveList; private TextView thinking; private TextView whiteTitleText, blackTitleText, engineTitleText; private TextView whiteFigText, blackFigText; private SharedPreferences settings; private float scrollSensitivity; private boolean invertScrollDirection; private boolean leftHanded; private boolean soundEnabled; private MediaPlayer moveSound; private boolean vibrateEnabled; private boolean animateMoves; private boolean autoScrollTitle; private boolean showVariationLine; private int autoMoveDelay; // Delay in auto forward/backward mode private AutoMode autoMode = AutoMode.OFF; private final Runnable amRunnable = new Runnable() { @Override public void run() { switch (autoMode) { case BACKWARD: ctrl.undoMove(); setAutoMode(autoMode); break; case FORWARD: ctrl.redoMove(); setAutoMode(autoMode); break; case OFF: break; } } }; /** * State of WRITE_EXTERNAL_STORAGE permission. */ private PermissionState storagePermission = PermissionState.UNKNOWN; private long lastVisibleMillis; // Time when GUI became invisible. 0 if currently visible. private long lastComputationMillis; // Time when engine last showed that it was computing. private PgnScreenText gameTextListener; private Typeface figNotation; private Typeface defaultThinkingListTypeFace; private boolean egtbForceReload = false; private String thinkingStr1 = ""; private String thinkingStr2 = ""; private String bookInfoStr = ""; private String variantStr = ""; private ArrayList<ArrayList<Move>> pvMoves = new ArrayList<>(); private ArrayList<Move> bookMoves = null; private ArrayList<Move> variantMoves = null; // Filename of network engine to configure private String networkEngineToConfig = ""; private boolean notificationActive = false; public static String getFilePathFromUri(Uri uri) { if (uri == null) { return null; } return uri.getPath(); } private static boolean reservedEngineName(String name) { return "cuckoochess".equals(name) || "stockfish".equals(name) || name.endsWith(".ini"); } private static boolean hasFenProvider(PackageManager manager) { Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.setType("application/x-chess-fen"); List<ResolveInfo> resolvers = manager.queryIntentActivities(i, 0); return (resolvers != null) && (resolvers.size() > 0); } /** * Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Pair<String, String> pair = getPgnOrFenIntent(); String intentPgnOrFen = pair.first; String intentFilename = pair.second; createDirectories(); PreferenceManager.setDefaultValues(this, R.xml.preferences, false); settings = PreferenceManager.getDefaultSharedPreferences(this); setWakeLock(false); figNotation = Typeface.createFromAsset(getAssets(), "fonts/KlinicSlabBold.otf"); setPieceNames(PGNOptions.PT_LOCAL); initUI(); gameTextListener = new PgnScreenText(pgnOptions); moveList.setOnLinkClickListener(gameTextListener); moveList.setBackgroundColor(Color.WHITE); if (ctrl != null) { ctrl.shutdownEngine(); } ctrl = new MDChessController(this, gameTextListener, pgnOptions); egtbForceReload = true; readPrefs(); TimeControlData tcData = new TimeControlData(); tcData.setTimeControl(timeControl, movesPerSession, timeIncrement); ctrl.newGame(gameMode, tcData); setAutoMode(AutoMode.OFF); { byte[] data = null; int version = 1; if (savedInstanceState != null) { data = savedInstanceState.getByteArray("gameState"); version = savedInstanceState.getInt("gameStateVersion", version); } else { String dataStr = settings.getString("gameState", null); version = settings.getInt("gameStateVersion", version); if (dataStr != null) { data = strToByteArr(dataStr); } } if (data != null) { ctrl.fromByteArray(data, version); } } ctrl.setGuiPaused(true); ctrl.setGuiPaused(false); ctrl.startGame(); if (intentPgnOrFen != null) { try { ctrl.setFENOrPGN(intentPgnOrFen); setBoardFlip(true); } catch (ChessParseError e) { // If FEN corresponds to illegal chess position, go into edit board mode. try { TextIO.readFEN(intentPgnOrFen); } catch (ChessParseError e2) { if (e2.pos != null) { startEditBoard(intentPgnOrFen); } } } } else if (intentFilename != null) { if (intentFilename.toLowerCase(Locale.US).endsWith(".fen") || intentFilename.toLowerCase(Locale.US).endsWith(".epd")) { loadFENFromFile(intentFilename); } else { loadPGNFromFile(intentFilename); } } } private void setPieceNames(int pieceType) { if (pieceType == PGNOptions.PT_FIGURINE) { TextIO.setPieceNames(figurinePieceNames); } else { TextIO.setPieceNames(getString(R.string.piece_names)); } } /** * Create directory structure on SD card. */ private void createDirectories() { if (storagePermission == PermissionState.UNKNOWN) { String extStorage = Manifest.permission.WRITE_EXTERNAL_STORAGE; if (ContextCompat.checkSelfPermission(this, extStorage) == PackageManager.PERMISSION_GRANTED) { storagePermission = PermissionState.GRANTED; } else { ActivityCompat.requestPermissions(this, new String[] { extStorage }, 0); storagePermission = PermissionState.REQUESTED; } } if (storagePermission != PermissionState.GRANTED) { return; } File extDir = Environment.getExternalStorageDirectory(); String sep = File.separator; boolean result; result = new File(extDir + sep + bookDir).mkdirs(); Log.d("Result" + extDir + sep + bookDir, "" + result); result = new File(extDir + sep + pgnDir).mkdirs(); Log.d("Result" + extDir + sep + pgnDir, "" + result); result = new File(extDir + sep + fenDir).mkdirs(); Log.d("Result" + extDir + sep + fenDir, "" + result); result = new File(extDir + sep + engineDir).mkdirs(); Log.d("Result" + extDir + sep + engineDir, "" + result); result = new File(extDir + sep + engineDir + sep + EngineUtil.openExchangeDir).mkdirs(); Log.d("Result" + extDir + sep + engineDir + sep + EngineUtil.openExchangeDir, "" + result); result = new File(extDir + sep + gtbDefaultDir).mkdirs(); Log.d("Result" + extDir + sep + gtbDefaultDir, "" + result); result = new File(extDir + sep + rtbDefaultDir).mkdirs(); Log.d("Result" + extDir + sep + rtbDefaultDir, "" + result); } @Override public void onRequestPermissionsResult(int code, @NonNull String[] permissions, @NonNull int[] results) { if (storagePermission == PermissionState.REQUESTED) { if ((results.length > 0) && (results[0] == PackageManager.PERMISSION_GRANTED)) { storagePermission = PermissionState.GRANTED; } else { storagePermission = PermissionState.DENIED; } } createDirectories(); } /** * Return true if the WRITE_EXTERNAL_STORAGE permission has been granted. */ private boolean storageAvailable() { return storagePermission == PermissionState.GRANTED; } /** * Return PGN/FEN data or filename from the Intent. Both can not be non-null. * * @return Pair of PGN/FEN data and filename. */ private Pair<String, String> getPgnOrFenIntent() { String pgnOrFen = null; String filename = null; try { Intent intent = getIntent(); Uri data = intent.getData(); if (data == null) { Bundle b = intent.getExtras(); if (b != null) { Object strm = b.get(Intent.EXTRA_STREAM); if (strm instanceof Uri) { data = (Uri) strm; if ("file".equals(data.getScheme())) { filename = data.getEncodedPath(); if (filename != null) { filename = Uri.decode(filename); } } } } } if (data == null) { if ((Intent.ACTION_SEND.equals(intent.getAction()) || Intent.ACTION_VIEW.equals(intent.getAction())) && ("application/x-chess-pgn".equals(intent.getType()) || "application/x-chess-fen".equals(intent.getType()))) { pgnOrFen = intent.getStringExtra(Intent.EXTRA_TEXT); } } else { String scheme = intent.getScheme(); if ("file".equals(scheme)) { filename = data.getEncodedPath(); if (filename != null) { filename = Uri.decode(filename); } } if ((filename == null) && ("content".equals(scheme) || "file".equals(scheme))) { ContentResolver resolver = getContentResolver(); InputStream in = resolver.openInputStream(intent.getData()); StringBuilder sb = new StringBuilder(); while (true) { byte[] buffer = new byte[16384]; int len = in != null ? in.read(buffer) : 0; if (len <= 0) { break; } sb.append(new String(buffer, 0, len)); } pgnOrFen = sb.toString(); } } } catch (IOException e) { Toast.makeText(getApplicationContext(), R.string.failed_to_read_pgn_data, Toast.LENGTH_SHORT).show(); } return new Pair<>(pgnOrFen, filename); } private byte[] strToByteArr(String str) { if (str == null) { return null; } int nBytes = str.length() / 2; byte[] ret = new byte[nBytes]; for (int i = 0; i < nBytes; i++) { int c1 = str.charAt(i * 2) - 'A'; int c2 = str.charAt(i * 2 + 1) - 'A'; ret[i] = (byte) (c1 * 16 + c2); } return ret; } private String byteArrToString(byte[] data) { if (data == null) { return null; } StringBuilder ret = new StringBuilder(32768); //int nBytes = data.length; for (byte aData : data) { int b = aData; if (b < 0) b += 256; char c1 = (char) ('A' + (b / 16)); char c2 = (char) ('A' + (b & 15)); ret.append(c1); ret.append(c2); } return ret.toString(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); //actionBarDrawerToggle.onConfigurationChanged(newConfig); reInitUI(); } /** * Re-initialize UI when layout should change because of rotation or handedness change. */ private void reInitUI() { ChessBoardPlay oldCB = cb; String statusStr = status.getText().toString(); initUI(); readPrefs(); cb.cursorX = oldCB.cursorX; cb.cursorY = oldCB.cursorY; cb.cursorVisible = oldCB.cursorVisible; cb.setPosition(oldCB.pos); cb.setFlipped(oldCB.flipped); cb.setDrawSquareLabels(oldCB.drawSquareLabels); cb.oneTouchMoves = oldCB.oneTouchMoves; cb.toggleSelection = oldCB.toggleSelection; cb.highlightLastMove = oldCB.highlightLastMove; cb.setBlindMode(oldCB.blindMode); setSelection(oldCB.selectedSquare); cb.userSelectedSquare = oldCB.userSelectedSquare; setStatusString(statusStr); moveList.setOnLinkClickListener(gameTextListener); moveListUpdated(); updateThinkingInfo(); ctrl.updateRemainingTime(); ctrl.updateMaterialDiffList(); } /** * Return true if the current orientation is landscape. */ private boolean landScapeView() { return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; } /** * Return true if left-handed layout should be used. */ private boolean leftHandedView() { return settings.getBoolean("leftHanded", false) && landScapeView(); } /** * Re-read preferences settings. */ private void handlePrefsChange() { if (leftHanded != leftHandedView()) { reInitUI(); } else { readPrefs(); } maybeAutoModeOff(gameMode); ctrl.setGameMode(gameMode); } private void initUI() { leftHanded = leftHandedView(); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); DrawerLayout drawer = (DrawerLayout) findViewById(R.id.main_layout); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); drawer.addDrawerListener(toggle); toggle.syncState(); NavigationView navigationView = (NavigationView) findViewById(R.id.nav_main); navigationView.setNavigationItemSelectedListener(this); // title lines need to be regenerated every time due to layout changes (rotations) View firstTitleLine = findViewById(R.id.title_line); whiteTitleText = (TextView) findViewById(R.id.txt_first); whiteTitleText.setSelected(true); blackTitleText = (TextView) findViewById(R.id.txt_third); blackTitleText.setSelected(true); engineTitleText = (TextView) findViewById(R.id.txt_second); 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()); status = (TextView) findViewById(R.id.status); moveListScroll = (ScrollView) findViewById(R.id.scrollView); moveList = (MoveListView) findViewById(R.id.moveList); thinking = (TextView) findViewById(R.id.thinking); defaultThinkingListTypeFace = thinking.getTypeface(); status.setFocusable(false); moveListScroll.setFocusable(false); moveList.setFocusable(false); thinking.setFocusable(false); class ClickListener implements OnClickListener, OnTouchListener { // --Commented out by Inspection (31/10/2016 10:41 PM):private float touchX = -1; @Override public void onClick(View v) { //boolean left = touchX <= v.getWidth() / 2.0; //drawerLayout.openDrawer(left ? GravityCompat.START : GravityCompat.END); //touchX = -1; } @Override public boolean onTouch(View v, MotionEvent event) { //touchX = event.getX(); return false; } } ClickListener listener = new ClickListener(); firstTitleLine.setOnClickListener(listener); firstTitleLine.setOnTouchListener(listener); cb = (ChessBoardPlay) findViewById(R.id.chessboard); cb.setFocusable(true); cb.requestFocus(); cb.setClickable(true); cb.setPgnOptions(pgnOptions); cb.setOnTouchListener(new OnTouchListener() { private final Handler handler = new Handler(); private boolean pending = false; private final Runnable runnable = new Runnable() { public void run() { pending = false; handler.removeCallbacks(runnable); ((Vibrator) getSystemService(Context.VIBRATOR_SERVICE)).vibrate(20); boardMenuDialog(); } }; private boolean pendingClick = false; private int sq0 = -1; private float scrollX = 0; private float scrollY = 0; private float prevX = 0; private float prevY = 0; @Override public boolean onTouch(View v, MotionEvent event) { int action = MotionEventCompat.getActionMasked(event); switch (action) { case MotionEvent.ACTION_DOWN: handler.postDelayed(runnable, ViewConfiguration.getLongPressTimeout()); pending = true; pendingClick = true; sq0 = cb.eventToSquare(event); scrollX = 0; scrollY = 0; prevX = event.getX(); prevY = event.getY(); break; case MotionEvent.ACTION_MOVE: if (pending) { int sq = cb.eventToSquare(event); if (sq != sq0) { handler.removeCallbacks(runnable); pendingClick = false; } float currX = event.getX(); float currY = event.getY(); if (onScroll(currX - prevX, currY - prevY)) { handler.removeCallbacks(runnable); pendingClick = false; } prevX = currX; prevY = currY; } break; case MotionEvent.ACTION_UP: if (pending) { pending = false; handler.removeCallbacks(runnable); if (!pendingClick) { break; } int sq = cb.eventToSquare(event); if (sq == sq0) { if (ctrl.humansTurn()) { Move m = cb.mousePressed(sq); if (m != null) { setAutoMode(AutoMode.OFF); ctrl.makeHumanMove(m); } setEgtbHints(cb.getSelectedSquare()); } } } break; case MotionEvent.ACTION_CANCEL: pending = false; handler.removeCallbacks(runnable); break; } return true; } private boolean onScroll(float distanceX, float distanceY) { if (invertScrollDirection) { distanceX = -distanceX; distanceY = -distanceY; } if ((scrollSensitivity > 0) && (cb.sqSize > 0)) { scrollX += distanceX; scrollY += distanceY; final float scrollUnit = cb.sqSize * scrollSensitivity; if (Math.abs(scrollX) >= Math.abs(scrollY)) { // Undo/redo int nRedo = 0, nUndo = 0; while (scrollX > scrollUnit) { nRedo++; scrollX -= scrollUnit; } while (scrollX < -scrollUnit) { nUndo++; scrollX += scrollUnit; } if (nUndo + nRedo > 0) { scrollY = 0; setAutoMode(AutoMode.OFF); } if (nRedo + nUndo > 1) { boolean analysis = gameMode.analysisMode(); boolean human = gameMode.playerWhite() || gameMode.playerBlack(); if (analysis || !human) { ctrl.setGameMode(new GameMode(GameMode.TWO_PLAYERS)); } } for (int i = 0; i < nRedo; i++) ctrl.redoMove(); for (int i = 0; i < nUndo; i++) ctrl.undoMove(); ctrl.setGameMode(gameMode); return nRedo + nUndo > 0; } else { // Next/previous variation int varDelta = 0; while (scrollY > scrollUnit) { varDelta++; scrollY -= scrollUnit; } while (scrollY < -scrollUnit) { varDelta--; scrollY += scrollUnit; } if (varDelta != 0) { scrollX = 0; setAutoMode(AutoMode.OFF); ctrl.changeVariation(varDelta); } return varDelta != 0; } } return false; } }); /*cb.setOnTrackballListener(new ChessBoard.OnTrackballListener() { public void onTrackballEvent(MotionEvent event) { if (ctrl.humansTurn()) { Move m = cb.handleTrackballEvent(event); if (m != null) { setAutoMode(AutoMode.OFF); ctrl.makeHumanMove(m); } setEgtbHints(cb.getSelectedSquare()); } } });*/ moveList.setOnLongClickListener(new OnLongClickListener() { public boolean onLongClick(View v) { moveListMenuDialog(); return true; } }); thinking.setOnLongClickListener(new OnLongClickListener() { public boolean onLongClick(View v) { if (mShowThinking || gameMode.analysisMode()) { if (!pvMoves.isEmpty()) { thinkingMenuDialog(); } } return true; } }); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (ctrl != null) { byte[] data = ctrl.toByteArray(); outState.putByteArray("gameState", data); outState.putInt("gameStateVersion", 3); } } @Override public void onBackPressed() { DrawerLayout drawer = (DrawerLayout) findViewById(R.id.main_layout); if (drawer.isDrawerOpen(GravityCompat.START)) { drawer.closeDrawer(GravityCompat.START); } else { super.onBackPressed(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main_drawer, menu); View actionUndo = findViewById(R.id.action_undo); if (actionUndo != null) { actionUndo.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { goBackMenuDialog(); return true; } }); } View actionRedo = findViewById(R.id.action_redo); if (actionRedo != null) { actionRedo.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { goForwardMenuDialog(); return true; } }); } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_undo) { setAutoMode(AutoMode.OFF); ctrl.undoMove(); } else if (id == R.id.action_redo) { setAutoMode(AutoMode.OFF); ctrl.redoMove(); } return super.onOptionsItemSelected(item); } @SuppressWarnings("StatementWithEmptyBody") @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { // Handle navigation view item clicks here. int id = item.getItemId(); if (id == R.id.nav_new_game) { gameDialog(); } else if (id == R.id.nav_edit_board) { startEditBoard(ctrl.getFEN()); } else if (id == R.id.nav_flip_board) { boardFlipped = !cb.flipped; cb.setFlipped(boardFlipped); } else if (id == R.id.nav_load_pgn) { selectPgnFileDialog(); } else if (id == R.id.nav_resign) { if (ctrl.humansTurn()) { //removeDialog(CONFIRM_RESIGN_DIALOG); //showDialog(CONFIRM_RESIGN_DIALOG); resignDialog(); } } else if (id == R.id.nav_force_computer) { ctrl.stopSearch(); } else if (id == R.id.nav_open_book) { if (storageAvailable()) { selectBookDialog(); } } else if (id == R.id.nav_theme) { setColorThemeDialog(); } else if (id == R.id.nav_about) { aboutDialog(); } else if (id == R.id.nav_engine) { if (storageAvailable()) { manageEnginesDialog(); } else { selectEngineDialog(true); } } else if (id == R.id.nav_settings) { Intent i = new Intent(MDChess.this, Preferences.class); startActivityForResult(i, RESULT_SETTINGS); } else if (id == R.id.nav_file_pgn) { fileMenuDialog(); } else if (id == R.id.nav_game_mode) { gameModeDialog(); } DrawerLayout drawer = (DrawerLayout) findViewById(R.id.main_layout); drawer.closeDrawer(GravityCompat.START); return true; } @Override protected void onResume() { lastVisibleMillis = 0; if (ctrl != null) { ctrl.setGuiPaused(false); } notificationActive = true; updateNotification(); super.onResume(); } @Override protected void onPause() { if (ctrl != null) { setAutoMode(AutoMode.OFF); ctrl.setGuiPaused(true); byte[] data = ctrl.toByteArray(); Editor editor = settings.edit(); String dataStr = byteArrToString(data); editor.putString("gameState", dataStr); editor.putInt("gameStateVersion", 3); editor.apply(); } lastVisibleMillis = System.currentTimeMillis(); updateNotification(); super.onPause(); } @Override protected void onDestroy() { setAutoMode(AutoMode.OFF); if (ctrl != null) { ctrl.shutdownEngine(); } setNotification(false); super.onDestroy(); } private int getIntSetting(String settingName, int defaultValue) { String tmp = settings.getString(settingName, String.format(Locale.US, "%d", defaultValue)); return Integer.parseInt(tmp); } private void readPrefs() { int modeNr = getIntSetting("gameMode", 1); gameMode = new GameMode(modeNr); String oldPlayerName = playerName; playerName = settings.getString("playerName", "Player"); boardFlipped = settings.getBoolean("boardFlipped", false); autoSwapSides = settings.getBoolean("autoSwapSides", false); playerNameFlip = settings.getBoolean("playerNameFlip", true); setBoardFlip(!playerName.equals(oldPlayerName)); boolean drawSquareLabels = settings.getBoolean("drawSquareLabels", false); cb.setDrawSquareLabels(drawSquareLabels); cb.oneTouchMoves = settings.getBoolean("oneTouchMoves", false); cb.toggleSelection = getIntSetting("squareSelectType", 0) == 1; cb.highlightLastMove = settings.getBoolean("highlightLastMove", true); cb.setBlindMode(settings.getBoolean("blindMode", false)); mShowThinking = settings.getBoolean("showThinking", false); mShowStats = settings.getBoolean("showStats", true); numPV = settings.getInt("numPV", 1); ctrl.setMultiPVMode(numPV); mWhiteBasedScores = settings.getBoolean("whiteBasedScores", false); maxNumArrows = getIntSetting("thinkingArrows", 2); mShowBookHints = settings.getBoolean("bookHints", false); String engine = settings.getString("engine", "cuckoochess"); int strength = settings.getInt("strength", 1000); setEngineStrength(engine, strength); mPonderMode = settings.getBoolean("ponderMode", false); if (!mPonderMode) { ctrl.stopPonder(); } timeControl = getIntSetting("timeControl", 120000); movesPerSession = getIntSetting("movesPerSession", 60); timeIncrement = getIntSetting("timeIncrement", 0); autoMoveDelay = getIntSetting("autoDelay", 5000); scrollSensitivity = Float.parseFloat(settings.getString("scrollSensitivity", "2")); invertScrollDirection = settings.getBoolean("invertScrollDirection", false); discardVariations = settings.getBoolean("discardVariations", false); Util.setFullScreenMode(this, settings); boolean useWakeLock = settings.getBoolean("wakeLock", false); setWakeLock(useWakeLock); int fontSize = getIntSetting("fontSize", 12); int statusFontSize = fontSize; Configuration config = getResources().getConfiguration(); if (config.orientation == Configuration.ORIENTATION_PORTRAIT) { statusFontSize = Math.min(statusFontSize, 16); } status.setTextSize(statusFontSize); soundEnabled = settings.getBoolean("soundEnabled", false); vibrateEnabled = settings.getBoolean("vibrateEnabled", false); animateMoves = settings.getBoolean("animateMoves", true); autoScrollTitle = settings.getBoolean("autoScrollTitle", true); setTitleScrolling(); //guideShowOnStart = settings.getBoolean("guideShowOnStart", true); bookOptions.filename = settings.getString("bookFile", ""); bookOptions.maxLength = getIntSetting("bookMaxLength", 1000000); bookOptions.preferMainLines = settings.getBoolean("bookPreferMainLines", false); bookOptions.tournamentMode = settings.getBoolean("bookTournamentMode", false); bookOptions.random = (settings.getInt("bookRandom", 500) - 500) * (3.0 / 500); setBookOptions(); File extDir = Environment.getExternalStorageDirectory(); String sep = File.separator; engineOptions.hashMB = getIntSetting("hashMB", 16); engineOptions.unSafeHash = new File(extDir + sep + engineDir + sep + ".unsafehash").exists(); engineOptions.hints = settings.getBoolean("tbHints", false); engineOptions.hintsEdit = settings.getBoolean("tbHintsEdit", false); engineOptions.rootProbe = settings.getBoolean("tbRootProbe", true); engineOptions.engineProbe = settings.getBoolean("tbEngineProbe", true); String gtbPath = settings.getString("gtbPath", "").trim(); if (gtbPath.length() == 0) { gtbPath = extDir.getAbsolutePath() + sep + gtbDefaultDir; } engineOptions.gtbPath = gtbPath; engineOptions.gtbPathNet = settings.getString("gtbPathNet", "").trim(); String rtbPath = settings.getString("rtbPath", "").trim(); if (rtbPath.length() == 0) { rtbPath = extDir.getAbsolutePath() + sep + rtbDefaultDir; } engineOptions.rtbPath = rtbPath; engineOptions.rtbPathNet = settings.getString("rtbPathNet", "").trim(); setEngineOptions(false); setEgtbHints(cb.getSelectedSquare()); updateThinkingInfo(); pgnOptions.view.variations = settings.getBoolean("viewVariations", true); pgnOptions.view.comments = settings.getBoolean("viewComments", true); pgnOptions.view.nag = settings.getBoolean("viewNAG", true); pgnOptions.view.headers = settings.getBoolean("viewHeaders", false); final int oldViewPieceType = pgnOptions.view.pieceType; pgnOptions.view.pieceType = getIntSetting("viewPieceType", PGNOptions.PT_LOCAL); showVariationLine = settings.getBoolean("showVariationLine", false); pgnOptions.imp.variations = settings.getBoolean("importVariations", true); pgnOptions.imp.comments = settings.getBoolean("importComments", true); pgnOptions.imp.nag = settings.getBoolean("importNAG", true); pgnOptions.exp.variations = settings.getBoolean("exportVariations", true); pgnOptions.exp.comments = settings.getBoolean("exportComments", true); pgnOptions.exp.nag = settings.getBoolean("exportNAG", true); pgnOptions.exp.playerAction = settings.getBoolean("exportPlayerAction", false); pgnOptions.exp.clockInfo = settings.getBoolean("exportTime", false); ColorTheme.instance().readColors(settings); cb.setColors(); gameTextListener.clear(); setPieceNames(pgnOptions.view.pieceType); ctrl.prefsChanged(oldViewPieceType != pgnOptions.view.pieceType); // update the typeset in case of a change anyway, cause it could occur // as well in rotation setFigurineNotation(pgnOptions.view.pieceType == PGNOptions.PT_FIGURINE, fontSize); } /** * Change the Pieces into figurine or regular (i.e. letters) display */ private void setFigurineNotation(boolean displayAsFigures, int fontSize) { if (displayAsFigures) { // increase the font cause it has different kerning and looks small float increaseFontSize = fontSize * 1.1f; moveList.setTypeface(figNotation, increaseFontSize); thinking.setTypeface(figNotation); thinking.setTextSize(increaseFontSize); } else { moveList.setTypeface(null, fontSize); thinking.setTypeface(defaultThinkingListTypeFace); thinking.setTextSize(fontSize); } } /** * Enable/disable title bar scrolling. */ private void setTitleScrolling() { TextUtils.TruncateAt where = autoScrollTitle ? TextUtils.TruncateAt.MARQUEE : TextUtils.TruncateAt.END; whiteTitleText.setEllipsize(where); blackTitleText.setEllipsize(where); } @SuppressLint("Wakelock") private synchronized void setWakeLock(boolean enableLock) { if (enableLock) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } else { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } } private void setEngineStrength(String engine, int strength) { if (!storageAvailable()) { if (!"stockfish".equals(engine) && !"cuckoochess".equals(engine)) { engine = "stockfish"; } } ctrl.setEngineStrength(engine, strength); setEngineTitle(engine, strength); } private void setEngineTitle(String engine, int strength) { String eName = ""; if (EngineUtil.isOpenExchangeEngine(engine)) { String engineFileName = new File(engine).getName(); ChessEngineResolver resolver = new ChessEngineResolver(this); List<ChessEngine> engines = resolver.resolveEngines(); for (ChessEngine ce : engines) { if (EngineUtil.openExchangeFileName(ce).equals(engineFileName)) { eName = ce.getName(); break; } } } else if (engine.contains("/")) { int idx = engine.lastIndexOf('/'); eName = engine.substring(idx + 1); } else { eName = getString( engine.equals("cuckoochess") ? R.string.cuckoochess_engine : R.string.stockfish_engine); boolean analysis = (ctrl != null) && ctrl.analysisMode(); if ((strength < 1000) && !analysis) { eName = String.format(Locale.US, "%s: %d%%", eName, strength / 10); } } engineTitleText.setText(eName); } /** * Update center field in second header line. */ public final void updateTimeControlTitle() { /*int[] tmpInfo = ctrl.getTimeLimit(); //StringBuilder sb = new StringBuilder(); int tc = tmpInfo[0]; int mps = tmpInfo[1]; int inc = tmpInfo[2]; /*if (mps > 0) { sb.append(mps); sb.append("/"); } sb.append(timeToString(tc)); if ((inc > 0) || (mps <= 0)) { sb.append("+"); sb.append(tmpInfo[2] / 1000); }*/ //status.setText(sb.toString()); } @Override public void updateEngineTitle() { String engine = settings.getString("engine", "stockfish"); int strength = settings.getInt("strength", 1000); setEngineTitle(engine, strength); } @Override public void updateMaterialDifferenceTitle(Util.MaterialDiff diff) { whiteFigText.setText(diff.white); blackFigText.setText(diff.black); } private void setBookOptions() { BookOptions options = new BookOptions(bookOptions); if (options.filename.length() > 0) { String sep = File.separator; if (!options.filename.startsWith(sep)) { File extDir = Environment.getExternalStorageDirectory(); options.filename = extDir.getAbsolutePath() + sep + bookDir + sep + options.filename; } } ctrl.setBookOptions(options); } private void setEngineOptions(boolean restart) { computeNetEngineID(); ctrl.setEngineOptions(new EngineOptions(engineOptions), restart); Probe.getInstance().setPath(engineOptions.gtbPath, engineOptions.rtbPath, egtbForceReload); egtbForceReload = false; } private void computeNetEngineID() { String id = ""; try { String engine = settings.getString("engine", "stockfish"); if (EngineUtil.isNetEngine(engine)) { String[] lines = Util.readFile(engine); if (lines.length >= 3) { id = lines[1] + ":" + lines[2]; } } } catch (IOException e) { Log.d("Exception", e.toString()); } engineOptions.networkID = id; } private void setEgtbHints(int sq) { if (!engineOptions.hints || (sq < 0)) { cb.setSquareDecorations(null); return; } Probe gtbProbe = Probe.getInstance(); ArrayList<Pair<Integer, ProbeResult>> x = gtbProbe.movePieceProbe(cb.pos, sq); if (x == null) { cb.setSquareDecorations(null); return; } ArrayList<SquareDecoration> sd = new ArrayList<>(); for (Pair<Integer, ProbeResult> p : x) { sd.add(new SquareDecoration(p.first, p.second)); } cb.setSquareDecorations(sd); } private void startEditBoard(String fen) { Intent i = new Intent(MDChess.this, EditBoard.class); i.setAction(fen); startActivityForResult(i, RESULT_EDITBOARD); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case RESULT_SETTINGS: handlePrefsChange(); break; case RESULT_EDITBOARD: if (resultCode == RESULT_OK) { try { String fen = data.getAction(); ctrl.setFENOrPGN(fen); setBoardFlip(false); } catch (ChessParseError e) { Log.d("Exception", e.toString()); } } break; case RESULT_LOAD_PGN: if (resultCode == RESULT_OK) { try { String pgn = data.getAction(); int modeNr = ctrl.getGameMode().getModeNr(); if ((modeNr != GameMode.ANALYSIS) && (modeNr != GameMode.EDIT_GAME)) { newGameMode(); } ctrl.setFENOrPGN(pgn); setBoardFlip(true); } catch (ChessParseError e) { Toast.makeText(getApplicationContext(), getParseErrString(e), Toast.LENGTH_SHORT).show(); } } break; case RESULT_SELECT_SCID: if (resultCode == RESULT_OK) { String pathName = data.getAction(); if (pathName != null) { Editor editor = settings.edit(); editor.putString("currentScidFile", pathName); editor.putInt("currFT", FT_SCID); editor.apply(); Intent i = new Intent(MDChess.this, LoadScid.class); i.setAction("org.mdc.chess.loadScid"); i.putExtra("org.mdc.chess.pathname", pathName); startActivityForResult(i, RESULT_LOAD_PGN); } } break; case RESULT_OI_PGN_LOAD: if (resultCode == RESULT_OK) { String pathName = getFilePathFromUri(data.getData()); if (pathName != null) { loadPGNFromFile(pathName); } } break; case RESULT_OI_PGN_SAVE: if (resultCode == RESULT_OK) { String pathName = getFilePathFromUri(data.getData()); if (pathName != null) { if ((pathName.length() > 0) && !pathName.contains(".")) { pathName += ".pgn"; } savePGNToFile(pathName); } } break; case RESULT_OI_FEN_LOAD: if (resultCode == RESULT_OK) { String pathName = getFilePathFromUri(data.getData()); if (pathName != null) { loadFENFromFile(pathName); } } break; case RESULT_GET_FEN: if (resultCode == RESULT_OK) { String fen = data.getStringExtra(Intent.EXTRA_TEXT); if (fen == null) { String pathName = getFilePathFromUri(data.getData()); loadFENFromFile(pathName); } setFenHelper(fen); } break; case RESULT_LOAD_FEN: if (resultCode == RESULT_OK) { String fen = data.getAction(); setFenHelper(fen); } break; case RESULT_EDITOPTIONS: if (resultCode == RESULT_OK) { @SuppressWarnings("unchecked") Map<String, String> uciOpts = (Map<String, String>) data .getSerializableExtra("org.mdc.chess.ucioptions"); ctrl.setEngineUCIOptions(uciOpts); } break; } } /** * Set new game mode. */ private void newGameMode() { Editor editor = settings.edit(); String gameModeStr = String.format(Locale.US, "%d", GameMode.EDIT_GAME); editor.putString("gameMode", gameModeStr); editor.apply(); gameMode = new GameMode(GameMode.EDIT_GAME); maybeAutoModeOff(gameMode); ctrl.setGameMode(gameMode); } private String getParseErrString(ChessParseError e) { if (e.resourceId == -1) { return e.getMessage(); } else { return getString(e.resourceId); } } private int nameMatchScore(String name, String match) { if (name == null) { return 0; } String lName = name.toLowerCase(Locale.US); String lMatch = match.toLowerCase(Locale.US); if (name.equals(match)) { return 6; } if (lName.equals(lMatch)) { return 5; } if (name.startsWith(match)) { return 4; } if (lName.startsWith(lMatch)) { return 3; } if (name.contains(match)) { return 2; } if (lName.contains(lMatch)) { return 1; } return 0; } private void setBoardFlip() { setBoardFlip(false); } /** * Set a boolean preference setting. */ private void setBooleanPref(boolean value) { Editor editor = settings.edit(); editor.putBoolean("boardFlipped", value); editor.apply(); } private void setBoardFlip(boolean matchPlayerNames) { boolean flipped = boardFlipped; if (playerNameFlip && matchPlayerNames && (ctrl != null)) { final TreeMap<String, String> headers = new TreeMap<>(); ctrl.getHeaders(headers); int whiteMatch = nameMatchScore(headers.get("White"), playerName); int blackMatch = nameMatchScore(headers.get("Black"), playerName); if ((flipped && (whiteMatch > blackMatch)) || (!flipped && (whiteMatch < blackMatch))) { flipped = !flipped; boardFlipped = flipped; setBooleanPref(flipped); } } if (autoSwapSides) { if (gameMode.analysisMode()) { flipped = !cb.pos.whiteMove; } else if (gameMode.playerWhite() && gameMode.playerBlack()) { flipped = !cb.pos.whiteMove; } else { flipped = !gameMode.playerWhite() && (gameMode.playerBlack() || !cb.pos.whiteMove); } // two computers } cb.setFlipped(flipped); } @Override public void setSelection(int sq) { cb.setSelection(cb.highlightLastMove ? sq : -1); cb.userSelectedSquare = false; setEgtbHints(sq); } @Override public void setStatus(GameStatus s) { String str; switch (s.state) { case ALIVE: str = Integer.valueOf(s.moveNr).toString(); if (s.white) { str += ". " + getString(R.string.whites_move); } else { str += "... " + getString(R.string.blacks_move); } if (s.ponder) str += " (" + getString(R.string.ponder) + ")"; if (s.thinking) str += " (" + getString(R.string.thinking) + ")"; if (s.analyzing) str += " (" + getString(R.string.analyzing) + ")"; break; case WHITE_MATE: str = getString(R.string.white_mate); break; case BLACK_MATE: str = getString(R.string.black_mate); break; case WHITE_STALEMATE: case BLACK_STALEMATE: str = getString(R.string.stalemate); break; case DRAW_REP: { str = getString(R.string.draw_rep); if (s.drawInfo.length() > 0) { str = str + " [" + s.drawInfo + "]"; } break; } case DRAW_50: { str = getString(R.string.draw_50); if (s.drawInfo.length() > 0) { str = str + " [" + s.drawInfo + "]"; } break; } case DRAW_NO_MATE: str = getString(R.string.draw_no_mate); break; case DRAW_AGREE: str = getString(R.string.draw_agree); break; case RESIGN_WHITE: str = getString(R.string.resign_white); break; case RESIGN_BLACK: str = getString(R.string.resign_black); break; default: throw new RuntimeException(); } setStatusString(str); } private void setStatusString(String str) { status.setText(str); } @Override public void moveListUpdated() { moveList.setText(gameTextListener.getText()); int currPos = gameTextListener.getCurrPos(); int line = moveList.getLineForOffset(currPos); if (line >= 0) { int y = (line - 1) * moveList.getLineHeight(); moveListScroll.scrollTo(0, y); } } @Override public boolean whiteBasedScores() { return mWhiteBasedScores; } @Override public boolean ponderMode() { return mPonderMode; } @Override public Context getContext() { return this; } @Override public String playerName() { return playerName; } @Override public boolean discardVariations() { return discardVariations; } /** * Report a move made that is a candidate for GUI animation. */ public void setAnimMove(Position sourcePos, Move move, boolean forward) { if (animateMoves && (move != null)) { cb.setAnimMove(sourcePos, move, forward); } } @Override public void setPosition(Position pos, String variantInfo, ArrayList<Move> variantMoves) { variantStr = variantInfo; this.variantMoves = variantMoves; cb.setPosition(pos); setBoardFlip(); updateThinkingInfo(); setEgtbHints(cb.getSelectedSquare()); } @Override public void setThinkingInfo(ThinkingInfo ti) { thinkingStr1 = ti.pvStr; thinkingStr2 = ti.statStr; bookInfoStr = ti.bookInfo; this.pvMoves = ti.pvMoves; this.bookMoves = ti.bookMoves; updateThinkingInfo(); if (ctrl.computerBusy()) { lastComputationMillis = System.currentTimeMillis(); } else { lastComputationMillis = 0; } updateNotification(); } private void updateThinkingInfo() { boolean thinkingEmpty = true; { String s = ""; if (mShowThinking || gameMode.analysisMode()) { s = thinkingStr1; if (s.length() > 0) thinkingEmpty = false; if (mShowStats) { if (!thinkingEmpty) { s += "\n"; } s += thinkingStr2; if (s.length() > 0) thinkingEmpty = false; } } thinking.setText(s, TextView.BufferType.SPANNABLE); } if (mShowBookHints && (bookInfoStr.length() > 0)) { String s = ""; if (!thinkingEmpty) { s += "<br>"; } s += Util.boldStart + getString(R.string.book) + Util.boldStop + bookInfoStr; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { thinking.append(Html.fromHtml(s, Html.FROM_HTML_MODE_LEGACY)); } else { //noinspection deprecation thinking.append(Html.fromHtml(s)); } thinkingEmpty = false; } if (showVariationLine && (variantStr.indexOf(' ') >= 0)) { String s = ""; if (!thinkingEmpty) { s += "<br>"; } s += Util.boldStart + getString(R.string.variation) + Util.boldStop + variantStr; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { thinking.append(Html.fromHtml(s, Html.FROM_HTML_MODE_LEGACY)); } else { //noinspection deprecation thinking.append(Html.fromHtml(s)); } thinkingEmpty = false; } thinking.setVisibility(thinkingEmpty ? View.GONE : View.VISIBLE); List<Move> hints = null; if (mShowThinking || gameMode.analysisMode()) { ArrayList<ArrayList<Move>> pvMovesTmp = pvMoves; if (pvMovesTmp.size() == 1) { hints = pvMovesTmp.get(0); } else if (pvMovesTmp.size() > 1) { hints = new ArrayList<>(); for (ArrayList<Move> pv : pvMovesTmp) { if (!pv.isEmpty()) { hints.add(pv.get(0)); } } } } if ((hints == null) && mShowBookHints) { hints = bookMoves; } if (((hints == null) || hints.isEmpty()) && (variantMoves != null) && variantMoves.size() > 1) { hints = variantMoves; } if ((hints != null) && (hints.size() > maxNumArrows)) { hints = hints.subList(0, maxNumArrows); } cb.setMoveHints(hints); } private void gameDialog() { new MaterialDialog.Builder(this).title(R.string.option_new_game).content(R.string.start_new_game) .positiveText(R.string.black).negativeText(R.string.white).neutralText(R.string.yes) .onPositive(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { startNewGame(1); } }).onNeutral(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { startNewGame(2); } }).onNegative(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { startNewGame(0); } }).show(); } private void resignDialog() { new MaterialDialog.Builder(this).title(R.string.option_resign_game).positiveText(R.string.yes) .negativeText(R.string.no).neutralText(R.string.option_new_game) .onPositive(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { if (ctrl.humansTurn()) { ctrl.resignGame(); } } }).onNeutral(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { gameDialog(); } }).onNegative(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { } }).show(); } private void startNewGame(int type) { if (type != 2) { int gameModeType = (type == 0) ? GameMode.PLAYER_WHITE : GameMode.PLAYER_BLACK; Editor editor = settings.edit(); String gameModeStr = String.format(Locale.US, "%d", gameModeType); editor.putString("gameMode", gameModeStr); editor.apply(); gameMode = new GameMode(gameModeType); } // savePGNToFile(".autosave.pgn", true); TimeControlData tcData = new TimeControlData(); tcData.setTimeControl(timeControl, movesPerSession, timeIncrement); ctrl.newGame(gameMode, tcData); ctrl.startGame(); setBoardFlip(true); updateEngineTitle(); } private void promoteDialog() { final CharSequence[] items = { getString(R.string.queen), getString(R.string.rook), getString(R.string.bishop), getString(R.string.knight) }; new MaterialDialog.Builder(this).title(R.string.promote_pawn_to).items(items) .itemsCallback(new MaterialDialog.ListCallback() { @Override public void onSelection(MaterialDialog dialog, View view, int which, CharSequence text) { ctrl.reportPromotePiece(which); } }).show(); } private void clipBoardDialog() { final int COPY_GAME = 0; final int COPY_POSITION = 1; final int PASTE = 2; setAutoMode(AutoMode.OFF); List<CharSequence> lst = new ArrayList<>(); List<Integer> actions = new ArrayList<>(); lst.add(getString(R.string.copy_game)); actions.add(COPY_GAME); lst.add(getString(R.string.copy_position)); actions.add(COPY_POSITION); lst.add(getString(R.string.paste)); actions.add(PASTE); final List<Integer> finalActions = actions; new MaterialDialog.Builder(this).title(R.string.tools_menu).items(lst) .itemsCallback(new MaterialDialog.ListCallback() { @Override public void onSelection(MaterialDialog dialog, View view, int which, CharSequence text) { switch (finalActions.get(which)) { case COPY_GAME: { String pgn = ctrl.getPGN(); ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); clipboard.setPrimaryClip(new ClipData("MD Chess game", new String[] { "application/x-chess-pgn", ClipDescription.MIMETYPE_TEXT_PLAIN }, new ClipData.Item(pgn))); break; } case COPY_POSITION: { String fen = ctrl.getFEN() + "\n"; ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); clipboard.setPrimaryClip(new ClipData(fen, new String[] { "application/x-chess-fen", ClipDescription.MIMETYPE_TEXT_PLAIN }, new ClipData.Item(fen))); break; } case PASTE: { ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); if (clipboard.hasPrimaryClip()) { ClipData clip = clipboard.getPrimaryClip(); StringBuilder fenPgn = new StringBuilder(); for (int i = 0; i < clip.getItemCount(); i++) { fenPgn.append(clip.getItemAt(i).coerceToText(getApplicationContext())); } try { ctrl.setFENOrPGN(fenPgn.toString()); setBoardFlip(true); } catch (ChessParseError e) { Toast.makeText(getApplicationContext(), getParseErrString(e), Toast.LENGTH_SHORT).show(); } } break; } } } }).show(); } private void boardMenuDialog() { final int CLIPBOARD = 0; final int FILEMENU = 1; final int SHARE = 2; final int GET_FEN = 3; setAutoMode(AutoMode.OFF); List<CharSequence> lst = new ArrayList<>(); List<Integer> actions = new ArrayList<>(); lst.add(getString(R.string.clipboard)); actions.add(CLIPBOARD); if (storageAvailable()) { lst.add(getString(R.string.option_file)); actions.add(FILEMENU); } lst.add(getString(R.string.share)); actions.add(SHARE); if (hasFenProvider(getPackageManager())) { lst.add(getString(R.string.get_fen)); actions.add(GET_FEN); } final List<Integer> finalActions = actions; new MaterialDialog.Builder(this).title(R.string.tools_menu).items(lst) .itemsCallback(new MaterialDialog.ListCallback() { @Override public void onSelection(MaterialDialog dialog, View view, int which, CharSequence text) { switch (finalActions.get(which)) { case CLIPBOARD: { clipBoardDialog(); break; } case FILEMENU: { fileMenuDialog(); break; } case SHARE: { shareGame(); break; } case GET_FEN: getFen(); break; } } }).show(); } private void shareGame() { Intent i = new Intent(Intent.ACTION_SEND); //noinspection deprecation i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); i.setType("text/plain"); i.putExtra(Intent.EXTRA_TEXT, ctrl.getPGN()); try { startActivity(Intent.createChooser(i, getString(R.string.share_pgn_game))); } catch (ActivityNotFoundException ex) { // Ignore } } private void fileMenuDialog() { final int LOAD_LAST_FILE = 0; final int LOAD_GAME = 1; final int LOAD_POS = 2; final int LOAD_SCID_GAME = 3; final int SAVE_GAME = 4; setAutoMode(AutoMode.OFF); List<CharSequence> lst = new ArrayList<>(); List<Integer> actions = new ArrayList<>(); if (currFileType() != FT_NONE) { lst.add(getString(R.string.load_last_file)); actions.add(LOAD_LAST_FILE); } lst.add(getString(R.string.load_game)); actions.add(LOAD_GAME); lst.add(getString(R.string.load_position)); actions.add(LOAD_POS); if (hasScidProvider()) { lst.add(getString(R.string.load_scid_game)); actions.add(LOAD_SCID_GAME); } lst.add(getString(R.string.save_game)); actions.add(SAVE_GAME); final List<Integer> finalActions = actions; new MaterialDialog.Builder(this).title(R.string.load_save_menu).items(lst) .itemsCallback(new MaterialDialog.ListCallback() { @Override public void onSelection(MaterialDialog dialog, View view, int which, CharSequence text) { switch (finalActions.get(which)) { case LOAD_LAST_FILE: loadLastFile(); break; case LOAD_GAME: selectFile(R.string.select_pgn_file, R.string.pgn_load, "currentPGNFile", pgnDir, SELECT_PGN_FILE_DIALOG, RESULT_OI_PGN_LOAD); break; case SAVE_GAME: selectFile(R.string.select_pgn_file_save, R.string.pgn_save, "currentPGNFile", pgnDir, SELECT_PGN_FILE_SAVE_DIALOG, RESULT_OI_PGN_SAVE); break; case LOAD_POS: selectFile(R.string.select_fen_file, R.string.pgn_load, "currentFENFile", fenDir, SELECT_FEN_FILE_DIALOG, RESULT_OI_FEN_LOAD); break; case LOAD_SCID_GAME: selectScidFile(); break; } } }).show(); } /** * Open dialog to select a game/position from the last used file. */ private void loadLastFile() { String path = currPathName(); if (path.length() == 0) { return; } setAutoMode(AutoMode.OFF); switch (currFileType()) { case FT_PGN: loadPGNFromFile(path); break; case FT_SCID: { Intent data = new Intent(path); onActivityResult(RESULT_SELECT_SCID, RESULT_OK, data); break; } case FT_FEN: loadFENFromFile(path); break; } } private void aboutDialog() { //AlertDialog.Builder builder = new AlertDialog.Builder(this); String title = getString(R.string.app_name); WebView wv = new WebView(this); //builder.setView(wv); InputStream is = getResources().openRawResource(R.raw.about); String data = Util.readFromStream(is); if (data == null) { data = ""; } try { is.close(); } catch (IOException e1) { Log.d("Exception", e1.toString()); } wv.loadDataWithBaseURL(null, data, "text/html", "utf-8", null); try { PackageInfo pi = getPackageManager().getPackageInfo("org.mdc.chess", 0); title += " " + pi.versionName; } catch (NameNotFoundException e) { Log.d("Exception", e.toString()); } new MaterialDialog.Builder(this).title(title).customView(wv, true).show(); } private void selectBookDialog() { String[] fileNames = findFilesInDirectory(bookDir, new FileNameFilter() { @Override public boolean accept(String filename) { int dotIdx = filename.lastIndexOf("."); if (dotIdx < 0) { return false; } String ext = filename.substring(dotIdx + 1); return (ext.equals("ctg") || ext.equals("bin")); } }); final int numFiles = fileNames.length; CharSequence[] items = new CharSequence[numFiles + 1]; System.arraycopy(fileNames, 0, items, 0, numFiles); items[numFiles] = getString(R.string.internal_book); final CharSequence[] finalItems = items; int defaultItem = numFiles; for (int i = 0; i < numFiles; i++) { if (bookOptions.filename.equals(items[i])) { defaultItem = i; break; } } new MaterialDialog.Builder(this).title(R.string.select_opening_book_file).items(items) .itemsCallbackSingleChoice(defaultItem, new MaterialDialog.ListCallbackSingleChoice() { @Override public boolean onSelection(MaterialDialog dialog, View view, int which, CharSequence text) { /** * If you use alwaysCallSingleChoiceCallback(), which is * discussed below, * returning false here won't allow the newly selected radio * button to actually be selected. **/ Editor editor = settings.edit(); String bookFile = ""; if (which < numFiles) { bookFile = finalItems[which].toString(); } editor.putString("bookFile", bookFile); editor.apply(); bookOptions.filename = bookFile; setBookOptions(); dialog.dismiss(); return true; } }).show(); } private void selectEngineDialog(final boolean abortOnCancel) { final ArrayList<String> items = new ArrayList<>(); final ArrayList<String> ids = new ArrayList<>(); ids.add("stockfish"); items.add(getString(R.string.stockfish_engine)); ids.add("cuckoochess"); items.add(getString(R.string.cuckoochess_engine)); if (storageAvailable()) { final String sep = File.separator; final String base = Environment.getExternalStorageDirectory() + sep + engineDir + sep; { ChessEngineResolver resolver = new ChessEngineResolver(this); List<ChessEngine> engines = resolver.resolveEngines(); ArrayList<Pair<String, String>> oexEngines = new ArrayList<>(); for (ChessEngine engine : engines) { if ((engine.getName() != null) && (engine.getFileName() != null) && (engine.getPackageName() != null)) { oexEngines.add(new Pair<>(EngineUtil.openExchangeFileName(engine), engine.getName())); } } Collections.sort(oexEngines, new Comparator<Pair<String, String>>() { @Override public int compare(Pair<String, String> lhs, Pair<String, String> rhs) { return lhs.second.compareTo(rhs.second); } }); for (Pair<String, String> eng : oexEngines) { ids.add(base + EngineUtil.openExchangeDir + sep + eng.first); items.add(eng.second); } } String[] fileNames = findFilesInDirectory(engineDir, new FileNameFilter() { @Override public boolean accept(String filename) { return !reservedEngineName(filename); } }); for (String file : fileNames) { ids.add(base + file); items.add(file); } } String currEngine = ctrl.getEngine(); int defaultItem = 0; final int nEngines = items.size(); for (int i = 0; i < nEngines; i++) { if (ids.get(i).equals(currEngine)) { defaultItem = i; break; } } new MaterialDialog.Builder(this).title(R.string.select_chess_engine).items(items) .itemsCallbackSingleChoice(defaultItem, new MaterialDialog.ListCallbackSingleChoice() { @Override public boolean onSelection(MaterialDialog dialog, View view, int which, CharSequence text) { /** * If you use alwaysCallSingleChoiceCallback(), which is * discussed below, * returning false here won't allow the newly selected radio * button to actually be selected. **/ Editor editor = settings.edit(); String engine = ids.get(which); editor.putString("engine", engine); editor.apply(); dialog.dismiss(); int strength = settings.getInt("strength", 1000); setEngineOptions(false); setEngineStrength(engine, strength); return true; } }).cancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialogInterface) { if (!abortOnCancel) { manageEnginesDialog(); } } }).show(); } private void selectPgnFileDialog() { selectFileDialog(pgnDir, R.string.select_pgn_file, R.string.no_pgn_files, "currentPGNFile", new Loader() { @Override public void load(String pathName) { loadPGNFromFile(pathName); } }); } private void selectFenFileDialog() { selectFileDialog(fenDir, R.string.select_fen_file, R.string.no_fen_files, "currentFENFile", new Loader() { @Override public void load(String pathName) { loadFENFromFile(pathName); } }); } private void selectFileDialog(final String defaultDir, int selectFileMsg, int noFilesMsg, String settingsName, final Loader loader) { setAutoMode(AutoMode.OFF); final String[] files = findFilesInDirectory(defaultDir, null); final int numFiles = files.length; if (numFiles == 0) { new MaterialDialog.Builder(this).title(R.string.app_name).content(noFilesMsg); } else { int defaultItem = 0; String currentFile = settings.getString(settingsName, ""); currentFile = new File(currentFile).getName(); for (int i = 0; i < numFiles; i++) { if (currentFile.equals(files[i])) { defaultItem = i; break; } } new MaterialDialog.Builder(this).title(selectFileMsg).items(files) .itemsCallbackSingleChoice(defaultItem, new MaterialDialog.ListCallbackSingleChoice() { @Override public boolean onSelection(MaterialDialog dialog, View view, int which, CharSequence text) { /** * If you use alwaysCallSingleChoiceCallback(), which is * discussed below, * returning false here won't allow the newly selected radio * button to actually be selected. **/ String sep = File.separator; String fn = files[which]; String pathName = Environment.getExternalStorageDirectory() + sep + defaultDir + sep + fn; loader.load(pathName); return true; } }).show(); } } private void selectPgnFileSaveDialog() { setAutoMode(AutoMode.OFF); final String[] fileNames = findFilesInDirectory(pgnDir, null); final int numFiles = fileNames.length; int defaultItem = 0; String currentPGNFile = settings.getString("currentPGNFile", ""); currentPGNFile = new File(currentPGNFile).getName(); for (int i = 0; i < numFiles; i++) { if (currentPGNFile.equals(fileNames[i])) { defaultItem = i; break; } } CharSequence[] items = new CharSequence[numFiles + 1]; System.arraycopy(fileNames, 0, items, 0, numFiles); items[numFiles] = getString(R.string.new_file); new MaterialDialog.Builder(this).title(R.string.select_pgn_file_save).items(items) .itemsCallbackSingleChoice(defaultItem, new MaterialDialog.ListCallbackSingleChoice() { @Override public boolean onSelection(MaterialDialog dialog, View view, int which, CharSequence text) { /** * If you use alwaysCallSingleChoiceCallback(), which is * discussed below, * returning false here won't allow the newly selected radio * button to actually be selected. **/ String pgnFile; if (which >= numFiles) { dialog.dismiss(); selectPgnSaveNewFileDialog(); } else { dialog.dismiss(); pgnFile = fileNames[which]; String sep = File.separator; String pathName = Environment.getExternalStorageDirectory() + sep + pgnDir + sep + pgnFile; savePGNToFile(pathName); } return true; } }).show(); } private void selectPgnSaveNewFileDialog() { setAutoMode(AutoMode.OFF); View content = View.inflate(this, R.layout.create_pgn_file, null); final EditText fileNameView = (EditText) content.findViewById(R.id.create_pgn_filename); final TextInputLayout fileNameWrapper = (TextInputLayout) content .findViewById(R.id.create_pgn_filename_wrapper); fileNameWrapper.setHint(content.getResources().getString(R.string.filename)); fileNameView.setText(""); final Runnable savePGN = new Runnable() { public void run() { String pgnFile = fileNameView.getText().toString(); if ((pgnFile.length() > 0) && !pgnFile.contains(".")) { pgnFile += ".pgn"; } String sep = File.separator; String pathName = Environment.getExternalStorageDirectory() + sep + pgnDir + sep + pgnFile; savePGNToFile(pathName); } }; new MaterialDialog.Builder(this).title(R.string.select_pgn_file_save).customView(content, true) .positiveText(android.R.string.ok).negativeText(R.string.cancel) .onPositive(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { savePGN.run(); } }).onNegative(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { } }).show(); fileNameView.setOnKeyListener(new OnKeyListener() { public boolean onKey(View v, int keyCode, KeyEvent event) { if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { savePGN.run(); return true; } return false; } }); } private void setColorThemeDialog() { String[] themes = new String[ColorTheme.themeNames.length]; for (int i = 0; i < themes.length; i++) { themes[i] = getString(ColorTheme.themeNames[i]); } new MaterialDialog.Builder(this).title(R.string.select_color_theme).items(themes) .itemsCallbackSingleChoice(-1, new MaterialDialog.ListCallbackSingleChoice() { @Override public boolean onSelection(MaterialDialog dialog, View view, int which, CharSequence text) { /** * If you use alwaysCallSingleChoiceCallback(), which is discussed below, * returning false here won't allow the newly selected radio button to * actually be selected. **/ ColorTheme.instance().setTheme(settings, which); cb.setColors(); gameTextListener.clear(); ctrl.prefsChanged(false); dialog.dismiss(); return true; } }).show(); } private void gameModeDialog() { final CharSequence[] items = { getString(R.string.analysis_mode), getString(R.string.edit_replay_game), getString(R.string.play_white), getString(R.string.play_black), getString(R.string.two_players), getString(R.string.comp_vs_comp) }; new MaterialDialog.Builder(this).title(R.string.select_game_mode).items(items) .itemsCallbackSingleChoice(-1, new MaterialDialog.ListCallbackSingleChoice() { @Override public boolean onSelection(MaterialDialog dialog, View view, int which, CharSequence text) { /** * If you use alwaysCallSingleChoiceCallback(), which is discussed below, * returning false here won't allow the newly selected radio button to * actually be selected. **/ int gameModeType = -1; /* only flip site in case the player was specified resp. changed */ boolean flipSite = false; switch (which) { case 0: gameModeType = GameMode.ANALYSIS; break; case 1: gameModeType = GameMode.EDIT_GAME; break; case 2: gameModeType = GameMode.PLAYER_WHITE; flipSite = true; break; case 3: gameModeType = GameMode.PLAYER_BLACK; flipSite = true; break; case 4: gameModeType = GameMode.TWO_PLAYERS; break; case 5: gameModeType = GameMode.TWO_COMPUTERS; break; default: break; } if (gameModeType >= 0) { Editor editor = settings.edit(); String gameModeStr = String.format(Locale.US, "%d", gameModeType); editor.putString("gameMode", gameModeStr); editor.apply(); gameMode = new GameMode(gameModeType); maybeAutoModeOff(gameMode); ctrl.setGameMode(gameMode); setBoardFlip(flipSite); } return true; } }).show(); } private void moveListMenuDialog() { final int EDIT_HEADERS = 0; final int EDIT_COMMENTS = 1; final int REMOVE_SUBTREE = 2; final int MOVE_VAR_UP = 3; final int MOVE_VAR_DOWN = 4; final int ADD_NULL_MOVE = 5; setAutoMode(AutoMode.OFF); List<CharSequence> lst = new ArrayList<>(); List<Integer> actions = new ArrayList<>(); lst.add(getString(R.string.edit_headers)); actions.add(EDIT_HEADERS); if (ctrl.humansTurn()) { lst.add(getString(R.string.edit_comments)); actions.add(EDIT_COMMENTS); } lst.add(getString(R.string.truncate_gametree)); actions.add(REMOVE_SUBTREE); if (ctrl.canMoveVariationUp()) { lst.add(getString(R.string.move_var_up)); actions.add(MOVE_VAR_UP); } if (ctrl.canMoveVariationDown()) { lst.add(getString(R.string.move_var_down)); actions.add(MOVE_VAR_DOWN); } boolean allowNullMove = (gameMode.analysisMode() || (gameMode.playerWhite() && gameMode.playerBlack() && !gameMode.clocksActive())) && !ctrl.inCheck(); if (allowNullMove) { lst.add(getString(R.string.add_null_move)); actions.add(ADD_NULL_MOVE); } final List<Integer> finalActions = actions; new MaterialDialog.Builder(this).title(R.string.edit_game).items(lst) .itemsCallback(new MaterialDialog.ListCallback() { @Override public void onSelection(MaterialDialog dialog, View view, int which, CharSequence text) { switch (finalActions.get(which)) { case EDIT_HEADERS: { final TreeMap<String, String> headers = new TreeMap<>(); ctrl.getHeaders(headers); View content = View.inflate(MDChess.this, R.layout.edit_headers, null); final TextView event, site, date, round, white, black; event = (TextView) content.findViewById(R.id.ed_header_event); site = (TextView) content.findViewById(R.id.ed_header_site); date = (TextView) content.findViewById(R.id.ed_header_date); round = (TextView) content.findViewById(R.id.ed_header_round); white = (TextView) content.findViewById(R.id.ed_header_white); black = (TextView) content.findViewById(R.id.ed_header_black); event.setText(headers.get("Event")); site.setText(headers.get("Site")); date.setText(headers.get("Date")); round.setText(headers.get("Round")); white.setText(headers.get("White")); black.setText(headers.get("Black")); new MaterialDialog.Builder(MDChess.this).title(R.string.edit_headers) .customView(content, true).positiveText(android.R.string.ok) .negativeText(R.string.cancel) .onPositive(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { headers.put("Event", event.getText().toString().trim()); headers.put("Site", site.getText().toString().trim()); headers.put("Date", date.getText().toString().trim()); headers.put("Round", round.getText().toString().trim()); headers.put("White", white.getText().toString().trim()); headers.put("Black", black.getText().toString().trim()); ctrl.setHeaders(headers); setBoardFlip(true); } }).onNegative(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { } }).show(); break; } case EDIT_COMMENTS: { View content = View.inflate(MDChess.this, R.layout.edit_comments, null); MDChessController.CommentInfo commInfo = ctrl.getComments(); final TextView preComment, moveView, nag, postComment; preComment = (TextView) content.findViewById(R.id.ed_comments_pre); final TextInputLayout preCommentWrapper = (TextInputLayout) content .findViewById(R.id.ed_comments_pre_wrapper); preCommentWrapper.setHint(content.getResources().getString(R.string.comment_before)); moveView = (TextView) content.findViewById(R.id.ed_comments_move); nag = (TextView) content.findViewById(R.id.ed_comments_nag); postComment = (TextView) content.findViewById(R.id.ed_comments_post); final TextInputLayout postCommentWrapper = (TextInputLayout) content .findViewById(R.id.ed_comments_post_wrapper); postCommentWrapper.setHint(content.getResources().getString(R.string.comment_after)); preComment.setText(commInfo.preComment); postComment.setText(commInfo.postComment); moveView.setText(commInfo.move); String nagStr = Node.nagStr(commInfo.nag).trim(); if ((nagStr.length() == 0) && (commInfo.nag > 0)) { nagStr = String.format(Locale.US, "%d", commInfo.nag); } nag.setText(nagStr); new MaterialDialog.Builder(MDChess.this).title(R.string.edit_comments) .customView(content, true).positiveText(android.R.string.ok) .negativeText(R.string.cancel) .onPositive(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { String pre = preComment.getText().toString().trim(); String post = postComment.getText().toString().trim(); int nagVal = Node.strToNag(nag.getText().toString()); MDChessController.CommentInfo commInfo = new MDChessController.CommentInfo(); commInfo.preComment = pre; commInfo.postComment = post; commInfo.nag = nagVal; ctrl.setComments(commInfo); } }).onNegative(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { } }).show(); break; } case REMOVE_SUBTREE: ctrl.removeSubTree(); break; case MOVE_VAR_UP: ctrl.moveVariation(-1); break; case MOVE_VAR_DOWN: ctrl.moveVariation(1); break; case ADD_NULL_MOVE: ctrl.makeHumanNullMove(); break; } } }).show(); } private void thinkingMenuDialog() { final int ADD_ANALYSIS = 0; final int MULTIPV_DEC = 1; final int MULTIPV_INC = 2; final int HIDE_STATISTICS = 3; final int SHOW_STATISTICS = 4; List<CharSequence> lst = new ArrayList<>(); List<Integer> actions = new ArrayList<>(); lst.add(getString(R.string.add_analysis)); actions.add(ADD_ANALYSIS); int numPV = this.numPV; if (gameMode.analysisMode()) { int maxPV = ctrl.maxPV(); numPV = Math.min(numPV, maxPV); numPV = Math.max(numPV, 1); if (numPV > 1) { lst.add(getString(R.string.fewer_variations)); actions.add(MULTIPV_DEC); } if (numPV < maxPV) { lst.add(getString(R.string.more_variations)); actions.add(MULTIPV_INC); } } final int numPVF = numPV; if (thinkingStr1.length() > 0) { if (mShowStats) { lst.add(getString(R.string.hide_statistics)); actions.add(HIDE_STATISTICS); } else { lst.add(getString(R.string.show_statistics)); actions.add(SHOW_STATISTICS); } } final List<Integer> finalActions = actions; new MaterialDialog.Builder(this).title(R.string.analysis).items(lst) .itemsCallback(new MaterialDialog.ListCallback() { @Override public void onSelection(MaterialDialog dialog, View view, int which, CharSequence text) { switch (finalActions.get(which)) { case ADD_ANALYSIS: { ArrayList<ArrayList<Move>> pvMovesTmp = pvMoves; String[] pvStrs = thinkingStr1.split("\n"); for (int i = 0; i < pvMovesTmp.size(); i++) { ArrayList<Move> pv = pvMovesTmp.get(i); StringBuilder preComment = new StringBuilder(); if (i < pvStrs.length) { String[] tmp = pvStrs[i].split(" "); for (int j = 0; j < 2; j++) { if (j < tmp.length) { if (j > 0) preComment.append(' '); preComment.append(tmp[j]); } } if (preComment.length() > 0) preComment.append(':'); } boolean updateDefault = (i == 0); ctrl.addVariation(preComment.toString(), pv, updateDefault); } break; } case MULTIPV_DEC: setMultiPVMode(numPVF - 1); break; case MULTIPV_INC: setMultiPVMode(numPVF + 1); break; case HIDE_STATISTICS: case SHOW_STATISTICS: { mShowStats = finalActions.get(which) == SHOW_STATISTICS; Editor editor = settings.edit(); editor.putBoolean("showStats", mShowStats); editor.apply(); updateThinkingInfo(); break; } } } }).show(); } private void setMultiPVMode(int nPV) { numPV = nPV; Editor editor = settings.edit(); editor.putInt("numPV", numPV); editor.apply(); ctrl.setMultiPVMode(numPV); } private void goBackMenuDialog() { final int GOTO_START_GAME = 0; final int GOTO_START_VAR = 1; final int GOTO_PREV_VAR = 2; final int LOAD_PREV_GAME = 3; final int AUTO_BACKWARD = 4; setAutoMode(AutoMode.OFF); List<CharSequence> lst = new ArrayList<>(); List<Integer> actions = new ArrayList<>(); lst.add(getString(R.string.goto_start_game)); actions.add(GOTO_START_GAME); lst.add(getString(R.string.goto_start_variation)); actions.add(GOTO_START_VAR); if (ctrl.currVariation() > 0) { lst.add(getString(R.string.goto_prev_variation)); actions.add(GOTO_PREV_VAR); } final int currFT = currFileType(); final String currPathName = currPathName(); if ((currFT != FT_NONE) && !gameMode.clocksActive()) { lst.add(getString(R.string.load_prev_game)); actions.add(LOAD_PREV_GAME); } if (!gameMode.clocksActive()) { lst.add(getString(R.string.auto_backward)); actions.add(AUTO_BACKWARD); } final List<Integer> finalActions = actions; new MaterialDialog.Builder(this).title(R.string.go_back).items(lst) .itemsCallback(new MaterialDialog.ListCallback() { @Override public void onSelection(MaterialDialog dialog, View view, int which, CharSequence text) { switch (finalActions.get(which)) { case GOTO_START_GAME: ctrl.gotoMove(0); break; case GOTO_START_VAR: ctrl.gotoStartOfVariation(); break; case GOTO_PREV_VAR: ctrl.changeVariation(-1); break; case LOAD_PREV_GAME: Intent i; if (currFT == FT_PGN) { i = new Intent(MDChess.this, EditPGNLoad.class); i.setAction("org.mdc.chess.loadFilePrevGame"); i.putExtra("org.mdc.chess.pathname", currPathName); startActivityForResult(i, RESULT_LOAD_PGN); } else if (currFT == FT_SCID) { i = new Intent(MDChess.this, LoadScid.class); i.setAction("org.mdc.chess.loadScidPrevGame"); i.putExtra("org.mdc.chess.pathname", currPathName); startActivityForResult(i, RESULT_LOAD_PGN); } else if (currFT == FT_FEN) { i = new Intent(MDChess.this, LoadFEN.class); i.setAction("org.mdc.chess.loadPrevFen"); i.putExtra("org.mdc.chess.pathname", currPathName); startActivityForResult(i, RESULT_LOAD_FEN); } break; case AUTO_BACKWARD: setAutoMode(AutoMode.BACKWARD); break; } } }).show(); } private void goForwardMenuDialog() { final int GOTO_END_VAR = 0; final int GOTO_NEXT_VAR = 1; final int LOAD_NEXT_GAME = 2; final int AUTO_FORWARD = 3; setAutoMode(AutoMode.OFF); List<CharSequence> lst = new ArrayList<>(); List<Integer> actions = new ArrayList<>(); lst.add(getString(R.string.goto_end_variation)); actions.add(GOTO_END_VAR); if (ctrl.currVariation() < ctrl.numVariations() - 1) { lst.add(getString(R.string.goto_next_variation)); actions.add(GOTO_NEXT_VAR); } final int currFT = currFileType(); final String currPathName = currPathName(); if ((currFT != FT_NONE) && !gameMode.clocksActive()) { lst.add(getString(R.string.load_next_game)); actions.add(LOAD_NEXT_GAME); } if (!gameMode.clocksActive()) { lst.add(getString(R.string.auto_forward)); actions.add(AUTO_FORWARD); } final List<Integer> finalActions = actions; new MaterialDialog.Builder(this).title(R.string.go_forward).items(lst) .itemsCallback(new MaterialDialog.ListCallback() { @Override public void onSelection(MaterialDialog dialog, View view, int which, CharSequence text) { switch (finalActions.get(which)) { case GOTO_END_VAR: ctrl.gotoMove(Integer.MAX_VALUE); break; case GOTO_NEXT_VAR: ctrl.changeVariation(1); break; case LOAD_NEXT_GAME: Intent i; if (currFT == FT_PGN) { i = new Intent(MDChess.this, EditPGNLoad.class); i.setAction("org.mdc.chess.loadFileNextGame"); i.putExtra("org.mdc.chess.pathname", currPathName); startActivityForResult(i, RESULT_LOAD_PGN); } else if (currFT == FT_SCID) { i = new Intent(MDChess.this, LoadScid.class); i.setAction("org.mdc.chess.loadScidNextGame"); i.putExtra("org.mdc.chess.pathname", currPathName); startActivityForResult(i, RESULT_LOAD_PGN); } else if (currFT == FT_FEN) { i = new Intent(MDChess.this, LoadFEN.class); i.setAction("org.mdc.chess.loadNextFen"); i.putExtra("org.mdc.chess.pathname", currPathName); startActivityForResult(i, RESULT_LOAD_FEN); } break; case AUTO_FORWARD: setAutoMode(AutoMode.FORWARD); break; } } }).show(); } private void manageEnginesDialog() { final int SELECT_ENGINE = 0; final int SET_ENGINE_OPTIONS = 1; final int CONFIG_NET_ENGINE = 2; List<CharSequence> lst = new ArrayList<>(); List<Integer> actions = new ArrayList<>(); lst.add(getString(R.string.select_engine)); actions.add(SELECT_ENGINE); if (canSetEngineOptions()) { lst.add(getString(R.string.set_engine_options)); actions.add(SET_ENGINE_OPTIONS); } lst.add(getString(R.string.configure_network_engine)); actions.add(CONFIG_NET_ENGINE); final List<Integer> finalActions = actions; new MaterialDialog.Builder(this).title(R.string.option_manage_engines).items(lst) .itemsCallback(new MaterialDialog.ListCallback() { @Override public void onSelection(MaterialDialog dialog, View view, int which, CharSequence text) { switch (finalActions.get(which)) { case SELECT_ENGINE: selectEngineDialog(false); break; case SET_ENGINE_OPTIONS: setEngineOptions(); break; case CONFIG_NET_ENGINE: networkEngineDialog(); break; } } }).show(); } /** * Return true if engine UCI options can be set now. */ private boolean canSetEngineOptions() { if (!storageAvailable()) { return false; } UCIOptions uciOpts = ctrl.getUCIOptions(); if (uciOpts == null) { return false; } for (String name : uciOpts.getOptionNames()) { if (uciOpts.getOption(name).visible) { return true; } } return false; } /** * Start activity to set engine options. */ private void setEngineOptions() { Intent i = new Intent(MDChess.this, EditOptions.class); UCIOptions uciOpts = ctrl.getUCIOptions(); if (uciOpts != null) { i.putExtra("org.mdc.chess.ucioptions", uciOpts); i.putExtra("org.mdc.chess.enginename", engineTitleText.getText()); startActivityForResult(i, RESULT_EDITOPTIONS); } } private void networkEngineDialog() { String[] fileNames = findFilesInDirectory(engineDir, new FileNameFilter() { @Override public boolean accept(String filename) { return !reservedEngineName(filename) && EngineUtil.isNetEngine(filename); } }); final int numFiles = fileNames.length; final int numItems = numFiles + 1; final String[] items = new String[numItems]; final String[] ids = new String[numItems]; int idx = 0; String sep = File.separator; String base = Environment.getExternalStorageDirectory() + sep + engineDir + sep; for (String fileName : fileNames) { ids[idx] = base + fileName; items[idx] = fileName; idx++; } ids[idx] = ""; items[idx] = getString(R.string.new_engine); //idx++; String currEngine = ctrl.getEngine(); int defaultItem = 0; for (int i = 0; i < numItems; i++) { if (ids[i].equals(currEngine)) { defaultItem = i; break; } } new MaterialDialog.Builder(this).title(R.string.configure_network_engine).items(items) .itemsCallbackSingleChoice(defaultItem, new MaterialDialog.ListCallbackSingleChoice() { @Override public boolean onSelection(MaterialDialog dialog, View view, int which, CharSequence text) { /** * If you use alwaysCallSingleChoiceCallback(), which is * discussed below, * returning false here won't allow the newly selected radio * button to actually be selected. **/ if (which == numItems - 1) { newNetworkEngineDialog(); } else { networkEngineToConfig = ids[which]; networkEngineConfigDialog(); } return true; } }).negativeText(R.string.cancel).onNegative(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { manageEnginesDialog(); } }).show(); } // Ask for name of new network engine private void newNetworkEngineDialog() { View content = View.inflate(this, R.layout.create_network_engine, null); final EditText engineNameView = (EditText) content.findViewById(R.id.create_network_engine); final TextInputLayout engineNameWrapper = (TextInputLayout) content .findViewById(R.id.create_network_engine_wrapper); engineNameWrapper.setHint(content.getResources().getString(R.string.engine_name)); engineNameView.setText(""); final Runnable createEngine = new Runnable() { public void run() { String engineName = engineNameView.getText().toString(); String sep = File.separator; String pathName = Environment.getExternalStorageDirectory() + sep + engineDir + sep + engineName; File file = new File(pathName); boolean nameOk = true; int errMsg = -1; if (engineName.contains("/")) { nameOk = false; errMsg = R.string.slash_not_allowed; } else if (reservedEngineName(engineName) || file.exists()) { nameOk = false; errMsg = R.string.engine_name_in_use; } if (!nameOk) { Toast.makeText(getApplicationContext(), errMsg, Toast.LENGTH_LONG).show(); networkEngineDialog(); return; } networkEngineToConfig = pathName; networkEngineConfigDialog(); } }; new MaterialDialog.Builder(this).title(R.string.create_network_engine).customView(content, true) .positiveText(android.R.string.ok).negativeText(R.string.cancel) .onPositive(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { createEngine.run(); } }).onNegative(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { networkEngineDialog(); } }).show(); engineNameView.setOnKeyListener(new OnKeyListener() { public boolean onKey(View v, int keyCode, KeyEvent event) { if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { createEngine.run(); return true; } return false; } }); } // Configure network engine settings private void networkEngineConfigDialog() { View content = View.inflate(this, R.layout.network_engine_config, null); final EditText hostNameView = (EditText) content.findViewById(R.id.network_engine_host); final EditText portView = (EditText) content.findViewById(R.id.network_engine_port); String hostName = ""; String port = "0"; try { if (EngineUtil.isNetEngine(networkEngineToConfig)) { String[] lines = Util.readFile(networkEngineToConfig); if (lines.length > 1) { hostName = lines[1]; } if (lines.length > 2) { port = lines[2]; } } } catch (IOException e1) { Log.d("Exception", e1.toString()); } hostNameView.setText(hostName); portView.setText(port); final Runnable writeConfig = new Runnable() { public void run() { String hostName = hostNameView.getText().toString(); String port = portView.getText().toString(); try { FileWriter fw = new FileWriter(new File(networkEngineToConfig), false); fw.write("NETE\n"); fw.write(hostName); fw.write("\n"); fw.write(port); fw.write("\n"); fw.close(); setEngineOptions(true); } catch (IOException e) { Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG).show(); } } }; new MaterialDialog.Builder(this).title(R.string.configure_network_engine).customView(content, true) .positiveText(android.R.string.ok).negativeText(R.string.cancel).neutralText(R.string.delete) .onPositive(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { writeConfig.run(); networkEngineDialog(); } }).onNeutral(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { deleteNetworkEngineDialog(); } }).onNegative(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { networkEngineDialog(); } }); portView.setOnKeyListener(new OnKeyListener() { public boolean onKey(View v, int keyCode, KeyEvent event) { if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { writeConfig.run(); networkEngineDialog(); return true; } return false; } }); } private void deleteNetworkEngineDialog() { String msg = networkEngineToConfig; if (msg.lastIndexOf('/') >= 0) { msg = msg.substring(msg.lastIndexOf('/') + 1); } new MaterialDialog.Builder(this).title(R.string.delete_network_engine) .content(getString(R.string.network_engine) + ": " + msg).positiveText(R.string.yes) .negativeText(R.string.no).onPositive(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { boolean result = new File(networkEngineToConfig).delete(); Log.d("Result delete", "" + result); String engine = settings.getString("engine", "stockfish"); if (engine.equals(networkEngineToConfig)) { engine = "stockfish"; Editor editor = settings.edit(); editor.putString("engine", engine); editor.apply(); dialog.dismiss(); int strength = settings.getInt("strength", 1000); setEngineOptions(false); setEngineStrength(engine, strength); } dialog.cancel(); networkEngineDialog(); } }).onNegative(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { networkEngineDialog(); } }).show(); } /** * Open a load/save file dialog. Uses OI file manager if available. */ private void selectFile(int titleMsg, int buttonMsg, String settingsName, String defaultDir, int dialog, int result) { setAutoMode(AutoMode.OFF); String action = "org.openintents.action.PICK_FILE"; Intent i = new Intent(action); String currentFile = settings.getString(settingsName, ""); String sep = File.separator; if (!currentFile.contains(sep)) { currentFile = Environment.getExternalStorageDirectory() + sep + defaultDir + sep + currentFile; } i.setData(Uri.fromFile(new File(currentFile))); i.putExtra("org.openintents.extra.TITLE", getString(titleMsg)); i.putExtra("org.openintents.extra.BUTTON_TEXT", getString(buttonMsg)); try { startActivityForResult(i, result); } catch (ActivityNotFoundException e) { if (dialog == SELECT_PGN_FILE_SAVE_DIALOG) { selectPgnFileSaveDialog(); } else if (dialog == SELECT_FEN_FILE_DIALOG) { selectFenFileDialog(); } } } private boolean hasScidProvider() { try { getPackageManager().getPackageInfo("org.scid.android", 0); return true; } catch (PackageManager.NameNotFoundException ex) { return false; } } private void selectScidFile() { setAutoMode(AutoMode.OFF); Intent intent = new Intent(); intent.setComponent(new ComponentName("org.scid.android", "org.scid.android.SelectFileActivity")); intent.setAction(".si4"); try { startActivityForResult(intent, RESULT_SELECT_SCID); } catch (ActivityNotFoundException e) { Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG).show(); } } private void getFen() { 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(); } } private int currFileType() { return settings.getInt("currFT", FT_NONE); } /** * Return path name for the last used PGN or SCID file. */ private String currPathName() { int ft = settings.getInt("currFT", FT_NONE); switch (ft) { case FT_PGN: { String ret = settings.getString("currentPGNFile", ""); String sep = File.separator; if (!ret.contains(sep)) { ret = Environment.getExternalStorageDirectory() + sep + pgnDir + sep + ret; } return ret; } case FT_SCID: return settings.getString("currentScidFile", ""); case FT_FEN: return settings.getString("currentFENFile", ""); default: return ""; } } private String[] findFilesInDirectory(String dirName, final FileNameFilter filter) { File extDir = Environment.getExternalStorageDirectory(); String sep = File.separator; File dir = new File(extDir.getAbsolutePath() + sep + dirName); File[] files = dir.listFiles(new FileFilter() { public boolean accept(File pathname) { return pathname.isFile() && ((filter == null) || filter.accept(pathname.getAbsolutePath())); } }); if (files == null) { files = new File[0]; } final int numFiles = files.length; String[] fileNames = new String[numFiles]; for (int i = 0; i < files.length; i++) { fileNames[i] = files[i].getName(); } Arrays.sort(fileNames, String.CASE_INSENSITIVE_ORDER); return fileNames; } /** * Save current game to a PGN file. */ private void savePGNToFile(String pathName) { String pgn = ctrl.getPGN(); Editor editor = settings.edit(); editor.putString("currentPGNFile", pathName); editor.putInt("currFT", FT_PGN); editor.apply(); Intent i = new Intent(MDChess.this, EditPGNSave.class); i.setAction("org.mdc.chess.saveFile"); i.putExtra("org.mdc.chess.pathname", pathName); i.putExtra("org.mdc.chess.pgn", pgn); i.putExtra("org.mdc.chess.silent", false); startActivity(i); } /** * Load a PGN game from a file. */ private void loadPGNFromFile(String pathName) { Editor editor = settings.edit(); editor.putString("currentPGNFile", pathName); editor.putInt("currFT", FT_PGN); editor.apply(); Intent i = new Intent(MDChess.this, EditPGNLoad.class); i.setAction("org.mdc.chess.loadFile"); i.putExtra("org.mdc.chess.pathname", pathName); startActivityForResult(i, RESULT_LOAD_PGN); } /** * Load a FEN position from a file. */ private void loadFENFromFile(String pathName) { if (pathName == null) { return; } Editor editor = settings.edit(); editor.putString("currentFENFile", pathName); editor.putInt("currFT", FT_FEN); editor.apply(); Intent i = new Intent(MDChess.this, LoadFEN.class); i.setAction("org.mdc.chess.loadFen"); i.putExtra("org.mdc.chess.pathname", pathName); startActivityForResult(i, RESULT_LOAD_FEN); } private void setFenHelper(String fen) { if (fen == null) { return; } try { ctrl.setFENOrPGN(fen); } catch (ChessParseError e) { // If FEN corresponds to illegal chess position, go into edit board mode. try { TextIO.readFEN(fen); } catch (ChessParseError e2) { if (e2.pos != null) { startEditBoard(fen); } } } } @Override public void requestPromotePiece() { promoteDialog(); } @Override public void reportInvalidMove(Move m) { String msg = String.format(Locale.US, "%s %s-%s", getString(R.string.invalid_move), TextIO.squareToString(m.from), TextIO.squareToString(m.to)); Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show(); } @Override public void reportEngineName(String engine) { String msg = String.format(Locale.US, "%s: %s", getString(R.string.engine), engine); Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show(); } @Override public void reportEngineError(String errMsg) { String msg = String.format(Locale.US, "%s: %s", getString(R.string.engine_error), errMsg); Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG).show(); } @Override public void computerMoveMade() { if (soundEnabled) { if (moveSound != null) { moveSound.release(); } try { moveSound = MediaPlayer.create(this, R.raw.movesound); if (moveSound != null) { moveSound.start(); } } catch (NotFoundException ex) { Log.d("Exception", ex.toString()); } } if (vibrateEnabled) { Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); v.vibrate(500); } } @Override public void runOnUIThread(Runnable runnable) { runOnUiThread(runnable); } /** * Decide if user should be warned about heavy CPU usage. */ private void updateNotification() { boolean warn = false; if (lastVisibleMillis != 0) { // GUI not visible warn = lastComputationMillis >= lastVisibleMillis + 60000; } setNotification(warn); } /** * Set/clear the "heavy CPU usage" notification. */ private void setNotification(boolean show) { if (notificationActive == show) { return; } notificationActive = show; final int cpuUsage = 1; String ns = Context.NOTIFICATION_SERVICE; NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns); if (show) { int icon = R.mipmap.ic_launcher; CharSequence tickerText = getString(R.string.heavy_cpu_usage); long when = System.currentTimeMillis(); Context context = getApplicationContext(); CharSequence contentTitle = getString(R.string.background_processing); CharSequence contentText = getString(R.string.lot_cpu_power); Intent notificationIntent = new Intent(this, CPUWarning.class); PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); @SuppressWarnings("deprecation") Notification notification = new Notification.Builder(context).setSmallIcon(icon).setTicker(tickerText) .setWhen(when).setOngoing(true).setContentTitle(contentTitle).setContentText(contentText) .setContentIntent(contentIntent).getNotification(); mNotificationManager.notify(cpuUsage, notification); } else { mNotificationManager.cancel(cpuUsage); } } private String timeToString(int time) { int secs = (int) Math.floor((time + 999) / 1000.0); boolean neg = false; if (secs < 0) { neg = true; secs = -secs; } int mins = secs / 60; secs -= mins * 60; StringBuilder ret = new StringBuilder(); if (neg) ret.append('-'); ret.append(mins); ret.append(':'); if (secs < 10) ret.append('0'); ret.append(secs); return ret.toString(); } @Override public void setRemainingTime(int wTime, int bTime, int nextUpdate) { if (ctrl.getGameMode().clocksActive()) { whiteTitleText.setText(getString(R.string.white_square_character) + " " + timeToString(wTime)); blackTitleText.setText(getString(R.string.black_square_character) + " " + timeToString(bTime)); } else { TreeMap<String, String> headers = new TreeMap<>(); ctrl.getHeaders(headers); whiteTitleText.setText(headers.get("White")); blackTitleText.setText(headers.get("Black")); } handlerTimer.removeCallbacks(r); if (nextUpdate > 0) { handlerTimer.postDelayed(r, nextUpdate); } } /** * Set automatic move forward/backward mode. */ private void setAutoMode(AutoMode am) { autoMode = am; switch (am) { case BACKWARD: case FORWARD: if (autoMoveDelay > 0) { autoModeTimer.postDelayed(amRunnable, autoMoveDelay); } break; case OFF: autoModeTimer.removeCallbacks(amRunnable); break; } } /** * Disable automatic move mode if clocks are active. */ private void maybeAutoModeOff(GameMode gm) { if (gm.clocksActive()) { setAutoMode(AutoMode.OFF); } } private enum AutoMode { OFF, FORWARD, BACKWARD } /** * State of requested permissions. */ private enum PermissionState { UNKNOWN, REQUESTED, GRANTED, DENIED } private interface Loader { void load(String pathName); } private interface FileNameFilter { boolean accept(String filename); } /** * PngTokenReceiver implementation that renders PGN data for screen display. */ static class PgnScreenText implements PgnToken.PgnTokenReceiver, MoveListView.OnLinkClickListener { // --Commented out by Inspection (21/10/2016 11:38 PM):Node currNode = null; final static int indentStep = 15; final PGNOptions options; final HashMap<Node, NodeInfo> nodeToCharPos; private final TreeMap<Integer, Node> offs2Node = new TreeMap<>(); int nestLevel = 0; boolean col0 = true; int currPos = 0, endPos = 0; boolean upToDate = false; int paraStart = 0; int paraIndent = 0; boolean paraBold = false; boolean pendingNewLine = false; BackgroundColorSpan bgSpan = new BackgroundColorSpan(Color.parseColor("#FFFFFF")); private SpannableStringBuilder sb = new SpannableStringBuilder(); private int prevType = PgnToken.EOF; PgnScreenText(PGNOptions options) { nodeToCharPos = new HashMap<>(); this.options = options; } public final CharSequence getText() { return sb; } public final int getCurrPos() { return currPos; } public boolean isUpToDate() { return upToDate; } private void newLine() { newLine(false); } private void newLine(boolean eof) { if (!col0) { if (paraIndent > 0) { int paraEnd = sb.length(); int indent = paraIndent * indentStep; sb.setSpan(new LeadingMarginSpan.Standard(indent), paraStart, paraEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } if (paraBold) { int paraEnd = sb.length(); sb.setSpan(new StyleSpan(Typeface.BOLD), paraStart, paraEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } if (!eof) { sb.append('\n'); } paraStart = sb.length(); paraIndent = nestLevel; paraBold = false; } col0 = true; } private void addMoveLink(Node node, int l0, int l1) { offs2Node.put(l0, node); offs2Node.put(l1, null); } @Override public boolean onLinkClick(int offs) { if (ctrl == null) { return false; } Map.Entry<Integer, Node> e = offs2Node.floorEntry(offs); if (e == null) { return false; } Node node = e.getValue(); if (node == null && e.getKey() == offs) { e = offs2Node.lowerEntry(e.getKey()); if (e != null) { node = e.getValue(); } } if (node == null) { return false; } // On android 4.1 this onClick method is called // even when you long click the move list. The test // below works around the problem. /*Dialog mlmd = moveListMenuDlg; if ((mlmd == null) || !mlmd.isShowing()) { df.setAutoMode(AutoMode.OFF); ctrl.goNode(node); }*/ return true; } public void processToken(Node node, int type, String token) { if ((prevType == PgnToken.RIGHT_BRACKET) && (type != PgnToken.LEFT_BRACKET)) { if (options.view.headers) { col0 = false; newLine(); } else { sb.clear(); paraBold = false; } } if (pendingNewLine) { if (type != PgnToken.RIGHT_PAREN) { newLine(); pendingNewLine = false; } } switch (type) { case PgnToken.STRING: sb.append(" \""); sb.append(token); sb.append('"'); break; case PgnToken.INTEGER: if ((prevType != PgnToken.LEFT_PAREN) && (prevType != PgnToken.RIGHT_BRACKET) && !col0) { sb.append(' '); } sb.append(token); col0 = false; break; case PgnToken.PERIOD: sb.append('.'); col0 = false; break; case PgnToken.ASTERISK: sb.append(" *"); col0 = false; break; case PgnToken.LEFT_BRACKET: sb.append('['); col0 = false; break; case PgnToken.RIGHT_BRACKET: sb.append("]\n"); col0 = false; break; case PgnToken.LEFT_PAREN: nestLevel++; if (col0) { paraIndent++; } newLine(); sb.append('('); col0 = false; break; case PgnToken.RIGHT_PAREN: sb.append(')'); nestLevel--; pendingNewLine = true; break; case PgnToken.NAG: sb.append(Node.nagStr(Integer.parseInt(token))); col0 = false; break; case PgnToken.SYMBOL: { if ((prevType != PgnToken.RIGHT_BRACKET) && (prevType != PgnToken.LEFT_BRACKET) && !col0) { sb.append(' '); } int l0 = sb.length(); sb.append(token); int l1 = sb.length(); nodeToCharPos.put(node, new NodeInfo(l0, l1)); addMoveLink(node, l0, l1); if (endPos < l0) endPos = l0; col0 = false; if (nestLevel == 0) paraBold = true; break; } case PgnToken.COMMENT: if (prevType == PgnToken.RIGHT_BRACKET) { break; } else if (nestLevel == 0) { nestLevel++; newLine(); nestLevel--; } else { if ((prevType != PgnToken.LEFT_PAREN) && !col0) { sb.append(' '); } } int l0 = sb.length(); sb.append(token.replaceAll("[ \t\r\n]+", " ").trim()); int l1 = sb.length(); int color = ColorTheme.instance().getColor(ColorTheme.PGN_COMMENT); sb.setSpan(new ForegroundColorSpan(color), l0, l1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); col0 = false; if (nestLevel == 0) { newLine(); } break; case PgnToken.EOF: newLine(true); upToDate = true; break; } prevType = type; } @Override public void clear() { sb = new SpannableStringBuilder(); offs2Node.clear(); prevType = PgnToken.EOF; nestLevel = 0; col0 = true; //currNode = null; currPos = 0; endPos = 0; nodeToCharPos.clear(); paraStart = 0; paraIndent = 0; paraBold = false; pendingNewLine = false; upToDate = false; } @Override public void setCurrent(Node node) { sb.removeSpan(bgSpan); NodeInfo ni = nodeToCharPos.get(node); if ((ni == null) && (node != null) && (node.getParent() != null)) { ni = nodeToCharPos.get(node.getParent()); } if (ni != null) { //int color = ColorTheme.instance().getColor(ColorTheme.CURRENT_MOVE); bgSpan = new BackgroundColorSpan(Color.WHITE); sb.setSpan(bgSpan, ni.l0, ni.l1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); currPos = ni.l0; } else { currPos = 0; } //currNode = node; } private static class NodeInfo { final int l0; final int l1; NodeInfo(int ls, int le) { l0 = ls; l1 = le; } } } }