]> gitweb.fperrin.net Git - Dictionary.git/commitdiff
go
authorthadh <thadh@THADH-LAPTOP.ad.corp.google.com>
Sun, 25 Oct 2009 02:02:27 +0000 (19:02 -0700)
committerthadh <thadh@THADH-LAPTOP.ad.corp.google.com>
Sun, 25 Oct 2009 02:02:27 +0000 (19:02 -0700)
.classpath
AndroidManifest.xml
res/layout/main.xml
src/com/hughes/android/dictionary/DictionaryActivity.java
src/com/hughes/android/dictionary/Language.java

index 323ccea1b446b9c4f0c4130397b3772e0f10c81e..8a2ab5a74bc09751ee88251399bd40f3d94f5d15 100755 (executable)
@@ -4,5 +4,6 @@
        <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>\r
        <classpathentry combineaccessrules="false" exported="true" kind="src" path="/Util"/>\r
        <classpathentry kind="src" path="gen"/>\r
+       <classpathentry kind="lib" path="jars/icu4j-4_2_1-src.jar/icu4j.jar"/>\r
        <classpathentry kind="output" path="bin"/>\r
 </classpath>\r
index 76e1a537e091abdf8a2f84e807ef3fa544ca5dc1..4acdfe26e70586fc38c2d2022abc779958832420 100755 (executable)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.hughes.android.dictionary" android:versionName="1.2" android:versionCode="5">
-       
+
        <uses-sdk android:minSdkVersion="2" />
        <uses-permission android:name="android.permission.INTERNET"/>
 
                <activity android:name="AboutActivity"/>
                <activity android:name="PreferenceActivity"/>
                <activity android:name="DownloadActivity"/>
+    <activity android:name="NoDictionaryActivity"/>
+
+    <uses-library android:name="android.test.runner" />
+
        </application>
 
+       <instrumentation android:name="android.test.InstrumentationTestRunner"
+           android:targetPackage="com.hughes.android.dictionary"
+           android:label="DictionaryActivityTest" />
+           
+           
 </manifest> 
\ No newline at end of file
index 256ce67228ae13e9110a7683cb094dd82a83ba84..2862dcdcce86a10ef349f21fd72a154fcd51026e 100755 (executable)
@@ -25,6 +25,7 @@
                                android:layout_width="wrap_content"
                                android:layout_height="wrap_content"
                                />
+                               
                        <Button
                                android:id="@+id/DownButton"
                                android:text="v"
@@ -32,6 +33,7 @@
                                android:layout_width="wrap_content"
                                android:layout_height="wrap_content" 
                                />
+                               
                        <Button 
                                android:id="@+id/UpButton"
                                android:text="^"
index b1488312e5e68d8ab0d8f303fb1de45e9af9ee04..a7780dce0a77478b51df11bad998d5511f854af5 100755 (executable)
@@ -14,7 +14,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import android.app.AlertDialog;
 import android.app.ListActivity;
 import android.content.Context;
-import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.Editor;
@@ -37,7 +36,6 @@ import android.view.ViewGroup;
 import android.view.ContextMenu.ContextMenuInfo;
 import android.view.MenuItem.OnMenuItemClickListener;
 import android.view.View.OnClickListener;
-import android.widget.AdapterView;
 import android.widget.BaseAdapter;
 import android.widget.Button;
 import android.widget.EditText;
@@ -45,195 +43,171 @@ import android.widget.ListView;
 import android.widget.TableLayout;
 import android.widget.TableRow;
 import android.widget.TextView;
-import android.widget.AdapterView.OnItemLongClickListener;
-import android.widget.AdapterView.OnItemSelectedListener;
 
 import com.hughes.android.dictionary.Dictionary.IndexEntry;
 import com.hughes.android.dictionary.Dictionary.LanguageData;
 import com.hughes.android.dictionary.Dictionary.Row;
 
 public class DictionaryActivity extends ListActivity {
-
-  static final Intent preferencesIntent = new Intent().setClassName(PreferenceActivity.class.getPackage().getName(), PreferenceActivity.class.getCanonicalName());
   
+  // TODO:
+  // * Only have one live SearchActivity, and a way to wait for it to die.
+  // * Don't destroy dict unless we're really shutting down (not on screen rotate).
+  // * Move (re-)init code to a method, set a flag if prefs might have changed, invoke re-init in onResume, which clears flag and reloads prefs.
+  // * Compress all the strings everywhere, put compression table in file.
+
   static final String LOG = "QuickDic";
   static final String PREF_DICT_ACTIVE_LANG = "DICT_DIR_PREF";
   static final String PREF_ACTIVE_SEARCH_TEXT = "ACTIVE_WORD_PREF";
 
-  private final Handler uiHandler = new Handler();
+  // package for test.
+  final Handler uiHandler = new Handler();
+
+  EditText searchText;
+
   private final Executor searchExecutor = Executors.newSingleThreadExecutor();
-  private final DictionaryListAdapter dictionaryListAdapter = new DictionaryListAdapter();
 
   // Never null.
+  private boolean prefsMightHaveChanged = true;
   private File wordList;
 
-  // Can be null.
-  private File dictFile = null;
   private RandomAccessFile dictRaf = null;
   private Dictionary dictionary = null;
-  private LanguageData activeLanguageData = null;
+
+  // Visible for testing.
+  LanguageListAdapter languageList = null;
 
   private SearchOperation searchOperation = null;
-  private int selectedRowIndex = -1;
-  private int selectedTokenRowIndex = -1;
-  
+
+  private int selectedRowIndex;
+  private int selectedTokenRowIndex;
 
   /** Called when the activity is first created. */
   @Override
   public void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     Log.d(LOG, "onCreate");
-    
-    if (Language.EN.sortCollator.compare("preppy", "pre-print") >= 0) {
-      Log.e(LOG, "Android java.text.Collator is buggy, lookups may not work properly.");
+
+    if (Language.EN.sortCollator.compare("pre-print", "preppy") >= 0) {
+      Log
+          .e(LOG,
+              "Android java.text.Collator is buggy, lookups may not work properly.");
     }
-    
-    setContentView(R.layout.main);
 
-    getSearchText().addTextChangedListener(new SearchTextWatcher());
+    initDictionaryAndPrefs();
+    if (dictRaf == null) {
+      return;
+    }
+
+    // UI init.
 
-    setListAdapter(dictionaryListAdapter);
+    setContentView(R.layout.main);
+    searchText = (EditText) findViewById(R.id.SearchText);
+
+    Log.d(LOG, "adding text changed listener");
+    searchText.addTextChangedListener(new SearchTextWatcher());
 
     // Language button.
     final Button langButton = (Button) findViewById(R.id.LangButton);
     langButton.setOnClickListener(new OnClickListener() {
       public void onClick(View v) {
-        switchLanguage();
-      }});
-
+        onLanguageButton();
+      }
+    });
     final Button upButton = (Button) findViewById(R.id.UpButton);
     upButton.setOnClickListener(new OnClickListener() {
       public void onClick(View v) {
-        if (dictionary == null) {
-          return;
-        }
-        final int destRowIndex;
-        final Row tokenRow = activeLanguageData.rows.get(selectedTokenRowIndex);
-        assert tokenRow.isToken();
-        final int prevTokenIndex = tokenRow.getIndex() - 1;
-        if (selectedRowIndex == selectedTokenRowIndex && selectedRowIndex > 0) {
-          destRowIndex = activeLanguageData.sortedIndex.get(prevTokenIndex).startRow;
-        } else {
-          destRowIndex = selectedTokenRowIndex;
-        }
-        jumpToRow(destRowIndex);
-      }});
-    
+        onUpButton();
+      }
+    });
     final Button downButton = (Button) findViewById(R.id.DownButton);
     downButton.setOnClickListener(new OnClickListener() {
       public void onClick(View v) {
-        if (dictionary == null) {
-          return;
-        }
-        final Row tokenRow = activeLanguageData.rows.get(selectedTokenRowIndex);
-        assert tokenRow.isToken();
-        final int nextTokenIndex = tokenRow.getIndex() + 1;
-        final int destRowIndex;
-        if (nextTokenIndex < activeLanguageData.sortedIndex.size()) {
-          destRowIndex = activeLanguageData.sortedIndex.get(nextTokenIndex).startRow;
-        } else {
-          destRowIndex = activeLanguageData.rows.size() - 1;
-        }
-        jumpToRow(destRowIndex);
-      }});
+        onDownButton();
+      }
+    });
 
     // ContextMenu.
     registerForContextMenu(getListView());
 
-    // ItemSelectedListener.
-    getListView().setOnItemSelectedListener(new OnItemSelectedListener() {
-      public void onItemSelected(AdapterView<?> arg0, View arg1, int rowIndex,
-          long arg3) {
-        if (activeLanguageData == null) {
-          return;
-        }
-        Log.d(LOG, "onItemSelected: " + rowIndex);        
-        selectedRowIndex = rowIndex;
-        selectedTokenRowIndex = activeLanguageData.getIndexEntryForRow(rowIndex).startRow;
-        updateSearchText();
-      }
-
-      public void onNothingSelected(AdapterView<?> arg0) {
-      }});
-    
-
-    // LongClickListener.
-    getListView().setOnItemLongClickListener((new OnItemLongClickListener() {
-      public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int rowIndex,
-          long arg3) {
-        selectedRowIndex = rowIndex;
-        return false;
-      }
-    }));
-
+    updateLangButton();
   }
