Support regular expression search in game load/save activity

Standard Java regular expression syntax is supported. The search is
case insensitive.
This commit is contained in:
Peter Osterlund 2019-09-22 08:48:42 +02:00
parent 15b62e9bc2
commit fbb2091d45
4 changed files with 66 additions and 17 deletions

View File

@ -72,6 +72,7 @@ public abstract class EditPGN extends AppCompatActivity {
private String lastSearchString = ""; private String lastSearchString = "";
private String lastFileName = ""; private String lastFileName = "";
private long lastModTime = -1; private long lastModTime = -1;
private boolean useRegExp = false;
private Thread workThread = null; private Thread workThread = null;
private boolean canceled = false; private boolean canceled = false;
@ -95,11 +96,13 @@ public abstract class EditPGN extends AppCompatActivity {
lastFileName = savedInstanceState.getString("lastFileName"); lastFileName = savedInstanceState.getString("lastFileName");
if (lastFileName == null) lastFileName = ""; if (lastFileName == null) lastFileName = "";
lastModTime = savedInstanceState.getLong("lastModTime"); lastModTime = savedInstanceState.getLong("lastModTime");
useRegExp = savedInstanceState.getBoolean("useRegExpSearch");
} else { } else {
defaultFilePos = settings.getLong("defaultFilePos", 0); defaultFilePos = settings.getLong("defaultFilePos", 0);
lastSearchString = settings.getString("lastSearchString", ""); lastSearchString = settings.getString("lastSearchString", "");
lastFileName = settings.getString("lastFileName", ""); lastFileName = settings.getString("lastFileName", "");
lastModTime = settings.getLong("lastModTime", 0); lastModTime = settings.getLong("lastModTime", 0);
useRegExp = settings.getBoolean("useRegExpSearch", false);
} }
Intent i = getIntent(); Intent i = getIntent();
@ -131,14 +134,14 @@ public abstract class EditPGN extends AppCompatActivity {
workThread = new Thread(() -> { workThread = new Thread(() -> {
if (!readFile()) if (!readFile())
return; return;
GameAdapter.ItemMatcher<GameInfo> m =
GameAdapter.getItemMatcher(lastSearchString, useRegExp);
int itemNo = getItemNo(gamesInFile, defaultFilePos) + (next ? 1 : -1); int itemNo = getItemNo(gamesInFile, defaultFilePos) + (next ? 1 : -1);
if (next) { if (next) {
while (itemNo < gamesInFile.size() && while (itemNo < gamesInFile.size() && !m.matches(gamesInFile.get(itemNo)))
!GameAdapter.matchItem(gamesInFile.get(itemNo), lastSearchString))
itemNo++; itemNo++;
} else { } else {
while (itemNo >= 0 && while (itemNo >= 0 && !m.matches(gamesInFile.get(itemNo)))
!GameAdapter.matchItem(gamesInFile.get(itemNo), lastSearchString))
itemNo--; itemNo--;
} }
final int loadItem = itemNo; final int loadItem = itemNo;
@ -205,6 +208,7 @@ public abstract class EditPGN extends AppCompatActivity {
outState.putString("lastSearchString", lastSearchString); outState.putString("lastSearchString", lastSearchString);
outState.putString("lastFileName", lastFileName); outState.putString("lastFileName", lastFileName);
outState.putLong("lastModTime", lastModTime); outState.putLong("lastModTime", lastModTime);
outState.putBoolean("useRegExpSearch", useRegExp);
} }
@Override @Override
@ -214,6 +218,7 @@ public abstract class EditPGN extends AppCompatActivity {
editor.putString("lastSearchString", lastSearchString); editor.putString("lastSearchString", lastSearchString);
editor.putString("lastFileName", lastFileName); editor.putString("lastFileName", lastFileName);
editor.putLong("lastModTime", lastModTime); editor.putLong("lastModTime", lastModTime);
editor.putBoolean("useRegExpSearch", useRegExp);
editor.apply(); editor.apply();
super.onPause(); super.onPause();
} }
@ -234,6 +239,8 @@ public abstract class EditPGN extends AppCompatActivity {
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.edit_file_options_menu, menu); getMenuInflater().inflate(R.menu.edit_file_options_menu, menu);
MenuItem item = menu.findItem(R.id.regexp_search);
item.setChecked(useRegExp);
return true; return true;
} }
@ -243,6 +250,14 @@ public abstract class EditPGN extends AppCompatActivity {
case R.id.item_delete_file: case R.id.item_delete_file:
reShowDialog(DELETE_PGN_FILE_DIALOG); reShowDialog(DELETE_PGN_FILE_DIALOG);
break; 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; return false;
} }
@ -480,17 +495,20 @@ public abstract class EditPGN extends AppCompatActivity {
} }
private void setFilterString(String s) { private void setFilterString(String s) {
boolean regExp = useRegExp;
Filter.FilterListener listener = (count) -> { Filter.FilterListener listener = (count) -> {
ArrayList<GameInfo> arr = aa.getValues(); ArrayList<GameInfo> arr = aa.getValues();
int itemNo = getItemNo(arr, currentFilePos); int itemNo = getItemNo(arr, currentFilePos);
if (itemNo < 0) if (itemNo < 0)
itemNo = 0; itemNo = 0;
while (itemNo < arr.size() && GameAdapter.ItemMatcher<GameInfo> m =
!GameAdapter.matchItem(arr.get(itemNo), lastSearchString)) GameAdapter.getItemMatcher(lastSearchString, regExp);
while (itemNo < arr.size() && !m.matches(arr.get(itemNo)))
itemNo++; itemNo++;
if (itemNo < arr.size()) if (itemNo < arr.size())
binding.listView.setSelectionFromTop(itemNo, 0); binding.listView.setSelectionFromTop(itemNo, 0);
}; };
aa.setUseRegExp(regExp);
aa.getFilter().filter(s, listener); aa.getFilter().filter(s, listener);
} }

