]> gitweb.fperrin.net Git - Dictionary.git/commitdiff
About dialog, added pictures, multi word search.
authorThad Hughes <thad.hughes@gmail.com>
Mon, 30 Jan 2012 06:07:34 +0000 (22:07 -0800)
committerThad Hughes <thad.hughes@gmail.com>
Mon, 30 Jan 2012 06:07:34 +0000 (22:07 -0800)
20 files changed:
res/drawable/arrow_down_float.png [new file with mode: 0644]
res/drawable/arrow_up_float.png [new file with mode: 0644]
res/drawable/btn_check_buttonless_on.png [new file with mode: 0644]
res/drawable/ic_input_delete.png [new file with mode: 0644]
res/layout/about_dictionary_dialog.xml [new file with mode: 0644]
res/layout/dictionary_activity.xml
res/values/languages.xml
res/values/strings.xml
src/com/hughes/android/dictionary/DictionaryActivity.java
src/com/hughes/android/dictionary/DictionaryApplication.java
src/com/hughes/android/dictionary/DictionaryManagerActivity.java
src/com/hughes/android/dictionary/DownloadActivity.java
src/com/hughes/android/dictionary/engine/Dictionary.java
src/com/hughes/android/dictionary/engine/Index.java
src/com/hughes/android/dictionary/engine/Language.java
src/com/hughes/android/dictionary/engine/PairEntry.java
src/com/hughes/android/dictionary/engine/RowBase.java
src/com/hughes/android/dictionary/engine/TextEntry.java
src/com/hughes/android/dictionary/engine/TokenRow.java
src/com/hughes/android/util/PersistentObjectCache.java

diff --git a/res/drawable/arrow_down_float.png b/res/drawable/arrow_down_float.png
new file mode 100644 (file)
index 0000000..dd82523
Binary files /dev/null and b/res/drawable/arrow_down_float.png differ
diff --git a/res/drawable/arrow_up_float.png b/res/drawable/arrow_up_float.png
new file mode 100644 (file)
index 0000000..9bc3d1c
Binary files /dev/null and b/res/drawable/arrow_up_float.png differ
diff --git a/res/drawable/btn_check_buttonless_on.png b/res/drawable/btn_check_buttonless_on.png
new file mode 100644 (file)
index 0000000..a10a37a
Binary files /dev/null and b/res/drawable/btn_check_buttonless_on.png differ
diff --git a/res/drawable/ic_input_delete.png b/res/drawable/ic_input_delete.png
new file mode 100644 (file)
index 0000000..ee4c911
Binary files /dev/null and b/res/drawable/ic_input_delete.png differ
diff --git a/res/layout/about_dictionary_dialog.xml b/res/layout/about_dictionary_dialog.xml
new file mode 100644 (file)
index 0000000..d143340
--- /dev/null
@@ -0,0 +1,12 @@
+<ScrollView android:id="@+id/ScrollView01"
+  xmlns:android="http://schemas.android.com/apk/res/android"
+  android:layout_width="fill_parent" 
+  android:layout_height="fill_parent">
+  
+  <TextView 
+    android:id="@+id/text"
+    android:layout_width="wrap_content" 
+    android:layout_height="wrap_content"
+    android:layout_gravity="center_horizontal"/>
+
+</ScrollView>
index 56bb7555863b3565988f168025acab6977a8dc67..1afaa70559d2c5b2d0eee3c109948e4179293192 100644 (file)
                                
       <Button 
         android:id="@+id/ClearSearchTextButton"
-        android:text="&lt;x"
-        android:minWidth="50dip"
+        android:drawableLeft="@+drawable/ic_input_delete"
+        android:background="#00000000"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        />
+        />  <!-- transparent -->
 
                        <Button 
                                android:id="@+id/LangButton"
-                               android:text="LANG"
+                               android:text=""
                                android:minWidth="50dip"
                                android:layout_width="wrap_content"
                                android:layout_height="wrap_content"
                                />
                                
                        <Button
-                               android:id="@+id/DownButton"
-                               android:text="v"
-                               android:minWidth="50dip"
-                               android:layout_width="wrap_content"
-                               android:layout_height="wrap_content" 
-                               />
+                   android:id="@+id/DownButton"
+        android:drawableLeft="@+drawable/arrow_down_float"
+        android:background="#00000000"
+        android:minWidth="30dip" 
+        android:minHeight="30dip"
+                   android:layout_width="wrap_content"
+                   android:layout_height="wrap_content"
+          />
                                
                        <Button 
                                android:id="@+id/UpButton"
-                               android:text="^"
-                               android:minWidth="50dip"
+        android:drawableLeft="@+drawable/arrow_up_float"
+        android:background="#00000000"
+        android:minWidth="30dip"
+        android:minHeight="30dip"
                                android:layout_width="wrap_content"
                                android:layout_height="wrap_content" 
                                />
                android:clickable="true" 
                android:focusableInTouchMode="true"
                android:focusable="true"/>
+  <TextView android:id="@android:id/empty"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:text="@+string/noSearchResults"/>
 
 </LinearLayout>
index de73d3c80d9d0aa17ae4230c6496d429b5f6d39d..31383bb9f9539566a91c66008cd8e7407564f068 100644 (file)
@@ -28,7 +28,7 @@
   <string name="HU">Hungarian</string>
   <string name="IS">Icelandic</string>
   <string name="ID">Indonesian</string>
-  <string name="GA">Irish</string>
+  <string name="GA">Gaelic (Irish, Scottish)</string>
   <string name="IT">Italian</string>
   <string name="LA">Latin</string>
   <string name="LV">Latvian</string>
   <string name="YI">Yiddish</string>
   <string name="ZU">Zulu</string>
 
