X-Git-Url: http://gitweb.fperrin.net/?a=blobdiff_plain;f=src%2Fcom%2Fhughes%2Fandroid%2Fdictionary%2FDictionaryActivity.java;h=922b9728cda321c11c62614ba554414496ce8025;hb=b8734478a4b5c46cdb260fadad9753cdc85809ae;hp=7f156cfea69d776c5c927da37223c06712fe76a6;hpb=d3d2ec689514a90d73e35ddb6fb832fb42e3990d;p=Dictionary.git diff --git a/src/com/hughes/android/dictionary/DictionaryActivity.java b/src/com/hughes/android/dictionary/DictionaryActivity.java index 7f156cf..922b972 100644 --- a/src/com/hughes/android/dictionary/DictionaryActivity.java +++ b/src/com/hughes/android/dictionary/DictionaryActivity.java @@ -1,45 +1,69 @@ package com.hughes.android.dictionary; import java.io.File; +import java.io.FileWriter; import java.io.IOException; +import java.io.PrintWriter; import java.io.RandomAccessFile; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.concurrent.Executor; import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicBoolean; import android.app.ListActivity; +import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.graphics.Typeface; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; +import android.text.ClipboardManager; import android.text.Editable; import android.text.Spannable; import android.text.TextWatcher; import android.text.style.StyleSpan; import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; import android.view.View; -import android.view.ViewGroup; import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.ListAdapter; +import android.widget.ListView; import android.widget.TableLayout; import android.widget.TableRow; import android.widget.TextView; +import android.widget.Toast; import com.hughes.android.dictionary.engine.Dictionary; import com.hughes.android.dictionary.engine.Index; import com.hughes.android.dictionary.engine.PairEntry; +import com.hughes.android.dictionary.engine.PairEntry.Pair; import com.hughes.android.dictionary.engine.RowBase; import com.hughes.android.dictionary.engine.TokenRow; +import com.hughes.android.dictionary.engine.TransliteratorManager; import com.hughes.android.util.PersistentObjectCache; public class DictionaryActivity extends ListActivity { static final String LOG = "QuickDic"; + + static final int VIBRATE_MILLIS = 100; + int dictIndex = 0; RandomAccessFile dictRaf = null; Dictionary dictionary = null; int indexIndex = 0; @@ -47,7 +71,12 @@ public class DictionaryActivity extends ListActivity { // package for test. final Handler uiHandler = new Handler(); - private final Executor searchExecutor = Executors.newSingleThreadExecutor(); + private final Executor searchExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "searchExecutor"); + } + }); private SearchOperation currentSearchOperation = null; EditText searchText; @@ -59,53 +88,125 @@ public class DictionaryActivity extends ListActivity { // Visible for testing. ListAdapter indexAdapter = null; + + final SearchTextWatcher searchTextWatcher = new SearchTextWatcher(); + //private Vibrator vibrator = null; + + public DictionaryActivity() { + } - public static Intent getIntent(final int dictIndex, final int indexIndex, final String searchToken) { + public static Intent getIntent(final Context context, final int dictIndex, final int indexIndex, final String searchToken) { + setDictionaryPrefs(context, dictIndex, indexIndex, searchToken); + final Intent intent = new Intent(); intent.setClassName(DictionaryActivity.class.getPackage().getName(), DictionaryActivity.class.getName()); - intent.putExtra(C.DICT_INDEX, dictIndex); - intent.putExtra(C.INDEX_INDEX, indexIndex); - intent.putExtra(C.SEARCH_TOKEN, searchToken); return intent; } + public static void setDictionaryPrefs(final Context context, + final int dictIndex, final int indexIndex, final String searchToken) { + final SharedPreferences.Editor prefs = PreferenceManager.getDefaultSharedPreferences(context).edit(); + prefs.putInt(C.DICT_INDEX, dictIndex); + prefs.putInt(C.INDEX_INDEX, indexIndex); + prefs.putString(C.SEARCH_TOKEN, searchToken); + prefs.commit(); + } + + public static void clearDictionaryPrefs(final Context context) { + final SharedPreferences.Editor prefs = PreferenceManager.getDefaultSharedPreferences(context).edit(); + prefs.remove(C.DICT_INDEX); + prefs.remove(C.INDEX_INDEX); + prefs.remove(C.SEARCH_TOKEN); + prefs.commit(); + Log.d(LOG, "Removed default dictionary prefs."); + } + @Override public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + ((DictionaryApplication)getApplication()).applyTheme(this); - PersistentObjectCache.init(this); - QuickDicConfig quickDicConfig = PersistentObjectCache.init( - this).read(C.DICTIONARY_CONFIGS, QuickDicConfig.class); + super.onCreate(savedInstanceState); + Log.d(LOG, "onCreate:" + this); - final Intent intent = getIntent(); + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - final DictionaryConfig dictionaryConfig = quickDicConfig.dictionaryConfigs.get(intent.getIntExtra(C.DICT_INDEX, 0)); try { + PersistentObjectCache.init(this); + QuickDicConfig quickDicConfig = PersistentObjectCache.init( + this).read(C.DICTIONARY_CONFIGS, QuickDicConfig.class); + dictIndex = prefs.getInt(C.DICT_INDEX, 0) ; + final DictionaryConfig dictionaryConfig = quickDicConfig.dictionaryConfigs.get(dictIndex); dictRaf = new RandomAccessFile(dictionaryConfig.localFile, "r"); dictionary = new Dictionary(dictRaf); - } catch (IOException e) { + } catch (Exception e) { Log.e(LOG, "Unable to load dictionary.", e); - // TODO: Start up the editor. + if (dictRaf != null) { + try { + dictRaf.close(); + } catch (IOException e1) { + Log.e(LOG, "Unable to close dictRaf.", e1); + } + dictRaf = null; + } + Toast.makeText(this, getString(R.string.invalidDictionary, "", e.getMessage()), Toast.LENGTH_LONG); + startActivity(DictionaryEditActivity.getIntent(dictIndex)); finish(); return; } - - indexIndex = intent.getIntExtra(C.INDEX_INDEX, 0); + + indexIndex = prefs.getInt(C.INDEX_INDEX, 0) % dictionary.indices.size(); + Log.d(LOG, "Loading index."); index = dictionary.indices.get(indexIndex); setListAdapter(new IndexAdapter(index)); + + // Pre-load the collators. + searchExecutor.execute(new Runnable() { + public void run() { + final long startMillis = System.currentTimeMillis(); + + TransliteratorManager.init(new TransliteratorManager.Callback() { + @Override + public void onTransliteratorReady() { + uiHandler.post(new Runnable() { + @Override + public void run() { + onSearchTextChange(searchText.getText().toString()); + } + }); + } + }); + + for (final Index index : dictionary.indices) { + Log.d(LOG, "Starting collator load for lang=" + index.sortLanguage.getSymbol()); + + final com.ibm.icu.text.Collator c = index.sortLanguage.getCollator(); + if (c.compare("pre-print", "preppy") >= 0) { + Log.e(LOG, c.getClass() + + " is buggy, lookups may not work properly."); + } + } + Log.d(LOG, "Loading collators took:" + + (System.currentTimeMillis() - startMillis)); + } + }); + setContentView(R.layout.dictionary_activity); searchText = (EditText) findViewById(R.id.SearchText); langButton = (Button) findViewById(R.id.LangButton); - searchText.addTextChangedListener(new SearchTextWatcher()); - + searchText.requestFocus(); + searchText.addTextChangedListener(searchTextWatcher); + final String search = prefs.getString(C.SEARCH_TOKEN, ""); + searchText.setText(search); + searchText.setSelection(0, search.length()); + Log.d(LOG, "Trying to restore searchText=" + search); final Button clearSearchTextButton = (Button) findViewById(R.id.ClearSearchTextButton); clearSearchTextButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { - //onClearSearchTextButton(clearSearchTextButton); + onClearSearchTextButton(clearSearchTextButton); } }); clearSearchTextButton.setVisibility(PreferenceManager.getDefaultSharedPreferences(this).getBoolean( @@ -118,36 +219,118 @@ public class DictionaryActivity extends ListActivity { onLanguageButton(); } }); + updateLangButton(); final Button upButton = (Button) findViewById(R.id.UpButton); upButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { - //onUpButton(); + onUpDownButton(true); } }); final Button downButton = (Button) findViewById(R.id.DownButton); downButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { - //onDownButton(); + onUpDownButton(false); + } + }); + + getListView().setOnItemSelectedListener(new ListView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View arg1, final int position, + long id) { + if (!searchText.isFocused()) { + // TODO: don't do this if multi words are entered. + final RowBase row = (RowBase) getListAdapter().getItem(position); + Log.d(LOG, "onItemSelected: " + row.index()); + final TokenRow tokenRow = row.getTokenRow(true); + searchText.setText(tokenRow.getToken()); + } + } + + @Override + public void onNothingSelected(AdapterView arg0) { } }); // ContextMenu. registerForContextMenu(getListView()); - updateLangButton(); + // Prefs. + wordList = new File(prefs.getString(getString(R.string.wordListFileKey), + getString(R.string.wordListFileDefault))); + saveOnlyFirstSubentry = prefs.getBoolean(getString(R.string.saveOnlyFirstSubentryKey), false); + //if (prefs.getBoolean(getString(R.string.vibrateOnFailedSearchKey), true)) { + // vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); + //} + Log.d(LOG, "wordList=" + wordList + ", saveOnlyFirstSubentry=" + saveOnlyFirstSubentry); + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (dictRaf == null) { + return; + } + setDictionaryPrefs(this, dictIndex, indexIndex, searchText.getText().toString()); + + // Before we close the RAF, we have to wind the current search down. + if (currentSearchOperation != null) { + Log.d(LOG, "Interrupting search to shut down."); + final SearchOperation searchOperation = currentSearchOperation; + currentSearchOperation = null; + searchOperation.interrupted.set(true); + synchronized (searchOperation) { + while (!searchOperation.done) { + try { + searchOperation.wait(); + } catch (InterruptedException e) { + Log.d(LOG, "Interrupted.", e); + } + } + } + } + + try { + Log.d(LOG, "Closing RAF."); + dictRaf.close(); + } catch (IOException e) { + Log.e(LOG, "Failed to close dictionary", e); + } + dictRaf = null; + } + + // -------------------------------------------------------------------------- + // Buttons + // -------------------------------------------------------------------------- + private void onClearSearchTextButton(final Button clearSearchTextButton) { + clearSearchTextButton.requestFocus(); + searchText.setText(""); + searchText.requestFocus(); + Log.d(LOG, "Trying to show soft keyboard."); + final InputMethodManager manager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + manager.showSoftInput(searchText, InputMethodManager.SHOW_IMPLICIT); } void updateLangButton() { langButton.setText(index.shortName.toUpperCase()); } - - - void onLanguageButton() { - // TODO: synchronized, stop search. + if (currentSearchOperation != null) { + currentSearchOperation.interrupted.set(true); + currentSearchOperation = null; + } indexIndex = (indexIndex + 1) % dictionary.indices.size(); index = dictionary.indices.get(indexIndex); @@ -158,15 +341,199 @@ public class DictionaryActivity extends ListActivity { onSearchTextChange(searchText.getText().toString()); } + void onUpDownButton(final boolean up) { + final int firstVisibleRow = getListView().getFirstVisiblePosition(); + final RowBase row = index.rows.get(firstVisibleRow); + final TokenRow tokenRow = row.getTokenRow(true); + final int destIndexEntry; + if (up) { + if (row != tokenRow) { + destIndexEntry = tokenRow.referenceIndex; + } else { + destIndexEntry = Math.max(tokenRow.referenceIndex - 1, 0); + } + } else { + // Down + destIndexEntry = Math.min(tokenRow.referenceIndex + 1, index.sortedIndexEntries.size()); + } + final Index.IndexEntry dest = index.sortedIndexEntries.get(destIndexEntry); + Log.d(LOG, "onUpDownButton, destIndexEntry=" + dest.token); + searchText.removeTextChangedListener(searchTextWatcher); + searchText.setText(dest.token); + jumpToRow(index.sortedIndexEntries.get(destIndexEntry).startRow); + searchText.addTextChangedListener(searchTextWatcher); + } + + // -------------------------------------------------------------------------- + // Options Menu + // -------------------------------------------------------------------------- + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + + { + final MenuItem preferences = menu.add(getString(R.string.preferences)); + preferences.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(final MenuItem menuItem) { + startActivity(new Intent(DictionaryActivity.this, + PreferenceActivity.class)); + return false; + } + }); + } + + { + final MenuItem dictionaryList = menu.add(getString(R.string.dictionaryList)); + dictionaryList.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(final MenuItem menuItem) { + startActivity(DictionaryListActivity.getIntent(DictionaryActivity.this)); + finish(); + return false; + } + }); + } + + { + final MenuItem dictionaryEdit = menu.add(getString(R.string.editDictionary)); + dictionaryEdit.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(final MenuItem menuItem) { + final Intent intent = DictionaryEditActivity.getIntent(dictIndex); + startActivity(intent); + return false; + } + }); + } + + return true; + } + + + // -------------------------------------------------------------------------- + // Context Menu + clicks + // -------------------------------------------------------------------------- + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + AdapterContextMenuInfo adapterContextMenuInfo = (AdapterContextMenuInfo) menuInfo; + final RowBase row = (RowBase) getListAdapter().getItem(adapterContextMenuInfo.position); + + final MenuItem addToWordlist = menu.add(getString(R.string.addToWordList, wordList.getName())); + addToWordlist.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + onAppendToWordList(row); + return false; + } + }); + + final MenuItem copy = menu.add(android.R.string.copy); + copy.setOnMenuItemClickListener(new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + onCopy(row); + return false; + } + }); + + } + + @Override + protected void onListItemClick(ListView l, View v, int row, long id) { + openContextMenu(v); + } + + void onAppendToWordList(final RowBase row) { + final StringBuilder rawText = new StringBuilder(); + rawText.append( + new SimpleDateFormat("yyyy.MM.dd HH:mm:ss").format(new Date())) + .append("\t"); + rawText.append(index.longName).append("\t"); + rawText.append(row.getTokenRow(true).getToken()).append("\t"); + rawText.append(row.getRawText(saveOnlyFirstSubentry)); + Log.d(LOG, "Writing : " + rawText); + try { + wordList.getParentFile().mkdirs(); + final PrintWriter out = new PrintWriter( + new FileWriter(wordList, true)); + out.println(rawText.toString()); + out.close(); + } catch (IOException e) { + Log.e(LOG, "Unable to append to " + wordList.getAbsolutePath(), e); + Toast.makeText(this, getString(R.string.failedAddingToWordList, wordList.getAbsolutePath()), Toast.LENGTH_LONG); + } + return; + } + + void onCopy(final RowBase row) { + Log.d(LOG, "Copy, row=" + row); + final StringBuilder result = new StringBuilder(); + result.append(row.getRawText(false)); + final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + clipboardManager.setText(result.toString()); + Log.d(LOG, "Copied: " + result); + } + + @Override + public boolean onKeyDown(final int keyCode, final KeyEvent event) { + if (event.getUnicodeChar() != 0) { + if (!searchText.hasFocus()) { + searchText.setText("" + (char) event.getUnicodeChar()); + onSearchTextChange(searchText.getText().toString()); + searchText.requestFocus(); + } + return true; + } + if (keyCode == KeyEvent.KEYCODE_BACK) { + Log.d(LOG, "Clearing dictionary prefs."); + DictionaryActivity.clearDictionaryPrefs(this); + } + return super.onKeyDown(keyCode, event); + } + + // -------------------------------------------------------------------------- // SearchOperation // -------------------------------------------------------------------------- private void searchFinished(final SearchOperation searchOperation) { - if (searchOperation == this.currentSearchOperation) { - setSelection(searchOperation.tokenRow.index()); - getListView().setSelected(true); + if (searchOperation.interrupted.get()) { + Log.d(LOG, "Search operation was interrupted: " + searchOperation); + return; } + if (searchOperation != this.currentSearchOperation) { + Log.d(LOG, "Stale searchOperation finished: " + searchOperation); + return; + } + + final Index.IndexEntry searchResult = searchOperation.searchResult; + Log.d(LOG, "searchFinished: " + searchOperation + ", searchResult=" + searchResult); + + currentSearchOperation = null; + + uiHandler.postDelayed(new Runnable() { + @Override + public void run() { + if (currentSearchOperation == null) { + jumpToRow(searchResult.startRow); + } else { + Log.d(LOG, "More coming, waiting for currentSearchOperation."); + } + } + }, 50); + +// if (!searchResult.success) { +// if (vibrator != null) { +// vibrator.vibrate(VIBRATE_MILLIS); +// } +// searchText.setText(searchResult.longestPrefixString); +// searchText.setSelection(searchResult.longestPrefixString.length()); +// return; +// } + + } + + private final void jumpToRow(final int row) { + setSelection(row); + getListView().setSelected(true); } final class SearchOperation implements Runnable { @@ -175,25 +542,42 @@ public class DictionaryActivity extends ListActivity { final String searchText; final Index index; - boolean failed = false; - TokenRow tokenRow; + long searchStartMillis; + + Index.IndexEntry searchResult; + + boolean done = false; SearchOperation(final String searchText, final Index index) { this.searchText = searchText.trim(); this.index = index; } + + public String toString() { + return String.format("SearchOperation(%s,%s)", searchText, interrupted.toString()); + } @Override public void run() { - tokenRow = index.findInsertionPoint(searchText, interrupted); - failed = false; // TODO - if (!interrupted.get()) { - uiHandler.post(new Runnable() { - @Override - public void run() { - searchFinished(SearchOperation.this); - } - }); + try { + searchStartMillis = System.currentTimeMillis(); + searchResult = index.findInsertionPoint(searchText, interrupted); + Log.d(LOG, "searchText=" + searchText + ", searchDuration=" + + (System.currentTimeMillis() - searchStartMillis) + ", interrupted=" + + interrupted.get()); + if (!interrupted.get()) { + uiHandler.post(new Runnable() { + @Override + public void run() { + searchFinished(SearchOperation.this); + } + }); + } + } finally { + synchronized (this) { + done = true; + this.notifyAll(); + } } } } @@ -217,13 +601,13 @@ public class DictionaryActivity extends ListActivity { } @Override - public Object getItem(int position) { + public RowBase getItem(int position) { return index.rows.get(position); } @Override public long getItemId(int position) { - return position; + return getItem(position).index(); } @Override @@ -241,7 +625,7 @@ public class DictionaryActivity extends ListActivity { private View getView(PairEntry.Row row, ViewGroup parent) { final TableLayout result = new TableLayout(parent.getContext()); final PairEntry entry = row.getEntry(); - final int rowCount = entry.pairs.length; + final int rowCount = entry.pairs.size(); for (int r = 0; r < rowCount; ++r) { final TableRow tableRow = new TableRow(result.getContext()); @@ -267,7 +651,8 @@ public class DictionaryActivity extends ListActivity { column2.setWidth(1); // TODO: color words by gender - final String col1Text = index.swapPairEntries ? entry.pairs[r].lang2 : entry.pairs[r].lang1; + final Pair pair = entry.pairs.get(r); + final String col1Text = index.swapPairEntries ? pair.lang2 : pair.lang1; column1.setText(col1Text, TextView.BufferType.SPANNABLE); final Spannable col1Spannable = (Spannable) column1.getText(); @@ -279,7 +664,7 @@ public class DictionaryActivity extends ListActivity { startPos += token.length(); } - final String col2Text = index.swapPairEntries ? entry.pairs[r].lang1 : entry.pairs[r].lang2; + final String col2Text = index.swapPairEntries ? pair.lang1 : pair.lang2; column2.setText(col2Text, TextView.BufferType.NORMAL); result.addView(tableRow); @@ -301,19 +686,28 @@ public class DictionaryActivity extends ListActivity { // SearchText // -------------------------------------------------------------------------- - void onSearchTextChange(final String searchText) { - Log.d(LOG, "onSearchTextChange: " + searchText); + void onSearchTextChange(final String text) { + if (dictRaf == null) { + Log.d(LOG, "searchText changed during shutdown, doing nothing."); + return; + } + if (!searchText.isFocused()) { + Log.d(LOG, "searchText changed without focus, doing nothing."); + return; + } + Log.d(LOG, "onSearchTextChange: " + text); if (currentSearchOperation != null) { + Log.d(LOG, "Interrupting currentSearchOperation."); currentSearchOperation.interrupted.set(true); } - currentSearchOperation = new SearchOperation(searchText, index); + currentSearchOperation = new SearchOperation(text, index); searchExecutor.execute(currentSearchOperation); } private class SearchTextWatcher implements TextWatcher { public void afterTextChanged(final Editable searchTextEditable) { - Log.d(LOG, "Search text changed: " + searchText.getText()); if (searchText.hasFocus()) { + Log.d(LOG, "Search text changed with focus: " + searchText.getText()); // If they were typing to cause the change, update the UI. onSearchTextChange(searchText.getText().toString()); }