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