+  
+  <string name="AZ">Azeri</string>
+  <string name="EU">Basque</string>
+  <string name="BR">Breton</string>
+  <string name="MR">Burmese</string>
+  <string name="FO">Faroese</string>
+  <string name="GL">Galician</string>
+  <string name="KA">Georgian</string>
+  <string name="HT">Haitian Creole</string>
+  <string name="LB">Luxembourgish</string>
+  <string name="MK">Macedonian</string>
+   
 </resources>
\ No newline at end of file
index 557988d578ec3cf6977693298ceb75588c5026d4..17f0efd678cdd279430834d0992a2038c4d610e9 100644 (file)
   <string name="selectDictionary">Select dictionary…</string>
   <string name="switchToLanguage">Switch to %s</string>
   <string name="addToWordList">Add to word list: %s</string>
-  <string name="searchForSelection">Find: %s</string>
+  <string name="searchForSelection">Search: "%s"</string>
   <string name="failedAddingToWordList">Failure adding to word list: %s</string>
   <string name="unzippingDictionary">Unzipping dictionary…</string>
   <string name="failedToUnzipDictionary">Failed to unzip dictionary…</string>
   <string name="invalidDictionary">Invalid dictionary: file=%1$s, error=%2$s</string>
+  <string name="noSearchResults">No search results.</string>
+  <string name="aboutDictionary">About dictionary…</string> 
+
+  <!-- About dictionary. -->
+  <string name="dictionaryPath">File: %s</string> 
+  <string name="dictionarySize">Size: %d bytes</string> 
+  <string name="dictionaryCreationTime">Created: %tc</string> 
+  <string name="indexName">Index: %s</string> 
+  <string name="mainTokenCount">Num words: %d</string> 
+  
   
        <!-- About. -->
        <string name="titleWithVersion">QuickDic 3.1</string>
index d9ef0cd9500cbf0d1cfccbce4749487e30500b39..f79b13f49a8558c67cf4ef106fc507dc979d46ef 100644 (file)
@@ -20,8 +20,12 @@ import java.io.IOException;
 import java.io.PrintWriter;\r
 import java.io.RandomAccessFile;\r
 import java.text.SimpleDateFormat;\r
+import java.util.Arrays;\r
+import java.util.Collections;\r
 import java.util.Date;\r
+import java.util.LinkedHashSet;\r
 import java.util.List;\r
+import java.util.Set;\r
 import java.util.concurrent.Executor;\r
 import java.util.concurrent.Executors;\r
 import java.util.concurrent.ThreadFactory;\r
@@ -29,7 +33,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.regex.Matcher;\r
 import java.util.regex.Pattern;\r
 \r
-import android.app.Activity;\r
 import android.app.Dialog;\r
 import android.app.ListActivity;\r
 import android.content.Context;\r
@@ -53,6 +56,7 @@ import android.view.ContextMenu.ContextMenuInfo;
 import android.view.KeyEvent;\r
 import android.view.Menu;\r
 import android.view.MenuItem;\r
+import android.view.WindowManager;\r
 import android.view.MenuItem.OnMenuItemClickListener;\r
 import android.view.MotionEvent;\r
 import android.view.View;\r
