]> gitweb.fperrin.net Git - Dictionary.git/blob - src/com/hughes/android/dictionary/DictionaryActivity.java
go
[Dictionary.git] / src / com / hughes / android / dictionary / DictionaryActivity.java
1 package com.hughes.android.dictionary;
2
3 import java.io.File;
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.Date;
10 import java.util.concurrent.Executor;
11 import java.util.concurrent.Executors;
12 import java.util.concurrent.atomic.AtomicBoolean;
13
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;
50
51 import com.hughes.android.dictionary.Dictionary.IndexEntry;
52 import com.hughes.android.dictionary.Dictionary.LanguageData;
53 import com.hughes.android.dictionary.Dictionary.Row;
54
55 public class DictionaryActivity extends ListActivity {
56
57   static final Intent preferencesIntent = new Intent().setClassName(PreferenceActivity.class.getPackage().getName(), PreferenceActivity.class.getCanonicalName());
58   
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";
62
63   private final Handler uiHandler = new Handler();
64   private final Executor searchExecutor = Executors.newSingleThreadExecutor();
65   private final DictionaryListAdapter dictionaryListAdapter = new DictionaryListAdapter();
66
67   // Never null.
68   private File wordList;
69
70   // Can be null.
71   private File dictFile = null;
72   private RandomAccessFile dictRaf = null;
73   private Dictionary dictionary = null;
74   private LanguageData activeLanguageData = null;
75
76   private SearchOperation searchOperation = null;
77   private int selectedRowIndex = -1;
78   private int selectedTokenRowIndex = -1;
79   
80
81   /** Called when the activity is first created. */
82   @Override
83   public void onCreate(Bundle savedInstanceState) {
84     super.onCreate(savedInstanceState);
85     Log.d(LOG, "onCreate");
86     
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.");
89     }
90     
91     setContentView(R.layout.main);
92
93     getSearchText().addTextChangedListener(new SearchTextWatcher());
94
95     setListAdapter(dictionaryListAdapter);
96
97     // Language button.
98     final Button langButton = (Button) findViewById(R.id.LangButton);
99     langButton.setOnClickListener(new OnClickListener() {
100       public void onClick(View v) {
101         switchLanguage();
102       }});
103
104     final Button upButton = (Button) findViewById(R.id.UpButton);
105     upButton.setOnClickListener(new OnClickListener() {
106       public void onClick(View v) {
107         if (dictionary == null) {
108           return;
109         }
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;
116         } else {
117           destRowIndex = selectedTokenRowIndex;
118         }
119         jumpToRow(destRowIndex);
120       }});
121     
122     final Button downButton = (Button) findViewById(R.id.DownButton);
123     downButton.setOnClickListener(new OnClickListener() {
124       public void onClick(View v) {
125         if (dictionary == null) {
126           return;
127         }
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;
134         } else {
135           destRowIndex = activeLanguageData.rows.size() - 1;
136         }
137         jumpToRow(destRowIndex);
138       }});
139
140     // ContextMenu.
141     registerForContextMenu(getListView());
142
143     // ItemSelectedListener.
144     getListView().setOnItemSelectedListener(new OnItemSelectedListener() {
145       public void onItemSelected(AdapterView<?> arg0, View arg1, int rowIndex,
146           long arg3) {
147         if (activeLanguageData == null) {
148           return;
149         }
150         Log.d(LOG, "onItemSelected: " + rowIndex);        
151         selectedRowIndex = rowIndex;
152         selectedTokenRowIndex = activeLanguageData.getIndexEntryForRow(rowIndex).startRow;
153         updateSearchText();
154       }
155
156       public void onNothingSelected(AdapterView<?> arg0) {
157       }});
158     
159
160     // LongClickListener.
161     getListView().setOnItemLongClickListener((new OnItemLongClickListener() {
162       public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int rowIndex,
163           long arg3) {
164         selectedRowIndex = rowIndex;
165         return false;
166       }
167     }));
168
169   }
170   
171   @Override
172   public void onResume() {
173     super.onResume();
174
175     // Have to close, because we might have downloaded a new copy of the dictionary.
176     closeCurrentDictionary();
177
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);
184
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();
193         }});
194       alert.show();
195       return;
196     }
197     
198     final byte lang = prefs.getInt(PREF_DICT_ACTIVE_LANG, Entry.LANG1) == Entry.LANG1 ? Entry.LANG1 : Entry.LANG2;
199       
200     try {
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);
207     }
208
209     updateLangButton();
210
211     final String searchText = prefs.getString(PREF_ACTIVE_SEARCH_TEXT, "");
212     getSearchText().setText(searchText);
213     onSearchTextChange(searchText);
214   }
215   
216   @Override
217   public void onPause() {
218     super.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());
223       prefs.commit();
224     }
225   }
226   
227   
228   @Override
229   public void onStop() {
230     super.onStop();
231     closeCurrentDictionary();
232   }
233
234   private void closeCurrentDictionary() {
235     dictionary = null;
236     activeLanguageData = null;
237     try {
238       if (dictRaf != null) {
239         dictRaf.close();
240       }
241     } catch (IOException e) {
242       throw new RuntimeException(e);
243     }
244     dictRaf = null;
245   }
246   
247   public String getSelectedRowRawText() {
248     return activeLanguageData.rowToString(activeLanguageData.rows.get(selectedRowIndex));
249   }
250   
251   public EditText getSearchText() {
252     return (EditText) findViewById(R.id.SearchText);
253   }
254   
255   // ----------------------------------------------------------------
256   // OptionsMenu
257   // ----------------------------------------------------------------
258
259   private MenuItem switchLanguageMenuItem = null;
260   
261
262   @Override
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) {
267         switchLanguage();
268         return false;
269       }});
270
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);
275         return false;
276       }});
277
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));
285         } else {
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");
293           }
294         }
295         intent.putExtra(AboutActivity.CURRENT_DICT_INFO, currentDictInfo.toString());
296         startActivity(intent);
297         return false;
298       }});
299
300     final MenuItem download = menu.add(getString(R.string.downloadDictionary));
301     download.setOnMenuItemClickListener(new OnMenuItemClickListener(){
302       public boolean onMenuItemClick(final MenuItem menuItem) {
303         startDownloadDictActivity();
304         return false;
305       }});
306
307     return true;
308   }
309   
310   @Override
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));
316     }
317     switchLanguageMenuItem.setEnabled(dictionary != null);
318     return super.onPrepareOptionsMenu(menu);
319   }
320
321   void switchLanguage() {
322     if (dictionary == null) {
323       return;
324     }
325     activeLanguageData = dictionary.languageDatas[(activeLanguageData == dictionary.languageDatas[0]) ? 1 : 0];
326     selectedRowIndex = 0;
327     selectedTokenRowIndex = 0;
328     updateLangButton();
329     dictionaryListAdapter.notifyDataSetChanged();
330     onSearchTextChange(getSearchText().getText().toString());
331   }
332   
333   void updateLangButton() {
334     final Button langButton = (Button) findViewById(R.id.LangButton);
335     langButton.setText(activeLanguageData.language.symbol);
336   }
337   
338   // ----------------------------------------------------------------
339   // ContextMenu
340   // ----------------------------------------------------------------
341   
342   @Override
343   public void onCreateContextMenu(ContextMenu menu, View v,
344       ContextMenuInfo menuInfo) {
345     if (selectedRowIndex == -1) {
346       return;
347     }
348     
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);
358         try {
359           wordList.getParentFile().mkdirs();
360           final PrintWriter out = new PrintWriter(new FileWriter(wordList, true));
361           out.println(rawText.toString());
362           out.close();
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());
367           alert.show();
368         }
369         return false;
370       }
371     });
372
373     final MenuItem copy = menu.add(android.R.string.copy);
374     copy.setOnMenuItemClickListener(new OnMenuItemClickListener() {
375       public boolean onMenuItemClick(MenuItem item) {
376         Log.d(LOG, "Copy.");
377         final StringBuilder result = new StringBuilder();
378         result.append(getSelectedRowRawText());
379         final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
380         clipboardManager.setText(result.toString());
381         return false;
382       }
383     });
384
385   }
386   
387   @Override
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();
395       }
396       return true;
397     }
398     return super.onKeyDown(keyCode, event);
399   }
400
401   @Override
402   protected void onListItemClick(ListView l, View v, int row, long id) {
403     selectedRowIndex = row;
404     Log.d(LOG, "Clicked: " + getSelectedRowRawText());
405     openContextMenu(getListView());
406   }
407
408   void onSearchTextChange(final String searchText) {
409     Log.d(LOG, "onSearchTextChange: " + searchText);
410     if (dictionary == null) {
411       return;
412     }
413     if (searchOperation != null) {
414       searchOperation.interrupted.set(true);
415     }
416     searchOperation = new SearchOperation(searchText);
417     searchExecutor.execute(searchOperation);
418   }
419   
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?
426     updateSearchText();
427   }
428
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);
436       }
437     }
438   }
439
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);
450   }
451
452   private final class SearchOperation implements Runnable {
453     final String searchText;
454     final AtomicBoolean interrupted = new AtomicBoolean(false);
455
456     public SearchOperation(final String searchText) {
457       this.searchText = searchText;
458     }
459
460     public void run() {
461       Log.d(LOG, "SearchOperation: " + searchText);
462       final int indexLocation = activeLanguageData.lookup(searchText, interrupted);
463       if (interrupted.get()) {
464         return;
465       }
466       final IndexEntry indexEntry = activeLanguageData.sortedIndex
467           .get(indexLocation);
468       Log.d(LOG, "SearchOperation completed: " + indexEntry.toString());
469       uiHandler.post(new Runnable() {
470         public void run() {
471           jumpToRow(indexEntry.startRow);
472         }
473       });
474     }
475   }
476
477   private class DictionaryListAdapter extends BaseAdapter {
478
479     public int getCount() {
480       if (dictionary == null) {
481         return 0;
482       }
483       return activeLanguageData.rows.size();
484     }
485
486     public Dictionary.Row getItem(int rowIndex) {
487       final LanguageData activeLanguageData = DictionaryActivity.this.activeLanguageData;
488       if (activeLanguageData == null) {
489         return null;
490       }
491       assert rowIndex < activeLanguageData.rows.size();
492       return activeLanguageData.rows.get(rowIndex);
493     }
494
495     public long getItemId(int rowIndex) {
496       return rowIndex;
497     }
498
499     public View getView(final int rowIndex, final View convertView,
500         final ViewGroup parent) {
501       final Row row = getItem(rowIndex);
502
503       // Token row.
504       if (row == null || row.isToken()) {
505         TextView result = null;
506         if (convertView instanceof TextView) {
507           result = (TextView) convertView;
508         } else {
509           result = new TextView(parent.getContext());
510         }
511         if (row == null) {
512           return result;
513         }
514         result.setText(activeLanguageData.rowToString(row));
515         result.setTextAppearance(parent.getContext(),
516             android.R.style.TextAppearance_Large);
517         result.setClickable(false);
518         return result;
519       }
520
521       // Entry row(s).
522       final TableLayout result = new TableLayout(parent.getContext());
523
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());
528         
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;
533         
534         if (r>0){
535           final TextView spacer = new TextView(tableRow.getContext());
536           spacer.setText(r == 0 ? "\95 " : " \95 ");
537           tableRow.addView(spacer);
538         }
539         tableRow.addView(column1, layoutParams);
540         if (r > 0) {
541           final TextView spacer = new TextView(tableRow.getContext());
542           spacer.setText(r == 0 ? "\95 " : " \95 ");
543           tableRow.addView(spacer);
544         }
545         tableRow.addView(column2, layoutParams);
546         
547         column1.setWidth(1);
548         column2.setWidth(1);
549         // column1.setTextAppearance(parent.getContext(), android.R.style.Text);
550         
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();
555         int startPos = 0;
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();
560         }
561         
562         column2.setText(entry.getAllText(Entry.otherLang(activeLanguageData.lang))[r], TextView.BufferType.NORMAL);
563         
564         result.addView(tableRow);
565       }
566       
567       return result;
568     }
569   }  // DictionaryListAdapter
570
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());
576       }
577     }
578
579     public void beforeTextChanged(CharSequence arg0, int arg1, int arg2,
580         int arg3) {
581     }
582
583     public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
584     }
585   }
586   
587 }