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