@@ -94,6 +98,7 @@ public class DictionaryActivity extends ListActivity {
   Dictionary dictionary = null;\r
   int indexIndex = 0;\r
   Index index = null;\r
+  List<RowBase> rowsToShow = null;  // if not null, just show these rows.\r
   \r
   // package for test.\r
   final Handler uiHandler = new Handler();\r
@@ -280,11 +285,12 @@ public class DictionaryActivity extends ListActivity {
       public void onItemSelected(AdapterView<?> adapterView, View arg1, final int position,\r
           long id) {\r
         if (!searchText.isFocused()) {\r
-          // TODO: don't do this if multi words are entered.\r
-          final RowBase row = (RowBase) getListAdapter().getItem(position);\r
-          Log.d(LOG, "onItemSelected: " + row.index());\r
-          final TokenRow tokenRow = row.getTokenRow(true);\r
-          searchText.setText(tokenRow.getToken());\r
+          if (!isFiltered()) {\r
+            final RowBase row = (RowBase) getListAdapter().getItem(position);\r
+            Log.d(LOG, "onItemSelected: " + row.index());\r
+            final TokenRow tokenRow = row.getTokenRow(true);\r
+            searchText.setText(tokenRow.getToken());\r
+          }\r
         }\r
       }\r
 \r
@@ -385,7 +391,7 @@ public class DictionaryActivity extends ListActivity {
     searchText.requestFocus();\r
     Log.d(LOG, "Trying to show soft keyboard.");\r
     final InputMethodManager manager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);\r
-    manager.showSoftInput(searchText, InputMethodManager.SHOW_FORCED);\r
+    manager.showSoftInput(searchText, InputMethodManager.SHOW_IMPLICIT);\r
   }\r
   \r
   void updateLangButton() {\r
@@ -472,6 +478,9 @@ public class DictionaryActivity extends ListActivity {
   }\r
   \r
   void onUpDownButton(final boolean up) {\r
+    if (isFiltered()) {\r
+      return;\r
+    }\r
     final int firstVisibleRow = getListView().getFirstVisiblePosition();\r
     final RowBase row = index.rows.get(firstVisibleRow);\r
     final TokenRow tokenRow = row.getTokenRow(true);\r
@@ -490,6 +499,7 @@ public class DictionaryActivity extends ListActivity {
     Log.d(LOG, "onUpDownButton, destIndexEntry=" + dest.token);\r
     searchText.removeTextChangedListener(searchTextWatcher);\r
     searchText.setText(dest.token);\r
+    Selection.moveToRightEdge(searchText.getText(), searchText.getLayout());\r
     jumpToRow(index.sortedIndexEntries.get(destIndexEntry).startRow);\r
     searchText.addTextChangedListener(searchTextWatcher);\r
   }\r
@@ -513,6 +523,45 @@ public class DictionaryActivity extends ListActivity {
       });\r
     }\r
 \r
+    {\r
+      final MenuItem aboutDictionary = menu.add(getString(R.string.aboutDictionary));\r
+      aboutDictionary.setOnMenuItemClickListener(new OnMenuItemClickListener() {\r
+        public boolean onMenuItemClick(final MenuItem menuItem) {\r
+          final Context context = getListView().getContext();\r
+          final Dialog dialog = new Dialog(context);\r
+          dialog.setContentView(R.layout.about_dictionary_dialog);\r
+          final TextView textView = (TextView) dialog.findViewById(R.id.text);\r
+\r
+          final String name = application.getDictionaryName(dictFile.getName());\r
+          dialog.setTitle(name);\r
+          \r
+          final StringBuilder builder = new StringBuilder();\r
+          final DictionaryInfo dictionaryInfo = Dictionary.getDictionaryInfo(dictFile);\r
+          if (dictionaryInfo != null) {\r
+            builder.append(dictionaryInfo.dictInfo).append("\n\n");\r
+            builder.append(getString(R.string.dictionaryPath, dictFile.getPath())).append("\n");\r
+            builder.append(getString(R.string.dictionarySize, dictionaryInfo.uncompressedBytes)).append("\n");\r
+            builder.append(getString(R.string.dictionaryCreationTime, dictionaryInfo.creationMillis)).append("\n");\r
+            for (final IndexInfo indexInfo : dictionaryInfo.indexInfos) {\r
+              builder.append("\n");\r
+              builder.append(getString(R.string.indexName, indexInfo.shortName)).append("\n");\r
+              builder.append(getString(R.string.mainTokenCount, indexInfo.mainTokenCount)).append("\n");\r
+            }\r
+          } else {\r
+            builder.append(getString(R.string.invalidDictionary));\r
+          }\r
+          textView.setText(builder.toString());\r
+          \r
+          dialog.show();\r
+          final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();\r
+          layoutParams.width = WindowManager.LayoutParams.FILL_PARENT;\r
+          layoutParams.height = WindowManager.LayoutParams.FILL_PARENT;\r
+          dialog.getWindow().setAttributes(layoutParams);\r
+          return false;\r
+        }\r
+      });\r
+    }\r
+\r
     return true;\r
   }\r
 \r
@@ -664,26 +713,26 @@ public class DictionaryActivity extends ListActivity {
     Log.d(LOG, "searchFinished: " + searchOperation + ", searchResult=" + searchResult);\r
 \r
     currentSearchOperation = null;\r
-\r
     uiHandler.postDelayed(new Runnable() {\r
       @Override\r
       public void run() {\r
         if (currentSearchOperation == null) {\r
-          jumpToRow(searchResult.startRow);\r
+          if (searchResult != null) {\r
+            if (isFiltered()) {\r
+              clearFiltered();\r
+            }\r
+            jumpToRow(searchResult.startRow);\r
+          } else if (searchOperation.multiWordSearchResult != null) {\r
+            // Multi-row search....\r
+            setFiltered(searchOperation);\r
+          } else {\r
+            throw new IllegalStateException("This should never happen.");\r
+          }\r
         } else {\r
           Log.d(LOG, "More coming, waiting for currentSearchOperation.");\r
         }\r
       }\r
-    }, 50);\r
-    \r
-//    if (!searchResult.success) {\r
-//      if (vibrator != null) {\r
-//        vibrator.vibrate(VIBRATE_MILLIS);\r
-//      }\r
-//      searchText.setText(searchResult.longestPrefixString);\r
-//      searchText.setSelection(searchResult.longestPrefixString.length());\r
-//      return;\r
-//    }\r
+    }, 20);\r
     \r
   }\r
   \r
@@ -692,15 +741,18 @@ public class DictionaryActivity extends ListActivity {
     getListView().setSelected(true);\r
   }\r
 \r