-  
-  @Override
-  public void onResume() {
-    super.onResume();
 
-    // Have to close, because we might have downloaded a new copy of the dictionary.
+  private void initDictionaryAndPrefs() {
+    if (!prefsMightHaveChanged) {
+      return;
+    }
     closeCurrentDictionary();
-
-    final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
-    wordList = new File(prefs.getString(getString(R.string.wordListFileKey), getString(R.string.wordListFileDefault)));
-    final File newDictFile = new File(prefs.getString(getString(R.string.dictFileKey), getString(R.string.dictFileDefault)));
-    dictFile = newDictFile;
+    
+    final SharedPreferences prefs = PreferenceManager
+        .getDefaultSharedPreferences(this);
+    wordList = new File(prefs.getString(getString(R.string.wordListFileKey),
+        getString(R.string.wordListFileDefault)));
     Log.d(LOG, "wordList=" + wordList);
-    Log.d(LOG, "dictFile=" + dictFile);
 
+    final File dictFile = new File(prefs.getString(getString(R.string.dictFileKey),
+        getString(R.string.dictFileDefault)));
+    Log.d(LOG, "dictFile=" + dictFile);
     if (!dictFile.canRead()) {
-      dictionaryListAdapter.notifyDataSetChanged();
-      Log.d(LOG, "Unable to read dictionary file.");
-      final AlertDialog alert = new AlertDialog.Builder(DictionaryActivity.this).create();
-      alert.setMessage(String.format(getString(R.string.unableToReadDictionaryFile), dictFile.getAbsolutePath()));
-      alert.setButton(getString(R.string.downloadDictionary), new DialogInterface.OnClickListener() {
-        public void onClick(DialogInterface dialog, int which) {
-          startDownloadDictActivity();
-        }});
-      alert.show();
-      return;
+      Log.w(LOG, "Unable to read dictionary file.");
+      this.startActivity(new Intent(this, NoDictionaryActivity.class));
+      finish();
     }
-    
-    final byte lang = prefs.getInt(PREF_DICT_ACTIVE_LANG, Entry.LANG1) == Entry.LANG1 ? Entry.LANG1 : Entry.LANG2;
-      
+
     try {
       dictRaf = new RandomAccessFile(dictFile, "r");
       dictionary = new Dictionary(dictRaf);
-      activeLanguageData = dictionary.languageDatas[lang];
-      dictionaryListAdapter.notifyDataSetChanged();
     } catch (Exception e) {
       throw new RuntimeException(e);
     }
 
-    updateLangButton();
+    final byte lang = prefs.getInt(PREF_DICT_ACTIVE_LANG, Entry.LANG1) == Entry.LANG1 ? Entry.LANG1
+        : Entry.LANG2;
 
-    final String searchText = prefs.getString(PREF_ACTIVE_SEARCH_TEXT, "");
-    getSearchText().setText(searchText);
-    onSearchTextChange(searchText);
+    languageList = new LanguageListAdapter(dictionary.languageDatas[lang]);
+    setListAdapter(languageList);
+    prefsMightHaveChanged = false;
   }
-  
+
+  @Override
+  public void onResume() {
+    super.onResume();
+    
+    if (prefsMightHaveChanged) {
+      
+    }
+    
+    final SharedPreferences prefs = PreferenceManager
+        .getDefaultSharedPreferences(this);
+    final String searchTextString = prefs
+        .getString(PREF_ACTIVE_SEARCH_TEXT, "");
+    searchText.setText(searchTextString);
+    onSearchTextChange(searchTextString);
+  }
+
   @Override
   public void onPause() {
     super.onPause();
-    if (activeLanguageData != null) {
-      final Editor prefs = PreferenceManager.getDefaultSharedPreferences(this).edit();
-      prefs.putInt(PREF_DICT_ACTIVE_LANG, activeLanguageData.lang);
-      prefs.putString(PREF_ACTIVE_SEARCH_TEXT, getSearchText().getText().toString());
-      prefs.commit();
-    }
+    final Editor prefs = PreferenceManager.getDefaultSharedPreferences(this)
+        .edit();
+    prefs.putInt(PREF_DICT_ACTIVE_LANG, languageList.languageData.lang);
+    prefs.putString(PREF_ACTIVE_SEARCH_TEXT, searchText.getText().toString());
+    prefs.commit();
   }
-  
-  
+
   @Override
   public void onStop() {
     super.onStop();
-    closeCurrentDictionary();
+    if (isFinishing()) {
+      closeCurrentDictionary();
+    }
   }
 
   private void closeCurrentDictionary() {
+    searchOperation.stopAndWait();
+    languageList = null;
+    setListAdapter(null);
     dictionary = null;
-    activeLanguageData = null;
     try {
       if (dictRaf != null) {
         dictRaf.close();
@@ -243,153 +217,184 @@ public class DictionaryActivity extends ListActivity {
     }
     dictRaf = null;
   }
-  
+
   public String getSelectedRowRawText() {
-    return activeLanguageData.rowToString(activeLanguageData.rows.get(selectedRowIndex));
+    final int row = getSelectedItemPosition();
+    return row < 0 ? "" : languageList.languageData
+        .rowToString(languageList.languageData.rows.get(row));
   }
-  
-  public EditText getSearchText() {
-    return (EditText) findViewById(R.id.SearchText);
-  }
-  
+
   // ----------------------------------------------------------------
   // OptionsMenu
   // ----------------------------------------------------------------
 
   private MenuItem switchLanguageMenuItem = null;
-  
 
   @Override
   public boolean onCreateOptionsMenu(final Menu menu) {
     switchLanguageMenuItem = menu.add(getString(R.string.switchToLanguage));
-    switchLanguageMenuItem.setOnMenuItemClickListener(new OnMenuItemClickListener(){
-      public boolean onMenuItemClick(final MenuItem menuItem) {
-        switchLanguage();
-        return false;
-      }});
+    switchLanguageMenuItem
+        .setOnMenuItemClickListener(new OnMenuItemClickListener() {
+          public boolean onMenuItemClick(final MenuItem menuItem) {
+            onLanguageButton();
+            return false;
+          }
+        });
 
     final MenuItem preferences = menu.add(getString(R.string.preferences));
-    preferences.setOnMenuItemClickListener(new OnMenuItemClickListener(){
+    preferences.setOnMenuItemClickListener(new OnMenuItemClickListener() {
       public boolean onMenuItemClick(final MenuItem menuItem) {
-        startActivity(preferencesIntent);
+        prefsMightHaveChanged = true;
+        startActivity(new Intent(DictionaryActivity.this,
+            PreferenceActivity.class));
         return false;
-      }});
+      }
+    });
 
     final MenuItem about = menu.add(getString(R.string.about));
-    about.setOnMenuItemClickListener(new OnMenuItemClickListener(){
+    about.setOnMenuItemClickListener(new OnMenuItemClickListener() {
       public boolean onMenuItemClick(final MenuItem menuItem) {
-        final Intent intent = new Intent().setClassName(AboutActivity.class.getPackage().getName(), AboutActivity.class.getCanonicalName());
+        final Intent intent = new Intent().setClassName(AboutActivity.class
+            .getPackage().getName(), AboutActivity.class.getCanonicalName());
         final StringBuilder currentDictInfo = new StringBuilder();
         if (dictionary == null) {
           currentDictInfo.append(getString(R.string.noDictLoaded));
         } else {
           currentDictInfo.append(dictionary.dictionaryInfo).append("\n\n");
-          currentDictInfo.append("Entry count: " + dictionary.entries.size()).append("\n");
+          currentDictInfo.append("Entry count: " + dictionary.entries.size())
+              .append("\n");
           for (int i = 0; i < 2; ++i) {
-            final LanguageData languageData = dictionary.languageDatas[i]; 
+            final LanguageData languageData = dictionary.languageDatas[i];
             currentDictInfo.append(languageData.language.symbol).append(":\n");
-            currentDictInfo.append("  Unique token count: " + languageData.sortedIndex.size()).append("\n");
-            currentDictInfo.append("  Row count: " + languageData.rows.size()).append("\n");
+            currentDictInfo.append(
+                "  Unique token count: " + languageData.sortedIndex.size())
+                .append("\n");
+            currentDictInfo.append("  Row count: " + languageData.rows.size())
+                .append("\n");
           }
         }
-        intent.putExtra(AboutActivity.CURRENT_DICT_INFO, currentDictInfo.toString());
+        intent.putExtra(AboutActivity.CURRENT_DICT_INFO, currentDictInfo
+            .toString());
         startActivity(intent);
         return false;
-      }});
+      }
+    });
 
     final MenuItem download = menu.add(getString(R.string.downloadDictionary));
-    download.setOnMenuItemClickListener(new OnMenuItemClickListener(){
+    download.setOnMenuItemClickListener(new OnMenuItemClickListener() {
       public boolean onMenuItemClick(final MenuItem menuItem) {
-        startDownloadDictActivity();
+        startDownloadDictActivity(DictionaryActivity.this);
         return false;
-      }});
+      }
+    });
 
     return true;
   }
