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.DialogInterface;
17 import android.content.Intent;
18 import android.content.SharedPreferences;
19 import android.graphics.Typeface;
20 import android.os.Bundle;
21 import android.os.Handler;
22 import android.preference.PreferenceManager;
23 import android.text.Editable;
24 import android.text.Spannable;
25 import android.text.TextWatcher;
26 import android.text.style.StyleSpan;
27 import android.util.Log;
28 import android.view.ContextMenu;
29 import android.view.KeyEvent;
30 import android.view.Menu;
31 import android.view.MenuItem;
32 import android.view.View;
33 import android.view.ViewGroup;
34 import android.view.ContextMenu.ContextMenuInfo;
35 import android.view.MenuItem.OnMenuItemClickListener;
36 import android.view.View.OnClickListener;
37 import android.widget.AdapterView;
38 import android.widget.BaseAdapter;
39 import android.widget.Button;
40 import android.widget.EditText;
41 import android.widget.ListView;
42 import android.widget.TableLayout;
43 import android.widget.TableRow;
44 import android.widget.TextView;
45 import android.widget.AdapterView.OnItemLongClickListener;
46 import android.widget.AdapterView.OnItemSelectedListener;
48 import com.hughes.android.dictionary.Dictionary.IndexEntry;
49 import com.hughes.android.dictionary.Dictionary.LanguageData;
50 import com.hughes.android.dictionary.Dictionary.Row;
52 public class DictionaryActivity extends ListActivity {
54 String WORD_LIST_FILE;
56 String DICT_FETCH_URL;
58 static final Intent preferencesIntent = new Intent().setClassName(PreferenceActivity.class.getPackage().getName(), PreferenceActivity.class.getCanonicalName());
60 private final Handler uiHandler = new Handler();
61 private final Executor searchExecutor = Executors.newSingleThreadExecutor();
62 private final DictionaryListAdapter dictionaryListAdapter = new DictionaryListAdapter();
65 private File wordList = new File("/sdcard/wordList.txt");
68 private File dictFile = null;
69 private RandomAccessFile dictRaf = null;
70 private Dictionary dictionary = null;
71 private LanguageData activeLangaugeData = null;
73 private SearchOperation searchOperation = null;
74 private int selectedRowIndex = -1;
75 private int selectedTokenRowIndex = -1;
78 /** Called when the activity is first created. */
80 public void onCreate(Bundle savedInstanceState) {
81 super.onCreate(savedInstanceState);
82 WORD_LIST_FILE = getResources().getString(R.string.wordListFile);
83 DICT_FILE = getResources().getString(R.string.dictFile);
84 DICT_FETCH_URL = getResources().getString(R.string.dictFetchUrl);
86 Log.d("THAD", "onCreate");
92 public void onResume() {
95 // Have to close, because we might have downloaded a new copy of the dictionary.
96 closeCurrentDictionary();
98 final SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
99 wordList = new File(settings.getString(WORD_LIST_FILE, wordList.getAbsolutePath()));
100 final File newDictFile = new File(settings.getString(DICT_FILE, "/sdcard/de-en.dict"));
101 dictFile = newDictFile;
102 Log.d("THAD", "wordList=" + wordList);
103 Log.d("THAD", "dictFile=" + dictFile);
105 if (!dictFile.canRead()) {
106 dictionaryListAdapter.notifyDataSetChanged();
107 Log.d("THAD", "Unable to read dictionary file.");
108 final AlertDialog alert = new AlertDialog.Builder(DictionaryActivity.this).create();
109 alert.setMessage("Unable to read dictionary file: " + wordList.getAbsolutePath());
110 alert.setButton("Download dictionary from Internet", new DialogInterface.OnClickListener() {
111 public void onClick(DialogInterface dialog, int which) {
112 startDownloadDictActivity();
119 dictRaf = new RandomAccessFile(dictFile, "r");
120 dictionary = new Dictionary(dictRaf);
121 activeLangaugeData = dictionary.languageDatas[Entry.LANG1];
122 dictionaryListAdapter.notifyDataSetChanged();
123 } catch (Exception e) {
124 throw new RuntimeException(e);
127 setContentView(R.layout.main);
129 getSearchText().addTextChangedListener(new DictionaryTextWatcher());
131 setListAdapter(dictionaryListAdapter);
134 final Button langButton = (Button) findViewById(R.id.LangButton);
135 langButton.setOnClickListener(new OnClickListener() {
136 public void onClick(View v) {
141 final Button upButton = (Button) findViewById(R.id.UpButton);
142 upButton.setOnClickListener(new OnClickListener() {
143 public void onClick(View v) {
144 final int destRowIndex;
145 final Row tokenRow = activeLangaugeData.rows.get(selectedTokenRowIndex);
146 assert tokenRow.isToken();
147 final int prevTokenIndex = tokenRow.getIndex() - 1;
148 if (selectedRowIndex == selectedTokenRowIndex && selectedRowIndex > 0) {
149 destRowIndex = activeLangaugeData.sortedIndex.get(prevTokenIndex).startRow;
151 destRowIndex = selectedTokenRowIndex;
153 jumpToRow(destRowIndex);
155 final Button downButton = (Button) findViewById(R.id.DownButton);
156 downButton.setOnClickListener(new OnClickListener() {
157 public void onClick(View v) {
158 final Row tokenRow = activeLangaugeData.rows.get(selectedTokenRowIndex);
159 assert tokenRow.isToken();
160 final int nextTokenIndex = tokenRow.getIndex() + 1;
161 final int destRowIndex;
162 if (nextTokenIndex < activeLangaugeData.sortedIndex.size()) {
163 destRowIndex = activeLangaugeData.sortedIndex.get(nextTokenIndex).startRow;
165 destRowIndex = activeLangaugeData.rows.size() - 1;
167 jumpToRow(destRowIndex);
171 registerForContextMenu(getListView());
173 // ItemSelectedListener.
174 getListView().setOnItemSelectedListener(new OnItemSelectedListener() {
175 public void onItemSelected(AdapterView<?> arg0, View arg1, int rowIndex,
177 Log.d("THAD", "onItemSelected: " + rowIndex);
178 selectedRowIndex = rowIndex;
179 selectedTokenRowIndex = activeLangaugeData.getIndexEntryForRow(rowIndex).startRow;
183 public void onNothingSelected(AdapterView<?> arg0) {
187 // LongClickListener.
188 getListView().setOnItemLongClickListener((new OnItemLongClickListener() {
189 public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int rowIndex,
191 selectedRowIndex = rowIndex;
196 onSearchTextChange("");
201 public void onStop() {
203 closeCurrentDictionary();
206 private void closeCurrentDictionary() {
208 activeLangaugeData = null;
210 if (dictRaf != null) {
213 } catch (IOException e) {
214 throw new RuntimeException(e);
219 public String getSelectedRowRawText() {
220 return activeLangaugeData.rowToString(activeLangaugeData.rows.get(selectedRowIndex));
223 public EditText getSearchText() {
224 return (EditText) findViewById(R.id.SearchText);
227 // ----------------------------------------------------------------
229 // ----------------------------------------------------------------
231 private MenuItem switchLanguageMenuItem = null;
235 public boolean onCreateOptionsMenu(final Menu menu) {
236 switchLanguageMenuItem = menu.add("Switch to language.");
237 switchLanguageMenuItem.setOnMenuItemClickListener(new OnMenuItemClickListener(){
238 public boolean onMenuItemClick(final MenuItem menuItem) {
243 final MenuItem preferences = menu.add("Preferences...");
244 preferences.setOnMenuItemClickListener(new OnMenuItemClickListener(){
245 public boolean onMenuItemClick(final MenuItem menuItem) {
246 startActivity(preferencesIntent);
250 final MenuItem about = menu.add("About...");
251 about.setOnMenuItemClickListener(new OnMenuItemClickListener(){
252 public boolean onMenuItemClick(final MenuItem menuItem) {
253 final Intent intent = new Intent().setClassName(AboutActivity.class.getPackage().getName(), AboutActivity.class.getCanonicalName());
254 final StringBuilder currentDictInfo = new StringBuilder();
255 if (dictionary == null) {
256 currentDictInfo.append("No dictionary loaded.");
258 currentDictInfo.append("Entry count: " + dictionary.entries.size()).append("\n");
259 for (int i = 0; i < 2; ++i) {
260 final LanguageData languageData = dictionary.languageDatas[i];
261 currentDictInfo.append(languageData.language.symbol).append(":\n");
262 currentDictInfo.append(" Unique token count: " + languageData.sortedIndex.size()).append("\n");
263 currentDictInfo.append(" Row count: " + languageData.rows.size()).append("\n");
266 intent.putExtra(AboutActivity.CURRENT_DICT_INFO, currentDictInfo.toString());
267 startActivity(intent);
271 final MenuItem download = menu.add("Download dictionary...");
272 download.setOnMenuItemClickListener(new OnMenuItemClickListener(){
273 public boolean onMenuItemClick(final MenuItem menuItem) {
274 startDownloadDictActivity();
282 public boolean onPrepareOptionsMenu(final Menu menu) {
283 if (dictionary != null) {
284 switchLanguageMenuItem.setTitle(String.format("Switch to %s", dictionary.languageDatas[Entry.otherLang(activeLangaugeData.lang)].language.symbol));
286 switchLanguageMenuItem.setEnabled(dictionary != null);
287 return super.onPrepareOptionsMenu(menu);
290 void switchLanguage() {
291 activeLangaugeData = dictionary.languageDatas[(activeLangaugeData == dictionary.languageDatas[0]) ? 1 : 0];
292 selectedRowIndex = 0;
293 selectedTokenRowIndex = 0;
295 dictionaryListAdapter.notifyDataSetChanged();
296 onSearchTextChange(getSearchText().getText().toString());
299 void updateLangButton() {
300 final Button langButton = (Button) findViewById(R.id.LangButton);
301 langButton.setText(activeLangaugeData.language.symbol);
304 // ----------------------------------------------------------------
306 // ----------------------------------------------------------------
309 public void onCreateContextMenu(ContextMenu menu, View v,
310 ContextMenuInfo menuInfo) {
311 if (selectedRowIndex == -1) {
314 final MenuItem addToWordlist = menu.add("Add to wordlist: " + wordList.getName());
315 addToWordlist.setOnMenuItemClickListener(new OnMenuItemClickListener() {
316 public boolean onMenuItemClick(MenuItem item) {
317 final StringBuilder rawText = new StringBuilder();
318 final String word = activeLangaugeData.getIndexEntryForRow(selectedRowIndex).word;
319 rawText.append(new SimpleDateFormat("yyyy.MM.dd HH:mm:ss").format(new Date())).append("\t");
320 rawText.append(word).append("\t");
321 rawText.append(getSelectedRowRawText());
322 Log.d("THAD", "Writing : " + rawText);
324 wordList.getParentFile().mkdirs();
325 final PrintWriter out = new PrintWriter(new FileWriter(wordList, true));
326 out.println(rawText.toString());
328 } catch (IOException e) {
329 Log.e("THAD", "Unable to append to " + wordList.getAbsolutePath(), e);
330 final AlertDialog alert = new AlertDialog.Builder(DictionaryActivity.this).create();
331 alert.setMessage("Failed to append to file: " + wordList.getAbsolutePath());
340 public boolean onKeyDown(int keyCode, KeyEvent event) {
341 if (event.getUnicodeChar() != 0) {
342 final EditText searchText = getSearchText();
343 if (!searchText.hasFocus()) {
344 searchText.setText("" + (char)event.getUnicodeChar());
345 onSearchTextChange(searchText.getText().toString());
346 searchText.requestFocus();
350 return super.onKeyDown(keyCode, event);
354 protected void onListItemClick(ListView l, View v, int row, long id) {
355 selectedRowIndex = row;
356 Log.d("THAD", "Clicked: " + getSelectedRowRawText());
357 openContextMenu(getListView());
360 void onSearchTextChange(final String searchText) {
361 Log.d("THAD", "onSearchTextChange: " + searchText);
362 if (searchOperation != null) {
363 searchOperation.interrupted.set(true);
365 searchOperation = new SearchOperation(searchText);
366 searchExecutor.execute(searchOperation);
369 private void jumpToRow(final int rowIndex) {
370 Log.d("THAD", "jumpToRow: " + rowIndex);
371 selectedRowIndex = rowIndex;
372 selectedTokenRowIndex = activeLangaugeData.getIndexEntryForRow(rowIndex).startRow;
373 getListView().setSelection(rowIndex);
374 getListView().setSelected(true); // TODO: is this doing anything?
378 private void updateSearchText() {
379 final EditText searchText = getSearchText();
380 if (!searchText.hasFocus()) {
381 final String word = activeLangaugeData.getIndexEntryForRow(selectedRowIndex).word;
382 if (!word.equals(searchText.getText().toString())) {
383 Log.d("THAD", "updateSearchText: setText: " + word);
384 searchText.setText(word);
389 private void startDownloadDictActivity() {
390 final Intent intent = new Intent().setClassName(
391 DownloadActivity.class.getPackage().getName(),
392 DownloadActivity.class.getCanonicalName());
393 final SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(DictionaryActivity.this);
394 final String dictFetchUrl = settings.getString(DICT_FETCH_URL, null);
395 final String dictFileName = settings.getString(DICT_FILE, null);
396 intent.putExtra(DownloadActivity.SOURCE, dictFetchUrl);
397 intent.putExtra(DownloadActivity.DEST, dictFileName);
398 startActivity(intent);
401 private final class SearchOperation implements Runnable {
402 final String searchText;
403 final AtomicBoolean interrupted = new AtomicBoolean(false);
405 public SearchOperation(final String searchText) {
406 this.searchText = searchText;
410 Log.d("THAD", "SearchOperation: " + searchText);
411 final int indexLocation = activeLangaugeData.lookup(searchText, interrupted);
412 if (interrupted.get()) {
415 final IndexEntry indexEntry = activeLangaugeData.sortedIndex
417 uiHandler.post(new Runnable() {
419 jumpToRow(indexEntry.startRow);
425 private class DictionaryListAdapter extends BaseAdapter {
427 public int getCount() {
428 return dictionary != null ? activeLangaugeData.rows.size() : 0;
431 public Dictionary.Row getItem(int rowIndex) {
432 assert rowIndex < activeLangaugeData.rows.size();
433 return activeLangaugeData.rows.get(rowIndex);
436 public long getItemId(int rowIndex) {
440 public View getView(final int rowIndex, final View convertView,
441 final ViewGroup parent) {
442 final Row row = getItem(rowIndex);
446 TextView result = null;
447 if (convertView instanceof TextView) {
448 result = (TextView) convertView;
450 result = new TextView(parent.getContext());
452 result.setText(activeLangaugeData.rowToString(row));
453 result.setTextAppearance(parent.getContext(),
454 android.R.style.TextAppearance_Large);
455 result.setClickable(false);
460 final TableLayout result = new TableLayout(parent.getContext());
462 final Entry entry = dictionary.entries.get(row.getIndex());
463 final int rowCount = entry.getRowCount();
464 for (int r = 0; r < rowCount; ++r) {
465 final TableRow tableRow = new TableRow(result.getContext());
467 TextView column1 = new TextView(tableRow.getContext());
468 TextView column2 = new TextView(tableRow.getContext());
469 final TableRow.LayoutParams layoutParams = new TableRow.LayoutParams();
470 layoutParams.weight = 0.5f;
473 final TextView spacer = new TextView(tableRow.getContext());
474 spacer.setText(r == 0 ? "
\95 " : "
\95 ");
475 tableRow.addView(spacer);
477 tableRow.addView(column1, layoutParams);
479 final TextView spacer = new TextView(tableRow.getContext());
480 spacer.setText(r == 0 ? "
\95 " : "
\95 ");
481 tableRow.addView(spacer);
483 tableRow.addView(column2, layoutParams);
487 // column1.setTextAppearance(parent.getContext(), android.R.style.Text);
489 // TODO: color words by gender
490 final String col1Text = entry.getAllText(activeLangaugeData.lang)[r];
491 column1.setText(col1Text, TextView.BufferType.SPANNABLE);
492 final Spannable col1Spannable = (Spannable) column1.getText();
494 final String token = activeLangaugeData.getIndexEntryForRow(rowIndex).word;
495 while ((startPos = col1Text.indexOf(token, startPos)) != -1) {
496 col1Spannable.setSpan(new StyleSpan(Typeface.BOLD), startPos, startPos + token.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
497 startPos += token.length();
500 column2.setText(entry.getAllText(Entry.otherLang(activeLangaugeData.lang))[r], TextView.BufferType.NORMAL);
502 result.addView(tableRow);
507 } // DictionaryListAdapter
509 private class DictionaryTextWatcher implements TextWatcher {
510 public void afterTextChanged(final Editable searchText) {
511 if (getSearchText().hasFocus()) {
512 // If they were typing to cause the change, update the UI.
513 onSearchTextChange(searchText.toString());
517 public void beforeTextChanged(CharSequence arg0, int arg1, int arg2,
521 public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {