1 package com.hughes.android.dictionary;
4 import java.io.FileWriter;
5 import java.io.IOException;
6 import java.io.PrintWriter;
7 import java.io.RandomAccessFile;
8 import java.text.SimpleDateFormat;
9 import java.util.Arrays;
10 import java.util.Date;
11 import java.util.concurrent.Executor;
12 import java.util.concurrent.Executors;
13 import java.util.concurrent.atomic.AtomicBoolean;
15 import android.app.ListActivity;
16 import android.content.Context;
17 import android.content.Intent;
18 import android.content.SharedPreferences;
19 import android.content.SharedPreferences.Editor;
20 import android.graphics.Typeface;
21 import android.os.Bundle;
22 import android.os.Handler;
23 import android.preference.PreferenceManager;
24 import android.text.ClipboardManager;
25 import android.text.Editable;
26 import android.text.Spannable;
27 import android.text.TextWatcher;
28 import android.text.style.StyleSpan;
29 import android.util.Log;
30 import android.view.ContextMenu;
31 import android.view.KeyEvent;
32 import android.view.Menu;
33 import android.view.MenuItem;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.view.ContextMenu.ContextMenuInfo;
37 import android.view.MenuItem.OnMenuItemClickListener;
38 import android.view.View.OnClickListener;
39 import android.view.inputmethod.InputMethodManager;
40 import android.widget.AdapterView;
41 import android.widget.BaseAdapter;
42 import android.widget.Button;
43 import android.widget.EditText;
44 import android.widget.ListView;
45 import android.widget.TableLayout;
46 import android.widget.TableRow;
47 import android.widget.TextView;
48 import android.widget.Toast;
50 import com.hughes.android.dictionary.engine.Dictionary;
51 import com.hughes.android.dictionary.engine.Language;
52 import com.ibm.icu.text.Collator;
54 public class DictionaryActivityOld extends ListActivity {
57 // * Easy reverse lookup.
58 // * Download latest dicts.
59 // * http://ftp.tu-chemnitz.de/pub/Local/urz/ding/de-en-devel/
60 // * http://www1.dict.cc/translation_file_request.php?l=e
61 // * Compress all the strings everywhere, put compression table in file.
63 // * Only one way to way for current search to end. (won't do).
65 static final String LOG = "QuickDic";
66 static final String PREF_DICT_ACTIVE_LANG = "DICT_DIR_PREF";
67 static final String PREF_ACTIVE_SEARCH_TEXT = "ACTIVE_WORD_PREF";
70 final Handler uiHandler = new Handler();
71 private final Executor searchExecutor = Executors.newSingleThreadExecutor();
75 int lastSelectedRow = 0; // TODO: I'm evil.
77 private boolean prefsMightHaveChanged = true;
80 private File wordList = null;
81 private RandomAccessFile dictRaf = null;
82 private Dictionary dictionary = null;
83 private boolean saveOnlyFirstSubentry = false;
85 // Visible for testing.
86 IndexAdapter indexAdapter = null;
87 private SearchOperation searchOperation = null;
89 public DictionaryActivity() {
91 searchExecutor.execute(new Runnable() {
93 final long startMillis = System.currentTimeMillis();
94 for (final String lang : Arrays.asList("EN", "DE")) {
95 Language.lookup(lang).getFindCollator();
96 final Collator c = Language.lookup(lang).getSortCollator();
97 if (c.compare("pre-print", "preppy") >= 0) {
98 Log.e(LOG, c.getClass() + " is buggy, lookups may not work properly.");
101 Log.d(LOG, "Loading collators took:" + (System.currentTimeMillis() - startMillis));
107 /** Called when the activity is first created. */
109 public void onCreate(Bundle savedInstanceState) {
110 super.onCreate(savedInstanceState);
111 Log.d(LOG, "onCreate:" + this);
114 initDictionaryAndPrefs();
115 } catch (Exception e) {
121 setContentView(R.layout.dictionary_activity);
122 searchText = (EditText) findViewById(R.id.SearchText);
123 langButton = (Button) findViewById(R.id.LangButton);
125 searchText.addTextChangedListener(new SearchTextWatcher());
127 getListView().setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
128 public void onItemSelected(AdapterView<?> arg0, View arg1, int row,
132 public void onNothingSelected(AdapterView<?> arg0) {
136 getListView().setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
137 public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int row,
144 final Button clearSearchTextButton = (Button) findViewById(R.id.ClearSearchTextButton);
145 clearSearchTextButton.setOnClickListener(new OnClickListener() {
146 public void onClick(View v) {
147 onClearSearchTextButton(clearSearchTextButton);
150 clearSearchTextButton.setVisibility(PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
151 getString(R.string.showClearSearchTextButtonKey), true) ? View.VISIBLE
154 final Button langButton = (Button) findViewById(R.id.LangButton);
155 langButton.setOnClickListener(new OnClickListener() {
156 public void onClick(View v) {
161 final Button upButton = (Button) findViewById(R.id.UpButton);
162 upButton.setOnClickListener(new OnClickListener() {
163 public void onClick(View v) {
167 final Button downButton = (Button) findViewById(R.id.DownButton);
168 downButton.setOnClickListener(new OnClickListener() {
169 public void onClick(View v) {
175 registerForContextMenu(getListView());
180 private void initDictionaryAndPrefs() throws Exception {
181 if (!prefsMightHaveChanged) {
184 closeCurrentDictionary();
186 final SharedPreferences prefs = PreferenceManager
187 .getDefaultSharedPreferences(this);
188 wordList = new File(prefs.getString(getString(R.string.wordListFileKey),
189 getString(R.string.wordListFileDefault)));
190 Log.d(LOG, "wordList=" + wordList);
192 saveOnlyFirstSubentry = prefs.getBoolean(getString(R.string.saveOnlyFirstSubentryKey), false);
194 final File dictFile = new File(prefs.getString(getString(R.string.dictFileKey),
195 getString(R.string.dictFileDefault)));
196 Log.d(LOG, "dictFile=" + dictFile);
199 if (!dictFile.canRead()) {
200 throw new IOException("Unable to read dictionary file.");
203 dictRaf = new RandomAccessFile(dictFile, "r");
204 final long startMillis = System.currentTimeMillis();
205 dictionary = new Dictionary(dictRaf);
206 Log.d(LOG, "Read dictionary millis: " + (System.currentTimeMillis() - startMillis));
207 } catch (IOException e) {
208 Log.e(LOG, "Couldn't open dictionary.", e);
210 this.startActivity(new asdfIntent(this, DictionaryEditActivity.class));
214 final byte lang = prefs.getInt(PREF_DICT_ACTIVE_LANG, SimpleEntry.LANG1) == SimpleEntry.LANG1 ? SimpleEntry.LANG1
217 indexAdapter = new IndexAdapter(dictionary.languageDatas[lang]);
218 setListAdapter(indexAdapter);
219 prefsMightHaveChanged = false;
223 public void onResume() {
225 Log.d(LOG, "onResume:" + this);
228 initDictionaryAndPrefs();
229 } catch (Exception e) {
233 final SharedPreferences prefs = PreferenceManager
234 .getDefaultSharedPreferences(this);
235 final String searchTextString = prefs
236 .getString(PREF_ACTIVE_SEARCH_TEXT, "");
237 searchText.setText(searchTextString);
238 getListView().requestFocus();
239 onSearchTextChange(searchTextString);
243 public void onPause() {
245 Log.d(LOG, "onPause:" + this);
246 final Editor prefs = PreferenceManager.getDefaultSharedPreferences(this)
248 prefs.putInt(PREF_DICT_ACTIVE_LANG, indexAdapter.languageData.lang);
249 prefs.putString(PREF_ACTIVE_SEARCH_TEXT, searchText.getText().toString());
254 public void onStop() {
256 Log.d(LOG, "onStop:" + this);
258 Log.i(LOG, "isFinishing()==true, closing dictionary.");
259 closeCurrentDictionary();
263 private void closeCurrentDictionary() {
264 Log.i(LOG, "closeCurrentDictionary");
265 if (dictionary == null) {
270 setListAdapter(null);
271 Log.d(LOG, "setListAdapter finished.");
274 if (dictRaf != null) {
277 } catch (IOException e) {
278 throw new RuntimeException(e);
283 public String getSelectedRowRawText(final boolean onlyFirstSubentry) {
284 final Row row = indexAdapter.languageData.rows.get(getSelectedRow());
285 return indexAdapter.languageData.rowToString(row, onlyFirstSubentry);
288 // ----------------------------------------------------------------
290 // ----------------------------------------------------------------
292 private MenuItem switchLanguageMenuItem = null;
295 public boolean onCreateOptionsMenu(final Menu menu) {
296 switchLanguageMenuItem = menu.add(getString(R.string.switchToLanguage));
297 switchLanguageMenuItem
298 .setOnMenuItemClickListener(new OnMenuItemClickListener() {
299 public boolean onMenuItemClick(final MenuItem menuItem) {
305 final MenuItem preferences = menu.add(getString(R.string.preferences));
306 preferences.setOnMenuItemClickListener(new OnMenuItemClickListener() {
307 public boolean onMenuItemClick(final MenuItem menuItem) {
308 prefsMightHaveChanged = true;
309 startActivity(new Intent(DictionaryActivity.this,
310 PreferenceActivity.class));
315 final MenuItem about = menu.add(getString(R.string.about));
316 about.setOnMenuItemClickListener(new OnMenuItemClickListener() {
317 public boolean onMenuItemClick(final MenuItem menuItem) {
318 final Intent intent = new Intent().setClassName(AboutActivity.class
319 .getPackage().getName(), AboutActivity.class.getCanonicalName());
320 final String currentDictInfo;
321 if (dictionary == null) {
322 currentDictInfo = getString(R.string.noDictLoaded);
324 final LanguageData lang0 = dictionary.languageDatas[0];
325 final LanguageData lang1 = dictionary.languageDatas[1];
326 currentDictInfo = getString(R.string.aboutText, dictionary.dictionaryInfo, dictionary.entries.size(),
327 lang0.language.symbol, lang0.sortedIndex.size(), lang0.rows.size(),
328 lang1.language.symbol, lang1.sortedIndex.size(), lang1.rows.size());
330 intent.putExtra(AboutActivity.CURRENT_DICT_INFO, currentDictInfo
332 startActivity(intent);
337 final MenuItem download = menu.add(getString(R.string.downloadDictionary));
338 download.setOnMenuItemClickListener(new OnMenuItemClickListener() {
339 public boolean onMenuItemClick(final MenuItem menuItem) {
340 prefsMightHaveChanged = true;
341 startDownloadDictActivity(DictionaryActivity.this);
350 public boolean onPrepareOptionsMenu(final Menu menu) {
351 switchLanguageMenuItem.setTitle(getString(R.string.switchToLanguage,
352 dictionary.languageDatas[SimpleEntry
353 .otherLang(indexAdapter.languageData.lang)].language.symbol));
354 return super.onPrepareOptionsMenu(menu);
357 void updateLangButton() {
358 langButton.setText(indexAdapter.languageData.language.symbol);
361 // ----------------------------------------------------------------
363 // ----------------------------------------------------------------
365 void onLanguageButton() {
367 indexAdapter = new IndexAdapter(
368 dictionary.languageDatas[(indexAdapter.languageData == dictionary.languageDatas[0]) ? 1
370 Log.d(LOG, "onLanguageButton, newLang=" + indexAdapter.languageData.language.symbol);
371 setListAdapter(indexAdapter);
373 onSearchTextChange(searchText.getText().toString());
377 final int destRowIndex = indexAdapter.languageData.getPrevTokenRow(getSelectedRow());
378 Log.d(LOG, "onUpButton, destRowIndex=" + destRowIndex);
379 jumpToRow(indexAdapter, destRowIndex);
382 void onDownButton() {
383 final int destRowIndex = indexAdapter.languageData.getNextTokenRow(getSelectedRow());
384 Log.d(LOG, "onDownButton, destRowIndex=" + destRowIndex);
385 jumpToRow(indexAdapter, destRowIndex);
388 void onAppendToWordList() {
389 final int row = getSelectedRow();
393 final StringBuilder rawText = new StringBuilder();
394 final String word = indexAdapter.languageData.getIndexEntryForRow(row).word;
396 new SimpleDateFormat("yyyy.MM.dd HH:mm:ss").format(new Date()))
398 rawText.append(word).append("\t");
399 rawText.append(getSelectedRowRawText(saveOnlyFirstSubentry));
400 Log.d(LOG, "Writing : " + rawText);
402 wordList.getParentFile().mkdirs();
403 final PrintWriter out = new PrintWriter(
404 new FileWriter(wordList, true));
405 out.println(rawText.toString());
407 } catch (IOException e) {
408 Log.e(LOG, "Unable to append to " + wordList.getAbsolutePath(), e);
409 Toast.makeText(this, getString(R.string.failedAddingToWordList, wordList.getAbsolutePath()), Toast.LENGTH_LONG);
415 final int row = getSelectedRow();
419 Log.d(LOG, "Copy." + DictionaryActivity.this.getSelectedRow());
420 final StringBuilder result = new StringBuilder();
421 result.append(getSelectedRowRawText(false));
422 final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
423 clipboardManager.setText(result.toString());
424 Log.d(LOG, "Copied: " + result);
428 public boolean onKeyDown(int keyCode, KeyEvent event) {
429 if (event.getUnicodeChar() != 0) {
430 if (!searchText.hasFocus()) {
431 searchText.setText("" + (char) event.getUnicodeChar());
432 onSearchTextChange(searchText.getText().toString());
433 searchText.requestFocus();
437 return super.onKeyDown(keyCode, event);
441 protected void onListItemClick(ListView l, View v, int row, long id) {
443 openContextMenu(getListView());
446 void onSearchTextChange(final String searchText) {
447 Log.d(LOG, "onSearchTextChange: " + searchText);
448 synchronized (this) {
449 searchOperation = new SearchOperation(indexAdapter, searchText.trim(), searchOperation);
450 searchExecutor.execute(searchOperation);
454 private void onClearSearchTextButton(final Button clearSearchTextButton) {
455 clearSearchTextButton.requestFocus();
456 searchText.setText("");
457 searchText.requestFocus();
458 Log.d(LOG, "Trying to show soft keyboard.");
459 final InputMethodManager manager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
460 manager.showSoftInput(searchText, InputMethodManager.SHOW_IMPLICIT);
463 // ----------------------------------------------------------------
465 // ----------------------------------------------------------------
468 public void onCreateContextMenu(ContextMenu menu, View v,
469 ContextMenuInfo menuInfo) {
470 final int row = getSelectedRow();
475 final MenuItem addToWordlist = menu.add(getString(R.string.addToWordList, wordList.getName()));
476 addToWordlist.setOnMenuItemClickListener(new OnMenuItemClickListener() {
477 public boolean onMenuItemClick(MenuItem item) {
478 onAppendToWordList();
483 final MenuItem copy = menu.add(android.R.string.copy);
484 copy.setOnMenuItemClickListener(new OnMenuItemClickListener() {
485 public boolean onMenuItemClick(MenuItem item) {
493 private void jumpToRow(final IndexAdapter dictionaryListAdapter,
494 final int rowIndex) {
495 Log.d(LOG, "jumpToRow: " + rowIndex);
496 if (dictionaryListAdapter != this.indexAdapter) {
497 Log.w(LOG, "skipping jumpToRow for old list adapter: " + rowIndex);
500 setSelection(rowIndex);
501 setSelectedRow(rowIndex);
502 getListView().setSelected(true);
505 // TODO: delete me somehow.
506 private int getSelectedRow() {
507 return lastSelectedRow;
509 private void setSelectedRow(final int row) {
510 lastSelectedRow = row;
511 Log.d(LOG, "Selected: " + getSelectedRowRawText(true));
515 private void updateSearchText() {
516 Log.d(LOG, "updateSearchText");
517 final int selectedRowIndex = getSelectedRow();
518 if (!searchText.hasFocus()) {
519 if (selectedRowIndex >= 0) {
520 final String word = indexAdapter.languageData
521 .getIndexEntryForRow(selectedRowIndex).word;
522 if (!word.equals(searchText.getText().toString())) {
523 Log.d(LOG, "updateSearchText: setText: " + word);
524 searchText.setText(word);
527 Log.w(LOG, "updateSearchText: nothing selected.");
532 static void startDownloadDictActivity(final Context context) {
533 final Intent intent = new Intent(context, DownloadActivity.class);
534 final SharedPreferences prefs = PreferenceManager
535 .getDefaultSharedPreferences(context);
536 final String dictFetchUrl = prefs.getString(context
537 .getString(R.string.dictFetchUrlKey), context
538 .getString(R.string.dictFetchUrlDefault));
539 final String dictFileName = prefs.getString(context
540 .getString(R.string.dictFileKey), context
541 .getString(R.string.dictFileDefault));
542 intent.putExtra(DownloadActivity.SOURCE, dictFetchUrl);
543 intent.putExtra(DownloadActivity.DEST, dictFileName);
544 context.startActivity(intent);
547 class IndexAdapter extends BaseAdapter {
549 // Visible for testing.
550 final LanguageData languageData;
552 IndexAdapter(final LanguageData languageData) {
553 this.languageData = languageData;
556 public int getCount() {
557 return languageData.rows.size();
560 public Dictionary.Row getItem(int rowIndex) {
561 assert rowIndex < languageData.rows.size();
562 return languageData.rows.get(rowIndex);
565 public long getItemId(int rowIndex) {
569 public View getView(final int rowIndex, final View convertView,
570 final ViewGroup parent) {
571 final Row row = getItem(rowIndex);
575 TextView result = null;
576 if (convertView instanceof TextView) {
577 result = (TextView) convertView;
579 result = new TextView(parent.getContext());
584 result.setText(languageData.rowToString(row, false));
585 result.setTextAppearance(parent.getContext(),
586 android.R.style.TextAppearance_Large);
587 result.setClickable(false);
592 final TableLayout result = new TableLayout(parent.getContext());
594 final SimpleEntry entry = new SimpleEntry(null, null);//.entries.get(row.getIndex());
595 final int rowCount = entry.getRowCount();
596 for (int r = 0; r < rowCount; ++r) {
597 final TableRow tableRow = new TableRow(result.getContext());
599 TextView column1 = new TextView(tableRow.getContext());
600 TextView column2 = new TextView(tableRow.getContext());
601 final TableRow.LayoutParams layoutParams = new TableRow.LayoutParams();
602 layoutParams.weight = 0.5f;
605 final TextView spacer = new TextView(tableRow.getContext());
606 spacer.setText(r == 0 ? "� " : " � ");
607 tableRow.addView(spacer);
609 tableRow.addView(column1, layoutParams);
611 final TextView spacer = new TextView(tableRow.getContext());
612 spacer.setText(r == 0 ? "� " : " � ");
613 tableRow.addView(spacer);
615 tableRow.addView(column2, layoutParams);
619 // column1.setTextAppearance(parent.getContext(), android.R.style.Text);
621 // TODO: color words by gender
622 final String col1Text = entry.getAllText(languageData.lang)[r];
623 column1.setText(col1Text, TextView.BufferType.SPANNABLE);
624 final Spannable col1Spannable = (Spannable) column1.getText();
626 final String token = languageData.getIndexEntryForRow(rowIndex).word;
627 while ((startPos = col1Text.indexOf(token, startPos)) != -1) {
628 col1Spannable.setSpan(new StyleSpan(Typeface.BOLD), startPos,
629 startPos + token.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
630 startPos += token.length();
634 entry.getAllText(SimpleEntry.otherLang(languageData.lang))[r],
635 TextView.BufferType.NORMAL);
637 result.addView(tableRow);
643 } // DictionaryListAdapter
645 private final class SearchOperation implements Runnable {
646 SearchOperation previousSearchOperation;
648 final IndexAdapter listAdapter;
649 final LanguageData languageData;
650 final String searchText;
651 final AtomicBoolean interrupted = new AtomicBoolean(false);
652 boolean searchFinished = false;
654 SearchOperation(final IndexAdapter listAdapter,
655 final String searchText, final SearchOperation previousSearchOperation) {
656 this.listAdapter = listAdapter;
657 this.languageData = listAdapter.languageData;
658 this.searchText = searchText;
659 this.previousSearchOperation = previousSearchOperation;
663 if (previousSearchOperation != null) {
664 previousSearchOperation.stopAndWait();
666 previousSearchOperation = null;
668 Log.d(LOG, "SearchOperation: " + searchText);
669 final int indexLocation = languageData.lookup(searchText, interrupted);
670 if (!interrupted.get()) {
671 final IndexEntry indexEntry = languageData.sortedIndex.get(indexLocation);
673 Log.d(LOG, "SearchOperation completed: " + indexEntry.toString());
674 uiHandler.post(new Runnable() {
676 // Check is just a performance operation.
677 if (!interrupted.get()) {
678 // This is safe, because it checks that the listAdapter hasn't changed.
679 jumpToRow(listAdapter, indexEntry.startRow);
681 synchronized (DictionaryActivity.this) {
682 searchOperation = null;
683 DictionaryActivity.this.notifyAll();
688 synchronized (this) {
689 searchFinished = true;
694 private void stopAndWait() {
695 interrupted.set(true);
696 synchronized (this) {
697 while (!searchFinished) {
698 Log.d(LOG, "stopAndWait: " + searchText);
701 } catch (InterruptedException e) {
702 Log.e(LOG, "Interrupted", e);
709 void waitForSearchEnd() {
710 synchronized (this) {
711 while (searchOperation != null) {
712 Log.d(LOG, "waitForSearchEnd");
715 } catch (InterruptedException e) {
716 Log.e(LOG, "Interrupted.", e);
722 private class SearchTextWatcher implements TextWatcher {
723 public void afterTextChanged(final Editable searchTextEditable) {
724 Log.d(LOG, "Search text changed: " + searchText.getText().toString());
725 if (searchText.hasFocus()) {
726 // If they were typing to cause the change, update the UI.
727 onSearchTextChange(searchText.getText().toString());
731 public void beforeTextChanged(CharSequence arg0, int arg1, int arg2,
735 public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {