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.wordListFileKey);
83 DICT_FILE = getResources().getString(R.string.dictFileKey);
84 DICT_FETCH_URL = getResources().getString(R.string.dictFetchUrlKey);
86 Log.d("THAD", "onCreate");
90 public void onResume() {
93 // Have to close, because we might have downloaded a new copy of the dictionary.
94 closeCurrentDictionary();
96 final SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
97 wordList = new File(settings.getString(WORD_LIST_FILE, wordList.getAbsolutePath()));
98 final File newDictFile = new File(settings.getString(DICT_FILE, "/sdcard/de-en.dict"));
99 dictFile = newDictFile;
100 Log.d("THAD", "wordList=" + wordList);
101 Log.d("THAD", "dictFile=" + dictFile);
103 if (!dictFile.canRead()) {
104 dictionaryListAdapter.notifyDataSetChanged();
105 Log.d("THAD", "Unable to read dictionary file.");
106 final AlertDialog alert = new AlertDialog.Builder(DictionaryActivity.this).create();
107 alert.setMessage("Unable to read dictionary file: " + dictFile.getAbsolutePath());
108 alert.setButton("Download dictionary", new DialogInterface.OnClickListener() {
109 public void onClick(DialogInterface dialog, int which) {
110 startDownloadDictActivity();
117 dictRaf = new RandomAccessFile(dictFile, "r");
118 dictionary = new Dictionary(dictRaf);
119 activeLangaugeData = dictionary.languageDatas[Entry.LANG1];
120 dictionaryListAdapter.notifyDataSetChanged();
121 } catch (Exception e) {
122 throw new RuntimeException(e);
125 setContentView(R.layout.main);
127 getSearchText().addTextChangedListener(new DictionaryTextWatcher());
129 setListAdapter(dictionaryListAdapter);
132 final Button langButton = (Button) findViewById(R.id.LangButton);
133 langButton.setOnClickListener(new OnClickListener() {
134 public void onClick(View v) {
139 final Button upButton = (Button) findViewById(R.id.UpButton);
140 upButton.setOnClickListener(new OnClickListener() {
141 public void onClick(View v) {
142 if (dictionary == null) {
145 final int destRowIndex;
146 final Row tokenRow = activeLangaugeData.rows.get(selectedTokenRowIndex);
147 assert tokenRow.isToken();
148 final int prevTokenIndex = tokenRow.getIndex() - 1;
149 if (selectedRowIndex == selectedTokenRowIndex && selectedRowIndex > 0) {
150 destRowIndex = activeLangaugeData.sortedIndex.get(prevTokenIndex).startRow;
152 destRowIndex = selectedTokenRowIndex;
154 jumpToRow(destRowIndex);
156 final Button downButton = (Button) findViewById(R.id.DownButton);
157 downButton.setOnClickListener(new OnClickListener() {
158 public void onClick(View v) {
159 if (dictionary == null) {
162 final Row tokenRow = activeLangaugeData.rows.get(selectedTokenRowIndex);
163 assert tokenRow.isToken();
164 final int nextTokenIndex = tokenRow.getIndex() + 1;
165 final int destRowIndex;
166 if (nextTokenIndex < activeLangaugeData.sortedIndex.size()) {
167 destRowIndex = activeLangaugeData.sortedIndex.get(nextTokenIndex).startRow;
169 destRowIndex = activeLangaugeData.rows.size() - 1;
171 jumpToRow(destRowIndex);
175 registerForContextMenu(getListView());
177 // ItemSelectedListener.
178 getListView().setOnItemSelectedListener(new OnItemSelectedListener() {
179 public void onItemSelected(AdapterView<?> arg0, View arg1, int rowIndex,
181 Log.d("THAD", "onItemSelected: " + rowIndex);
182 selectedRowIndex = rowIndex;
183 selectedTokenRowIndex = activeLangaugeData.getIndexEntryForRow(rowIndex).startRow;
187 public void onNothingSelected(AdapterView<?> arg0) {
191 // LongClickListener.
192 getListView().setOnItemLongClickListener((new OnItemLongClickListener() {
193 public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int rowIndex,
195 selectedRowIndex = rowIndex;
200 onSearchTextChange("");
205 public void onStop() {
207 closeCurrentDictionary();
210 private void closeCurrentDictionary() {
212 activeLangaugeData = null;
214 if (dictRaf != null) {
217 } catch (IOException e) {
218 throw new RuntimeException(e);
223 public String getSelectedRowRawText() {
224 return activeLangaugeData.rowToString(activeLangaugeData.rows.get(selectedRowIndex));
227 public EditText getSearchText() {
228 return (EditText) findViewById(R.id.SearchText);
231 // ----------------------------------------------------------------
233 // ----------------------------------------------------------------
235 private MenuItem switchLanguageMenuItem = null;
239 public boolean onCreateOptionsMenu(final Menu menu) {
240 switchLanguageMenuItem = menu.add("Switch to language.");
241 switchLanguageMenuItem.setOnMenuItemClickListener(new OnMenuItemClickListener(){
242 public boolean onMenuItemClick(final MenuItem menuItem) {
247 final MenuItem preferences = menu.add("Preferences...");
248 preferences.setOnMenuItemClickListener(new OnMenuItemClickListener(){
249 public boolean onMenuItemClick(final MenuItem menuItem) {
250 startActivity(preferencesIntent);
254 final MenuItem about = menu.add("About...");
255 about.setOnMenuItemClickListener(new OnMenuItemClickListener(){
256 public boolean onMenuItemClick(final MenuItem menuItem) {
257 final Intent intent = new Intent().setClassName(AboutActivity.class.getPackage().getName(), AboutActivity.class.getCanonicalName());
258 final StringBuilder currentDictInfo = new StringBuilder();
259 if (dictionary == null) {
260 currentDictInfo.append("No dictionary loaded.");
262 currentDictInfo.append(dictionary.dictionaryInfo).append("\n\n");
263 currentDictInfo.append("Entry count: " + dictionary.entries.size()).append("\n");
264 for (int i = 0; i < 2; ++i) {
265 final LanguageData languageData = dictionary.languageDatas[i];
266 currentDictInfo.append(languageData.language.symbol).append(":\n");
267 currentDictInfo.append(" Unique token count: " + languageData.sortedIndex.size()).append("\n");
268 currentDictInfo.append(" Row count: " + languageData.rows.size()).append("\n");
271 intent.putExtra(AboutActivity.CURRENT_DICT_INFO, currentDictInfo.toString());
272 startActivity(intent);
276 final MenuItem download = menu.add("Download dictionary...");
277 download.setOnMenuItemClickListener(new OnMenuItemClickListener(){
278 public boolean onMenuItemClick(final MenuItem menuItem) {
279 startDownloadDictActivity();
287 public boolean onPrepareOptionsMenu(final Menu menu) {
288 if (dictionary != null) {
289 switchLanguageMenuItem.setTitle(String.format("Switch to %s", dictionary.languageDatas[Entry.otherLang(activeLangaugeData.lang)].language.symbol));
291 switchLanguageMenuItem.setEnabled(dictionary != null);
292 return super.onPrepareOptionsMenu(menu);
295 void switchLanguage() {
296 if (dictionary == null) {
299 activeLangaugeData = dictionary.languageDatas[(activeLangaugeData == dictionary.languageDatas[0]) ? 1 : 0];
300 selectedRowIndex = 0;
301 selectedTokenRowIndex = 0;
303 dictionaryListAdapter.notifyDataSetChanged();
304 onSearchTextChange(getSearchText().getText().toString());
307 void updateLangButton() {
308 final Button langButton = (Button) findViewById(R.id.LangButton);
309 langButton.setText(activeLangaugeData.language.symbol);
312 // ----------------------------------------------------------------
314 // ----------------------------------------------------------------
317 public void onCreateContextMenu(ContextMenu menu, View v,
318 ContextMenuInfo menuInfo) {
319 if (selectedRowIndex == -1) {
322 final MenuItem addToWordlist = menu.add("Add to wordlist: " + wordList.getName());
323 addToWordlist.setOnMenuItemClickListener(new OnMenuItemClickListener() {
324 public boolean onMenuItemClick(MenuItem item) {
325 final StringBuilder rawText = new StringBuilder();
326 final String word = activeLangaugeData.getIndexEntryForRow(selectedRowIndex).word;
327 rawText.append(new SimpleDateFormat("yyyy.MM.dd HH:mm:ss").format(new Date())).append("\t");
328 rawText.append(word).append("\t");
329 rawText.append(getSelectedRowRawText());
330 Log.d("THAD", "Writing : " + rawText);
332 wordList.getParentFile().mkdirs();
333 final PrintWriter out = new PrintWriter(new FileWriter(wordList, true));
334 out.println(rawText.toString());
336 } catch (IOException e) {
337 Log.e("THAD", "Unable to append to " + wordList.getAbsolutePath(), e);
338 final AlertDialog alert = new AlertDialog.Builder(DictionaryActivity.this).create();
339 alert.setMessage("Failed to append to file: " + wordList.getAbsolutePath());
348 public boolean onKeyDown(int keyCode, KeyEvent event) {
349 if (event.getUnicodeChar() != 0) {
350 final EditText searchText = getSearchText();
351 if (!searchText.hasFocus()) {
352 searchText.setText("" + (char)event.getUnicodeChar());
353 onSearchTextChange(searchText.getText().toString());
354 searchText.requestFocus();
358 return super.onKeyDown(keyCode, event);
362 protected void onListItemClick(ListView l, View v, int row, long id) {
363 selectedRowIndex = row;
364 Log.d("THAD", "Clicked: " + getSelectedRowRawText());
365 openContextMenu(getListView());
368 void onSearchTextChange(final String searchText) {
369 Log.d("THAD", "onSearchTextChange: " + searchText);
370 if (dictionary == null) {
373 if (searchOperation != null) {
374 searchOperation.interrupted.set(true);
376 searchOperation = new SearchOperation(searchText);
377 searchExecutor.execute(searchOperation);
380 private void jumpToRow(final int rowIndex) {
381 Log.d("THAD", "jumpToRow: " + rowIndex);
382 selectedRowIndex = rowIndex;
383 selectedTokenRowIndex = activeLangaugeData.getIndexEntryForRow(rowIndex).startRow;
384 getListView().setSelection(rowIndex);
385 getListView().setSelected(true); // TODO: is this doing anything?
389 private void updateSearchText() {
390 final EditText searchText = getSearchText();
391 if (!searchText.hasFocus()) {
392 final String word = activeLangaugeData.getIndexEntryForRow(selectedRowIndex).word;
393 if (!word.equals(searchText.getText().toString())) {
394 Log.d("THAD", "updateSearchText: setText: " + word);
395 searchText.setText(word);
400 private void startDownloadDictActivity() {
401 final Intent intent = new Intent().setClassName(
402 DownloadActivity.class.getPackage().getName(),
403 DownloadActivity.class.getCanonicalName());
404 final SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(DictionaryActivity.this);
405 final String dictFetchUrl = settings.getString(DICT_FETCH_URL, getResources().getString(R.string.dictFetchUrl));
406 final String dictFileName = settings.getString(DICT_FILE, getResources().getString(R.string.dictFile));
407 intent.putExtra(DownloadActivity.SOURCE, dictFetchUrl);
408 intent.putExtra(DownloadActivity.DEST, dictFileName);
409 startActivity(intent);
412 private final class SearchOperation implements Runnable {
413 final String searchText;
414 final AtomicBoolean interrupted = new AtomicBoolean(false);
416 public SearchOperation(final String searchText) {
417 this.searchText = searchText;
421 Log.d("THAD", "SearchOperation: " + searchText);
422 final int indexLocation = activeLangaugeData.lookup(searchText, interrupted);
423 if (interrupted.get()) {
426 final IndexEntry indexEntry = activeLangaugeData.sortedIndex
428 uiHandler.post(new Runnable() {
430 jumpToRow(indexEntry.startRow);
436 private class DictionaryListAdapter extends BaseAdapter {
438 public int getCount() {
439 return dictionary != null ? activeLangaugeData.rows.size() : 0;
442 public Dictionary.Row getItem(int rowIndex) {
443 assert rowIndex < activeLangaugeData.rows.size();
444 return activeLangaugeData.rows.get(rowIndex);
447 public long getItemId(int rowIndex) {
451 public View getView(final int rowIndex, final View convertView,
452 final ViewGroup parent) {
453 final Row row = getItem(rowIndex);
457 TextView result = null;
458 if (convertView instanceof TextView) {
459 result = (TextView) convertView;
461 result = new TextView(parent.getContext());
463 result.setText(activeLangaugeData.rowToString(row));
464 result.setTextAppearance(parent.getContext(),
465 android.R.style.TextAppearance_Large);
466 result.setClickable(false);
471 final TableLayout result = new TableLayout(parent.getContext());
473 final Entry entry = dictionary.entries.get(row.getIndex());
474 final int rowCount = entry.getRowCount();
475 for (int r = 0; r < rowCount; ++r) {
476 final TableRow tableRow = new TableRow(result.getContext());
478 TextView column1 = new TextView(tableRow.getContext());
479 TextView column2 = new TextView(tableRow.getContext());
480 final TableRow.LayoutParams layoutParams = new TableRow.LayoutParams();
481 layoutParams.weight = 0.5f;
484 final TextView spacer = new TextView(tableRow.getContext());
485 spacer.setText(r == 0 ? "
\95 " : "
\95 ");
486 tableRow.addView(spacer);
488 tableRow.addView(column1, layoutParams);
490 final TextView spacer = new TextView(tableRow.getContext());
491 spacer.setText(r == 0 ? "
\95 " : "
\95 ");
492 tableRow.addView(spacer);
494 tableRow.addView(column2, layoutParams);
498 // column1.setTextAppearance(parent.getContext(), android.R.style.Text);
500 // TODO: color words by gender
501 final String col1Text = entry.getAllText(activeLangaugeData.lang)[r];
502 column1.setText(col1Text, TextView.BufferType.SPANNABLE);
503 final Spannable col1Spannable = (Spannable) column1.getText();
505 final String token = activeLangaugeData.getIndexEntryForRow(rowIndex).word;
506 while ((startPos = col1Text.indexOf(token, startPos)) != -1) {
507 col1Spannable.setSpan(new StyleSpan(Typeface.BOLD), startPos, startPos + token.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
508 startPos += token.length();
511 column2.setText(entry.getAllText(Entry.otherLang(activeLangaugeData.lang))[r], TextView.BufferType.NORMAL);
513 result.addView(tableRow);
518 } // DictionaryListAdapter
520 private class DictionaryTextWatcher implements TextWatcher {
521 public void afterTextChanged(final Editable searchText) {
522 if (getSearchText().hasFocus()) {
523 // If they were typing to cause the change, update the UI.
524 onSearchTextChange(searchText.toString());
528 public void beforeTextChanged(CharSequence arg0, int arg1, int arg2,
532 public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {