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