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;
10 import java.util.concurrent.Executor;
11 import java.util.concurrent.Executors;
12 import java.util.concurrent.atomic.AtomicBoolean;
14 import android.app.AlertDialog;
15 import android.app.ListActivity;
16 import android.content.Context;
17 import android.content.DialogInterface;
18 import android.content.Intent;
19 import android.content.SharedPreferences;
20 import android.content.SharedPreferences.Editor;
21 import android.graphics.Typeface;
22 import android.os.Bundle;
23 import android.os.Handler;
24 import android.preference.PreferenceManager;
25 import android.text.ClipboardManager;
26 import android.text.Editable;
27 import android.text.Spannable;
28 import android.text.TextWatcher;
29 import android.text.style.StyleSpan;
30 import android.util.Log;
31 import android.view.ContextMenu;
32 import android.view.KeyEvent;
33 import android.view.Menu;
34 import android.view.MenuItem;
35 import android.view.View;
36 import android.view.ViewGroup;
37 import android.view.ContextMenu.ContextMenuInfo;
38 import android.view.MenuItem.OnMenuItemClickListener;
39 import android.view.View.OnClickListener;
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.AdapterView.OnItemLongClickListener;
49 import android.widget.AdapterView.OnItemSelectedListener;
51 import com.hughes.android.dictionary.Dictionary.IndexEntry;
52 import com.hughes.android.dictionary.Dictionary.LanguageData;
53 import com.hughes.android.dictionary.Dictionary.Row;
55 public class DictionaryActivity extends ListActivity {
57 static final Intent preferencesIntent = new Intent().setClassName(PreferenceActivity.class.getPackage().getName(), PreferenceActivity.class.getCanonicalName());
59 static final String LOG = "QuickDic";
60 static final String PREF_DICT_ACTIVE_LANG = "DICT_DIR_PREF";
61 static final String PREF_ACTIVE_SEARCH_TEXT = "ACTIVE_WORD_PREF";
63 private final Handler uiHandler = new Handler();
64 private final Executor searchExecutor = Executors.newSingleThreadExecutor();
65 private final DictionaryListAdapter dictionaryListAdapter = new DictionaryListAdapter();
68 private File wordList;
71 private File dictFile = null;
72 private RandomAccessFile dictRaf = null;
73 private Dictionary dictionary = null;
74 private LanguageData activeLanguageData = null;
76 private SearchOperation searchOperation = null;
77 private int selectedRowIndex = -1;
78 private int selectedTokenRowIndex = -1;
81 /** Called when the activity is first created. */
83 public void onCreate(Bundle savedInstanceState) {
84 super.onCreate(savedInstanceState);
85 Log.d(LOG, "onCreate");
87 if (Language.EN.sortCollator.compare("preppy", "pre-print") >= 0) {
88 Log.e(LOG, "Android java.text.Collator is buggy, lookups may not work properly.");
91 setContentView(R.layout.main);
93 getSearchText().addTextChangedListener(new SearchTextWatcher());
95 setListAdapter(dictionaryListAdapter);
98 final Button langButton = (Button) findViewById(R.id.LangButton);
99 langButton.setOnClickListener(new OnClickListener() {
100 public void onClick(View v) {
104 final Button upButton = (Button) findViewById(R.id.UpButton);
105 upButton.setOnClickListener(new OnClickListener() {
106 public void onClick(View v) {
107 if (dictionary == null) {
110 final int destRowIndex;
111 final Row tokenRow = activeLanguageData.rows.get(selectedTokenRowIndex);
112 assert tokenRow.isToken();
113 final int prevTokenIndex = tokenRow.getIndex() - 1;
114 if (selectedRowIndex == selectedTokenRowIndex && selectedRowIndex > 0) {
115 destRowIndex = activeLanguageData.sortedIndex.get(prevTokenIndex).startRow;
117 destRowIndex = selectedTokenRowIndex;
119 jumpToRow(destRowIndex);
122 final Button downButton = (Button) findViewById(R.id.DownButton);
123 downButton.setOnClickListener(new OnClickListener() {
124 public void onClick(View v) {
125 if (dictionary == null) {
128 final Row tokenRow = activeLanguageData.rows.get(selectedTokenRowIndex);
129 assert tokenRow.isToken();
130 final int nextTokenIndex = tokenRow.getIndex() + 1;
131 final int destRowIndex;
132 if (nextTokenIndex < activeLanguageData.sortedIndex.size()) {
133 destRowIndex = activeLanguageData.sortedIndex.get(nextTokenIndex).startRow;
135 destRowIndex = activeLanguageData.rows.size() - 1;
137 jumpToRow(destRowIndex);
141 registerForContextMenu(getListView());
143 // ItemSelectedListener.
144 getListView().setOnItemSelectedListener(new OnItemSelectedListener() {
145 public void onItemSelected(AdapterView<?> arg0, View arg1, int rowIndex,
147 if (activeLanguageData == null) {
150 Log.d(LOG, "onItemSelected: " + rowIndex);
151 selectedRowIndex = rowIndex;
152 selectedTokenRowIndex = activeLanguageData.getIndexEntryForRow(rowIndex).startRow;
156 public void onNothingSelected(AdapterView<?> arg0) {
160 // LongClickListener.
161 getListView().setOnItemLongClickListener((new OnItemLongClickListener() {
162 public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int rowIndex,
164 selectedRowIndex = rowIndex;
172 public void onResume() {
175 // Have to close, because we might have downloaded a new copy of the dictionary.
176 closeCurrentDictionary();
178 final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
179 wordList = new File(prefs.getString(getString(R.string.wordListFileKey), getString(R.string.wordListFileDefault)));
180 final File newDictFile = new File(prefs.getString(getString(R.string.dictFileKey), getString(R.string.dictFileDefault)));
181 dictFile = newDictFile;
182 Log.d(LOG, "wordList=" + wordList);
183 Log.d(LOG, "dictFile=" + dictFile);
185 if (!dictFile.canRead()) {
186 dictionaryListAdapter.notifyDataSetChanged();
187 Log.d(LOG, "Unable to read dictionary file.");
188 final AlertDialog alert = new AlertDialog.Builder(DictionaryActivity.this).create();
189 alert.setMessage(String.format(getString(R.string.unableToReadDictionaryFile), dictFile.getAbsolutePath()));
190 alert.setButton(getString(R.string.downloadDictionary), new DialogInterface.OnClickListener() {
191 public void onClick(DialogInterface dialog, int which) {
192 startDownloadDictActivity();
198 final byte lang = prefs.getInt(PREF_DICT_ACTIVE_LANG, Entry.LANG1) == Entry.LANG1 ? Entry.LANG1 : Entry.LANG2;
201 dictRaf = new RandomAccessFile(dictFile, "r");
202 dictionary = new Dictionary(dictRaf);
203 activeLanguageData = dictionary.languageDatas[lang];
204 dictionaryListAdapter.notifyDataSetChanged();
205 } catch (Exception e) {
206 throw new RuntimeException(e);
211 final String searchText = prefs.getString(PREF_ACTIVE_SEARCH_TEXT, "");
212 getSearchText().setText(searchText);
213 onSearchTextChange(searchText);
217 public void onPause() {
219 if (activeLanguageData != null) {
220 final Editor prefs = PreferenceManager.getDefaultSharedPreferences(this).edit();
221 prefs.putInt(PREF_DICT_ACTIVE_LANG, activeLanguageData.lang);
222 prefs.putString(PREF_ACTIVE_SEARCH_TEXT, getSearchText().getText().toString());
229 public void onStop() {
231 closeCurrentDictionary();
234 private void closeCurrentDictionary() {
236 activeLanguageData = null;
238 if (dictRaf != null) {
241 } catch (IOException e) {
242 throw new RuntimeException(e);
247 public String getSelectedRowRawText() {
248 return activeLanguageData.rowToString(activeLanguageData.rows.get(selectedRowIndex));
251 public EditText getSearchText() {
252 return (EditText) findViewById(R.id.SearchText);
255 // ----------------------------------------------------------------
257 // ----------------------------------------------------------------
259 private MenuItem switchLanguageMenuItem = null;
263 public boolean onCreateOptionsMenu(final Menu menu) {
264 switchLanguageMenuItem = menu.add(getString(R.string.switchToLanguage));
265 switchLanguageMenuItem.setOnMenuItemClickListener(new OnMenuItemClickListener(){
266 public boolean onMenuItemClick(final MenuItem menuItem) {
271 final MenuItem preferences = menu.add(getString(R.string.preferences));
272 preferences.setOnMenuItemClickListener(new OnMenuItemClickListener(){
273 public boolean onMenuItemClick(final MenuItem menuItem) {
274 startActivity(preferencesIntent);
278 final MenuItem about = menu.add(getString(R.string.about));
279 about.setOnMenuItemClickListener(new OnMenuItemClickListener(){
280 public boolean onMenuItemClick(final MenuItem menuItem) {
281 final Intent intent = new Intent().setClassName(AboutActivity.class.getPackage().getName(), AboutActivity.class.getCanonicalName());
282 final StringBuilder currentDictInfo = new StringBuilder();
283 if (dictionary == null) {
284 currentDictInfo.append(getString(R.string.noDictLoaded));
286 currentDictInfo.append(dictionary.dictionaryInfo).append("\n\n");
287 currentDictInfo.append("Entry count: " + dictionary.entries.size()).append("\n");
288 for (int i = 0; i < 2; ++i) {
289 final LanguageData languageData = dictionary.languageDatas[i];
290 currentDictInfo.append(languageData.language.symbol).append(":\n");
291 currentDictInfo.append(" Unique token count: " + languageData.sortedIndex.size()).append("\n");
292 currentDictInfo.append(" Row count: " + languageData.rows.size()).append("\n");
295 intent.putExtra(AboutActivity.CURRENT_DICT_INFO, currentDictInfo.toString());
296 startActivity(intent);
300 final MenuItem download = menu.add(getString(R.string.downloadDictionary));
301 download.setOnMenuItemClickListener(new OnMenuItemClickListener(){
302 public boolean onMenuItemClick(final MenuItem menuItem) {
303 startDownloadDictActivity();
311 public boolean onPrepareOptionsMenu(final Menu menu) {
312 if (dictionary != null) {
313 switchLanguageMenuItem.setTitle(String.format(
314 getString(R.string.switchToLanguage), dictionary.languageDatas[Entry
315 .otherLang(activeLanguageData.lang)].language.symbol));
317 switchLanguageMenuItem.setEnabled(dictionary != null);
318 return super.onPrepareOptionsMenu(menu);
321 void switchLanguage() {
322 if (dictionary == null) {
325 activeLanguageData = dictionary.languageDatas[(activeLanguageData == dictionary.languageDatas[0]) ? 1 : 0];
326 selectedRowIndex = 0;
327 selectedTokenRowIndex = 0;
329 dictionaryListAdapter.notifyDataSetChanged();
330 onSearchTextChange(getSearchText().getText().toString());
333 void updateLangButton() {
334 final Button langButton = (Button) findViewById(R.id.LangButton);
335 langButton.setText(activeLanguageData.language.symbol);
338 // ----------------------------------------------------------------
340 // ----------------------------------------------------------------
343 public void onCreateContextMenu(ContextMenu menu, View v,
344 ContextMenuInfo menuInfo) {
345 if (selectedRowIndex == -1) {
349 final MenuItem addToWordlist = menu.add(String.format(getString(R.string.addToWordList), wordList.getName()));
350 addToWordlist.setOnMenuItemClickListener(new OnMenuItemClickListener() {
351 public boolean onMenuItemClick(MenuItem item) {
352 final StringBuilder rawText = new StringBuilder();
353 final String word = activeLanguageData.getIndexEntryForRow(selectedRowIndex).word;
354 rawText.append(new SimpleDateFormat("yyyy.MM.dd HH:mm:ss").format(new Date())).append("\t");
355 rawText.append(word).append("\t");
356 rawText.append(getSelectedRowRawText());
357 Log.d(LOG, "Writing : " + rawText);
359 wordList.getParentFile().mkdirs();
360 final PrintWriter out = new PrintWriter(new FileWriter(wordList, true));
361 out.println(rawText.toString());
363 } catch (IOException e) {
364 Log.e(LOG, "Unable to append to " + wordList.getAbsolutePath(), e);
365 final AlertDialog alert = new AlertDialog.Builder(DictionaryActivity.this).create();
366 alert.setMessage("Failed to append to file: " + wordList.getAbsolutePath());
373 final MenuItem copy = menu.add(android.R.string.copy);
374 copy.setOnMenuItemClickListener(new OnMenuItemClickListener() {
375 public boolean onMenuItemClick(MenuItem item) {
377 final StringBuilder result = new StringBuilder();
378 result.append(getSelectedRowRawText());
379 final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
380 clipboardManager.setText(result.toString());
388 public boolean onKeyDown(int keyCode, KeyEvent event) {
389 if (event.getUnicodeChar() != 0) {
390 final EditText searchText = getSearchText();
391 if (!searchText.hasFocus()) {
392 searchText.setText("" + (char)event.getUnicodeChar());
393 onSearchTextChange(searchText.getText().toString());
394 searchText.requestFocus();
398 return super.onKeyDown(keyCode, event);
402 protected void onListItemClick(ListView l, View v, int row, long id) {
403 selectedRowIndex = row;
404 Log.d(LOG, "Clicked: " + getSelectedRowRawText());
405 openContextMenu(getListView());
408 void onSearchTextChange(final String searchText) {
409 Log.d(LOG, "onSearchTextChange: " + searchText);
410 if (dictionary == null) {
413 if (searchOperation != null) {
414 searchOperation.interrupted.set(true);
416 searchOperation = new SearchOperation(searchText);
417 searchExecutor.execute(searchOperation);
420 private void jumpToRow(final int rowIndex) {
421 Log.d(LOG, "jumpToRow: " + rowIndex);
422 selectedRowIndex = rowIndex;
423 selectedTokenRowIndex = activeLanguageData.getIndexEntryForRow(rowIndex).startRow;
424 getListView().setSelection(rowIndex);
425 getListView().setSelected(true); // TODO: is this doing anything?
429 private void updateSearchText() {
430 final EditText searchText = getSearchText();
431 if (!searchText.hasFocus()) {
432 final String word = activeLanguageData.getIndexEntryForRow(selectedRowIndex).word;
433 if (!word.equals(searchText.getText().toString())) {
434 Log.d(LOG, "updateSearchText: setText: " + word);
435 searchText.setText(word);
440 private void startDownloadDictActivity() {
441 final Intent intent = new Intent().setClassName(
442 DownloadActivity.class.getPackage().getName(),
443 DownloadActivity.class.getCanonicalName());
444 final SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(DictionaryActivity.this);
445 final String dictFetchUrl = settings.getString(getString(R.string.dictFetchUrlKey), getString(R.string.dictFetchUrlDefault));
446 final String dictFileName = settings.getString(getString(R.string.dictFileKey), getString(R.string.dictFileDefault));
447 intent.putExtra(DownloadActivity.SOURCE, dictFetchUrl);
448 intent.putExtra(DownloadActivity.DEST, dictFileName);
449 startActivity(intent);
452 private final class SearchOperation implements Runnable {
453 final String searchText;
454 final AtomicBoolean interrupted = new AtomicBoolean(false);
456 public SearchOperation(final String searchText) {
457 this.searchText = searchText;
461 Log.d(LOG, "SearchOperation: " + searchText);
462 final int indexLocation = activeLanguageData.lookup(searchText, interrupted);
463 if (interrupted.get()) {
466 final IndexEntry indexEntry = activeLanguageData.sortedIndex
468 Log.d(LOG, "SearchOperation completed: " + indexEntry.toString());
469 uiHandler.post(new Runnable() {
471 jumpToRow(indexEntry.startRow);
477 private class DictionaryListAdapter extends BaseAdapter {
479 public int getCount() {
480 if (dictionary == null) {
483 return activeLanguageData.rows.size();
486 public Dictionary.Row getItem(int rowIndex) {
487 final LanguageData activeLanguageData = DictionaryActivity.this.activeLanguageData;
488 if (activeLanguageData == null) {
491 assert rowIndex < activeLanguageData.rows.size();
492 return activeLanguageData.rows.get(rowIndex);
495 public long getItemId(int rowIndex) {
499 public View getView(final int rowIndex, final View convertView,
500 final ViewGroup parent) {
501 final Row row = getItem(rowIndex);
504 if (row == null || row.isToken()) {
505 TextView result = null;
506 if (convertView instanceof TextView) {
507 result = (TextView) convertView;
509 result = new TextView(parent.getContext());
514 result.setText(activeLanguageData.rowToString(row));
515 result.setTextAppearance(parent.getContext(),
516 android.R.style.TextAppearance_Large);
517 result.setClickable(false);
522 final TableLayout result = new TableLayout(parent.getContext());
524 final Entry entry = dictionary.entries.get(row.getIndex());
525 final int rowCount = entry.getRowCount();
526 for (int r = 0; r < rowCount; ++r) {
527 final TableRow tableRow = new TableRow(result.getContext());
529 TextView column1 = new TextView(tableRow.getContext());
530 TextView column2 = new TextView(tableRow.getContext());
531 final TableRow.LayoutParams layoutParams = new TableRow.LayoutParams();
532 layoutParams.weight = 0.5f;
535 final TextView spacer = new TextView(tableRow.getContext());
536 spacer.setText(r == 0 ? "
\95 " : "
\95 ");
537 tableRow.addView(spacer);
539 tableRow.addView(column1, layoutParams);
541 final TextView spacer = new TextView(tableRow.getContext());
542 spacer.setText(r == 0 ? "
\95 " : "
\95 ");
543 tableRow.addView(spacer);
545 tableRow.addView(column2, layoutParams);
549 // column1.setTextAppearance(parent.getContext(), android.R.style.Text);
551 // TODO: color words by gender
552 final String col1Text = entry.getAllText(activeLanguageData.lang)[r];
553 column1.setText(col1Text, TextView.BufferType.SPANNABLE);
554 final Spannable col1Spannable = (Spannable) column1.getText();
556 final String token = activeLanguageData.getIndexEntryForRow(rowIndex).word;
557 while ((startPos = col1Text.indexOf(token, startPos)) != -1) {
558 col1Spannable.setSpan(new StyleSpan(Typeface.BOLD), startPos, startPos + token.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
559 startPos += token.length();
562 column2.setText(entry.getAllText(Entry.otherLang(activeLanguageData.lang))[r], TextView.BufferType.NORMAL);
564 result.addView(tableRow);
569 } // DictionaryListAdapter
571 private class SearchTextWatcher implements TextWatcher {
572 public void afterTextChanged(final Editable searchText) {
573 if (getSearchText().hasFocus()) {
574 // If they were typing to cause the change, update the UI.
575 onSearchTextChange(searchText.toString());
579 public void beforeTextChanged(CharSequence arg0, int arg1, int arg2,
583 public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {