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