import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
-import android.app.AlertDialog;
import android.app.ListActivity;
import android.content.Context;
import android.content.Intent;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.View.OnClickListener;
+import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
+import android.widget.Toast;
import com.hughes.android.dictionary.Dictionary.IndexEntry;
import com.hughes.android.dictionary.Dictionary.LanguageData;
public class DictionaryActivity extends ListActivity {
// TODO:
- // * Only have one live SearchActivity, and a way to wait for it to die.
- // * Don't destroy dict unless we're really shutting down (not on screen rotate).
- // * Move (re-)init code to a method, set a flag if prefs might have changed, invoke re-init in onResume, which clears flag and reloads prefs.
+ // * Version magic number at end of dictionary, checked for correctness. (Throw exception in ctor, remember to check for it).
+ // * Localize "about" string if % codes
+ // * Toast when word gets added to word list.
// * Compress all the strings everywhere, put compression table in file.
+ // Done:
+ // * Only one way to way for current search to end. (won't do).
static final String LOG = "QuickDic";
static final String PREF_DICT_ACTIVE_LANG = "DICT_DIR_PREF";
// package for test.
final Handler uiHandler = new Handler();
+ private final Executor searchExecutor = Executors.newSingleThreadExecutor();
EditText searchText;
+ Button langButton;
+ int lastSelectedRow = 0;
- private final Executor searchExecutor = Executors.newSingleThreadExecutor();
+ private boolean prefsMightHaveChanged = true;
// Never null.
- private boolean prefsMightHaveChanged = true;
private File wordList;
-
private RandomAccessFile dictRaf = null;
private Dictionary dictionary = null;
// Visible for testing.
LanguageListAdapter languageList = null;
-
private SearchOperation searchOperation = null;
- private int selectedRowIndex;
- private int selectedTokenRowIndex;
-
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- Log.d(LOG, "onCreate");
+ Log.d(LOG, "onCreate:" + this);
if (Language.EN.sortCollator.compare("pre-print", "preppy") >= 0) {
Log
setContentView(R.layout.main);
searchText = (EditText) findViewById(R.id.SearchText);
-
+ langButton = (Button) findViewById(R.id.LangButton);
+
Log.d(LOG, "adding text changed listener");
searchText.addTextChangedListener(new SearchTextWatcher());
+
+ getListView().setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2,
+ long arg3) {
+ lastSelectedRow = arg2;
+ updateSearchText();
+ }
+ public void onNothingSelected(AdapterView<?> arg0) {
+ }
+ });
// Language button.
final Button langButton = (Button) findViewById(R.id.LangButton);
updateLangButton();
}
-
+
private void initDictionaryAndPrefs() {
if (!prefsMightHaveChanged) {
return;
dictRaf = new RandomAccessFile(dictFile, "r");
dictionary = new Dictionary(dictRaf);
} catch (Exception e) {
- throw new RuntimeException(e);
+ Log.e(LOG, "Couldn't open dictionary.", e);
+ return;
}
final byte lang = prefs.getInt(PREF_DICT_ACTIVE_LANG, Entry.LANG1) == Entry.LANG1 ? Entry.LANG1
@Override
public void onResume() {
super.onResume();
-
- if (prefsMightHaveChanged) {
-
- }
+ Log.d(LOG, "onResume:" + this);
+
+ initDictionaryAndPrefs();
final SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(this);
.getString(PREF_ACTIVE_SEARCH_TEXT, "");
searchText.setText(searchTextString);
onSearchTextChange(searchTextString);
+ getListView().requestFocus();
}
@Override
public void onPause() {
super.onPause();
+ Log.d(LOG, "onPause:" + this);
final Editor prefs = PreferenceManager.getDefaultSharedPreferences(this)
.edit();
prefs.putInt(PREF_DICT_ACTIVE_LANG, languageList.languageData.lang);
@Override
public void onStop() {
super.onStop();
+ Log.d(LOG, "onStop:" + this);
if (isFinishing()) {
+ Log.i(LOG, "isFinishing()==true, closing dictionary.");
closeCurrentDictionary();
}
}
private void closeCurrentDictionary() {
Log.i(LOG, "closeCurrentDictionary");
- if (searchOperation != null) {
- searchOperation.stopAndWait();
- searchOperation = null;
- }
+ waitForSearchEnd();
languageList = null;
setListAdapter(null);
dictionary = null;
}
public String getSelectedRowRawText() {
- final int row = getSelectedItemPosition();
+ final int row = getSelectedRow();
return row < 0 ? "" : languageList.languageData
.rowToString(languageList.languageData.rows.get(row));
}
@Override
public boolean onPrepareOptionsMenu(final Menu menu) {
- switchLanguageMenuItem.setTitle(String.format(
- getString(R.string.switchToLanguage), dictionary.languageDatas[Entry
+ switchLanguageMenuItem.setTitle(getString(R.string.switchToLanguage,
+ dictionary.languageDatas[Entry
.otherLang(languageList.languageData.lang)].language.symbol));
return super.onPrepareOptionsMenu(menu);
}
void updateLangButton() {
- final Button langButton = (Button) findViewById(R.id.LangButton);
langButton.setText(languageList.languageData.language.symbol);
}
// ----------------------------------------------------------------
void onLanguageButton() {
+ waitForSearchEnd();
languageList = new LanguageListAdapter(
dictionary.languageDatas[(languageList.languageData == dictionary.languageDatas[0]) ? 1
: 0]);
}
void onUpButton() {
- final int destRowIndex;
- final Row tokenRow = languageList.languageData.rows
- .get(selectedTokenRowIndex);
- assert tokenRow.isToken();
- final int prevTokenIndex = tokenRow.getIndex() - 1;
- if (selectedRowIndex == selectedTokenRowIndex && selectedRowIndex > 0) {
- destRowIndex = languageList.languageData.sortedIndex
- .get(prevTokenIndex).startRow;
- Log.d(LOG, "onUpButton, jumping back a word, destRowIndex=" + destRowIndex);
- } else {
- destRowIndex = selectedTokenRowIndex;
- Log.d(LOG, "onUpButton, jumping to top of word, destRowIndex=" + destRowIndex);
- }
+ final int destRowIndex = languageList.languageData.getPrevTokenRow(lastSelectedRow);
+ Log.d(LOG, "onUpButton, destRowIndex=" + destRowIndex);
jumpToRow(languageList, destRowIndex);
}
void onDownButton() {
- final Row tokenRow = languageList.languageData.rows
- .get(selectedTokenRowIndex);
- assert tokenRow.isToken();
- final int nextTokenIndex = tokenRow.getIndex() + 1;
- final int destRowIndex;
- if (nextTokenIndex < languageList.languageData.sortedIndex.size()) {
- destRowIndex = languageList.languageData.sortedIndex
- .get(nextTokenIndex).startRow;
- Log.d(LOG, "onDownButton, jumping down a word, destRowIndex=" + destRowIndex);
- } else {
- destRowIndex = languageList.languageData.rows.size() - 1;
- Log.d(LOG, "onDownButton, jumping to end of dict, destRowIndex=" + destRowIndex);
- }
+ final int destRowIndex = languageList.languageData.getNextTokenRow(lastSelectedRow);
+ Log.d(LOG, "onDownButton, destRowIndex=" + destRowIndex);
jumpToRow(languageList, destRowIndex);
}
void onAppendToWordList() {
- final int row = getSelectedItemPosition();
+ final int row = getSelectedRow();
if (row < 0) {
return;
}
out.close();
} catch (IOException e) {
Log.e(LOG, "Unable to append to " + wordList.getAbsolutePath(), e);
- final AlertDialog alert = new AlertDialog.Builder(
- DictionaryActivity.this).create();
- alert.setMessage("Failed to append to file: "
- + wordList.getAbsolutePath());
- alert.show();
+ Toast.makeText(this, getString(R.string.failedAddingToWordList, wordList.getAbsolutePath()), Toast.LENGTH_LONG);
}
return;
}
void onCopy() {
- final int row = getSelectedItemPosition();
+ final int row = getSelectedRow();
if (row < 0) {
return;
}
- Log.d(LOG, "Copy." + DictionaryActivity.this.getSelectedItemPosition());
+ Log.d(LOG, "Copy." + DictionaryActivity.this.getSelectedRow());
final StringBuilder result = new StringBuilder();
result.append(getSelectedRowRawText());
final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
void onSearchTextChange(final String searchText) {
Log.d(LOG, "onSearchTextChange: " + searchText);
- searchOperation = new SearchOperation(languageList, searchText, searchOperation);
- searchExecutor.execute(searchOperation);
+ synchronized (this) {
+ searchOperation = new SearchOperation(languageList, searchText, searchOperation);
+ searchExecutor.execute(searchOperation);
+ }
}
+
+
// ----------------------------------------------------------------
// ContextMenu
// ----------------------------------------------------------------
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
- final int row = getSelectedItemPosition();
+ final int row = getSelectedRow();
if (row < 0) {
return;
}
- final MenuItem addToWordlist = menu.add(String.format(
- getString(R.string.addToWordList), wordList.getName()));
+ final MenuItem addToWordlist = menu.add(getString(R.string.addToWordList, wordList.getName()));
addToWordlist.setOnMenuItemClickListener(new OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
onAppendToWordList();
Log.w(LOG, "skipping jumpToRow for old list adapter: " + rowIndex);
return;
}
- // selectedTokenRowIndex =
- // languageList.languageData.getIndexEntryForRow(rowIndex).startRow;
setSelection(rowIndex);
- getListView().setSelected(true); // TODO: is this doing anything?
+ lastSelectedRow = rowIndex; // TODO: find a way to delete this.
updateSearchText();
+ getListView().setSelected(true);
+ }
+
+ // TODO: delete me somehow.
+ private int getSelectedRow() {
+ return lastSelectedRow;
}
private void updateSearchText() {
Log.d(LOG, "updateSearchText");
+ final int selectedRowIndex = getSelectedRow();
if (!searchText.hasFocus()) {
- final String word = languageList.languageData
- .getIndexEntryForRow(selectedRowIndex).word;
- if (!word.equals(searchText.getText().toString())) {
- Log.d(LOG, "updateSearchText: setText: " + word);
- searchText.setText(word);
+ if (selectedRowIndex >= 0) {
+ final String word = languageList.languageData
+ .getIndexEntryForRow(selectedRowIndex).word;
+ if (!word.equals(searchText.getText().toString())) {
+ Log.d(LOG, "updateSearchText: setText: " + word);
+ searchText.setText(word);
+ }
+ } else {
+ Log.w(LOG, "updateSearchText: nothing selected.");
}
}
}
final LanguageData languageData;
final String searchText;
final AtomicBoolean interrupted = new AtomicBoolean(false);
- boolean finished = false;
+ boolean searchFinished = false;
SearchOperation(final LanguageListAdapter listAdapter,
final String searchText, final SearchOperation previousSearchOperation) {
Log.d(LOG, "SearchOperation: " + searchText);
final int indexLocation = languageData.lookup(searchText, interrupted);
- if (interrupted.get()) {
- return;
- }
- final IndexEntry indexEntry = languageData.sortedIndex.get(indexLocation);
-
- Log.d(LOG, "SearchOperation completed: " + indexEntry.toString());
- uiHandler.post(new Runnable() {
- public void run() {
- // Check is just a performance operation.
- if (!interrupted.get()) {
- // This is safe, because it checks that the listAdapter hasn't changed.
- jumpToRow(listAdapter, indexEntry.startRow);
+ if (!interrupted.get()) {
+ final IndexEntry indexEntry = languageData.sortedIndex.get(indexLocation);
+
+ Log.d(LOG, "SearchOperation completed: " + indexEntry.toString());
+ uiHandler.post(new Runnable() {
+ public void run() {
+ // Check is just a performance operation.
+ if (!interrupted.get()) {
+ // This is safe, because it checks that the listAdapter hasn't changed.
+ jumpToRow(listAdapter, indexEntry.startRow);
+ }
+ synchronized (DictionaryActivity.this) {
+ searchOperation = null;
+ DictionaryActivity.this.notifyAll();
+ }
}
- }
- });
-
+ });
+ }
synchronized (this) {
- finished = true;
+ searchFinished = true;
this.notifyAll();
}
}
- public void stopAndWait() {
+ private void stopAndWait() {
interrupted.set(true);
synchronized (this) {
- while (!finished) {
+ while (!searchFinished) {
Log.d(LOG, "stopAndWait: " + searchText);
try {
this.wait();
}
}
}
-
-
+ } // SearchOperation
+
+ void waitForSearchEnd() {
+ synchronized (this) {
+ while (searchOperation != null) {
+ try {
+ this.wait();
+ } catch (InterruptedException e) {
+ Log.e(LOG, "Interrupted.", e);
+ }
+ }
+ }
}
private class SearchTextWatcher implements TextWatcher {
final DictionaryActivity dict = getActivity();\r
dict.finish();\r
}\r
+ \r
+ abstract class NotifyRunnable implements Runnable {\r
+ boolean finished = false;\r
+ public final void run() {\r
+ assertEquals(false, finished);\r
+ run2();\r
+ synchronized (this) {\r
+ finished = true;\r
+ this.notifyAll();\r
+ }\r
+ }\r
+ public void waitForFinish() throws InterruptedException {\r
+ synchronized (this) {\r
+ while (!finished) {\r
+ this.wait();\r
+ }\r
+ finished = false;\r
+ }\r
+ getActivity().waitForSearchEnd();\r
+ }\r
+ protected abstract void run2();\r
+ }\r
\r
- public void testSwitchLanguage() throws Exception {\r
+ private void postAndWait(final NotifyRunnable notifyRunnable) throws Exception {\r
+ getActivity().uiHandler.post(notifyRunnable);\r
+ notifyRunnable.waitForFinish();\r
+ }\r
\r
+ public void resetDictionary() throws Exception {\r
final DictionaryActivity dict = getActivity();\r
-\r
- final Runnable switchLang = new Runnable() {\r
- public void run() {\r
- getActivity().onLanguageButton();\r
- }};\r
-\r
+ \r
if (dict.languageList.languageData.language == Language.EN) {\r
- dict.uiHandler.post(switchLang);\r
- Thread.sleep(100);\r
+ postAndWait(switchLangRunnable());\r
}\r
- \r
assertEquals(Language.DE, dict.languageList.languageData.language);\r
- \r
- dict.uiHandler.post(switchLang);\r
- Thread.sleep(100);\r
+\r
+ postAndWait(new NotifyRunnable() {\r
+ protected void run2() {\r
+ dict.searchText.setText("");\r
+ dict.onSearchTextChange("");\r
+ }\r
+ });\r
+ }\r
+\r
+ public void testSwitchLanguage() throws Exception {\r
+ final DictionaryActivity dict = getActivity();\r
+ resetDictionary();\r
+\r
+ final NotifyRunnable switchLang = switchLangRunnable();\r
+\r
+ postAndWait(switchLang);\r
assertEquals(Language.EN, dict.languageList.languageData.language);\r
+ assertEquals("EN", dict.langButton.getText().toString());\r
\r
- dict.uiHandler.post(switchLang);\r
- Thread.sleep(200);\r
+ postAndWait(switchLang);\r
assertEquals(Language.DE, dict.languageList.languageData.language);\r
+ assertEquals("DE", dict.langButton.getText().toString());\r
\r
dict.finish();\r
}\r
\r
+ public void testUpDownArrows() throws Exception {\r
+ final DictionaryActivity dict = getActivity();\r
+ resetDictionary();\r
+ assertEquals(0, dict.getSelectedItemPosition());\r
+ \r
+ final NotifyRunnable upButton = new NotifyRunnable() {\r
+ protected void run2() {\r
+ dict.onUpButton();\r
+ }\r
+ };\r
+ final NotifyRunnable downButton = new NotifyRunnable() {\r
+ protected void run2() {\r
+ dict.onDownButton();\r
+ }\r
+ };\r
+ \r
+ dict.getListView().requestFocus();\r
+ assertTrue(dict.getListView().isFocused());\r
+ \r
+ String word1 = "-1";\r
+ String word2 = "-14";\r
+ String word3 = "-15";\r
+\r
+ postAndWait(upButton);\r
+ assertEquals(0, dict.getSelectedItemPosition());\r
+ assertEquals(word1, dict.searchText.getText().toString());\r
+\r
+ postAndWait(downButton);\r
+ assertEquals(2, dict.getSelectedItemPosition());\r
+ assertEquals(word2, dict.searchText.getText().toString());\r
+ \r
+ postAndWait(downButton);\r
+ assertEquals(4, dict.getSelectedItemPosition());\r
+ assertEquals(word3, dict.searchText.getText().toString());\r
+\r
+ postAndWait(upButton);\r
+ assertEquals(2, dict.getSelectedItemPosition());\r
+ assertEquals(word2, dict.searchText.getText().toString());\r
+ \r
+ postAndWait(upButton);\r
+ assertEquals(0, dict.getSelectedItemPosition());\r
+ assertEquals(word1, dict.searchText.getText().toString());\r
+ \r
+ postAndWait(upButton);\r
+ assertEquals(0, dict.getSelectedItemPosition());\r
+\r
+ postAndWait(downButton);\r
+ assertEquals(2, dict.getSelectedItemPosition());\r
+\r
+ dict.finish();\r
+ }\r
+\r
+ private NotifyRunnable switchLangRunnable() {\r
+ final NotifyRunnable switchLang = new NotifyRunnable() {\r
+ public void run2() {\r
+ getActivity().onLanguageButton();\r
+ }};\r
+ return switchLang;\r
+ }\r
+\r
}\r
import java.util.List;\r
import java.util.Map;\r
import java.util.Set;\r
+import java.util.regex.Matcher;\r
import java.util.regex.Pattern;\r
\r
import com.hughes.util.raf.RAFFactory;\r
return lang == LANG1 ? LANG2 : LANG1;\r
}\r
\r
-\r
+/*\r
+Lu Letter, Uppercase\r
+Ll Letter, Lowercase\r
+Lt Letter, Titlecase\r
+Lm Letter, Modifier\r
+Lo Letter, Other\r
+Mn Mark, Nonspacing\r
+Mc Mark, Spacing Combining\r
+Me Mark, Enclosing\r
+Nd Number, Decimal Digit\r
+Nl Number, Letter\r
+No Number, Other\r
+Pc Punctuation, Connector\r
+Pd Punctuation, Dash\r
+Ps Punctuation, Open\r
+Pe Punctuation, Close\r
+Pi Punctuation, Initial quote (may behave like Ps or Pe depending on usage)\r
+Pf Punctuation, Final quote (may behave like Ps or Pe depending on usage)\r
+Po Punctuation, Other\r
+Sm Symbol, Math\r
+Sc Symbol, Currency\r
+Sk Symbol, Modifier\r
+So Symbol, Other\r
+Zs Separator, Space\r
+Zl Separator, Line\r
+Zp Separator, Paragraph\r
+*/\r
+\r
+ static Pattern htmlDecimalCode = Pattern.compile("&#([0-9]+);");\r
+ static Pattern htmlCode = Pattern.compile("&#[^;]+;");\r
+ \r
static Entry parseFromLine(String line, final boolean hasMultipleSubentries) {\r
+ \r
line = line.replaceAll("<", "<");\r
line = line.replaceAll(">", ">");\r
+ Matcher matcher;\r
+ while ((matcher = htmlDecimalCode.matcher(line)).find()) {\r
+ final int intVal = Integer.parseInt(matcher.group(1));\r
+ final String charCode = "" + ((char) intVal);\r
+ System.out.println("Replacing " + matcher.group() + " with " + charCode);\r
+ line = matcher.replaceAll(charCode);\r
+ }\r
+ if ((matcher = htmlCode.matcher(line)).find()) {\r
+ System.err.println("HTML code: " + matcher.group());\r
+ }\r
+ \r
final String[] parts = lineSplitPattern.split(line);\r
if (parts.length != 2) {\r
System.err.println("Entry:" + "Invalid line: " + line);\r
bracketToClose.put(" '", "' ");\r
}\r
\r
- static final Pattern WHITESPACE = Pattern.compile("\\s+");\r
+ // This used to be called WHITESPACE.\r
+ static final Pattern NON_TOKEN_CHAR = Pattern.compile("\\s+");\r
\r
public Set<String> getIndexableTokens(final byte lang) {\r
final Set<String> result = new LinkedHashSet<String>();\r
text = text.replaceAll("\"-", "-");\r
text = text.replaceAll("-\"", "-");\r
text = text.replaceAll("[\"/\\()<>\\[\\],;?!.]", " ");\r
- text = text.replaceAll("[:] ", " ");\r
- text = text.replaceAll(" [:]", " ");\r
+ text = text.replaceAll("[-:] ", " ");\r
+ text = text.replaceAll(" [-:]", " ");\r
\r
// Now be really conservative about what we allow inside a token:\r
// See: http://unicode.org/Public/UNIDATA/UCD.html#General_Category_Values\r
- text = text.replaceAll("[^-:\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nd}\\p{Nl}\\p{No}]", " ");\r
- \r
- result.addAll(Arrays.asList(WHITESPACE.split(text)));\r
+ text = text.replaceAll("[^-:\\p{L}\\p{N}\\p{S}]", " ");\r
+ result.addAll(Arrays.asList(NON_TOKEN_CHAR.split(text)));\r
\r
text = text.replaceAll("[-]", " ");\r
- result.addAll(Arrays.asList(WHITESPACE.split(text)));\r
+ result.addAll(Arrays.asList(NON_TOKEN_CHAR.split(text)));\r
\r
final Set<String> result2 = new LinkedHashSet<String>();\r
for (final String token : result) {\r