View File

@ -28,6 +28,8 @@ import android.widget.Filterable;
import android.widget.TextView; import android.widget.TextView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
/** /**
* An adapter for displaying an ArrayList<GameInfo> in a ListView. * An adapter for displaying an ArrayList<GameInfo> in a ListView.
@ -38,6 +40,7 @@ public class GameAdapter<T> extends BaseAdapter implements Filterable {
private final LayoutInflater inflater; private final LayoutInflater inflater;
private int resource; 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<T> objects) { public GameAdapter(Context context, int resource, ArrayList<T> objects) {
origValues = objects; origValues = objects;
@ -83,6 +86,10 @@ public class GameAdapter<T> extends BaseAdapter implements Filterable {
return filter; return filter;
} }
public void setUseRegExp(boolean regExp) {
useRegExp = regExp;
}
private class GameFilter extends Filter { private class GameFilter extends Filter {
@Override @Override
protected FilterResults performFiltering(CharSequence constraint) { protected FilterResults performFiltering(CharSequence constraint) {
@ -91,10 +98,10 @@ public class GameAdapter<T> extends BaseAdapter implements Filterable {
res.values = origValues; res.values = origValues;
res.count = origValues.size(); res.count = origValues.size();
} else { } else {
String s = constraint.toString().toLowerCase(); ItemMatcher<T> m = getItemMatcher(constraint.toString(), useRegExp);
ArrayList<T> newValues = new ArrayList<>(); ArrayList<T> newValues = new ArrayList<>();
for (T item : origValues) for (T item : origValues)
if (matchItem(item, s)) if (m.matches(item))
newValues.add(item); newValues.add(item);
res.values = newValues; res.values = newValues;
res.count = newValues.size(); res.count = newValues.size();
@ -110,10 +117,27 @@ public class GameAdapter<T> extends BaseAdapter implements Filterable {
} }
} }
/** Return true if matchStr matches item. interface ItemMatcher<U> {
* @param item The item to check. The toString() value converted to lowercase is used. /** Return true if item matches the search criteria. */
* @param matchStr The match string. Must be lowercase. */ boolean matches(U item);
static <U> boolean matchItem(U item, String matchStr) { }
return item.toString().toLowerCase().contains(matchStr);
/** 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 <U> ItemMatcher<U> 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);
}
} }
} }

View File

@ -1,6 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <menu xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<item <item
android:id="@+id/item_delete_file" android:id="@+id/item_delete_file"
android:title="@string/delete_file" /> android:title="@string/delete_file" />
<item
android:id="@+id/regexp_search"
android:title="@string/regexp_search"
android:checkable="true"
app:showAsAction="never" />
</menu> </menu>

View File

@ -196,6 +196,7 @@ If you are running on battery power, it is recommended that you change settings
<string name="delete_file">Delete file</string> <string name="delete_file">Delete file</string>
<string name="delete_file_question">Delete file?</string> <string name="delete_file_question">Delete file?</string>
<string name="delete_named_file">Delete file %s?</string> <string name="delete_named_file">Delete file %s?</string>
<string name="regexp_search">Regular Expression Search</string>
<string name="game_saved">Game saved</string> <string name="game_saved">Game saved</string>
<string name="failed_to_save_game">Failed to save game</string> <string name="failed_to_save_game">Failed to save game</string>
<string name="failed_to_delete_game">Failed to delete game</string> <string name="failed_to_delete_game">Failed to delete game</string>