]> gitweb.fperrin.net Git - Dictionary.git/blob - src/com/hughes/android/dictionary/DictionaryActivityOld.java.old
go
[Dictionary.git] / src / com / hughes / android / dictionary / DictionaryActivityOld.java.old
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.Arrays;
10 import java.util.Date;
11 import java.util.concurrent.Executor;
12 import java.util.concurrent.Executors;
13 import java.util.concurrent.atomic.AtomicBoolean;
14
15 import android.app.ListActivity;
16 import android.content.Context;
17 import android.content.Intent;
18 import android.content.SharedPreferences;
19 import android.content.SharedPreferences.Editor;
20 import android.graphics.Typeface;
21 import android.os.Bundle;
22 import android.os.Handler;
23 import android.preference.PreferenceManager;
24 import android.text.ClipboardManager;
25 import android.text.Editable;
26 import android.text.Spannable;
27 import android.text.TextWatcher;
28 import android.text.style.StyleSpan;
29 import android.util.Log;
30 import android.view.ContextMenu;
31 import android.view.KeyEvent;
32 import android.view.Menu;
33 import android.view.MenuItem;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.view.ContextMenu.ContextMenuInfo;
37 import android.view.MenuItem.OnMenuItemClickListener;
38 import android.view.View.OnClickListener;
39 import android.view.inputmethod.InputMethodManager;
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.Toast;
49
50 import com.hughes.android.dictionary.engine.Dictionary;
51 import com.hughes.android.dictionary.engine.Language;
52 import com.ibm.icu.text.Collator;
53
54 public class DictionaryActivityOld extends ListActivity {
55   
56   // TO DO:
57   // * Easy reverse lookup.
58   // * Download latest dicts.
59   //   * http://ftp.tu-chemnitz.de/pub/Local/urz/ding/de-en-devel/
60   //   * http://www1.dict.cc/translation_file_request.php?l=e
61   // * Compress all the strings everywhere, put compression table in file.
62   // Done:
63   // * Only one way to way for current search to end. (won't do).
64
65   static final String LOG = "QuickDic";
66   static final String PREF_DICT_ACTIVE_LANG = "DICT_DIR_PREF";
67   static final String PREF_ACTIVE_SEARCH_TEXT = "ACTIVE_WORD_PREF";
68
69   // package for test.
70   final Handler uiHandler = new Handler();
71   private final Executor searchExecutor = Executors.newSingleThreadExecutor();
72
73   EditText searchText;
74   Button langButton;
75   int lastSelectedRow = 0;  // TODO: I'm evil.
76
77   private boolean prefsMightHaveChanged = true;
78
79   // Never null.
80   private File wordList = null;
81   private RandomAccessFile dictRaf = null;
82   private Dictionary dictionary = null;
83   private boolean saveOnlyFirstSubentry = false;
84
85   // Visible for testing.
86   IndexAdapter indexAdapter = null;
87   private SearchOperation searchOperation = null;
88   
89   public DictionaryActivity() {
90
91     searchExecutor.execute(new Runnable() {
92       public void run() {
93         final long startMillis = System.currentTimeMillis();
94         for (final String lang : Arrays.asList("EN", "DE")) {
95           Language.lookup(lang).getFindCollator(); 
96           final Collator c = Language.lookup(lang).getSortCollator(); 
97           if (c.compare("pre-print", "preppy") >= 0) {
98             Log.e(LOG, c.getClass() + " is buggy, lookups may not work properly.");
99           }
100         }
101         Log.d(LOG, "Loading collators took:" + (System.currentTimeMillis() - startMillis));
102       }
103     });
104
105   }
106
107   /** Called when the activity is first created. */
108   @Override
109   public void onCreate(Bundle savedInstanceState) {
110     super.onCreate(savedInstanceState);
111     Log.d(LOG, "onCreate:" + this);
112
113     try {
114       initDictionaryAndPrefs();
115     } catch (Exception e) {
116       return;
117     }
118
119     // UI init.
120
121     setContentView(R.layout.dictionary_activity);
122     searchText = (EditText) findViewById(R.id.SearchText);
123     langButton = (Button) findViewById(R.id.LangButton);
124     
125     searchText.addTextChangedListener(new SearchTextWatcher());
126     
127     getListView().setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
128       public void onItemSelected(AdapterView<?> arg0, View arg1, int row,
129           long arg3) {
130         setSelectedRow(row);
131       }
132       public void onNothingSelected(AdapterView<?> arg0) {
133       }
134     });
135     
136     getListView().setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
137       public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int row,
138           long arg3) {
139         setSelectedRow(row);
140         return false;
141       }
142     });
143     
144     final Button clearSearchTextButton = (Button) findViewById(R.id.ClearSearchTextButton);
145     clearSearchTextButton.setOnClickListener(new OnClickListener() {
146       public void onClick(View v) {
147         onClearSearchTextButton(clearSearchTextButton);
148       }
149     });
150     clearSearchTextButton.setVisibility(PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
151         getString(R.string.showClearSearchTextButtonKey), true) ? View.VISIBLE
152         : View.GONE);
153     
154     final Button langButton = (Button) findViewById(R.id.LangButton);
155     langButton.setOnClickListener(new OnClickListener() {
156       public void onClick(View v) {
157         onLanguageButton();
158       }
159     });
160     
161     final Button upButton = (Button) findViewById(R.id.UpButton);
162     upButton.setOnClickListener(new OnClickListener() {
163       public void onClick(View v) {
164         onUpButton();
165       }
166     });
167     final Button downButton = (Button) findViewById(R.id.DownButton);
168     downButton.setOnClickListener(new OnClickListener() {
169       public void onClick(View v) {
170         onDownButton();
171       }
172     });
173
174     // ContextMenu.
175     registerForContextMenu(getListView());
176
177     updateLangButton();
178   }
179   
180   private void initDictionaryAndPrefs() throws Exception {
181     if (!prefsMightHaveChanged) {
182       return;
183     }
184     closeCurrentDictionary();
185     
186     final SharedPreferences prefs = PreferenceManager
187         .getDefaultSharedPreferences(this);
188     wordList = new File(prefs.getString(getString(R.string.wordListFileKey),
189         getString(R.string.wordListFileDefault)));
190     Log.d(LOG, "wordList=" + wordList);
191     
192     saveOnlyFirstSubentry = prefs.getBoolean(getString(R.string.saveOnlyFirstSubentryKey), false);
193
194     final File dictFile = new File(prefs.getString(getString(R.string.dictFileKey),
195         getString(R.string.dictFileDefault)));
196     Log.d(LOG, "dictFile=" + dictFile);
197     
198     try {
199       if (!dictFile.canRead()) {
200         throw new IOException("Unable to read dictionary file.");
201       }
202       
203       dictRaf = new RandomAccessFile(dictFile, "r");
204       final long startMillis = System.currentTimeMillis();
205       dictionary = new Dictionary(dictRaf);
206       Log.d(LOG, "Read dictionary millis: " + (System.currentTimeMillis() - startMillis));
207     } catch (IOException e) {
208       Log.e(LOG, "Couldn't open dictionary.", e);
209       
210       this.startActivity(new asdfIntent(this, DictionaryEditActivity.class));
211       finish();
212     }
213     
214     final byte lang = prefs.getInt(PREF_DICT_ACTIVE_LANG, SimpleEntry.LANG1) == SimpleEntry.LANG1 ? SimpleEntry.LANG1
215         : SimpleEntry.LANG2;
216     
217     indexAdapter = new IndexAdapter(dictionary.languageDatas[lang]);
218     setListAdapter(indexAdapter);
219     prefsMightHaveChanged = false;
220   }
221
222   @Override
223   public void onResume() {
224     super.onResume();
225     Log.d(LOG, "onResume:" + this);
226
227     try {
228       initDictionaryAndPrefs();
229     } catch (Exception e) {
230       return;
231     }
232     
233     final SharedPreferences prefs = PreferenceManager
234         .getDefaultSharedPreferences(this);
235     final String searchTextString = prefs
236         .getString(PREF_ACTIVE_SEARCH_TEXT, "");
237     searchText.setText(searchTextString);
238     getListView().requestFocus();
239     onSearchTextChange(searchTextString);
240   }
241
242   @Override
243   public void onPause() {
244     super.onPause();
245     Log.d(LOG, "onPause:" + this);
246     final Editor prefs = PreferenceManager.getDefaultSharedPreferences(this)
247         .edit();
248     prefs.putInt(PREF_DICT_ACTIVE_LANG, indexAdapter.languageData.lang);
249     prefs.putString(PREF_ACTIVE_SEARCH_TEXT, searchText.getText().toString());
250     prefs.commit();
251   }
252
253   @Override
254   public void onStop() {
255     super.onStop();
256     Log.d(LOG, "onStop:" + this);
257     if (isFinishing()) {
258       Log.i(LOG, "isFinishing()==true, closing dictionary.");
259       closeCurrentDictionary();
260     }
261   }
262
263   private void closeCurrentDictionary() {
264     Log.i(LOG, "closeCurrentDictionary");
265     if (dictionary == null) {
266       return;
267     }
268     waitForSearchEnd();
269     indexAdapter = null;
270     setListAdapter(null);
271     Log.d(LOG, "setListAdapter finished.");
272     dictionary = null;
273     try {
274       if (dictRaf != null) {
275         dictRaf.close();
276       }
277     } catch (IOException e) {
278       throw new RuntimeException(e);
279     }
280     dictRaf = null;
281   }
282
283   public String getSelectedRowRawText(final boolean onlyFirstSubentry) {
284     final Row row = indexAdapter.languageData.rows.get(getSelectedRow());
285     return indexAdapter.languageData.rowToString(row, onlyFirstSubentry);
286   }
287
288   // ----------------------------------------------------------------
289   // OptionsMenu
290   // ----------------------------------------------------------------
291
292   private MenuItem switchLanguageMenuItem = null;
293
294   @Override
295   public boolean onCreateOptionsMenu(final Menu menu) {
296     switchLanguageMenuItem = menu.add(getString(R.string.switchToLanguage));
297     switchLanguageMenuItem
298         .setOnMenuItemClickListener(new OnMenuItemClickListener() {
299           public boolean onMenuItemClick(final MenuItem menuItem) {
300             onLanguageButton();
301             return false;
302           }
303         });
304
305     final MenuItem preferences = menu.add(getString(R.string.preferences));
306     preferences.setOnMenuItemClickListener(new OnMenuItemClickListener() {
307       public boolean onMenuItemClick(final MenuItem menuItem) {
308         prefsMightHaveChanged = true;
309         startActivity(new Intent(DictionaryActivity.this,
310             PreferenceActivity.class));
311         return false;
312       }
313     });
314
315     final MenuItem about = menu.add(getString(R.string.about));
316     about.setOnMenuItemClickListener(new OnMenuItemClickListener() {
317       public boolean onMenuItemClick(final MenuItem menuItem) {
318         final Intent intent = new Intent().setClassName(AboutActivity.class
319             .getPackage().getName(), AboutActivity.class.getCanonicalName());
320         final String currentDictInfo;
321         if (dictionary == null) {
322           currentDictInfo = getString(R.string.noDictLoaded);
323         } else {
324           final LanguageData lang0 = dictionary.languageDatas[0];
325           final LanguageData lang1 = dictionary.languageDatas[1];
326           currentDictInfo = getString(R.string.aboutText, dictionary.dictionaryInfo, dictionary.entries.size(), 
327               lang0.language.symbol, lang0.sortedIndex.size(), lang0.rows.size(),
328               lang1.language.symbol, lang1.sortedIndex.size(), lang1.rows.size());
329         }
330         intent.putExtra(AboutActivity.CURRENT_DICT_INFO, currentDictInfo
331             .toString());
332         startActivity(intent);
333         return false;
334       }
335     });
336
337     final MenuItem download = menu.add(getString(R.string.downloadDictionary));
338     download.setOnMenuItemClickListener(new OnMenuItemClickListener() {
339       public boolean onMenuItemClick(final MenuItem menuItem) {
340         prefsMightHaveChanged = true;
341         startDownloadDictActivity(DictionaryActivity.this);
342         return false;
343       }
344     });
345
346     return true;
347   }
348
349   @Override
350   public boolean onPrepareOptionsMenu(final Menu menu) {
351     switchLanguageMenuItem.setTitle(getString(R.string.switchToLanguage,
352         dictionary.languageDatas[SimpleEntry
353             .otherLang(indexAdapter.languageData.lang)].language.symbol));
354     return super.onPrepareOptionsMenu(menu);
355   }
356   
357   void updateLangButton() {
358     langButton.setText(indexAdapter.languageData.language.symbol);
359   }
360
361   // ----------------------------------------------------------------
362   // Event handlers.
363   // ----------------------------------------------------------------
364   
365   void onLanguageButton() {
366     waitForSearchEnd();
367     indexAdapter = new IndexAdapter(
368         dictionary.languageDatas[(indexAdapter.languageData == dictionary.languageDatas[0]) ? 1
369             : 0]);
370     Log.d(LOG, "onLanguageButton, newLang=" + indexAdapter.languageData.language.symbol);
371     setListAdapter(indexAdapter);
372     updateLangButton();
373     onSearchTextChange(searchText.getText().toString());
374   }
375
376   void onUpButton() {
377     final int destRowIndex = indexAdapter.languageData.getPrevTokenRow(getSelectedRow());
378     Log.d(LOG, "onUpButton, destRowIndex=" + destRowIndex);
379     jumpToRow(indexAdapter, destRowIndex);
380   }
381
382   void onDownButton() {
383     final int destRowIndex = indexAdapter.languageData.getNextTokenRow(getSelectedRow());
384     Log.d(LOG, "onDownButton, destRowIndex=" + destRowIndex);
385     jumpToRow(indexAdapter, destRowIndex);
386   }
387
388   void onAppendToWordList() {
389     final int row = getSelectedRow();
390     if (row < 0) {
391       return;
392     }
393     final StringBuilder rawText = new StringBuilder();
394     final String word = indexAdapter.languageData.getIndexEntryForRow(row).word;
395     rawText.append(
396         new SimpleDateFormat("yyyy.MM.dd HH:mm:ss").format(new Date()))
397         .append("\t");
398     rawText.append(word).append("\t");
399     rawText.append(getSelectedRowRawText(saveOnlyFirstSubentry));
400     Log.d(LOG, "Writing : " + rawText);
401     try {
402       wordList.getParentFile().mkdirs();
403       final PrintWriter out = new PrintWriter(
404           new FileWriter(wordList, true));
405       out.println(rawText.toString());
406       out.close();
407     } catch (IOException e) {
408       Log.e(LOG, "Unable to append to " + wordList.getAbsolutePath(), e);
409       Toast.makeText(this, getString(R.string.failedAddingToWordList, wordList.getAbsolutePath()), Toast.LENGTH_LONG);
410     }
411     return;
412   }
413
414   void onCopy() {
415     final int row = getSelectedRow();
416     if (row < 0) {
417       return;
418     }
419     Log.d(LOG, "Copy." + DictionaryActivity.this.getSelectedRow());
420     final StringBuilder result = new StringBuilder();
421     result.append(getSelectedRowRawText(false));
422     final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
423     clipboardManager.setText(result.toString());
424     Log.d(LOG, "Copied: " + result);
425   }
426
427   @Override
428   public boolean onKeyDown(int keyCode, KeyEvent event) {
429     if (event.getUnicodeChar() != 0) {
430       if (!searchText.hasFocus()) {
431         searchText.setText("" + (char) event.getUnicodeChar());
432         onSearchTextChange(searchText.getText().toString());
433         searchText.requestFocus();
434       }
435       return true;
436     }
437     return super.onKeyDown(keyCode, event);
438   }
439
440   @Override
441   protected void onListItemClick(ListView l, View v, int row, long id) {
442     setSelectedRow(row);
443     openContextMenu(getListView());
444   }
445
446   void onSearchTextChange(final String searchText) {
447     Log.d(LOG, "onSearchTextChange: " + searchText);
448     synchronized (this) {
449       searchOperation = new SearchOperation(indexAdapter, searchText.trim(), searchOperation);
450       searchExecutor.execute(searchOperation);
451     }
452   }
453
454   private void onClearSearchTextButton(final Button clearSearchTextButton) {
455     clearSearchTextButton.requestFocus();
456     searchText.setText("");
457     searchText.requestFocus();
458     Log.d(LOG, "Trying to show soft keyboard.");
459     final InputMethodManager manager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
460     manager.showSoftInput(searchText, InputMethodManager.SHOW_IMPLICIT);
461   }
462
463   // ----------------------------------------------------------------
464   // ContextMenu
465   // ----------------------------------------------------------------
466
467   @Override
468   public void onCreateContextMenu(ContextMenu menu, View v,
469       ContextMenuInfo menuInfo) {
470     final int row = getSelectedRow();
471     if (row < 0) {
472       return;
473     }
474
475     final MenuItem addToWordlist = menu.add(getString(R.string.addToWordList, wordList.getName()));
476     addToWordlist.setOnMenuItemClickListener(new OnMenuItemClickListener() {
477       public boolean onMenuItemClick(MenuItem item) {
478         onAppendToWordList();
479         return false;
480       }
481     });
482
483     final MenuItem copy = menu.add(android.R.string.copy);
484     copy.setOnMenuItemClickListener(new OnMenuItemClickListener() {
485       public boolean onMenuItemClick(MenuItem item) {
486         onCopy();
487         return false;
488       }
489     });
490
491   }
492
493   private void jumpToRow(final IndexAdapter dictionaryListAdapter,
494       final int rowIndex) {
495     Log.d(LOG, "jumpToRow: " + rowIndex);
496     if (dictionaryListAdapter != this.indexAdapter) {
497       Log.w(LOG, "skipping jumpToRow for old list adapter: " + rowIndex);
498       return;
499     }
500     setSelection(rowIndex);
501     setSelectedRow(rowIndex);
502     getListView().setSelected(true);
503   }
504   
505   // TODO: delete me somehow.
506   private int getSelectedRow() {
507     return lastSelectedRow;
508   }
509   private void setSelectedRow(final int row) {
510     lastSelectedRow = row;
511     Log.d(LOG, "Selected: " + getSelectedRowRawText(true));
512     updateSearchText();
513   }
514
515   private void updateSearchText() {
516     Log.d(LOG, "updateSearchText");
517     final int selectedRowIndex = getSelectedRow();
518     if (!searchText.hasFocus()) {
519       if (selectedRowIndex >= 0) {
520         final String word = indexAdapter.languageData
521             .getIndexEntryForRow(selectedRowIndex).word;
522         if (!word.equals(searchText.getText().toString())) {
523           Log.d(LOG, "updateSearchText: setText: " + word);
524           searchText.setText(word);
525         }
526       } else {
527         Log.w(LOG, "updateSearchText: nothing selected.");
528       }
529     }
530   }
531
532   static void startDownloadDictActivity(final Context context) {
533     final Intent intent = new Intent(context, DownloadActivity.class);
534     final SharedPreferences prefs = PreferenceManager
535         .getDefaultSharedPreferences(context);
536     final String dictFetchUrl = prefs.getString(context
537         .getString(R.string.dictFetchUrlKey), context
538         .getString(R.string.dictFetchUrlDefault));
539     final String dictFileName = prefs.getString(context
540         .getString(R.string.dictFileKey), context
541         .getString(R.string.dictFileDefault));
542     intent.putExtra(DownloadActivity.SOURCE, dictFetchUrl);
543     intent.putExtra(DownloadActivity.DEST, dictFileName);
544     context.startActivity(intent);
545   }
546
547   class IndexAdapter extends BaseAdapter {
548
549     // Visible for testing.
550     final LanguageData languageData;
551
552     IndexAdapter(final LanguageData languageData) {
553       this.languageData = languageData;
554     }
555
556     public int getCount() {
557       return languageData.rows.size();
558     }
559
560     public Dictionary.Row getItem(int rowIndex) {
561       assert rowIndex < languageData.rows.size();
562       return languageData.rows.get(rowIndex);
563     }
564
565     public long getItemId(int rowIndex) {
566       return rowIndex;
567     }
568
569     public View getView(final int rowIndex, final View convertView,
570         final ViewGroup parent) {
571       final Row row = getItem(rowIndex);
572
573       // Token row.
574       if (row.isToken()) {
575         TextView result = null;
576         if (convertView instanceof TextView) {
577           result = (TextView) convertView;
578         } else {
579           result = new TextView(parent.getContext());
580         }
581         if (row == null) {
582           return result;
583         }
584         result.setText(languageData.rowToString(row, false));
585         result.setTextAppearance(parent.getContext(),
586             android.R.style.TextAppearance_Large);
587         result.setClickable(false);
588         return result;
589       }
590
591       // Entry row(s).
592       final TableLayout result = new TableLayout(parent.getContext());
593
594       final SimpleEntry entry = new SimpleEntry(null, null);//.entries.get(row.getIndex());
595       final int rowCount = entry.getRowCount();
596       for (int r = 0; r < rowCount; ++r) {
597         final TableRow tableRow = new TableRow(result.getContext());
598
599         TextView column1 = new TextView(tableRow.getContext());
600         TextView column2 = new TextView(tableRow.getContext());
601         final TableRow.LayoutParams layoutParams = new TableRow.LayoutParams();
602         layoutParams.weight = 0.5f;
603
604         if (r > 0) {
605           final TextView spacer = new TextView(tableRow.getContext());
606           spacer.setText(r == 0 ? "� " : " � ");
607           tableRow.addView(spacer);
608         }
609         tableRow.addView(column1, layoutParams);
610         if (r > 0) {
611           final TextView spacer = new TextView(tableRow.getContext());
612           spacer.setText(r == 0 ? "� " : " � ");
613           tableRow.addView(spacer);
614         }
615         tableRow.addView(column2, layoutParams);
616
617         column1.setWidth(1);
618         column2.setWidth(1);
619         // column1.setTextAppearance(parent.getContext(), android.R.style.Text);
620
621         // TODO: color words by gender
622         final String col1Text = entry.getAllText(languageData.lang)[r];
623         column1.setText(col1Text, TextView.BufferType.SPANNABLE);
624         final Spannable col1Spannable = (Spannable) column1.getText();
625         int startPos = 0;
626         final String token = languageData.getIndexEntryForRow(rowIndex).word;
627         while ((startPos = col1Text.indexOf(token, startPos)) != -1) {
628           col1Spannable.setSpan(new StyleSpan(Typeface.BOLD), startPos,
629               startPos + token.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
630           startPos += token.length();
631         }
632
633         column2.setText(
634             entry.getAllText(SimpleEntry.otherLang(languageData.lang))[r],
635             TextView.BufferType.NORMAL);
636
637         result.addView(tableRow);
638       }
639
640       return result;
641     }
642
643   } // DictionaryListAdapter
644
645   private final class SearchOperation implements Runnable {
646     SearchOperation previousSearchOperation;
647     
648     final IndexAdapter listAdapter;
649     final LanguageData languageData;
650     final String searchText;
651     final AtomicBoolean interrupted = new AtomicBoolean(false);
652     boolean searchFinished = false;
653
654     SearchOperation(final IndexAdapter listAdapter,
655         final String searchText, final SearchOperation previousSearchOperation) {
656       this.listAdapter = listAdapter;
657       this.languageData = listAdapter.languageData;
658       this.searchText = searchText;
659       this.previousSearchOperation = previousSearchOperation;
660     }
661
662     public void run() {
663       if (previousSearchOperation != null) {
664         previousSearchOperation.stopAndWait();
665       }
666       previousSearchOperation = null;
667       
668       Log.d(LOG, "SearchOperation: " + searchText);
669       final int indexLocation = languageData.lookup(searchText, interrupted);
670       if (!interrupted.get()) {
671         final IndexEntry indexEntry = languageData.sortedIndex.get(indexLocation);
672         
673         Log.d(LOG, "SearchOperation completed: " + indexEntry.toString());
674         uiHandler.post(new Runnable() {
675           public void run() {
676             // Check is just a performance operation.
677             if (!interrupted.get()) {
678               // This is safe, because it checks that the listAdapter hasn't changed.
679               jumpToRow(listAdapter, indexEntry.startRow);
680             }
681             synchronized (DictionaryActivity.this) {
682               searchOperation = null;
683               DictionaryActivity.this.notifyAll();
684             }
685           }
686         });
687       }      
688       synchronized (this) {
689         searchFinished = true;
690         this.notifyAll();
691       }
692     }
693     
694     private void stopAndWait() {
695       interrupted.set(true);
696       synchronized (this) {
697         while (!searchFinished) {
698           Log.d(LOG, "stopAndWait: " + searchText);
699           try {
700             this.wait();
701           } catch (InterruptedException e) {
702             Log.e(LOG, "Interrupted", e);
703           }
704         }
705       }
706     }
707   }  // SearchOperation
708
709   void waitForSearchEnd() {
710     synchronized (this) {
711       while (searchOperation != null) {
712         Log.d(LOG, "waitForSearchEnd");
713         try {
714           this.wait();
715         } catch (InterruptedException e) {
716           Log.e(LOG, "Interrupted.", e);
717         }
718       }
719     }
720   }
721
722   private class SearchTextWatcher implements TextWatcher {
723     public void afterTextChanged(final Editable searchTextEditable) {
724       Log.d(LOG, "Search text changed: " + searchText.getText().toString());
725       if (searchText.hasFocus()) {
726         // If they were typing to cause the change, update the UI.
727         onSearchTextChange(searchText.getText().toString());
728       }
729     }
730
731     public void beforeTextChanged(CharSequence arg0, int arg1, int arg2,
732         int arg3) {
733     }
734
735     public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
736     }
737   }
738
739 }