]> 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.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;
47
48 import com.hughes.android.dictionary.Dictionary.IndexEntry;
49 import com.hughes.android.dictionary.Dictionary.LanguageData;
50 import com.hughes.android.dictionary.Dictionary.Row;
51
52 public class DictionaryActivity extends ListActivity {
53
54   String WORD_LIST_FILE; 
55   String DICT_FILE; 
56   String DICT_FETCH_URL; 
57   
58   static final Intent preferencesIntent = new Intent().setClassName(PreferenceActivity.class.getPackage().getName(), PreferenceActivity.class.getCanonicalName());
59
60   private final Handler uiHandler = new Handler();
61   private final Executor searchExecutor = Executors.newSingleThreadExecutor();
62   private final DictionaryListAdapter dictionaryListAdapter = new DictionaryListAdapter();
63
64   // Never null.
65   private File wordList = new File("/sdcard/wordList.txt");
66
67   // Can be null.
68   private File dictFile = null;
69   private RandomAccessFile dictRaf = null;
70   private Dictionary dictionary = null;
71   private LanguageData activeLangaugeData = null;
72
73   private SearchOperation searchOperation = null;
74   private int selectedRowIndex = -1;
75   private int selectedTokenRowIndex = -1;
76   
77
78   /** Called when the activity is first created. */
79   @Override
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); 
85
86     Log.d("THAD", "onCreate");
87   }
88   
89   @Override
90   public void onResume() {
91     super.onResume();
92
93     // Have to close, because we might have downloaded a new copy of the dictionary.
94     closeCurrentDictionary();
95
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);
102
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();
111         }});
112       alert.show();
113       return;
114     }
115     
116     try {
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);
123     }
124
125     setContentView(R.layout.main);
126
127     getSearchText().addTextChangedListener(new DictionaryTextWatcher());
128
129     setListAdapter(dictionaryListAdapter);
130
131     // Language button.
132     final Button langButton = (Button) findViewById(R.id.LangButton);
133     langButton.setOnClickListener(new OnClickListener() {
134       public void onClick(View v) {
135         switchLanguage();
136       }});
137     updateLangButton();
138
139     final Button upButton = (Button) findViewById(R.id.UpButton);
140     upButton.setOnClickListener(new OnClickListener() {
141       public void onClick(View v) {
142         if (dictionary == null) {
143           return;
144         }
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;
151         } else {
152           destRowIndex = selectedTokenRowIndex;
153         }
154         jumpToRow(destRowIndex);
155       }});
156     final Button downButton = (Button) findViewById(R.id.DownButton);
157     downButton.setOnClickListener(new OnClickListener() {
158       public void onClick(View v) {
159         if (dictionary == null) {
160           return;
161         }
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;
168         } else {
169           destRowIndex = activeLangaugeData.rows.size() - 1;
170         }
171         jumpToRow(destRowIndex);
172       }});
173
174     // ContextMenu.
175     registerForContextMenu(getListView());
176
177     // ItemSelectedListener.
178     getListView().setOnItemSelectedListener(new OnItemSelectedListener() {
179       public void onItemSelected(AdapterView<?> arg0, View arg1, int rowIndex,
180           long arg3) {
181         Log.d("THAD", "onItemSelected: " + rowIndex);
182         selectedRowIndex = rowIndex;
183         selectedTokenRowIndex = activeLangaugeData.getIndexEntryForRow(rowIndex).startRow;
184         updateSearchText();
185       }
186
187       public void onNothingSelected(AdapterView<?> arg0) {
188       }});
189     
190
191     // LongClickListener.
192     getListView().setOnItemLongClickListener((new OnItemLongClickListener() {
193       public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int rowIndex,
194           long arg3) {
195         selectedRowIndex = rowIndex;
196         return false;
197       }
198     }));
199
200     onSearchTextChange("");
201   }
202   
203   
204   @Override
205   public void onStop() {
206     super.onStop();
207     closeCurrentDictionary();
208   }
209
210   private void closeCurrentDictionary() {
211     dictionary = null;
212     activeLangaugeData = null;
213     try {
214       if (dictRaf != null) {
215         dictRaf.close();
216       }
217     } catch (IOException e) {
218       throw new RuntimeException(e);
219     }
220     dictRaf = null;
221   }
222   
223   public String getSelectedRowRawText() {
224     return activeLangaugeData.rowToString(activeLangaugeData.rows.get(selectedRowIndex));
225   }
226   
227   public EditText getSearchText() {
228     return (EditText) findViewById(R.id.SearchText);
229   }
230   
231   // ----------------------------------------------------------------
232   // OptionsMenu
233   // ----------------------------------------------------------------
234
235   private MenuItem switchLanguageMenuItem = null;
236   
237
238   @Override
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) {
243         switchLanguage();
244         return false;
245       }});
246
247     final MenuItem preferences = menu.add("Preferences...");
248     preferences.setOnMenuItemClickListener(new OnMenuItemClickListener(){
249       public boolean onMenuItemClick(final MenuItem menuItem) {
250         startActivity(preferencesIntent);
251         return false;
252       }});
253
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.");
261         } else {
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");
269           }
270         }
271         intent.putExtra(AboutActivity.CURRENT_DICT_INFO, currentDictInfo.toString());
272         startActivity(intent);
273         return false;
274       }});
275
276     final MenuItem download = menu.add("Download dictionary...");
277     download.setOnMenuItemClickListener(new OnMenuItemClickListener(){
278       public boolean onMenuItemClick(final MenuItem menuItem) {
279         startDownloadDictActivity();
280         return false;
281       }});
282
283     return true;
284   }
285   
286   @Override
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));
290     }
291     switchLanguageMenuItem.setEnabled(dictionary != null);
292     return super.onPrepareOptionsMenu(menu);
293   }
294
295   void switchLanguage() {
296     if (dictionary == null) {
297       return;
298     }
299     activeLangaugeData = dictionary.languageDatas[(activeLangaugeData == dictionary.languageDatas[0]) ? 1 : 0];
300     selectedRowIndex = 0;
301     selectedTokenRowIndex = 0;
302     updateLangButton();
303     dictionaryListAdapter.notifyDataSetChanged();
304     onSearchTextChange(getSearchText().getText().toString());
305   }
306   
307   void updateLangButton() {
308     final Button langButton = (Button) findViewById(R.id.LangButton);
309     langButton.setText(activeLangaugeData.language.symbol);
310   }
311   
312   // ----------------------------------------------------------------
313   // ContextMenu
314   // ----------------------------------------------------------------
315   
316   @Override
317   public void onCreateContextMenu(ContextMenu menu, View v,
318       ContextMenuInfo menuInfo) {
319     if (selectedRowIndex == -1) {
320       return;
321     }
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);
331         try {
332           wordList.getParentFile().mkdirs();
333           final PrintWriter out = new PrintWriter(new FileWriter(wordList, true));
334           out.println(rawText.toString());
335           out.close();
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());
340           alert.show();
341         }
342         return false;
343       }
344     });
345   }
346   
347   @Override
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();
355       }
356       return true;
357     }
358     return super.onKeyDown(keyCode, event);
359   }
360
361   @Override
362   protected void onListItemClick(ListView l, View v, int row, long id) {
363     selectedRowIndex = row;
364     Log.d("THAD", "Clicked: " + getSelectedRowRawText());
365     openContextMenu(getListView());
366   }
367
368   void onSearchTextChange(final String searchText) {
369     Log.d("THAD", "onSearchTextChange: " + searchText);
370     if (dictionary == null) {
371       return;
372     }
373     if (searchOperation != null) {
374       searchOperation.interrupted.set(true);
375     }
376     searchOperation = new SearchOperation(searchText);
377     searchExecutor.execute(searchOperation);
378   }
379   
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?
386     updateSearchText();
387   }
388
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);
396       }
397     }
398   }
399
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);
410   }
411
412   private final class SearchOperation implements Runnable {
413     final String searchText;
414     final AtomicBoolean interrupted = new AtomicBoolean(false);
415
416     public SearchOperation(final String searchText) {
417       this.searchText = searchText;
418     }
419
420     public void run() {
421       Log.d("THAD", "SearchOperation: " + searchText);
422       final int indexLocation = activeLangaugeData.lookup(searchText, interrupted);
423       if (interrupted.get()) {
424         return;
425       }
426       final IndexEntry indexEntry = activeLangaugeData.sortedIndex
427           .get(indexLocation);
428       uiHandler.post(new Runnable() {
429         public void run() {
430           jumpToRow(indexEntry.startRow);
431         }
432       });
433     }
434   }
435
436   private class DictionaryListAdapter extends BaseAdapter {
437
438     public int getCount() {
439       return dictionary != null ? activeLangaugeData.rows.size() : 0;
440     }
441
442     public Dictionary.Row getItem(int rowIndex) {
443       assert rowIndex < activeLangaugeData.rows.size();
444       return activeLangaugeData.rows.get(rowIndex);
445     }
446
447     public long getItemId(int rowIndex) {
448       return rowIndex;
449     }
450
451     public View getView(final int rowIndex, final View convertView,
452         final ViewGroup parent) {
453       final Row row = getItem(rowIndex);
454       
455       // Token row.
456       if (row.isToken()) {
457         TextView result = null;
458         if (convertView instanceof TextView) {
459           result = (TextView) convertView;
460         } else {
461           result = new TextView(parent.getContext());
462         }
463         result.setText(activeLangaugeData.rowToString(row));
464         result.setTextAppearance(parent.getContext(),
465             android.R.style.TextAppearance_Large);
466         result.setClickable(false);
467         return result;
468       }
469
470       // Entry row(s).
471       final TableLayout result = new TableLayout(parent.getContext());
472
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());
477         
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;
482         
483         if (r>0){
484           final TextView spacer = new TextView(tableRow.getContext());
485           spacer.setText(r == 0 ? "\95 " : " \95 ");
486           tableRow.addView(spacer);
487         }
488         tableRow.addView(column1, layoutParams);
489         if (r > 0) {
490           final TextView spacer = new TextView(tableRow.getContext());
491           spacer.setText(r == 0 ? "\95 " : " \95 ");
492           tableRow.addView(spacer);
493         }
494         tableRow.addView(column2, layoutParams);
495         
496         column1.setWidth(1);
497         column2.setWidth(1);
498         // column1.setTextAppearance(parent.getContext(), android.R.style.Text);
499         
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();
504         int startPos = 0;
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();
509         }
510         
511         column2.setText(entry.getAllText(Entry.otherLang(activeLangaugeData.lang))[r], TextView.BufferType.NORMAL);
512         
513         result.addView(tableRow);
514       }
515       
516       return result;
517     }
518   }  // DictionaryListAdapter
519
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());
525       }
526     }
527
528     public void beforeTextChanged(CharSequence arg0, int arg1, int arg2,
529         int arg3) {
530     }
531
532     public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
533     }
534   }
535   
536 }