-  
+
   @Override
   public boolean onPrepareOptionsMenu(final Menu menu) {
-    if (dictionary != null) {
-      switchLanguageMenuItem.setTitle(String.format(
-          getString(R.string.switchToLanguage), dictionary.languageDatas[Entry
-              .otherLang(activeLanguageData.lang)].language.symbol));
-    }
-    switchLanguageMenuItem.setEnabled(dictionary != null);
+    switchLanguageMenuItem.setTitle(String.format(
+        getString(R.string.switchToLanguage), dictionary.languageDatas[Entry
+            .otherLang(languageList.languageData.lang)].language.symbol));
     return super.onPrepareOptionsMenu(menu);
   }
-
-  void switchLanguage() {
-    if (dictionary == null) {
-      return;
-    }
-    activeLanguageData = dictionary.languageDatas[(activeLanguageData == dictionary.languageDatas[0]) ? 1 : 0];
-    selectedRowIndex = 0;
-    selectedTokenRowIndex = 0;
-    updateLangButton();
-    dictionaryListAdapter.notifyDataSetChanged();
-    onSearchTextChange(getSearchText().getText().toString());
-  }
   
   void updateLangButton() {
     final Button langButton = (Button) findViewById(R.id.LangButton);
-    langButton.setText(activeLanguageData.language.symbol);
+    langButton.setText(languageList.languageData.language.symbol);
   }
