]> gitweb.fperrin.net Git - Dictionary.git/blob - src/com/hughes/android/dictionary/DictionaryActivity.java
c6d397233eaa045e3fe2f9396f5bd754445a4d0b
[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.wordListFile); 
83     DICT_FILE = getResources().getString(R.string.dictFile); 
84     DICT_FETCH_URL = getResources().getString(R.string.dictFetchUrl); 
85
86     Log.d("THAD", "onCreate");
87     
88     
89   }
90   
91   @Override
92   public void onResume() {
93     super.onResume();
94
95     // Have to close, because we might have downloaded a new copy of the dictionary.
96     closeCurrentDictionary();
97
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);
104
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();
113         }});
114       alert.show();
115       return;
116     }
117     
118     try {
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);
125     }
126
127     setContentView(R.layout.main);
128
129     getSearchText().addTextChangedListener(new DictionaryTextWatcher());
130
131     setListAdapter(dictionaryListAdapter);
132
133     // Language button.
134     final Button langButton = (Button) findViewById(R.id.LangButton);
135     langButton.setOnClickListener(new OnClickListener() {
136       public void onClick(View v) {
137         switchLanguage();
138       }});
139     updateLangButton();
140
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;
150         } else {
151           destRowIndex = selectedTokenRowIndex;
152         }
153         jumpToRow(destRowIndex);
154       }});
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;
164         } else {
165           destRowIndex = activeLangaugeData.rows.size() - 1;
166         }
167         jumpToRow(destRowIndex);
168       }});
169
170     // ContextMenu.
171     registerForContextMenu(getListView());
172
173     // ItemSelectedListener.
174     getListView().setOnItemSelectedListener(new OnItemSelectedListener() {
175       public void onItemSelected(AdapterView<?> arg0, View arg1, int rowIndex,
176           long arg3) {
177         Log.d("THAD", "onItemSelected: " + rowIndex);
178         selectedRowIndex = rowIndex;
179         selectedTokenRowIndex = activeLangaugeData.getIndexEntryForRow(rowIndex).startRow;
180         updateSearchText();
181       }
182
183       public void onNothingSelected(AdapterView<?> arg0) {
184       }});
185     
186
187     // LongClickListener.
188     getListView().setOnItemLongClickListener((new OnItemLongClickListener() {
189       public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int rowIndex,
190           long arg3) {
191         selectedRowIndex = rowIndex;
192         return false;
193       }
194     }));
195
196     onSearchTextChange("");
197   }
198   
199   
200   @Override
201   public void onStop() {
202     super.onStop();
203     closeCurrentDictionary();
204   }
205
206   private void closeCurrentDictionary() {
207     dictionary = null;
208     activeLangaugeData = null;
209     try {
210       if (dictRaf != null) {
211         dictRaf.close();
212       }
213     } catch (IOException e) {
214       throw new RuntimeException(e);
215     }
216     dictRaf = null;
217   }
218   
219   public String getSelectedRowRawText() {
220     return activeLangaugeData.rowToString(activeLangaugeData.rows.get(selectedRowIndex));
221   }
222   
223   public EditText getSearchText() {
224     return (EditText) findViewById(R.id.SearchText);
225   }
226   
227   // ----------------------------------------------------------------
228   // OptionsMenu
229   // ----------------------------------------------------------------
230
231   private MenuItem switchLanguageMenuItem = null;
232   
233
234   @Override
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) {
239         switchLanguage();
240         return false;
241       }});
242
243     final MenuItem preferences = menu.add("Preferences...");
244     preferences.setOnMenuItemClickListener(new OnMenuItemClickListener(){
245       public boolean onMenuItemClick(final MenuItem menuItem) {
246         startActivity(preferencesIntent);
247         return false;
248       }});
249
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.");
257         } else {
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");
264           }
265         }
266         intent.putExtra(AboutActivity.CURRENT_DICT_INFO, currentDictInfo.toString());
267         startActivity(intent);
268         return false;
269       }});
270
271     final MenuItem download = menu.add("Download dictionary...");
272     download.setOnMenuItemClickListener(new OnMenuItemClickListener(){
273       public boolean onMenuItemClick(final MenuItem menuItem) {
274         startDownloadDictActivity();
275         return false;
276       }});
277
278     return true;
279   }
280   
281   @Override
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));
285     }
286     switchLanguageMenuItem.setEnabled(dictionary != null);
287     return super.onPrepareOptionsMenu(menu);
288   }
289
290   void switchLanguage() {
291     activeLangaugeData = dictionary.languageDatas[(activeLangaugeData == dictionary.languageDatas[0]) ? 1 : 0];
292     selectedRowIndex = 0;
293     selectedTokenRowIndex = 0;
294     updateLangButton();
295     dictionaryListAdapter.notifyDataSetChanged();
296     onSearchTextChange(getSearchText().getText().toString());
297   }
298   
299   void updateLangButton() {
300     final Button langButton = (Button) findViewById(R.id.LangButton);
301     langButton.setText(activeLangaugeData.language.symbol);
302   }
303   
304   // ----------------------------------------------------------------
305   // ContextMenu
306   // ----------------------------------------------------------------
307   
308   @Override
309   public void onCreateContextMenu(ContextMenu menu, View v,
310       ContextMenuInfo menuInfo) {
311     if (selectedRowIndex == -1) {
312       return;
313     }
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);
323         try {
324           wordList.getParentFile().mkdirs();
325           final PrintWriter out = new PrintWriter(new FileWriter(wordList, true));
326           out.println(rawText.toString());
327           out.close();
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());
332           alert.show();
333         }
334         return false;
335       }
336     });
337   }
338   
339   @Override
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();
347       }
348       return true;
349     }
350     return super.onKeyDown(keyCode, event);
351   }
352
353   @Override
354   protected void onListItemClick(ListView l, View v, int row, long id) {
355     selectedRowIndex = row;
356     Log.d("THAD", "Clicked: " + getSelectedRowRawText());
357     openContextMenu(getListView());
358   }
359
360   void onSearchTextChange(final String searchText) {
361     Log.d("THAD", "onSearchTextChange: " + searchText);
362     if (searchOperation != null) {
363       searchOperation.interrupted.set(true);
364     }
365     searchOperation = new SearchOperation(searchText);
366     searchExecutor.execute(searchOperation);
367   }
368   
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?
375     updateSearchText();
376   }
377
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);
385       }
386     }
387   }
388
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);
399   }
400
401   private final class SearchOperation implements Runnable {
402     final String searchText;
403     final AtomicBoolean interrupted = new AtomicBoolean(false);
404
405     public SearchOperation(final String searchText) {
406       this.searchText = searchText;
407     }
408
409     public void run() {
410       Log.d("THAD", "SearchOperation: " + searchText);
411       final int indexLocation = activeLangaugeData.lookup(searchText, interrupted);
412       if (interrupted.get()) {
413         return;
414       }
415       final IndexEntry indexEntry = activeLangaugeData.sortedIndex
416           .get(indexLocation);
417       uiHandler.post(new Runnable() {
418         public void run() {
419           jumpToRow(indexEntry.startRow);
420         }
421       });
422     }
423   }
424
425   private class DictionaryListAdapter extends BaseAdapter {
426
427     public int getCount() {
428       return dictionary != null ? activeLangaugeData.rows.size() : 0;
429     }
430
431     public Dictionary.Row getItem(int rowIndex) {
432       assert rowIndex < activeLangaugeData.rows.size();
433       return activeLangaugeData.rows.get(rowIndex);
434     }
435
436     public long getItemId(int rowIndex) {
437       return rowIndex;
438     }
439
440     public View getView(final int rowIndex, final View convertView,
441         final ViewGroup parent) {
442       final Row row = getItem(rowIndex);
443       
444       // Token row.
445       if (row.isToken()) {
446         TextView result = null;
447         if (convertView instanceof TextView) {
448           result = (TextView) convertView;
449         } else {
450           result = new TextView(parent.getContext());
451         }
452         result.setText(activeLangaugeData.rowToString(row));
453         result.setTextAppearance(parent.getContext(),
454             android.R.style.TextAppearance_Large);
455         result.setClickable(false);
456         return result;
457       }
458
459       // Entry row(s).
460       final TableLayout result = new TableLayout(parent.getContext());
461
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());
466         
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;
471         
472         if (r>0){
473           final TextView spacer = new TextView(tableRow.getContext());
474           spacer.setText(r == 0 ? "\95 " : " \95 ");
475           tableRow.addView(spacer);
476         }
477         tableRow.addView(column1, layoutParams);
478         if (r>0){
479           final TextView spacer = new TextView(tableRow.getContext());
480           spacer.setText(r == 0 ? "\95 " : " \95 ");
481           tableRow.addView(spacer);
482         }
483         tableRow.addView(column2, layoutParams);
484         
485         column1.setWidth(1);
486         column2.setWidth(1);
487         // column1.setTextAppearance(parent.getContext(), android.R.style.Text);
488         
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();
493         int startPos = 0;
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();
498         }
499         
500         column2.setText(entry.getAllText(Entry.otherLang(activeLangaugeData.lang))[r], TextView.BufferType.NORMAL);
501         
502         result.addView(tableRow);
503       }
504       
505       return result;
506     }
507   }  // DictionaryListAdapter
508
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());
514       }
515     }
516
517     public void beforeTextChanged(CharSequence arg0, int arg1, int arg2,
518         int arg3) {
519     }
520
521     public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
522     }
523   }
524   
525 }