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