-  
+
   // ----------------------------------------------------------------
-  // ContextMenu
+  // Event handlers.
   // ----------------------------------------------------------------
   
-  @Override
-  public void onCreateContextMenu(ContextMenu menu, View v,
-      ContextMenuInfo menuInfo) {
-    if (selectedRowIndex == -1) {
-      return;
+  void onLanguageButton() {
+    languageList = new LanguageListAdapter(
+        dictionary.languageDatas[(languageList.languageData == dictionary.languageDatas[0]) ? 1
+            : 0]);
+    setListAdapter(languageList);
+    updateLangButton();
+    onSearchTextChange(searchText.getText().toString());
+  }
+
+  void onUpButton() {
+    final int destRowIndex;
+    final Row tokenRow = languageList.languageData.rows
+        .get(selectedTokenRowIndex);
+    assert tokenRow.isToken();
+    final int prevTokenIndex = tokenRow.getIndex() - 1;
+    if (selectedRowIndex == selectedTokenRowIndex && selectedRowIndex > 0) {
+      destRowIndex = languageList.languageData.sortedIndex
+          .get(prevTokenIndex).startRow;
+    } else {
+      destRowIndex = selectedTokenRowIndex;
     }
-    
-    final MenuItem addToWordlist = menu.add(String.format(getString(R.string.addToWordList), wordList.getName()));
-    addToWordlist.setOnMenuItemClickListener(new OnMenuItemClickListener() {
-      public boolean onMenuItemClick(MenuItem item) {
-        final StringBuilder rawText = new StringBuilder();
-        final String word = activeLanguageData.getIndexEntryForRow(selectedRowIndex).word;
-        rawText.append(new SimpleDateFormat("yyyy.MM.dd HH:mm:ss").format(new Date())).append("\t");
-        rawText.append(word).append("\t");
-        rawText.append(getSelectedRowRawText());
-        Log.d(LOG, "Writing : " + rawText);
-        try {
-          wordList.getParentFile().mkdirs();
-          final PrintWriter out = new PrintWriter(new FileWriter(wordList, true));
-          out.println(rawText.toString());
-          out.close();
-        } catch (IOException e) {
-          Log.e(LOG, "Unable to append to " + wordList.getAbsolutePath(), e);
-          final AlertDialog alert = new AlertDialog.Builder(DictionaryActivity.this).create();
-          alert.setMessage("Failed to append to file: " + wordList.getAbsolutePath());
-          alert.show();
-        }
-        return false;
-      }
-    });
+    jumpToRow(languageList, destRowIndex);
+  }
 
-    final MenuItem copy = menu.add(android.R.string.copy);
-    copy.setOnMenuItemClickListener(new OnMenuItemClickListener() {
-      public boolean onMenuItemClick(MenuItem item) {
-        Log.d(LOG, "Copy.");
-        final StringBuilder result = new StringBuilder();
-        result.append(getSelectedRowRawText());
-        final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
-        clipboardManager.setText(result.toString());
-        return false;
-      }
-    });
+  void onDownButton() {
+    final Row tokenRow = languageList.languageData.rows
+        .get(selectedTokenRowIndex);
+    assert tokenRow.isToken();
+    final int nextTokenIndex = tokenRow.getIndex() + 1;
+    final int destRowIndex;
+    if (nextTokenIndex < languageList.languageData.sortedIndex.size()) {
+      destRowIndex = languageList.languageData.sortedIndex
+          .get(nextTokenIndex).startRow;
+    } else {
+      destRowIndex = languageList.languageData.rows.size() - 1;
+    }
+    jumpToRow(languageList, destRowIndex);
+  }
 
+  void onAppendToWordList() {
+    final int row = getSelectedItemPosition();
+    if (row < 0) {
+      return;
+    }
+    final StringBuilder rawText = new StringBuilder();
+    final String word = languageList.languageData.getIndexEntryForRow(row).word;
+    rawText.append(
+        new SimpleDateFormat("yyyy.MM.dd HH:mm:ss").format(new Date()))
+        .append("\t");
+    rawText.append(word).append("\t");
+    rawText.append(getSelectedRowRawText());
+    Log.d(LOG, "Writing : " + rawText);
+    try {
+      wordList.getParentFile().mkdirs();
+      final PrintWriter out = new PrintWriter(
+          new FileWriter(wordList, true));
+      out.println(rawText.toString());
+      out.close();
+    } catch (IOException e) {
+      Log.e(LOG, "Unable to append to " + wordList.getAbsolutePath(), e);
+      final AlertDialog alert = new AlertDialog.Builder(
+          DictionaryActivity.this).create();
+      alert.setMessage("Failed to append to file: "
+          + wordList.getAbsolutePath());
+      alert.show();
+    }
+    return;
   }
-  
+
+  void onCopy() {
+    final int row = getSelectedItemPosition();
+    if (row < 0) {
+      return;
+    }
+    Log.d(LOG, "Copy." + DictionaryActivity.this.getSelectedItemPosition());
+    final StringBuilder result = new StringBuilder();
+    result.append(getSelectedRowRawText());
+    final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
+    clipboardManager.setText(result.toString());
+    Log.d(LOG, "Copied: " + result);
+  }
+
   @Override
   public boolean onKeyDown(int keyCode, KeyEvent event) {
     if (event.getUnicodeChar() != 0) {
-      final EditText searchText = getSearchText();
       if (!searchText.hasFocus()) {
-        searchText.setText("" + (char)event.getUnicodeChar());
+        searchText.setText("" + (char) event.getUnicodeChar());
         onSearchTextChange(searchText.getText().toString());
         searchText.requestFocus();
       }
@@ -400,36 +405,66 @@ public class DictionaryActivity extends ListActivity {
 
   @Override
   protected void onListItemClick(ListView l, View v, int row, long id) {
-    selectedRowIndex = row;
     Log.d(LOG, "Clicked: " + getSelectedRowRawText());
     openContextMenu(getListView());
   }
 
   void onSearchTextChange(final String searchText) {
     Log.d(LOG, "onSearchTextChange: " + searchText);
-    if (dictionary == null) {
+    searchOperation = new SearchOperation(languageList, searchText, searchOperation);
+    searchExecutor.execute(searchOperation);
+  }
+
+  // ----------------------------------------------------------------
+  // ContextMenu
+  // ----------------------------------------------------------------
+
+  @Override
+  public void onCreateContextMenu(ContextMenu menu, View v,
+      ContextMenuInfo menuInfo) {
+    final int row = getSelectedItemPosition();
+    if (row < 0) {
       return;
     }
-    if (searchOperation != null) {
-      searchOperation.interrupted.set(true);
-    }
-    searchOperation = new SearchOperation(searchText);
-    searchExecutor.execute(searchOperation);
+
+    final MenuItem addToWordlist = menu.add(String.format(
+        getString(R.string.addToWordList), wordList.getName()));
+    addToWordlist.setOnMenuItemClickListener(new OnMenuItemClickListener() {
+      public boolean onMenuItemClick(MenuItem item) {
+        onAppendToWordList();
+        return false;
+      }
+    });
+
+    final MenuItem copy = menu.add(android.R.string.copy);
+    copy.setOnMenuItemClickListener(new OnMenuItemClickListener() {
+      public boolean onMenuItemClick(MenuItem item) {
+        onCopy();
+        return false;
+      }
+    });
+
   }
-  
-  private void jumpToRow(final int rowIndex) {
+
+  private void jumpToRow(final LanguageListAdapter dictionaryListAdapter,
+      final int rowIndex) {
     Log.d(LOG, "jumpToRow: " + rowIndex);
-    selectedRowIndex = rowIndex;
-    selectedTokenRowIndex = activeLanguageData.getIndexEntryForRow(rowIndex).startRow;
-    getListView().setSelection(rowIndex);
-    getListView().setSelected(true);  // TODO: is this doing anything?
+    if (dictionaryListAdapter != this.languageList) {
+      Log.w(LOG, "skipping jumpToRow for old list adapter: " + rowIndex);
+      return;
+    }
+    // selectedTokenRowIndex =
+    // languageList.languageData.getIndexEntryForRow(rowIndex).startRow;
+    setSelection(rowIndex);
+    getListView().setSelected(true); // TODO: is this doing anything?
     updateSearchText();
   }
 
   private void updateSearchText() {
-    final EditText searchText = getSearchText();
+    Log.d(LOG, "updateSearchText");
     if (!searchText.hasFocus()) {
-      final String word = activeLanguageData.getIndexEntryForRow(selectedRowIndex).word;
+      final String word = languageList.languageData
+          .getIndexEntryForRow(selectedRowIndex).word;
       if (!word.equals(searchText.getText().toString())) {
         Log.d(LOG, "updateSearchText: setText: " + word);
         searchText.setText(word);
@@ -437,59 +472,37 @@ public class DictionaryActivity extends ListActivity {
     }
   }
 
-  private void startDownloadDictActivity() {
-    final Intent intent = new Intent().setClassName(
-        DownloadActivity.class.getPackage().getName(),
-        DownloadActivity.class.getCanonicalName());
-    final SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(DictionaryActivity.this);
-    final String dictFetchUrl = settings.getString(getString(R.string.dictFetchUrlKey), getString(R.string.dictFetchUrlDefault));
-    final String dictFileName = settings.getString(getString(R.string.dictFileKey), getString(R.string.dictFileDefault));
+  static void startDownloadDictActivity(final Context context) {
+    final Intent intent = new Intent(context, DownloadActivity.class);
+    final SharedPreferences prefs = PreferenceManager
+        .getDefaultSharedPreferences(context);
+    final String dictFetchUrl = prefs.getString(context
+        .getString(R.string.dictFetchUrlKey), context
+        .getString(R.string.dictFetchUrlDefault));
+    final String dictFileName = prefs.getString(context
+        .getString(R.string.dictFileKey), context
+        .getString(R.string.dictFileDefault));
     intent.putExtra(DownloadActivity.SOURCE, dictFetchUrl);
     intent.putExtra(DownloadActivity.DEST, dictFileName);
-    startActivity(intent);
+    context.startActivity(intent);
   }
 
-  private final class SearchOperation implements Runnable {
-    final String searchText;
-    final AtomicBoolean interrupted = new AtomicBoolean(false);
+  class LanguageListAdapter extends BaseAdapter {
 
-    public SearchOperation(final String searchText) {
-      this.searchText = searchText;
-    }
+    // Visible for testing.
+    final LanguageData languageData;
 
-    public void run() {
-      Log.d(LOG, "SearchOperation: " + searchText);
-      final int indexLocation = activeLanguageData.lookup(searchText, interrupted);
-      if (interrupted.get()) {
-        return;
-      }
-      final IndexEntry indexEntry = activeLanguageData.sortedIndex
-          .get(indexLocation);
-      Log.d(LOG, "SearchOperation completed: " + indexEntry.toString());
-      uiHandler.post(new Runnable() {
-        public void run() {
-          jumpToRow(indexEntry.startRow);
-        }
-      });
+    LanguageListAdapter(final LanguageData languageData) {
+      this.languageData = languageData;
     }
-  }
-
-  private class DictionaryListAdapter extends BaseAdapter {
 
     public int getCount() {
-      if (dictionary == null) {
-        return 0;
-      }
-      return activeLanguageData.rows.size();
+      return languageData.rows.size();
     }
 
     public Dictionary.Row getItem(int rowIndex) {
-      final LanguageData activeLanguageData = DictionaryActivity.this.activeLanguageData;
-      if (activeLanguageData == null) {
-        return null;
-      }
-      assert rowIndex < activeLanguageData.rows.size();
-      return activeLanguageData.rows.get(rowIndex);
+      assert rowIndex < languageData.rows.size();
+      return languageData.rows.get(rowIndex);
     }
 
     public long getItemId(int rowIndex) {
@@ -501,7 +514,7 @@ public class DictionaryActivity extends ListActivity {
       final Row row = getItem(rowIndex);
 
       // Token row.
-      if (row == null || row.isToken()) {
+      if (row.isToken()) {
         TextView result = null;
         if (convertView instanceof TextView) {
           result = (TextView) convertView;
@@ -511,7 +524,7 @@ public class DictionaryActivity extends ListActivity {
         if (row == null) {
           return result;
         }
-        result.setText(activeLanguageData.rowToString(row));
+        result.setText(languageData.rowToString(row));
         result.setTextAppearance(parent.getContext(),
             android.R.style.TextAppearance_Large);
         result.setClickable(false);
@@ -525,13 +538,13 @@ public class DictionaryActivity extends ListActivity {
       final int rowCount = entry.getRowCount();
       for (int r = 0; r < rowCount; ++r) {
         final TableRow tableRow = new TableRow(result.getContext());
-        
+
         TextView column1 = new TextView(tableRow.getContext());
         TextView column2 = new TextView(tableRow.getContext());
         final TableRow.LayoutParams layoutParams = new TableRow.LayoutParams();
         layoutParams.weight = 0.5f;
-        
-        if (r>0){
+
+        if (r > 0) {
           final TextView spacer = new TextView(tableRow.getContext());
           spacer.setText(r == 0 ? "\95 " : " \95 ");
           tableRow.addView(spacer);
@@ -543,36 +556,105 @@ public class DictionaryActivity extends ListActivity {
           tableRow.addView(spacer);
         }
         tableRow.addView(column2, layoutParams);
-        
+
         column1.setWidth(1);
         column2.setWidth(1);
         // column1.setTextAppearance(parent.getContext(), android.R.style.Text);
-        
+
         // TODO: color words by gender
-        final String col1Text = entry.getAllText(activeLanguageData.lang)[r]; 
+        final String col1Text = entry.getAllText(languageData.lang)[r];
         column1.setText(col1Text, TextView.BufferType.SPANNABLE);
         final Spannable col1Spannable = (Spannable) column1.getText();
         int startPos = 0;
-        final String token = activeLanguageData.getIndexEntryForRow(rowIndex).word;
+        final String token = languageData.getIndexEntryForRow(rowIndex).word;
         while ((startPos = col1Text.indexOf(token, startPos)) != -1) {
-          col1Spannable.setSpan(new StyleSpan(Typeface.BOLD), startPos, startPos + token.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
-         startPos += token.length();
+          col1Spannable.setSpan(new StyleSpan(Typeface.BOLD), startPos,
+              startPos + token.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+          startPos += token.length();
         }
-        
-        column2.setText(entry.getAllText(Entry.otherLang(activeLanguageData.lang))[r], TextView.BufferType.NORMAL);
-        
+
+        column2.setText(
+            entry.getAllText(Entry.otherLang(languageData.lang))[r],
+            TextView.BufferType.NORMAL);
+
         result.addView(tableRow);
       }
-      
+
       return result;
     }
-  }  // DictionaryListAdapter
+
+  } // DictionaryListAdapter
+
+  private final class SearchOperation implements Runnable {
+    SearchOperation previousSearchOperation;
+    
+    final LanguageListAdapter listAdapter;
+    final LanguageData languageData;
+    final String searchText;
+    final AtomicBoolean interrupted = new AtomicBoolean(false);
+    boolean finished = false;
+
+    SearchOperation(final LanguageListAdapter listAdapter,
+        final String searchText, final SearchOperation previousSearchOperation) {
+      this.listAdapter = listAdapter;
+      this.languageData = listAdapter.languageData;
+      this.searchText = searchText;
+      this.previousSearchOperation = previousSearchOperation;
+    }
+
+    public void run() {
+      if (previousSearchOperation != null) {
+        previousSearchOperation.stopAndWait();
+      }
+      previousSearchOperation = null;
+      
+      Log.d(LOG, "SearchOperation: " + searchText);
+      final int indexLocation = languageData.lookup(searchText, interrupted);
+      if (interrupted.get()) {
+        return;
+      }
+      final IndexEntry indexEntry = languageData.sortedIndex.get(indexLocation);
+      
+      Log.d(LOG, "SearchOperation completed: " + indexEntry.toString());
+      uiHandler.post(new Runnable() {
+        public void run() {
+          // Check is just a performance operation.
+          if (!interrupted.get()) {
+            // This is safe, because it checks that the listAdapter hasn't changed.
+            jumpToRow(listAdapter, indexEntry.startRow);
+          }
+        }
+      });
+      
+      synchronized (this) {
+        finished = true;
+        this.notifyAll();
+      }
+    }
+    
+    public void stopAndWait() {
+      interrupted.set(true);
+      synchronized (this) {
+        while (!finished) {
+          Log.d(LOG, "stopAndWait: " + searchText);
+          try {
+            this.wait();
+          } catch (InterruptedException e) {
+            Log.e(LOG, "Interrupted", e);
+          }
+        }
+      }
+    }
+
+
+  }
 
   private class SearchTextWatcher implements TextWatcher {
-    public void afterTextChanged(final Editable searchText) {
-      if (getSearchText().hasFocus()) {
+    public void afterTextChanged(final Editable searchTextEditable) {
+      Log.d(LOG, "Search text changed: " + searchText.getText().toString());
+      if (searchText.hasFocus()) {
         // If they were typing to cause the change, update the UI.
-        onSearchTextChange(searchText.toString());
+        onSearchTextChange(searchText.getText().toString());
       }
     }
 
@@ -583,5 +665,5 @@ public class DictionaryActivity extends ListActivity {
     public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
     }
   }
-  
+
 }
\ No newline at end of file
index 9591bc08742a96d7c8d01e092dc03d0d6f8e9233..440a7d011be20d2032930d4d02b2b5f7ac66d478 100755 (executable)
@@ -1,11 +1,12 @@
 package com.hughes.android.dictionary;\r
 \r
-import java.text.Collator;\r
 import java.util.Comparator;\r
 import java.util.LinkedHashMap;\r
 import java.util.Locale;\r
 import java.util.Map;\r
 \r
+import com.ibm.icu.text.Collator;\r
+\r
 public class Language {\r
 \r
   final String symbol;\r