+  static final Pattern WHITESPACE = Pattern.compile("\\s+");\r
   final class SearchOperation implements Runnable {\r
     \r
     final AtomicBoolean interrupted = new AtomicBoolean(false);\r
     final String searchText;\r
+    List<String> searchTokens;  // filled in for multiWord.\r
     final Index index;\r
     \r
     long searchStartMillis;\r
 \r
     Index.IndexEntry searchResult;\r
+    List<RowBase> multiWordSearchResult;\r
     \r
     boolean done = false;\r
     \r
@@ -717,7 +769,13 @@ public class DictionaryActivity extends ListActivity {
     public void run() {\r
       try {\r
         searchStartMillis = System.currentTimeMillis();\r
-        searchResult = index.findInsertionPoint(searchText, interrupted);\r
+        final String[] searchTokenArray = WHITESPACE.split(searchText);\r
+        if (searchTokenArray.length == 1) {\r
+          searchResult = index.findInsertionPoint(searchText, interrupted);\r
+        } else {\r
+          searchTokens = Arrays.asList(searchTokenArray);\r
+          multiWordSearchResult = index.multiWordSearch(searchTokens, interrupted);\r
+        }\r
         Log.d(LOG, "searchText=" + searchText + ", searchDuration="\r
             + (System.currentTimeMillis() - searchStartMillis) + ", interrupted="\r
             + interrupted.get());\r
@@ -746,19 +804,29 @@ public class DictionaryActivity extends ListActivity {
   final class IndexAdapter extends BaseAdapter {\r
     \r
     final Index index;\r
+    final List<RowBase> rows;\r
+    final Set<String> toHighlight;\r
 \r
     IndexAdapter(final Index index) {\r
       this.index = index;\r
+      rows = index.rows;\r
+      this.toHighlight = null;\r
+    }\r
+\r
+    IndexAdapter(final Index index, final List<RowBase> rows, final List<String> toHighlight) {\r
+      this.index = index;\r
+      this.rows = rows;\r
+      this.toHighlight = new LinkedHashSet<String>(toHighlight);\r
     }\r
 \r
     @Override\r
     public int getCount() {\r
-      return index.rows.size();\r
+      return rows.size();\r
     }\r
 \r
     @Override\r
     public RowBase getItem(int position) {\r
-      return index.rows.get(position);\r
+      return rows.get(position);\r
     }\r
 \r
     @Override\r
@@ -768,7 +836,7 @@ public class DictionaryActivity extends ListActivity {
 \r
     @Override\r
     public View getView(int position, final View convertView, ViewGroup parent) {\r
-      final RowBase row = index.rows.get(position);\r
+      final RowBase row = getItem(position);\r
       if (row instanceof PairEntry.Row) {\r
         return getView(position, (PairEntry.Row) row, parent, convertView);\r
       } else if (row instanceof TokenRow) {\r
@@ -820,13 +888,15 @@ public class DictionaryActivity extends ListActivity {
         col2.setText(col2Text, TextView.BufferType.SPANNABLE);\r
         \r
         // Bold the token instances in col1.\r
+        final Set<String> toBold = toHighlight != null ? this.toHighlight : Collections.singleton(row.getTokenRow(true).getToken());\r
         final Spannable col1Spannable = (Spannable) col1.getText();\r
-        int startPos = 0;\r
-        final String token = row.getTokenRow(true).getToken();\r
-        while ((startPos = col1Text.indexOf(token, startPos)) != -1) {\r
-          col1Spannable.setSpan(new StyleSpan(Typeface.BOLD), startPos,\r
-              startPos + token.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);\r
-          startPos += token.length();\r
+        for (final String token : toBold) {\r
+          int startPos = 0;\r
+          while ((startPos = col1Text.indexOf(token, startPos)) != -1) {\r
+            col1Spannable.setSpan(new StyleSpan(Typeface.BOLD), startPos,\r
+                startPos + token.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);\r
+            startPos += token.length();\r
+          }\r
         }\r
         \r
         createTokenLinkSpans(col1, col1Spannable, col1Text);\r
@@ -1013,4 +1083,26 @@ public class DictionaryActivity extends ListActivity {
     }\r
   }\r
 \r
+  // --------------------------------------------------------------------------\r
+  // Filtered results.\r
+  // --------------------------------------------------------------------------\r
+\r
+  boolean isFiltered() {\r
+    return rowsToShow != null;\r
+  }\r
+\r
+  void setFiltered(final SearchOperation searchOperation) {\r
+    ((Button) findViewById(R.id.UpButton)).setEnabled(false);\r
+    ((Button) findViewById(R.id.DownButton)).setEnabled(false);\r
+    rowsToShow = searchOperation.multiWordSearchResult;\r
+    setListAdapter(new IndexAdapter(index, rowsToShow, searchOperation.searchTokens));\r
+  }\r
+\r
+  void clearFiltered() {\r
+    ((Button) findViewById(R.id.UpButton)).setEnabled(true);\r
+    ((Button) findViewById(R.id.DownButton)).setEnabled(true);\r
+    setListAdapter(new IndexAdapter(index));\r
+    rowsToShow = null;\r
+  }\r
+\r
 }\r
index c2379eef5daa5c2ea5fede89083385d95aeb3593..eb201c36b1161248039b88fc9b0bdd830002653b 100644 (file)
@@ -225,6 +225,7 @@ public class DictionaryApplication extends Application {
     final List<DictionaryInfo> toAddSorted = new ArrayList<DictionaryInfo>();
     final File[] dictDirFiles = DICT_DIR.listFiles();
     for (final File file : dictDirFiles) {
+      // TODO: delete zip files here.
       if (!file.getName().endsWith(".quickdic")) {
         continue;
       }
index 1ba06fc59fd7c845beb8a2fb645d5a4e5f3b2cd8..f055b8ebf4fbebe732c0eeda9505baa579d8525b 100644 (file)
@@ -221,6 +221,17 @@ public class DictionaryManagerActivity extends ListActivity {
       row.setOrientation(LinearLayout.HORIZONTAL);
       result.addView(row);
 
+      {
+      final TextView textView = new TextView(parent.getContext());
+      final String name = application.getDictionaryName(dictionaryInfo.uncompressedFilename);
+      textView.setText(name);
+      textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 22);
+      row.addView(textView);
+      LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT);
+      layoutParams.weight = 1.0f;
+      textView.setLayoutParams(layoutParams);
+      }
+      
       final boolean updateAvailable = application.updateAvailable(dictionaryInfo);
       final DictionaryInfo downloadable = application.getDownloadable(dictionaryInfo.uncompressedFilename); 
       if ((!application.isDictionaryOnDevice(dictionaryInfo.uncompressedFilename) || updateAvailable) && downloadable != null) {
@@ -242,16 +253,10 @@ public class DictionaryManagerActivity extends ListActivity {
         row.addView(downloadButton);
       } else {
         final ImageView checkMark = new ImageView(parent.getContext());
-        checkMark.setImageResource(android.R.drawable.checkbox_on_background);
+        checkMark.setImageResource(R.drawable.btn_check_buttonless_on);
         row.addView(checkMark);
       }
 
-      final TextView textView = new TextView(parent.getContext());
-      final String name = application.getDictionaryName(dictionaryInfo.uncompressedFilename);
-      textView.setText(name);
-      textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 22);
-      row.addView(textView);
-      
       // Add the information about each index.
       final LinearLayout row2 = new LinearLayout(parent.getContext());
       row2.setOrientation(LinearLayout.HORIZONTAL);
index ce41dd1e9d981489a2d33bc902787f44b178550e..513340fc4cd0e7b5c1eac53827a749ef68b0bc23 100644 (file)
@@ -98,7 +98,9 @@ public class DownloadActivity extends Activity {
 \r
         try {\r
           final File destFile = new File(dest);\r
-          destFile.getParentFile().mkdirs();\r
+          if (destFile.getAbsoluteFile().getParent() != null) {\r
+            destFile.getAbsoluteFile().getParentFile().mkdirs();\r
+          }\r
 \r
           final File destTmpFile = File.createTempFile("dictionaryDownload", "tmp", destFile\r
               .getParentFile());\r
index 7dfff64b672f082b8b9dc9d7281a94e287f68be7..2deb24f31790129f82d966e39d720492b56872eb 100644 (file)
@@ -32,7 +32,7 @@ public class Dictionary implements RAFSerializable<Dictionary> {
   
   static final int CACHE_SIZE = 5000;
   
-  static final int CURRENT_DICT_VERSION = 3;
+  static final int CURRENT_DICT_VERSION = 4;
   static final String END_OF_DICTIONARY = "END OF DICTIONARY";
   
   // persisted
@@ -71,13 +71,19 @@ public class Dictionary implements RAFSerializable<Dictionary> {
     dictInfo = raf.readUTF();
     
     // Load the sources, then seek past them, because reading them later disrupts the offset.
-    final RAFList<EntrySource> rafSources = RAFList.create(raf, new EntrySource.Serializer(this), raf.getFilePointer());
-    sources = new ArrayList<EntrySource>(rafSources);
-    raf.seek(rafSources.getEndOffset());
-    
-    pairEntries = CachingList.create(RAFList.create(raf, new PairEntry.Serializer(this), raf.getFilePointer()), CACHE_SIZE);
-    textEntries = CachingList.create(RAFList.create(raf, new TextEntry.Serializer(this), raf.getFilePointer()), CACHE_SIZE);
-    indices = CachingList.createFullyCached(RAFList.create(raf, indexSerializer, raf.getFilePointer()));
+    try {
+      final RAFList<EntrySource> rafSources = RAFList.create(raf, new EntrySource.Serializer(this), raf.getFilePointer());
+      sources = new ArrayList<EntrySource>(rafSources);
+      raf.seek(rafSources.getEndOffset());
+      
+      pairEntries = CachingList.create(RAFList.create(raf, new PairEntry.Serializer(this), raf.getFilePointer()), CACHE_SIZE);
+      textEntries = CachingList.create(RAFList.create(raf, new TextEntry.Serializer(this), raf.getFilePointer()), CACHE_SIZE);
+      indices = CachingList.createFullyCached(RAFList.create(raf, indexSerializer, raf.getFilePointer()));
+    } catch (RuntimeException e) {
+      final IOException ioe = new IOException("RuntimeException loading dictionary");
+      ioe.initCause(e);
+      throw ioe;
+    }
     final String end = raf.readUTF(); 
     if (!end.equals(END_OF_DICTIONARY)) {
       throw new IOException("Dictionary seems corrupt: " + end);
index 6107b8bafa99b9447ffb13c8bbd11eed6232edf7..3c772a1fa60dd65c01eba0226d1281ed17628889 100644 (file)
@@ -22,12 +22,14 @@ import java.io.PrintStream;
 import java.io.RandomAccessFile;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.EnumMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Pattern;
 
 import com.hughes.android.dictionary.DictionaryInfo;
 import com.hughes.android.dictionary.DictionaryInfo.IndexInfo;
@@ -35,6 +37,7 @@ import com.hughes.util.CachingList;
 import com.hughes.util.raf.RAFList;
 import com.hughes.util.raf.RAFSerializable;
 import com.hughes.util.raf.RAFSerializer;
+import com.hughes.util.raf.SerializableSerializer;
 import com.hughes.util.raf.UniformRAFList;
 import com.ibm.icu.text.Collator;
 import com.ibm.icu.text.Transliterator;
@@ -57,6 +60,9 @@ public final class Index implements RAFSerializable<Index> {
     
   // persisted
   public final List<IndexEntry> sortedIndexEntries;
+  
+  // persisted.
+  public final Set<String> stoplist;
 
   // One big list!
   // Various sub-types.
@@ -69,7 +75,7 @@ public final class Index implements RAFSerializable<Index> {
   
   // --------------------------------------------------------------------------
   
-  public Index(final Dictionary dict, final String shortName, final String longName, final Language sortLanguage, final String normalizerRules, final boolean swapPairEntries) {
+  public Index(final Dictionary dict, final String shortName, final String longName, final Language sortLanguage, final String normalizerRules, final boolean swapPairEntries, final Set<String> stoplist) {
     this.dict = dict;
     this.shortName = shortName;
     this.longName = longName;
@@ -77,6 +83,7 @@ public final class Index implements RAFSerializable<Index> {
     this.normalizerRules = normalizerRules;
     this.swapPairEntries = swapPairEntries;
     sortedIndexEntries = new ArrayList<IndexEntry>();
+    this.stoplist = stoplist;
     rows = new ArrayList<RowBase>();
     
     normalizer = null;
@@ -104,6 +111,11 @@ public final class Index implements RAFSerializable<Index> {
       mainTokenCount = raf.readInt();
     }
     sortedIndexEntries = CachingList.create(RAFList.create(raf, IndexEntry.SERIALIZER, raf.getFilePointer()), CACHE_SIZE);
+    if (dict.dictFileVersion >= 4) {
+      stoplist = new SerializableSerializer<Set<String>>().read(raf);
+    } else {
+      stoplist = Collections.emptySet();
+    }
     rows = CachingList.create(UniformRAFList.create(raf, new RowBase.Serializer(this), raf.getFilePointer()), CACHE_SIZE);
   }
   
@@ -118,6 +130,7 @@ public final class Index implements RAFSerializable<Index> {
       raf.writeInt(mainTokenCount);
     }
     RAFList.write(raf, sortedIndexEntries, IndexEntry.SERIALIZER);
+    new SerializableSerializer<Set<String>>().write(raf, stoplist);
     UniformRAFList.write(raf, (Collection<RowBase>) rows, new RowBase.Serializer(this), 5);
   }
 
@@ -233,57 +246,107 @@ public final class Index implements RAFSerializable<Index> {
     return new DictionaryInfo.IndexInfo(shortName, sortedIndexEntries.size(), mainTokenCount);
   }
   
-  final List<RowBase> multiWordSearch(final List<String> searchTokens, final AtomicBoolean interrupted) {
+  public final List<RowBase> multiWordSearch(final List<String> searchTokens, final AtomicBoolean interrupted) {
     final List<RowBase> result = new ArrayList<RowBase>();
-
-    // Heuristic: use the longest searchToken as the base.
-    String searchToken = null;
+    
+    final Set<String> normalizedNonStoplist = new LinkedHashSet<String>();
+    
+    final StringBuilder regex = new StringBuilder();
     for (int i = 0; i < searchTokens.size(); ++i) {
-      if (interrupted.get()) { return null; }
+      final String searchToken = searchTokens.get(i);
       final String normalized = normalizeToken(searchTokens.get(i));
       // Normalize them all.
       searchTokens.set(i, normalized);
-      if (searchToken == null || normalized.length() > searchToken.length()) {
-        searchToken = normalized;
+
+      if (!stoplist.contains(searchToken)) {
+        normalizedNonStoplist.add(normalized);
       }
+
+      if (regex.length() > 0) {
+        regex.append("[\\s]*");
+      }
+      regex.append(Pattern.quote(normalized));
     }
+    final Pattern pattern = Pattern.compile(regex.toString());
     
-    final int insertionPointIndex = findInsertionPointIndex(searchToken, interrupted);
-    if (insertionPointIndex == -1 || interrupted.get()) {
-      return null;
-    }
-    
+
     // The things that match.
-    // TODO: use a key
-    final Map<RowMatchType,Set<RowBase>> matches = new EnumMap<RowMatchType, Set<RowBase>>(RowMatchType.class);
+    final Map<RowMatchType,List<RowBase>> matches = new EnumMap<RowMatchType, List<RowBase>>(RowMatchType.class);
     for (final RowMatchType rowMatchType : RowMatchType.values()) {
-      matches.put(rowMatchType, new LinkedHashSet<RowBase>());
+      if (rowMatchType != RowMatchType.NO_MATCH) {
+        matches.put(rowMatchType, new ArrayList<RowBase>());
+      }
     }
     
-    for (int index = insertionPointIndex; index < sortedIndexEntries.size(); ++index) {
+    int bestRowCount = Integer.MAX_VALUE;
+    String bestToken = null;
+    for (final String searchToken : normalizedNonStoplist) {
+      final int insertionPointIndex = findInsertionPointIndex(searchToken, interrupted);
       if (interrupted.get()) { return null; }
-      final IndexEntry indexEntry = sortedIndexEntries.get(index);
-      if (!indexEntry.normalizedToken.equals(searchToken)) {
-        break;
+      if (insertionPointIndex == -1) {
+        // If we've typed "train statio", don't fail just because the index
+        // doesn't contain "statio".
+        continue;
       }
-      
-      for (int rowIndex = indexEntry.startRow; rowIndex < indexEntry.startRow + indexEntry.numRows; ++rowIndex) {
+
+      int rowCount = 0;
+      for (int index = insertionPointIndex; index < sortedIndexEntries.size(); ++index) {
         if (interrupted.get()) { return null; }
-        final RowBase row = rows.get(rowIndex);
-        final RowMatchType matchType = row.matches(searchTokens, normalizer, swapPairEntries);
-        if (matchType != RowMatchType.NO_MATCH) {
-          matches.get(matchType).add(row);
+        final IndexEntry indexEntry = sortedIndexEntries.get(index);
+        if (!indexEntry.normalizedToken.equals(searchToken)) {
+          break;
         }
+        rowCount += indexEntry.numRows;
+      }
+      
+      //System.out.println(searchToken + ", rowCount=" + rowCount);
+      if (rowCount < bestRowCount) {
+        bestRowCount = rowCount;
+        bestToken = searchToken;
       }
     }
     
-    for (final Set<RowBase> rows : matches.values()) {
-      result.addAll(rows);
+    final String searchToken = bestToken != null ? bestToken : searchTokens.get(0);
+    
+//    for (final String searchToken : searchTokens) {
+    
+    final int insertionPointIndex = findInsertionPointIndex(searchToken, interrupted);
+    if (interrupted.get()) { return null; }
+
+      
+//      System.out.println("Searching token: " + searchToken);
+
+  
+      for (int index = insertionPointIndex; index < sortedIndexEntries.size(); ++index) {
+        if (interrupted.get()) { return null; }
+        final IndexEntry indexEntry = sortedIndexEntries.get(index);
+        if (!indexEntry.normalizedToken.equals(searchToken)) {
+          break;
+        }
+
+//        System.out.println("Searching indexEntry: " + indexEntry.token);
+
+        for (int rowIndex = indexEntry.startRow; rowIndex < indexEntry.startRow + indexEntry.numRows; ++rowIndex) {
+          if (interrupted.get()) { return null; }
+          final RowBase row = rows.get(rowIndex);
+          final RowMatchType matchType = row.matches(searchTokens, pattern, normalizer(), swapPairEntries);
+          if (matchType != RowMatchType.NO_MATCH) {
+            matches.get(matchType).add(row);
+          }
+        }
+      }
+//    }  // searchTokens
+  
+    final RowBase.LengthComparator lengthComparator = new RowBase.LengthComparator(swapPairEntries);
+    for (final Collection<RowBase> rows : matches.values()) {
+      final List<RowBase> ordered = new ArrayList<RowBase>(rows);
+      Collections.sort(ordered, lengthComparator);
+      result.addAll(ordered);
     }
     
     return result;
   }
-
+  
   private String normalizeToken(final String searchToken) {
     if (TransliteratorManager.init(null)) {
       final Transliterator normalizer = normalizer();
index 74130c1d5964e1f35b4ce361f48227e9541ae854..c8c7938ce951b566ced1f45e478382d0de57cf36 100644 (file)
@@ -91,8 +91,17 @@ public class Language {
     isoCodeToResourceId.put("CI", R.string.CI);\r
     isoCodeToResourceId.put("YI", R.string.YI);\r
     isoCodeToResourceId.put("ZU", R.string.ZU);\r
-    \r
-    \r
+\r
+    isoCodeToResourceId.put("AZ", R.string.AZ);\r
+    isoCodeToResourceId.put("EU", R.string.EU);\r
+    isoCodeToResourceId.put("BR", R.string.BR);\r
+    isoCodeToResourceId.put("MR", R.string.MR);\r
+    isoCodeToResourceId.put("FO", R.string.FO);\r
+    isoCodeToResourceId.put("GL", R.string.GL);\r
+    isoCodeToResourceId.put("HT", R.string.HT);\r
+    isoCodeToResourceId.put("LB", R.string.LB);\r
+    isoCodeToResourceId.put("MK", R.string.MK);\r
+\r
     // Hack to allow lower-case ISO codes to work:\r
     for (final String isoCode : new ArrayList<String>(isoCodeToResourceId.keySet())) {\r
       isoCodeToResourceId.put(isoCode.toLowerCase(), isoCodeToResourceId.get(isoCode));\r
@@ -143,10 +152,16 @@ public class Language {
    */\r
   private static final String rtlChars =\r
       "\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC";\r
-  private static final Pattern RTL_TOKEN = Pattern.compile("[" + rtlChars + "]");\r
+\r
+  private static final String puncChars =\r
+      "\\[\\]\\(\\)\\{\\}\\=";\r
+\r
+  private static final Pattern RTL_LEFT_BOUNDARY = Pattern.compile("(["+ puncChars +"])([" + rtlChars + "])");\r
+  private static final Pattern RTL_RIGHT_BOUNDARY = Pattern.compile("([" + rtlChars + "])(["+ puncChars +"])");\r
   \r
-  public static String fixBidiText(final String text) {\r
-    // TODO: fix me!, use me!\r
+  public static String fixBidiText(String text) {\r
+//    text = RTL_LEFT_BOUNDARY.matcher(text).replaceAll("$1\u200e $2");\r
+//    text = RTL_RIGHT_BOUNDARY.matcher(text).replaceAll("$1 \u200e$2");\r
     return text;\r
   }\r
   \r
index e836a5cf2fc4237b5dc4322f2e356fdc14097d03..02d72b92e9ead7f83e88aa381a458256a190939f 100644 (file)
@@ -125,7 +125,7 @@ public class PairEntry extends AbstractEntry implements RAFSerializable<PairEntr
     }
 
     @Override
-    public RowMatchType matches(final List<String> searchTokens, final Transliterator normalizer, final boolean swapPairEntries) {
+    public RowMatchType matches(final List<String> searchTokens, final Pattern orderedMatchPattern, final Transliterator normalizer, final boolean swapPairEntries) {
       final int side = swapPairEntries ? 1 : 0;
       final List<Pair> pairs = getEntry().pairs;
       final String[] pairSides = new String[pairs.size()];
@@ -142,21 +142,23 @@ public class PairEntry extends AbstractEntry implements RAFSerializable<PairEntr
           return RowMatchType.NO_MATCH;
         }
       }
-      final StringBuilder regex = new StringBuilder();
-      for (final String searchToken : searchTokens) {
-        if (regex.length() > 0) {
-          regex.append("[\\s]*");
-        }
-        regex.append(Pattern.quote(searchToken));
-      }
-      final Pattern pattern = Pattern.compile(regex.toString());
       for (final String pairSide : pairSides) {
-        if (pattern.matcher(pairSide).matches()) {
+        if (orderedMatchPattern.matcher(pairSide).find()) {
           return RowMatchType.ORDERED_MATCH;
         }
       }
       return RowMatchType.BAG_OF_WORDS_MATCH;
     }
+    
+    @Override
+    public int getSideLength(boolean swapPairEntries) {
+      int result = 0;
+      final int side = swapPairEntries ? 1 : 0;
+      for (final Pair pair : getEntry().pairs) {
+        result += pair.get(side).length();
+      }
+      return result;
+    }
   
   }
 
index e84eb7377bd780a514d61f705b0d3561ca53488b..8896c88c00ceacc6560de71d5db5381e81496ea4 100644 (file)
@@ -17,7 +17,9 @@ package com.hughes.android.dictionary.engine;
 import java.io.IOException;
 import java.io.PrintStream;
 import java.io.RandomAccessFile;
+import java.util.Comparator;
 import java.util.List;
+import java.util.regex.Pattern;
 
 import com.hughes.util.IndexedObject;
 import com.hughes.util.raf.RAFListSerializer;
@@ -50,6 +52,34 @@ public abstract class RowBase extends IndexedObject {
     this.index = index;
     this.referenceIndex = referenceIndex;
   }
+  
+  static final class RowKey {
+    final Class<? extends RowBase> rowClass;
+    final int referenceIndex;
+    
+    private RowKey(Class<? extends RowBase> rowClass, int referenceIndex) {
+      this.rowClass = rowClass;
+      this.referenceIndex = referenceIndex;
+    }
+    
+    @Override
+    public boolean equals(Object o) {
+      if (!(o instanceof RowKey)) {
+        return false;
+      }
+      final RowKey that = (RowKey) o;
+      return this.referenceIndex == that.referenceIndex && this.rowClass.equals(that.rowClass);
+    }
+    
+    @Override
+    public int hashCode() {
+      return rowClass.hashCode() + referenceIndex;
+    }
+  }
+  
+  public RowKey getRowKey() {
+    return new RowKey(this.getClass(), referenceIndex);
+  }
 
   /**
    * @return the TokenRow that this row is "filed under".
@@ -100,7 +130,7 @@ public abstract class RowBase extends IndexedObject {
 
   public abstract String getRawText(final boolean compact);
   
-  public abstract RowMatchType matches(final List<String> searchTokens, final Transliterator normalizer, boolean swapPairEntries);
+  public abstract RowMatchType matches(final List<String> searchTokens, final Pattern orderedMatch, final Transliterator normalizer, boolean swapPairEntries);
 
   // RowBase must manage "disk-based" polymorphism.  All other polymorphism is
   // dealt with in the normal manner.
@@ -139,4 +169,24 @@ public abstract class RowBase extends IndexedObject {
     }
   }
   
+  public static final class LengthComparator implements Comparator<RowBase> {
+    
+    final boolean swapPairEntries;
+
+    public LengthComparator(boolean swapPairEntries) {
+      this.swapPairEntries = swapPairEntries;
+    }
+
+    @Override
+    public int compare(RowBase row1, RowBase row2) {
+      final int l1 = row1.getSideLength(swapPairEntries);
+      final int l2 = row2.getSideLength(swapPairEntries);
+      return l1 < l2 ? -1 : l1 == l2 ? 0 : 1;
+    }
+  }
+
+  public int getSideLength(boolean swapPairEntries) {
+    return getRawText(false).length();
+  }
+  
 }
index ec5bc3905627d4c1e3aef3eaf3ab3ab4d1e43bb9..8d878dd87a35a5dd00c8d9623fbbaf76a00c6eee 100644 (file)
@@ -18,6 +18,7 @@ import java.io.IOException;
 import java.io.PrintStream;
 import java.io.RandomAccessFile;
 import java.util.List;
+import java.util.regex.Pattern;
 
 import com.hughes.util.raf.RAFSerializable;
 import com.hughes.util.raf.RAFSerializer;
@@ -86,7 +87,7 @@ public class TextEntry extends AbstractEntry implements RAFSerializable<TextEntr
     }
     
     @Override
-    public RowMatchType matches(List<String> searchTokens, Transliterator normalizer, boolean swapPairEntries) {
+    public RowMatchType matches(final List<String> searchTokens, final Pattern orderedMatchPattern, Transliterator normalizer, boolean swapPairEntries) {
       return null;
     }
   }
index 7b7736c46bc899289db2c0c36b45c71bc49e54a4..29a23bcacfc6b51ef9fa689e0dbf4671b1babe79 100644 (file)
@@ -18,6 +18,7 @@ import java.io.IOException;
 import java.io.PrintStream;
 import java.io.RandomAccessFile;
 import java.util.List;
+import java.util.regex.Pattern;
 
 import com.ibm.icu.text.Transliterator;
 
@@ -65,7 +66,7 @@ public class TokenRow extends RowBase {
   }
 
   @Override
-  public RowMatchType matches(List<String> searchTokens, Transliterator normalizer, boolean swapPairEntries) {
+  public RowMatchType matches(List<String> searchTokens, final Pattern orderedMatchPattern, Transliterator normalizer, boolean swapPairEntries) {
     return RowMatchType.NO_MATCH;
   }
 
index 262e599c0e5ed64ce85cd0977067b17779a5d8dd..2a3a527b197b5d4fd861a8a4c678737ee3ff61e8 100644 (file)
@@ -22,7 +22,11 @@ import java.io.ObjectOutputStream;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
+import com.hughes.android.dictionary.DictionaryApplication;
+
+import android.app.Application;
 import android.content.Context;
+import android.os.Environment;
 import android.util.Log;
 
 public class PersistentObjectCache {
@@ -70,9 +74,10 @@ public class PersistentObjectCache {
   }
 
   private PersistentObjectCache(final Context context) {
-    dir = context.getFilesDir();
+    final File filesDir = context.getFilesDir();
+    dir = filesDir != null ? filesDir : Environment.getExternalStorageDirectory();
     if (dir == null) {
-      throw new RuntimeException("context.getFilesDir() == null");
+      throw new RuntimeException("context.getFilesDir() == " + context.getFilesDir() + ", Environment.getExternalStorageDirectory()=" + Environment.getExternalStorageDirectory());
     }
   }