diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/activities/EditPGN.java b/DroidFishApp/src/main/java/org/petero/droidfish/activities/EditPGN.java index c0f3d5c..a6b1214 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/activities/EditPGN.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/activities/EditPGN.java @@ -72,6 +72,7 @@ public abstract class EditPGN extends AppCompatActivity { private String lastSearchString = ""; private String lastFileName = ""; private long lastModTime = -1; + private boolean useRegExp = false; private Thread workThread = null; private boolean canceled = false; @@ -95,11 +96,13 @@ public abstract class EditPGN extends AppCompatActivity { lastFileName = savedInstanceState.getString("lastFileName"); if (lastFileName == null) lastFileName = ""; lastModTime = savedInstanceState.getLong("lastModTime"); + useRegExp = savedInstanceState.getBoolean("useRegExpSearch"); } else { defaultFilePos = settings.getLong("defaultFilePos", 0); lastSearchString = settings.getString("lastSearchString", ""); lastFileName = settings.getString("lastFileName", ""); lastModTime = settings.getLong("lastModTime", 0); + useRegExp = settings.getBoolean("useRegExpSearch", false); } Intent i = getIntent(); @@ -131,14 +134,14 @@ public abstract class EditPGN extends AppCompatActivity { workThread = new Thread(() -> { if (!readFile()) return; + GameAdapter.ItemMatcher m = + GameAdapter.getItemMatcher(lastSearchString, useRegExp); int itemNo = getItemNo(gamesInFile, defaultFilePos) + (next ? 1 : -1); if (next) { - while (itemNo < gamesInFile.size() && - !GameAdapter.matchItem(gamesInFile.get(itemNo), lastSearchString)) + while (itemNo < gamesInFile.size() && !m.matches(gamesInFile.get(itemNo))) itemNo++; } else { - while (itemNo >= 0 && - !GameAdapter.matchItem(gamesInFile.get(itemNo), lastSearchString)) + while (itemNo >= 0 && !m.matches(gamesInFile.get(itemNo))) itemNo--; } final int loadItem = itemNo; @@ -205,6 +208,7 @@ public abstract class EditPGN extends AppCompatActivity { outState.putString("lastSearchString", lastSearchString); outState.putString("lastFileName", lastFileName); outState.putLong("lastModTime", lastModTime); + outState.putBoolean("useRegExpSearch", useRegExp); } @Override @@ -214,6 +218,7 @@ public abstract class EditPGN extends AppCompatActivity { editor.putString("lastSearchString", lastSearchString); editor.putString("lastFileName", lastFileName); editor.putLong("lastModTime", lastModTime); + editor.putBoolean("useRegExpSearch", useRegExp); editor.apply(); super.onPause(); } @@ -234,6 +239,8 @@ public abstract class EditPGN extends AppCompatActivity { @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.edit_file_options_menu, menu); + MenuItem item = menu.findItem(R.id.regexp_search); + item.setChecked(useRegExp); return true; } @@ -243,6 +250,14 @@ public abstract class EditPGN extends AppCompatActivity { case R.id.item_delete_file: reShowDialog(DELETE_PGN_FILE_DIALOG); break; + case R.id.regexp_search: + useRegExp = !useRegExp; + item.setChecked(useRegExp); + if (binding != null) { + String s = binding.selectGameFilter.getText().toString(); + setFilterString(s); + } + break; } return false; } @@ -480,17 +495,20 @@ public abstract class EditPGN extends AppCompatActivity { } private void setFilterString(String s) { + boolean regExp = useRegExp; Filter.FilterListener listener = (count) -> { ArrayList arr = aa.getValues(); int itemNo = getItemNo(arr, currentFilePos); if (itemNo < 0) itemNo = 0; - while (itemNo < arr.size() && - !GameAdapter.matchItem(arr.get(itemNo), lastSearchString)) + GameAdapter.ItemMatcher m = + GameAdapter.getItemMatcher(lastSearchString, regExp); + while (itemNo < arr.size() && !m.matches(arr.get(itemNo))) itemNo++; if (itemNo < arr.size()) binding.listView.setSelectionFromTop(itemNo, 0); }; + aa.setUseRegExp(regExp); aa.getFilter().filter(s, listener); } diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/activities/GameAdapter.java b/DroidFishApp/src/main/java/org/petero/droidfish/activities/GameAdapter.java index ba72574..68e35ed 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/activities/GameAdapter.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/activities/GameAdapter.java @@ -28,16 +28,19 @@ import android.widget.Filterable; import android.widget.TextView; import java.util.ArrayList; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; /** * An adapter for displaying an ArrayList in a ListView. */ public class GameAdapter extends BaseAdapter implements Filterable { - private ArrayList origValues; // Unfiltered values - private ArrayList values; // Filtered values. Equal to origValues if no filter used + private ArrayList origValues; // Unfiltered values + private ArrayList values; // Filtered values. Equal to origValues if no filter used private final LayoutInflater inflater; private int resource; - private GameFilter filter; // Initialized at first use + private GameFilter filter; // Initialized at first use + private boolean useRegExp = false; // If true, use regular expression in filter public GameAdapter(Context context, int resource, ArrayList objects) { origValues = objects; @@ -83,6 +86,10 @@ public class GameAdapter extends BaseAdapter implements Filterable { return filter; } + public void setUseRegExp(boolean regExp) { + useRegExp = regExp; + } + private class GameFilter extends Filter { @Override protected FilterResults performFiltering(CharSequence constraint) { @@ -91,10 +98,10 @@ public class GameAdapter extends BaseAdapter implements Filterable { res.values = origValues; res.count = origValues.size(); } else { - String s = constraint.toString().toLowerCase(); + ItemMatcher m = getItemMatcher(constraint.toString(), useRegExp); ArrayList newValues = new ArrayList<>(); for (T item : origValues) - if (matchItem(item, s)) + if (m.matches(item)) newValues.add(item); res.values = newValues; res.count = newValues.size(); @@ -110,10 +117,27 @@ public class GameAdapter extends BaseAdapter implements Filterable { } } - /** Return true if matchStr matches item. - * @param item The item to check. The toString() value converted to lowercase is used. - * @param matchStr The match string. Must be lowercase. */ - static boolean matchItem(U item, String matchStr) { - return item.toString().toLowerCase().contains(matchStr); + interface ItemMatcher { + /** Return true if item matches the search criteria. */ + boolean matches(U item); + } + + /** Return an object that determines if an item matches given search criteria. + * @param matchStr The match string. + * @param useRegExp If true matchStr is interpreted as a regular expression. */ + static ItemMatcher getItemMatcher(String matchStr, boolean useRegExp) { + if (useRegExp) { + Pattern tmp; + try { + tmp = Pattern.compile(matchStr, Pattern.CASE_INSENSITIVE); + } catch (PatternSyntaxException ex) { + tmp = null; + } + Pattern p = tmp; + return item -> p == null || p.matcher(item.toString()).find(); + } else { + String s = matchStr.toLowerCase(); + return item -> item.toString().toLowerCase().contains(s); + } } } diff --git a/DroidFishApp/src/main/res/menu/edit_file_options_menu.xml b/DroidFishApp/src/main/res/menu/edit_file_options_menu.xml index 9ad6a53..7f82f25 100644 --- a/DroidFishApp/src/main/res/menu/edit_file_options_menu.xml +++ b/DroidFishApp/src/main/res/menu/edit_file_options_menu.xml @@ -1,6 +1,12 @@ - + + diff --git a/DroidFishApp/src/main/res/values/strings.xml b/DroidFishApp/src/main/res/values/strings.xml index ab2d3b2..43fd3d0 100644 --- a/DroidFishApp/src/main/res/values/strings.xml +++ b/DroidFishApp/src/main/res/values/strings.xml @@ -196,6 +196,7 @@ If you are running on battery power, it is recommended that you change settings Delete file Delete file? Delete file %s? + Regular Expression Search Game saved Failed to save game Failed to delete game