From f02e7763e42ebe15bfe8c188aabdc3e51df9f4a2 Mon Sep 17 00:00:00 2001 From: Peter Osterlund Date: Sat, 21 Mar 2020 13:40:12 +0100 Subject: [PATCH] Use file browser for file/directory settings The following settings can now be changed using the OI file manager: * Opening Book Settings -> Book Filename * Endgame Tablebases -> GTB Directory * Endgame Tablebases -> Syzygy Directory --- .../java/org/petero/droidfish/DroidFish.java | 10 +- .../activities/EditFilePreference.java | 151 ++++++++++++++++++ .../droidfish/activities/EditOptions.java | 55 +------ .../droidfish/activities/FileBrowseUtil.java | 76 +++++++++ .../droidfish/activities/Preferences.java | 30 +++- DroidFishApp/src/main/res/values/strings.xml | 1 + DroidFishApp/src/main/res/xml/preferences.xml | 18 ++- 7 files changed, 283 insertions(+), 58 deletions(-) create mode 100644 DroidFishApp/src/main/java/org/petero/droidfish/activities/EditFilePreference.java create mode 100644 DroidFishApp/src/main/java/org/petero/droidfish/activities/FileBrowseUtil.java diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/DroidFish.java b/DroidFishApp/src/main/java/org/petero/droidfish/DroidFish.java index 9dbfe92..37616db 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/DroidFish.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/DroidFish.java @@ -2408,17 +2408,25 @@ public class DroidFish extends Activity items[numFiles] = getString(R.string.internal_book); items[numFiles + 1] = getString(R.string.eco_book); items[numFiles + 2] = getString(R.string.no_book); + int defaultItem = numFiles; if ("eco:".equals(bookOptions.filename)) defaultItem = numFiles + 1; else if ("nobook:".equals(bookOptions.filename)) defaultItem = numFiles + 2; + String oldName = bookOptions.filename; + File extDir = Environment.getExternalStorageDirectory(); + String sep = File.separator; + String defDir = extDir.getAbsolutePath() + sep + bookDir + sep; + if (oldName.startsWith(defDir)) + oldName = oldName.substring(defDir.length()); for (int i = 0; i < numFiles; i++) { - if (bookOptions.filename.equals(items[i])) { + if (oldName.equals(items[i])) { defaultItem = i; break; } } + AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.select_opening_book_file); builder.setSingleChoiceItems(items, defaultItem, (dialog, item) -> { diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/activities/EditFilePreference.java b/DroidFishApp/src/main/java/org/petero/droidfish/activities/EditFilePreference.java new file mode 100644 index 0000000..7d07896 --- /dev/null +++ b/DroidFishApp/src/main/java/org/petero/droidfish/activities/EditFilePreference.java @@ -0,0 +1,151 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2020 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package org.petero.droidfish.activities; + +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Environment; +import android.preference.EditTextPreference; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import org.petero.droidfish.FileUtil; +import org.petero.droidfish.R; + +import java.io.File; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + + +/** A text preference representing a file or directory, with a corresponding browse button. */ +public class EditFilePreference extends EditTextPreference { + private boolean pickDirectory = false; // True to pick a directory, false to pick a file + private String defaultPath = ""; // Default path when current value does not define a path + private String ignorePattern = ""; // Regexp for values to be treated as non-paths + private View view; + + public EditFilePreference(Context context) { + super(context); + init(null); + } + + public EditFilePreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs); + } + + public EditFilePreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs); + } + + private void init(AttributeSet attrs) { + if (attrs != null) { + pickDirectory = attrs.getAttributeBooleanValue(null, "pickDirectory", false); + defaultPath = getStringValue(attrs, "defaultPath"); + ignorePattern = getStringValue(attrs, "ignorePattern"); + } + } + + private static String getStringValue(AttributeSet attrs, String name) { + String val = attrs.getAttributeValue(null, name); + return val == null ? "" : val; + } + + @Override + protected void onBindView(View view) { + super.onBindView(view); + this.view = view; + addBrowseButton(); + } + + private void addBrowseButton() { + if (view == null) + return; + + LinearLayout widgetFrameView = view.findViewById(android.R.id.widget_frame); + if (widgetFrameView == null) + return; + widgetFrameView.setVisibility(View.VISIBLE); + int count = widgetFrameView.getChildCount(); + if (count > 0) + widgetFrameView.removeViews(0, count); + + ImageView button = new ImageView(getContext()); + widgetFrameView.addView(button); + widgetFrameView.setMinimumWidth(0); + + boolean hasBrowser = FileBrowseUtil.hasBrowser(getContext().getPackageManager(), + pickDirectory); + FileBrowseUtil.setBrowseImage(getContext().getResources(), button, hasBrowser); + button.setOnClickListener(view -> browseFile()); + } + + private void browseFile() { + String currentPath = getText(); + if (matchPattern(currentPath)) + currentPath = ""; + String sep = File.separator; + if (currentPath.isEmpty() || !currentPath.contains(sep)) { + String extDir = Environment.getExternalStorageDirectory().getAbsolutePath(); + String newPath = extDir + sep + defaultPath; + if (!currentPath.isEmpty()) + newPath += sep + currentPath; + currentPath = newPath; + } + + final String action = FileBrowseUtil.getPickAction(pickDirectory); + final String title = getContext().getString(pickDirectory ? R.string.select_directory + : R.string.select_file); + Intent i = new Intent(action); + i.setData(Uri.fromFile(new File(currentPath))); + i.putExtra("org.openintents.extra.TITLE", title); + try { + Context context = getContext(); + if (context instanceof Preferences) { + Preferences prefs = ((Preferences)context); + prefs.runActivity(i, (resultCode, data) -> { + if (resultCode == Activity.RESULT_OK) { + String pathName = FileUtil.getFilePathFromUri(data.getData()); + if (pathName != null) + setText(pathName); + } + }); + } + } catch (ActivityNotFoundException ignore) { + } + } + + private boolean matchPattern(String s) { + if (ignorePattern.isEmpty()) + return false; + Pattern p; + try { + p = Pattern.compile(ignorePattern); + return p.matcher(s).find(); + } catch (PatternSyntaxException ex) { + return false; + } + } +} diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/activities/EditOptions.java b/DroidFishApp/src/main/java/org/petero/droidfish/activities/EditOptions.java index e5ace65..bf73b29 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/activities/EditOptions.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/activities/EditOptions.java @@ -24,34 +24,23 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.drawable.StateListDrawable; import android.net.Uri; import android.os.Bundle; -import android.os.Environment; import android.preference.PreferenceManager; import android.text.Editable; import android.text.TextWatcher; -import android.util.TypedValue; import android.view.KeyEvent; import android.view.View; -import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ArrayAdapter; import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.ImageView; - -import com.caverock.androidsvg.SVG; -import com.caverock.androidsvg.SVGParseException; import androidx.databinding.DataBindingUtil; import org.petero.droidfish.DroidFishApp; import org.petero.droidfish.FileUtil; import org.petero.droidfish.R; -import org.petero.droidfish.SVGPictureDrawable; import org.petero.droidfish.Util; import org.petero.droidfish.databinding.EditoptionsBinding; import org.petero.droidfish.databinding.UciOptionButtonBinding; @@ -90,10 +79,8 @@ public class EditOptions extends Activity { workDir = (String) i.getSerializableExtra("org.petero.droidfish.workDir"); hasBrowser = (Boolean) i.getSerializableExtra("org.petero.droidfish.localEngine"); if (uciOpts != null) { - if (hasBrowser) { - Intent browser = new Intent("org.openintents.action.PICK_FILE"); - hasBrowser = browser.resolveActivity(getPackageManager()) != null; - } + if (hasBrowser) + hasBrowser = FileBrowseUtil.hasBrowser(getPackageManager(), false); initUI(); } else { setResult(RESULT_CANCELED); @@ -267,10 +254,8 @@ public class EditOptions extends Activity { }); boolean isFileOption = hasBrowser && (o.name.toLowerCase().contains("file") || o.name.toLowerCase().contains("path")); - setBrowseImage(holder.eoBrowse, isFileOption); - holder.eoBrowse.setOnClickListener(view -> { - browseFile(so, holder.eoValue); - }); + FileBrowseUtil.setBrowseImage(getResources(), holder.eoBrowse, isFileOption); + holder.eoBrowse.setOnClickListener(view -> browseFile(so, holder.eoValue)); return holder.getRoot(); } default: @@ -278,42 +263,12 @@ public class EditOptions extends Activity { } } - private void setBrowseImage(ImageButton button, boolean visible) { - button.setVisibility(visible ? View.VISIBLE : View.GONE); - - Resources r = getResources(); - try { - SVG svg = SVG.getFromResource(r, R.raw.open_last_file); - button.setBackgroundDrawable(new SVGPictureDrawable(svg)); - } catch (SVGParseException ignore) { - } - - try { - SVG touched = SVG.getFromResource(r, R.raw.touch); - StateListDrawable sld = new StateListDrawable(); - sld.addState(new int[]{android.R.attr.state_pressed}, new SVGPictureDrawable(touched)); - button.setImageDrawable(sld); - } catch (SVGParseException ignore) { - } - - int bWidth = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - 36, r.getDisplayMetrics())); - int bHeight = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - 32, r.getDisplayMetrics())); - ViewGroup.LayoutParams lp = button.getLayoutParams(); - lp.width = bWidth; - lp.height = bHeight; - button.setLayoutParams(lp); - button.setPadding(0,0,0,0); - button.setScaleType(ImageView.ScaleType.FIT_XY); - } - private void browseFile(UCIOptions.StringOption so, EditText textField) { String currentFile = so.getStringValue(); String sep = File.separator; if (!currentFile.contains(sep)) currentFile = workDir + sep + currentFile; - Intent i = new Intent("org.openintents.action.PICK_FILE"); + Intent i = new Intent(FileBrowseUtil.getPickAction(false)); i.setData(Uri.fromFile(new File(currentFile))); i.putExtra("org.openintents.extra.TITLE", getString(R.string.select_file)); try { diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/activities/FileBrowseUtil.java b/DroidFishApp/src/main/java/org/petero/droidfish/activities/FileBrowseUtil.java new file mode 100644 index 0000000..bc0b568 --- /dev/null +++ b/DroidFishApp/src/main/java/org/petero/droidfish/activities/FileBrowseUtil.java @@ -0,0 +1,76 @@ +/* + DroidFish - An Android chess program. + Copyright (C) 2020 Peter Österlund, peterosterlund2@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package org.petero.droidfish.activities; + +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.drawable.StateListDrawable; +import android.util.TypedValue; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import com.caverock.androidsvg.SVG; +import com.caverock.androidsvg.SVGParseException; + +import org.petero.droidfish.R; +import org.petero.droidfish.SVGPictureDrawable; + +public class FileBrowseUtil { + + public static String getPickAction(boolean pickDirectory) { + return pickDirectory ? "org.openintents.action.PICK_DIRECTORY" + : "org.openintents.action.PICK_FILE"; + } + + public static boolean hasBrowser(PackageManager pMan, boolean pickDirectory) { + Intent browser = new Intent(getPickAction(pickDirectory)); + return browser.resolveActivity(pMan) != null; + } + + public static void setBrowseImage(Resources r, ImageView button, boolean visible) { + button.setVisibility(visible ? View.VISIBLE : View.GONE); + + try { + SVG svg = SVG.getFromResource(r, R.raw.open_last_file); + button.setBackgroundDrawable(new SVGPictureDrawable(svg)); + } catch (SVGParseException ignore) { + } + + try { + SVG touched = SVG.getFromResource(r, R.raw.touch); + StateListDrawable sld = new StateListDrawable(); + sld.addState(new int[]{android.R.attr.state_pressed}, new SVGPictureDrawable(touched)); + button.setImageDrawable(sld); + } catch (SVGParseException ignore) { + } + + int bWidth = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + 36, r.getDisplayMetrics())); + int bHeight = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + 32, r.getDisplayMetrics())); + ViewGroup.LayoutParams lp = button.getLayoutParams(); + lp.width = bWidth; + lp.height = bHeight; + button.setLayoutParams(lp); + button.setPadding(0,0,0,0); + button.setScaleType(ImageView.ScaleType.FIT_XY); + } +} diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/activities/Preferences.java b/DroidFishApp/src/main/java/org/petero/droidfish/activities/Preferences.java index 89984d3..c85dd91 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/activities/Preferences.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/activities/Preferences.java @@ -1,6 +1,6 @@ /* DroidFish - An Android chess program. - Copyright (C) 2011 Peter Österlund, peterosterlund2@gmail.com + Copyright (C) 2011,2020 Peter Österlund, peterosterlund2@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ import org.petero.droidfish.R; import org.petero.droidfish.Util; import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.os.Bundle; @@ -36,6 +37,9 @@ import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.ListView; +import java.util.HashMap; +import java.util.Map; + public class Preferences extends PreferenceActivity { private static int currentItem = -1; private static int initialItem = -1; @@ -100,4 +104,28 @@ public class Preferences extends PreferenceActivity { editor.putInt("prefsViewInitialItem", currentItem); editor.apply(); } + + public interface ActivityHandler { + void handleResult(int resultCode, Intent data); + } + + private int nextRequestCode = 129866295; + private Map handlers = new HashMap<>(); + + /** Start an activity and invoke handler when the activity finishes. */ + public void runActivity(Intent data, ActivityHandler handler) { + int requestCode = nextRequestCode++; + startActivityForResult(data, requestCode); + handlers.put(requestCode, handler); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + ActivityHandler handler = handlers.get(requestCode); + if (handler != null) { + handlers.remove(requestCode); + handler.handleResult(resultCode, data); + } + } } diff --git a/DroidFishApp/src/main/res/values/strings.xml b/DroidFishApp/src/main/res/values/strings.xml index c08aaa5..53caa80 100644 --- a/DroidFishApp/src/main/res/values/strings.xml +++ b/DroidFishApp/src/main/res/values/strings.xml @@ -82,6 +82,7 @@ If you are running on battery power, it is recommended that you change settings Save to PGN file Open FEN/EPD file Select file + Select directory Load Save Reading PGN file… diff --git a/DroidFishApp/src/main/res/xml/preferences.xml b/DroidFishApp/src/main/res/xml/preferences.xml index b154930..9528158 100644 --- a/DroidFishApp/src/main/res/xml/preferences.xml +++ b/DroidFishApp/src/main/res/xml/preferences.xml @@ -609,12 +609,14 @@ android:defaultValue="500" android:title="@string/prefs_bookRandom_title"> - - + - - + - - +