]> gitweb.fperrin.net Git - Dictionary.git/blob - src/com/hughes/android/dictionary/DictionaryActivity.java
Added Apache license.
[Dictionary.git] / src / com / hughes / android / dictionary / DictionaryActivity.java
1 // Copyright 2011 Google Inc. All Rights Reserved.\r
2 //\r
3 // Licensed under the Apache License, Version 2.0 (the "License");\r
4 // you may not use this file except in compliance with the License.\r
5 // You may obtain a copy of the License at\r
6 //\r
7 //     http://www.apache.org/licenses/LICENSE-2.0\r
8 //\r
9 // Unless required by applicable law or agreed to in writing, software\r
10 // distributed under the License is distributed on an "AS IS" BASIS,\r
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
12 // See the License for the specific language governing permissions and\r
13 // limitations under the License.\r
14 \r
15 package com.hughes.android.dictionary;\r
16 \r
17 import java.io.File;\r
18 import java.io.FileWriter;\r
19 import java.io.IOException;\r
20 import java.io.PrintWriter;\r
21 import java.io.RandomAccessFile;\r
22 import java.text.SimpleDateFormat;\r
23 import java.util.Date;\r
24 import java.util.concurrent.Executor;\r
25 import java.util.concurrent.Executors;\r
26 import java.util.concurrent.ThreadFactory;\r
27 import java.util.concurrent.atomic.AtomicBoolean;\r
28 \r
29 import android.app.ListActivity;\r
30 import android.content.Context;\r
31 import android.content.Intent;\r
32 import android.content.SharedPreferences;\r
33 import android.graphics.Typeface;\r
34 import android.os.Bundle;\r
35 import android.os.Handler;\r
36 import android.preference.PreferenceManager;\r
37 import android.text.ClipboardManager;\r
38 import android.text.Editable;\r
39 import android.text.Spannable;\r
40 import android.text.TextWatcher;\r
41 import android.text.style.StyleSpan;\r
42 import android.util.Log;\r
43 import android.view.ContextMenu;\r
44 import android.view.ContextMenu.ContextMenuInfo;\r
45 import android.view.KeyEvent;\r
46 import android.view.Menu;\r
47 import android.view.MenuItem;\r
48 import android.view.MenuItem.OnMenuItemClickListener;\r
49 import android.view.View;\r
50 import android.view.View.OnClickListener;\r
51 import android.view.ViewGroup;\r
52 import android.view.inputmethod.InputMethodManager;\r
53 import android.widget.AdapterView;\r
54 import android.widget.AdapterView.AdapterContextMenuInfo;\r
55 import android.widget.BaseAdapter;\r
56 import android.widget.Button;\r
57 import android.widget.EditText;\r
58 import android.widget.ListAdapter;\r
59 import android.widget.ListView;\r
60 import android.widget.TableLayout;\r
61 import android.widget.TableRow;\r
62 import android.widget.TextView;\r
63 import android.widget.Toast;\r
64 \r
65 import com.hughes.android.dictionary.engine.Dictionary;\r
66 import com.hughes.android.dictionary.engine.Index;\r
67 import com.hughes.android.dictionary.engine.PairEntry;\r
68 import com.hughes.android.dictionary.engine.PairEntry.Pair;\r
69 import com.hughes.android.dictionary.engine.RowBase;\r
70 import com.hughes.android.dictionary.engine.TokenRow;\r
71 import com.hughes.android.dictionary.engine.TransliteratorManager;\r
72 import com.hughes.android.util.PersistentObjectCache;\r
73 \r
74 public class DictionaryActivity extends ListActivity {\r
75 \r
76   static final String LOG = "QuickDic";\r
77   \r
78   static final int VIBRATE_MILLIS = 100;\r
79 \r
80   int dictIndex = 0;\r
81   RandomAccessFile dictRaf = null;\r
82   Dictionary dictionary = null;\r
83   int indexIndex = 0;\r
84   Index index = null;\r
85   \r
86   // package for test.\r
87   final Handler uiHandler = new Handler();\r
88   private final Executor searchExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {\r
89     @Override\r
90     public Thread newThread(Runnable r) {\r
91       return new Thread(r, "searchExecutor");\r
92     }\r
93   });\r
94   private SearchOperation currentSearchOperation = null;\r
95 \r
96   EditText searchText;\r
97   Button langButton;\r
98 \r
99   // Never null.\r
100   private File wordList = null;\r
101   private boolean saveOnlyFirstSubentry = false;\r
102 \r
103   // Visible for testing.\r
104   ListAdapter indexAdapter = null;\r
105   \r
106   final SearchTextWatcher searchTextWatcher = new SearchTextWatcher();\r
107 \r
108   //private Vibrator vibrator = null;\r
109   \r
110   public DictionaryActivity() {\r
111   }\r
112   \r
113   public static Intent getIntent(final Context context, final int dictIndex, final int indexIndex, final String searchToken) {\r
114     setDictionaryPrefs(context, dictIndex, indexIndex, searchToken);\r
115     \r
116     final Intent intent = new Intent();\r
117     intent.setClassName(DictionaryActivity.class.getPackage().getName(), DictionaryActivity.class.getName());\r
118     return intent;\r
119   }\r
120 \r
121   public static void setDictionaryPrefs(final Context context,\r
122       final int dictIndex, final int indexIndex, final String searchToken) {\r
123     final SharedPreferences.Editor prefs = PreferenceManager.getDefaultSharedPreferences(context).edit();\r
124     prefs.putInt(C.DICT_INDEX, dictIndex);\r
125     prefs.putInt(C.INDEX_INDEX, indexIndex);\r
126     prefs.putString(C.SEARCH_TOKEN, searchToken);\r
127     prefs.commit();\r
128   }\r
129 \r
130   public static void clearDictionaryPrefs(final Context context) {\r
131     final SharedPreferences.Editor prefs = PreferenceManager.getDefaultSharedPreferences(context).edit();\r
132     prefs.remove(C.DICT_INDEX);\r
133     prefs.remove(C.INDEX_INDEX);\r
134     prefs.remove(C.SEARCH_TOKEN);\r
135     prefs.commit();\r
136     Log.d(LOG, "Removed default dictionary prefs.");\r
137   }\r
138 \r
139   @Override\r
140   public void onCreate(Bundle savedInstanceState) {\r
141     ((DictionaryApplication)getApplication()).applyTheme(this);\r
142     \r
143     super.onCreate(savedInstanceState);\r
144     Log.d(LOG, "onCreate:" + this);\r
145     \r
146     final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);\r
147     \r
148     try {\r
149       PersistentObjectCache.init(this);\r
150       QuickDicConfig quickDicConfig = PersistentObjectCache.init(\r
151           this).read(C.DICTIONARY_CONFIGS, QuickDicConfig.class);\r
152       dictIndex = prefs.getInt(C.DICT_INDEX, 0) ;\r
153       final DictionaryConfig dictionaryConfig = quickDicConfig.dictionaryConfigs.get(dictIndex);\r
154       dictRaf = new RandomAccessFile(dictionaryConfig.localFile, "r");\r
155       dictionary = new Dictionary(dictRaf); \r
156     } catch (Exception e) {\r
157       Log.e(LOG, "Unable to load dictionary.", e);\r
158       if (dictRaf != null) {\r
159         try {\r
160           dictRaf.close();\r
161         } catch (IOException e1) {\r
162           Log.e(LOG, "Unable to close dictRaf.", e1);\r
163         }\r
164         dictRaf = null;\r
165       }\r
166       Toast.makeText(this, getString(R.string.invalidDictionary, "", e.getMessage()), Toast.LENGTH_LONG);\r
167       startActivity(DictionaryEditActivity.getIntent(dictIndex));\r
168       finish();\r
169       return;\r
170     }\r
171 \r
172     indexIndex = prefs.getInt(C.INDEX_INDEX, 0) % dictionary.indices.size();\r
173     Log.d(LOG, "Loading index.");\r
174     index = dictionary.indices.get(indexIndex);\r
175     setListAdapter(new IndexAdapter(index));\r
176 \r
177     // Pre-load the collators.\r
178     searchExecutor.execute(new Runnable() {\r
179       public void run() {\r
180         final long startMillis = System.currentTimeMillis();\r
181         \r
182         TransliteratorManager.init(new TransliteratorManager.Callback() {\r
183           @Override\r
184           public void onTransliteratorReady() {\r
185             uiHandler.post(new Runnable() {\r
186               @Override\r
187               public void run() {\r
188                 onSearchTextChange(searchText.getText().toString());\r
189               }\r
190             });\r
191           }\r
192         });\r
193         \r
194         for (final Index index : dictionary.indices) {\r
195           Log.d(LOG, "Starting collator load for lang=" + index.sortLanguage.getSymbol());\r
196           \r
197           final com.ibm.icu.text.Collator c = index.sortLanguage.getCollator();          \r
198           if (c.compare("pre-print", "preppy") >= 0) {\r
199             Log.e(LOG, c.getClass()\r
200                 + " is buggy, lookups may not work properly.");\r
201           }\r
202         }\r
203         Log.d(LOG, "Loading collators took:"\r
204             + (System.currentTimeMillis() - startMillis));\r
205       }\r
206     });\r
207     \r
208 \r
209     setContentView(R.layout.dictionary_activity);\r
210     searchText = (EditText) findViewById(R.id.SearchText);\r
211     langButton = (Button) findViewById(R.id.LangButton);\r
212     \r
213     searchText.requestFocus();\r
214     searchText.addTextChangedListener(searchTextWatcher);\r
215     final String search = prefs.getString(C.SEARCH_TOKEN, "");\r
216     searchText.setText(search);\r
217     searchText.setSelection(0, search.length());\r
218     Log.d(LOG, "Trying to restore searchText=" + search);\r
219     \r
220     final Button clearSearchTextButton = (Button) findViewById(R.id.ClearSearchTextButton);\r
221     clearSearchTextButton.setOnClickListener(new OnClickListener() {\r
222       public void onClick(View v) {\r
223         onClearSearchTextButton(clearSearchTextButton);\r
224       }\r
225     });\r
226     clearSearchTextButton.setVisibility(PreferenceManager.getDefaultSharedPreferences(this).getBoolean(\r
227         getString(R.string.showClearSearchTextButtonKey), true) ? View.VISIBLE\r
228         : View.GONE);\r
229     \r
230     final Button langButton = (Button) findViewById(R.id.LangButton);\r
231     langButton.setOnClickListener(new OnClickListener() {\r
232       public void onClick(View v) {\r
233         onLanguageButton();\r
234       }\r
235     });\r
236     updateLangButton();\r
237     \r
238     final Button upButton = (Button) findViewById(R.id.UpButton);\r
239     upButton.setOnClickListener(new OnClickListener() {\r
240       public void onClick(View v) {\r
241         onUpDownButton(true);\r
242       }\r
243     });\r
244     final Button downButton = (Button) findViewById(R.id.DownButton);\r
245     downButton.setOnClickListener(new OnClickListener() {\r
246       public void onClick(View v) {\r
247         onUpDownButton(false);\r
248       }\r
249     });\r
250 \r
251    getListView().setOnItemSelectedListener(new ListView.OnItemSelectedListener() {\r
252       @Override\r
253       public void onItemSelected(AdapterView<?> adapterView, View arg1, final int position,\r
254           long id) {\r
255         if (!searchText.isFocused()) {\r
256           // TODO: don't do this if multi words are entered.\r
257           final RowBase row = (RowBase) getListAdapter().getItem(position);\r
258           Log.d(LOG, "onItemSelected: " + row.index());\r
259           final TokenRow tokenRow = row.getTokenRow(true);\r
260           searchText.setText(tokenRow.getToken());\r
261         }\r
262       }\r
263 \r
264       @Override\r
265       public void onNothingSelected(AdapterView<?> arg0) {\r
266       }\r
267     });\r
268 \r
269     // ContextMenu.\r
270     registerForContextMenu(getListView());\r
271 \r
272     // Prefs.\r
273     wordList = new File(prefs.getString(getString(R.string.wordListFileKey),\r
274         getString(R.string.wordListFileDefault)));\r
275     saveOnlyFirstSubentry = prefs.getBoolean(getString(R.string.saveOnlyFirstSubentryKey), false);\r
276     //if (prefs.getBoolean(getString(R.string.vibrateOnFailedSearchKey), true)) {\r
277       // vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);\r
278     //}\r
279     Log.d(LOG, "wordList=" + wordList + ", saveOnlyFirstSubentry=" + saveOnlyFirstSubentry);\r
280   }\r
281   \r
282   @Override\r
283   protected void onResume() {\r
284     super.onResume();\r
285   }\r
286   \r
287   @Override\r
288   protected void onPause() {\r
289     super.onPause();\r
290   }\r
291 \r
292   @Override\r
293   protected void onDestroy() {\r
294     super.onDestroy();\r
295     if (dictRaf == null) {\r
296       return;\r
297     }\r
298     setDictionaryPrefs(this, dictIndex, indexIndex, searchText.getText().toString());\r
299     \r
300     // Before we close the RAF, we have to wind the current search down.\r
301     if (currentSearchOperation != null) {\r
302       Log.d(LOG, "Interrupting search to shut down.");\r
303       final SearchOperation searchOperation = currentSearchOperation;\r
304       currentSearchOperation = null;\r
305       searchOperation.interrupted.set(true);\r
306       synchronized (searchOperation) {\r
307         while (!searchOperation.done) {\r
308           try {\r
309             searchOperation.wait();\r
310           } catch (InterruptedException e) {\r
311             Log.d(LOG, "Interrupted.", e);\r
312           }\r
313         }\r
314       }\r
315     }\r
316     \r
317     try {\r
318       Log.d(LOG, "Closing RAF.");\r
319       dictRaf.close();\r
320     } catch (IOException e) {\r
321       Log.e(LOG, "Failed to close dictionary", e);\r
322     }\r
323     dictRaf = null;\r
324   }\r
325 \r
326   // --------------------------------------------------------------------------\r
327   // Buttons\r
328   // --------------------------------------------------------------------------\r
329 \r
330   private void onClearSearchTextButton(final Button clearSearchTextButton) {\r
331     clearSearchTextButton.requestFocus();\r
332     searchText.setText("");\r
333     searchText.requestFocus();\r
334     Log.d(LOG, "Trying to show soft keyboard.");\r
335     final InputMethodManager manager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);\r
336     manager.showSoftInput(searchText, InputMethodManager.SHOW_IMPLICIT);\r
337   }\r
338   \r
339   void updateLangButton() {\r
340     langButton.setText(index.shortName.toUpperCase());\r
341   }\r
342 \r
343   void onLanguageButton() {\r
344     if (currentSearchOperation != null) {\r
345       currentSearchOperation.interrupted.set(true);\r
346       currentSearchOperation = null;\r
347     }\r
348     \r
349     indexIndex = (indexIndex + 1) % dictionary.indices.size();\r
350     index = dictionary.indices.get(indexIndex);\r
351     indexAdapter = new IndexAdapter(index);\r
352     Log.d(LOG, "onLanguageButton, newLang=" + index.longName);\r
353     setListAdapter(indexAdapter);\r
354     updateLangButton();\r
355     onSearchTextChange(searchText.getText().toString());\r
356   }\r
357   \r
358   void onUpDownButton(final boolean up) {\r
359     final int firstVisibleRow = getListView().getFirstVisiblePosition();\r
360     final RowBase row = index.rows.get(firstVisibleRow);\r
361     final TokenRow tokenRow = row.getTokenRow(true);\r
362     final int destIndexEntry;\r
363     if (up) {\r
364       if (row != tokenRow) {\r
365         destIndexEntry = tokenRow.referenceIndex;\r
366       } else {\r
367         destIndexEntry = Math.max(tokenRow.referenceIndex - 1, 0);\r
368       }\r
369     } else {\r
370       // Down\r
371       destIndexEntry = Math.min(tokenRow.referenceIndex + 1, index.sortedIndexEntries.size());\r
372     }\r
373     final Index.IndexEntry dest = index.sortedIndexEntries.get(destIndexEntry);\r
374     Log.d(LOG, "onUpDownButton, destIndexEntry=" + dest.token);\r
375     searchText.removeTextChangedListener(searchTextWatcher);\r
376     searchText.setText(dest.token);\r
377     jumpToRow(index.sortedIndexEntries.get(destIndexEntry).startRow);\r
378     searchText.addTextChangedListener(searchTextWatcher);\r
379   }\r
380 \r
381   // --------------------------------------------------------------------------\r
382   // Options Menu\r
383   // --------------------------------------------------------------------------\r
384   \r
385   @Override\r
386   public boolean onCreateOptionsMenu(final Menu menu) {\r
387     \r
388     {\r
389       final MenuItem preferences = menu.add(getString(R.string.preferences));\r
390       preferences.setOnMenuItemClickListener(new OnMenuItemClickListener() {\r
391         public boolean onMenuItemClick(final MenuItem menuItem) {\r
392           startActivity(new Intent(DictionaryActivity.this,\r
393               PreferenceActivity.class));\r
394           return false;\r
395         }\r
396       });\r
397     }\r
398 \r
399     {\r
400       final MenuItem dictionaryList = menu.add(getString(R.string.dictionaryList));\r
401       dictionaryList.setOnMenuItemClickListener(new OnMenuItemClickListener() {\r
402         public boolean onMenuItemClick(final MenuItem menuItem) {\r
403           startActivity(DictionaryListActivity.getIntent(DictionaryActivity.this));\r
404           finish();\r
405           return false;\r
406         }\r
407       });\r
408     }\r
409 \r
410     {\r
411       final MenuItem dictionaryEdit = menu.add(getString(R.string.editDictionary));\r
412       dictionaryEdit.setOnMenuItemClickListener(new OnMenuItemClickListener() {\r
413         public boolean onMenuItemClick(final MenuItem menuItem) {\r
414           final Intent intent = DictionaryEditActivity.getIntent(dictIndex);\r
415           startActivity(intent);\r
416           return false;\r
417         }\r
418       });\r
419     }\r
420 \r
421     return true;\r
422   }\r
423 \r
424 \r
425   // --------------------------------------------------------------------------\r
426   // Context Menu + clicks\r
427   // --------------------------------------------------------------------------\r
428 \r
429   @Override\r
430   public void onCreateContextMenu(ContextMenu menu, View v,\r
431       ContextMenuInfo menuInfo) {\r
432     AdapterContextMenuInfo adapterContextMenuInfo = (AdapterContextMenuInfo) menuInfo;\r
433     final RowBase row = (RowBase) getListAdapter().getItem(adapterContextMenuInfo.position);\r
434 \r
435     final MenuItem addToWordlist = menu.add(getString(R.string.addToWordList, wordList.getName()));\r
436     addToWordlist.setOnMenuItemClickListener(new OnMenuItemClickListener() {\r
437       public boolean onMenuItemClick(MenuItem item) {\r
438         onAppendToWordList(row);\r
439         return false;\r
440       }\r
441     });\r
442 \r
443     final MenuItem copy = menu.add(android.R.string.copy);\r
444     copy.setOnMenuItemClickListener(new OnMenuItemClickListener() {\r
445       public boolean onMenuItemClick(MenuItem item) {\r
446         onCopy(row);\r
447         return false;\r
448       }\r
449     });\r
450 \r
451   }\r
452   \r
453   @Override\r
454   protected void onListItemClick(ListView l, View v, int row, long id) {\r
455     openContextMenu(v);\r
456   }\r
457   \r
458   void onAppendToWordList(final RowBase row) {\r
459     final StringBuilder rawText = new StringBuilder();\r
460     rawText.append(\r
461         new SimpleDateFormat("yyyy.MM.dd HH:mm:ss").format(new Date()))\r
462         .append("\t");\r
463     rawText.append(index.longName).append("\t");\r
464     rawText.append(row.getTokenRow(true).getToken()).append("\t");\r
465     rawText.append(row.getRawText(saveOnlyFirstSubentry));\r
466     Log.d(LOG, "Writing : " + rawText);\r
467     try {\r
468       wordList.getParentFile().mkdirs();\r
469       final PrintWriter out = new PrintWriter(\r
470           new FileWriter(wordList, true));\r
471       out.println(rawText.toString());\r
472       out.close();\r
473     } catch (IOException e) {\r
474       Log.e(LOG, "Unable to append to " + wordList.getAbsolutePath(), e);\r
475       Toast.makeText(this, getString(R.string.failedAddingToWordList, wordList.getAbsolutePath()), Toast.LENGTH_LONG);\r
476     }\r
477     return;\r
478   }\r
479 \r
480   void onCopy(final RowBase row) {\r
481     Log.d(LOG, "Copy, row=" + row);\r
482     final StringBuilder result = new StringBuilder();\r
483     result.append(row.getRawText(false));\r
484     final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);\r
485     clipboardManager.setText(result.toString());\r
486     Log.d(LOG, "Copied: " + result);\r
487   }\r
488 \r
489   @Override\r
490   public boolean onKeyDown(final int keyCode, final KeyEvent event) {\r
491     if (event.getUnicodeChar() != 0) {\r
492       if (!searchText.hasFocus()) {\r
493         searchText.setText("" + (char) event.getUnicodeChar());\r
494         onSearchTextChange(searchText.getText().toString());\r
495         searchText.requestFocus();\r
496       }\r
497       return true;\r
498     }\r
499     if (keyCode == KeyEvent.KEYCODE_BACK) {\r
500       Log.d(LOG, "Clearing dictionary prefs.");\r
501       DictionaryActivity.clearDictionaryPrefs(this);\r
502     }\r
503     return super.onKeyDown(keyCode, event);\r
504   }\r
505 \r
506 \r
507   // --------------------------------------------------------------------------\r
508   // SearchOperation\r
509   // --------------------------------------------------------------------------\r
510 \r
511   private void searchFinished(final SearchOperation searchOperation) {\r
512     if (searchOperation.interrupted.get()) {\r
513       Log.d(LOG, "Search operation was interrupted: " + searchOperation);\r
514       return;\r
515     }\r
516     if (searchOperation != this.currentSearchOperation) {\r
517       Log.d(LOG, "Stale searchOperation finished: " + searchOperation);\r
518       return;\r
519     }\r
520     \r
521     final Index.IndexEntry searchResult = searchOperation.searchResult;\r
522     Log.d(LOG, "searchFinished: " + searchOperation + ", searchResult=" + searchResult);\r
523 \r
524     currentSearchOperation = null;\r
525 \r
526     uiHandler.postDelayed(new Runnable() {\r
527       @Override\r
528       public void run() {\r
529         if (currentSearchOperation == null) {\r
530           jumpToRow(searchResult.startRow);\r
531         } else {\r
532           Log.d(LOG, "More coming, waiting for currentSearchOperation.");\r
533         }\r
534       }\r
535     }, 50);\r
536     \r
537 //    if (!searchResult.success) {\r
538 //      if (vibrator != null) {\r
539 //        vibrator.vibrate(VIBRATE_MILLIS);\r
540 //      }\r
541 //      searchText.setText(searchResult.longestPrefixString);\r
542 //      searchText.setSelection(searchResult.longestPrefixString.length());\r
543 //      return;\r
544 //    }\r
545     \r
546   }\r
547   \r
548   private final void jumpToRow(final int row) {\r
549     setSelection(row);\r
550     getListView().setSelected(true);\r
551   }\r
552 \r
553   final class SearchOperation implements Runnable {\r
554     \r
555     final AtomicBoolean interrupted = new AtomicBoolean(false);\r
556     final String searchText;\r
557     final Index index;\r
558     \r
559     long searchStartMillis;\r
560 \r
561     Index.IndexEntry searchResult;\r
562     \r
563     boolean done = false;\r
564     \r
565     SearchOperation(final String searchText, final Index index) {\r
566       this.searchText = searchText.trim();\r
567       this.index = index;\r
568     }\r
569     \r
570     public String toString() {\r
571       return String.format("SearchOperation(%s,%s)", searchText, interrupted.toString());\r
572     }\r
573 \r
574     @Override\r
575     public void run() {\r
576       try {\r
577         searchStartMillis = System.currentTimeMillis();\r
578         searchResult = index.findInsertionPoint(searchText, interrupted);\r
579         Log.d(LOG, "searchText=" + searchText + ", searchDuration="\r
580             + (System.currentTimeMillis() - searchStartMillis) + ", interrupted="\r
581             + interrupted.get());\r
582         if (!interrupted.get()) {\r
583           uiHandler.post(new Runnable() {\r
584             @Override\r
585             public void run() {            \r
586               searchFinished(SearchOperation.this);\r
587             }\r
588           });\r
589         }\r
590       } finally {\r
591         synchronized (this) {\r
592           done = true;\r
593           this.notifyAll();\r
594         }\r
595       }\r
596     }\r
597   }\r
598 \r
599   \r
600   // --------------------------------------------------------------------------\r
601   // IndexAdapter\r
602   // --------------------------------------------------------------------------\r
603 \r
604   static final class IndexAdapter extends BaseAdapter {\r
605     \r
606     final Index index;\r
607 \r
608     IndexAdapter(final Index index) {\r
609       this.index = index;\r
610     }\r
611 \r
612     @Override\r
613     public int getCount() {\r
614       return index.rows.size();\r
615     }\r
616 \r
617     @Override\r
618     public RowBase getItem(int position) {\r
619       return index.rows.get(position);\r
620     }\r
621 \r
622     @Override\r
623     public long getItemId(int position) {\r
624       return getItem(position).index();\r
625     }\r
626 \r
627     @Override\r
628     public View getView(int position, View convertView, ViewGroup parent) {\r
629       final RowBase row = index.rows.get(position);\r
630       if (row instanceof PairEntry.Row) {\r
631         return getView((PairEntry.Row) row, parent);\r
632       } else if (row instanceof TokenRow) {\r
633         return getView((TokenRow) row, parent);\r
634       } else {\r
635         throw new IllegalArgumentException("Unsupported Row type: " + row.getClass());\r
636       }\r
637     }\r
638 \r
639     private View getView(PairEntry.Row row, ViewGroup parent) {\r
640       final TableLayout result = new TableLayout(parent.getContext());\r
641       final PairEntry entry = row.getEntry();\r
642       final int rowCount = entry.pairs.size();\r
643       for (int r = 0; r < rowCount; ++r) {\r
644         final TableRow tableRow = new TableRow(result.getContext());\r
645 \r
646         TextView column1 = new TextView(tableRow.getContext());\r
647         TextView column2 = new TextView(tableRow.getContext());\r
648         final TableRow.LayoutParams layoutParams = new TableRow.LayoutParams();\r
649         layoutParams.weight = 0.5f;\r
650 \r
651         if (r > 0) {\r
652           final TextView spacer = new TextView(tableRow.getContext());\r
653           spacer.setText(" • ");\r
654           tableRow.addView(spacer);\r
655         }\r
656         tableRow.addView(column1, layoutParams);\r
657         if (r > 0) {\r
658           final TextView spacer = new TextView(tableRow.getContext());\r
659           spacer.setText(" • ");\r
660           tableRow.addView(spacer);\r
661         }\r
662         tableRow.addView(column2, layoutParams);\r
663 \r
664         column1.setWidth(1);\r
665         column2.setWidth(1);\r
666 \r
667         // TODO: color words by gender\r
668         final Pair pair = entry.pairs.get(r);\r
669         final String col1Text = index.swapPairEntries ? pair.lang2 : pair.lang1;\r
670         column1.setText(col1Text, TextView.BufferType.SPANNABLE);\r
671         final Spannable col1Spannable = (Spannable) column1.getText();\r
672         \r
673         int startPos = 0;\r
674         final String token = row.getTokenRow(true).getToken();\r
675         while ((startPos = col1Text.indexOf(token, startPos)) != -1) {\r
676           col1Spannable.setSpan(new StyleSpan(Typeface.BOLD), startPos,\r
677               startPos + token.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);\r
678           startPos += token.length();\r
679         }\r
680 \r
681         final String col2Text = index.swapPairEntries ? pair.lang1 : pair.lang2;\r
682         column2.setText(col2Text, TextView.BufferType.NORMAL);\r
683 \r
684         result.addView(tableRow);\r
685       }\r
686 \r
687       return result;\r
688     }\r
689 \r
690     private View getView(TokenRow row, ViewGroup parent) {\r
691       final TextView textView = new TextView(parent.getContext());\r
692       textView.setText(row.getToken());\r
693       textView.setTextSize(20);\r
694       return textView;\r
695     }\r
696     \r
697   }\r
698 \r
699   // --------------------------------------------------------------------------\r
700   // SearchText\r
701   // --------------------------------------------------------------------------\r
702 \r
703   void onSearchTextChange(final String text) {\r
704     if (dictRaf == null) {\r
705       Log.d(LOG, "searchText changed during shutdown, doing nothing.");\r
706       return;\r
707     }\r
708     if (!searchText.isFocused()) {\r
709       Log.d(LOG, "searchText changed without focus, doing nothing.");\r
710       return;\r
711     }\r
712     Log.d(LOG, "onSearchTextChange: " + text);    \r
713     if (currentSearchOperation != null) {\r
714       Log.d(LOG, "Interrupting currentSearchOperation.");\r
715       currentSearchOperation.interrupted.set(true);\r
716     }\r
717     currentSearchOperation = new SearchOperation(text, index);\r
718     searchExecutor.execute(currentSearchOperation);\r
719   }\r
720   \r
721   private class SearchTextWatcher implements TextWatcher {\r
722     public void afterTextChanged(final Editable searchTextEditable) {\r
723       if (searchText.hasFocus()) {\r
724         Log.d(LOG, "Search text changed with focus: " + searchText.getText());\r
725         // If they were typing to cause the change, update the UI.\r
726         onSearchTextChange(searchText.getText().toString());\r
727       }\r
728     }\r
729 \r
730     public void beforeTextChanged(CharSequence arg0, int arg1, int arg2,\r
731         int arg3) {\r
732     }\r
733 \r
734     public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {\r
735     }\r
736   }\r
737 \r
738 }\r