]> gitweb.fperrin.net Git - Dictionary.git/commitdiff
Added multiword search to dictionary.
authorThad Hughes <thad.hughes@gmail.com>
Thu, 26 Jan 2012 00:01:36 +0000 (16:01 -0800)
committerThad Hughes <thad.hughes@gmail.com>
Thu, 26 Jan 2012 00:01:36 +0000 (16:01 -0800)
13 files changed:
AndroidManifest.xml
res/raw/help.html
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/HelpActivity.java
src/com/hughes/android/dictionary/engine/Index.java
src/com/hughes/android/dictionary/engine/PairEntry.java
src/com/hughes/android/dictionary/engine/RowBase.java
src/com/hughes/android/dictionary/engine/RowMatchType.java [new file with mode: 0644]
src/com/hughes/android/dictionary/engine/TextEntry.java
src/com/hughes/android/dictionary/engine/TokenRow.java

index 5496c76a49b29415fd2e7352b97a4086ea45c5d8..3e5b8b709227958d6445381a31f1232aea4fdd1e 100644 (file)
@@ -55,6 +55,7 @@
   <activity android:name=".DictionaryActivity" />
   <activity android:name=".DictionaryEditActivity" />
   <activity android:name=".AboutActivity" />
+  <activity android:name=".HelpActivity" />
   <activity android:name=".PreferenceActivity" />
   <activity android:name=".DownloadActivity"
       android:configChanges="keyboardHidden|orientation"/>
index 681978f31e7457412007f67f9ab387892bf7adaa..3813f6dbb679d165e4a9faf98ba5148fd736af4b 100644 (file)
@@ -4,21 +4,25 @@
 </head>
 <body>
   <!-- Don't use links in the text below, it crashes the app. -->
-  <h1>Dictionary manager</h1>
+  <h2>Dictionary manager</h2>
   This screen lists all the available and installed dictionaries.
   <ul>
     <li> Click a dictionary to download or open it.
     <li> Long-click on a dictionary to move it to the top of the list or delete it.
   </ul>
 
-  <h1>Dictionary</h1>
+  <h2>Dictionary</h2>
   This screen shows dictionary entries.  This is a massive list that you can
   scroll all the way through, or you can type in the search text to jump 
   there in the list.  Entries in the list are filed in multiple places,
   under all relevant words.
-  <p> What to do:
+  <h3>Searching</h3>
   <ul>
     <li> Type a single word to search for it (multi-word searches not yet supported).
+    <li> QuickDic tries to sort words using a romanized transliteration, so you can try searching for non-Latin words using the Latin alphabet.
+  </ul>
+  <h3>Other</h3>
+  <ul>
     <li> If the search text box isn't focused, you can start typing and its contents will be replaced.
     <li> Click the button to the right of the search box to switch dictionary directions: EN-&gt;DE to DE-&gt;EN.
     <li> Long-click the button to the right of the search box to pick a new dictionary.
index 99f5cd082eff0ad4b94ce82d7bd7f52edb6c0003..557988d578ec3cf6977693298ceb75588c5026d4 100644 (file)
@@ -32,8 +32,6 @@
   <string name="searchText">Search Text</string>
   <string name="selectDictionary">Select dictionary…</string>
   <string name="switchToLanguage">Switch to %s</string>
-  <string name="preferences">Preferences…</string>
-  <string name="about">About QuickDic…</string> 
   <string name="addToWordList">Add to word list: %s</string>
   <string name="searchForSelection">Find: %s</string>
   <string name="failedAddingToWordList">Failure adding to word list: %s</string>
        <string name="downloading">Downloading: %1$,d of %2$,d bytes.</string>
   <string name="unzipping">Unzipping: %1$,d of %2$,d bytes.</string>
        <string name="downloadFinished">Finished: %,d bytes.</string>
-       <string name="errorDownloadingFile">"Error downloading file: \n%s"</string>
+       <string name="errorDownloadingFile">Error downloading file: \n%s</string>
 
+  <!-- Global. -->
+  <string name="about">About QuickDic…</string> 
+  <string name="preferences">Preferences…</string>
+  <string name="help">Help</string>
+  
        <!-- Preferences -->
        <string name="wordListFileKey">wordListFile</string>
        <string name="wordListFileTitle">Word list file</string>
index 66aee42061b2aa9267dc54c712527eaf7b335ee7..d9ef0cd9500cbf0d1cfccbce4749487e30500b39 100644 (file)
@@ -179,8 +179,8 @@ public class DictionaryActivity extends ListActivity {
       return;\r
     }\r
 \r
-    Log.d(LOG, "Loading index.");\r
     indexIndex = intent.getIntExtra(C.INDEX_INDEX, 0) % dictionary.indices.size();\r
+    Log.d(LOG, "Loading index " + indexIndex);\r
     index = dictionary.indices.get(indexIndex);\r
     setListAdapter(new IndexAdapter(index));\r
     \r
@@ -400,27 +400,6 @@ public class DictionaryActivity extends ListActivity {
     changeIndex((indexIndex + 1)% dictionary.indices.size());\r
   }\r
   \r
-  static class OpenIndexButton extends Button implements OnClickListener {\r
-\r
-    final Activity activity;\r
-    final Intent intent;\r
-\r
-    public OpenIndexButton(final Context context, final Activity activity, final String text, final Intent intent) {\r
-      super(context);\r
-      this.activity = activity;\r
-      this.intent = intent;\r
-      setOnClickListener(this);\r
-      setText(text, BufferType.NORMAL);\r
-    }\r
-\r
-    @Override\r
-    public void onClick(View v) {\r
-      activity.finish();\r
-      getContext().startActivity(intent);\r
-    }\r
-    \r
-  }\r
-\r
   void onLanguageButtonLongClick(final Context context) {\r
     final Dialog dialog = new Dialog(context);\r
     dialog.setContentView(R.layout.select_dictionary_dialog);\r
@@ -489,6 +468,7 @@ public class DictionaryActivity extends ListActivity {
     updateLangButton();\r
     searchText.requestFocus();  // Otherwise, nothing may happen.\r
     onSearchTextChange(searchText.getText().toString());\r
+    setDictionaryPrefs(this, dictFile, indexIndex, searchText.getText().toString());\r
   }\r
   \r
   void onUpDownButton(final boolean up) {\r
@@ -583,7 +563,6 @@ public class DictionaryActivity extends ListActivity {
   @Override\r
   protected void onListItemClick(ListView l, View v, int row, long id) {\r
     defocusSearchText();\r
-    \r
     if (clickOpensContextMenu && dictRaf != null) {\r
       openContextMenu(v);\r
     }\r
@@ -791,7 +770,7 @@ public class DictionaryActivity extends ListActivity {
     public View getView(int position, final View convertView, ViewGroup parent) {\r
       final RowBase row = index.rows.get(position);\r
       if (row instanceof PairEntry.Row) {\r
-        return getView((PairEntry.Row) row, parent, convertView);\r
+        return getView(position, (PairEntry.Row) row, parent, convertView);\r
       } else if (row instanceof TokenRow) {\r
         return getView((TokenRow) row, parent, convertView);\r
       } else {\r
@@ -799,7 +778,7 @@ public class DictionaryActivity extends ListActivity {
       }\r
     }\r
 \r
-    private View getView(PairEntry.Row row, ViewGroup parent, final View convertView) {\r
+    private View getView(final int position, PairEntry.Row row, ViewGroup parent, final View convertView) {\r
       final TableLayout result = new TableLayout(parent.getContext());\r
       final PairEntry entry = row.getEntry();\r
       final int rowCount = entry.pairs.size();\r
@@ -865,6 +844,20 @@ public class DictionaryActivity extends ListActivity {
           col2.setOnLongClickListener(textViewLongClickListenerIndex1);\r
         }\r
         \r
+        // Because we have a Button inside a ListView row:\r
+        // http://groups.google.com/group/android-developers/browse_thread/thread/3d96af1530a7d62a?pli=1\r
+        result.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);\r
+        result.setClickable(true);\r
+        result.setFocusable(true);\r
+        result.setLongClickable(true);\r
+        result.setBackgroundResource(android.R.drawable.menuitem_background);\r
+        result.setOnClickListener(new TextView.OnClickListener() {\r
+          @Override\r
+          public void onClick(View v) {\r
+            DictionaryActivity.this.onListItemClick(null, v, position, position);\r
+          }\r
+        });\r
+        \r
         result.addView(tableRow);\r
       }\r
 \r
index 77db79599f58c5b4dd79c344306c47b7a90e0e7f..c2379eef5daa5c2ea5fede89083385d95aeb3593 100644 (file)
@@ -281,20 +281,20 @@ public class DictionaryApplication extends Application {
 
   public void onCreateGlobalOptionsMenu(
       final Context context, final Menu menu) {
-    final MenuItem help = menu.add(getString(R.string.about));
-    help.setOnMenuItemClickListener(new OnMenuItemClickListener() {
+    final MenuItem about = menu.add(getString(R.string.about));
+    about.setOnMenuItemClickListener(new OnMenuItemClickListener() {
       public boolean onMenuItemClick(final MenuItem menuItem) {
-        startActivity(HelpActivity.getLaunchIntent());
+        final Intent intent = new Intent().setClassName(AboutActivity.class
+            .getPackage().getName(), AboutActivity.class.getCanonicalName());
+        context.startActivity(intent);
         return false;
       }
     });
 
-    final MenuItem about = menu.add(getString(R.string.about));
-    about.setOnMenuItemClickListener(new OnMenuItemClickListener() {
+    final MenuItem help = menu.add(getString(R.string.help));
+    help.setOnMenuItemClickListener(new OnMenuItemClickListener() {
       public boolean onMenuItemClick(final MenuItem menuItem) {
-        final Intent intent = new Intent().setClassName(AboutActivity.class
-            .getPackage().getName(), AboutActivity.class.getCanonicalName());
-        startActivity(intent);
+        context.startActivity(HelpActivity.getLaunchIntent());
         return false;
       }
     });
@@ -305,7 +305,7 @@ public class DictionaryApplication extends Application {
         PreferenceActivity.prefsMightHaveChanged = true;
         final Intent intent = new Intent().setClassName(PreferenceActivity.class
             .getPackage().getName(), PreferenceActivity.class.getCanonicalName());
-        startActivity(intent);
+        context.startActivity(intent);
         return false;
       }
     });
index 3638aac7f34f18fdb55dbc2744cbf6595e71c5ac..1ba06fc59fd7c845beb8a2fb645d5a4e5f3b2cd8 100644 (file)
@@ -31,7 +31,6 @@ import android.view.ContextMenu.ContextMenuInfo;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.MenuItem.OnMenuItemClickListener;
-import android.view.ViewGroup.LayoutParams;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
@@ -41,6 +40,7 @@ import android.widget.AdapterView.AdapterContextMenuInfo;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.BaseAdapter;
 import android.widget.Button;
+import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
@@ -240,6 +240,10 @@ public class DictionaryManagerActivity extends ListActivity {
         layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
         downloadButton.setLayoutParams(layoutParams);
         row.addView(downloadButton);
+      } else {
+        final ImageView checkMark = new ImageView(parent.getContext());
+        checkMark.setImageResource(android.R.drawable.checkbox_on_background);
+        row.addView(checkMark);
       }
 
       final TextView textView = new TextView(parent.getContext());
index 2d5c1418cb58b40a859c3466703c6c5c0e0c7d1a..0f424c9e7488752e6287cd6183f3df383e2981ce 100644 (file)
@@ -35,7 +35,7 @@ public final class HelpActivity extends Activity {
     super.onCreate(savedInstanceState);\r
     setContentView(R.layout.help_activity);\r
     final String html = StringUtil.readToString(getResources().openRawResource(R.raw.help));\r
-    final WebView webView = (WebView) findViewById(R.layout.help_activity);\r
+    final WebView webView = (WebView) findViewById(R.id.helpWebView);\r
     webView.loadData(html, "text/html", "utf-8");\r
   }\r
 \r
index 19e0ecc8ed63d8285710596482950a26af45655d..6107b8bafa99b9447ffb13c8bbd11eed6232edf7 100644 (file)
@@ -22,7 +22,11 @@ import java.io.PrintStream;
 import java.io.RandomAccessFile;
 import java.util.ArrayList;
 import java.util.Collection;
+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 com.hughes.android.dictionary.DictionaryInfo;
@@ -178,13 +182,12 @@ public final class Index implements RAFSerializable<Index> {
   }
   
   public IndexEntry findInsertionPoint(String token, final AtomicBoolean interrupted) {
-    if (TransliteratorManager.init(null)) {
-      final Transliterator normalizer = normalizer();
-      token = normalizer.transliterate(token);
-    } else {
-      // Do our best since the Transliterators aren't up yet.
-      token = token.toLowerCase();
-    }
+    final int index = findInsertionPointIndex(token, interrupted);
+    return index != -1 ? sortedIndexEntries.get(index) : null;
+  }
+  
+  public int findInsertionPointIndex(String token, final AtomicBoolean interrupted) {
+    token = normalizeToken(token);
 
     int start = 0;
     int end = sortedIndexEntries.size();
@@ -193,14 +196,14 @@ public final class Index implements RAFSerializable<Index> {
     while (start < end) {
       final int mid = (start + end) / 2;
       if (interrupted.get()) {
-        return null;
+        return -1;
       }
       final IndexEntry midEntry = sortedIndexEntries.get(mid);
 
       final int comp = sortCollator.compare(token, midEntry.normalizedToken());
       if (comp == 0) {
         final int result = windBackCase(token, mid, interrupted);
-        return sortedIndexEntries.get(result);
+        return result;
       } else if (comp < 0) {
         //System.out.println("Upper bound: " + midEntry + ", norm=" + midEntry.normalizedToken() + ", mid=" + mid);
         end = mid;
@@ -213,7 +216,7 @@ public final class Index implements RAFSerializable<Index> {
     // If we search for a substring of a string that's in there, return that.
     int result = Math.min(start, sortedIndexEntries.size() - 1);
     result = windBackCase(sortedIndexEntries.get(result).normalizedToken(), result, interrupted);
-    return sortedIndexEntries.get(result);
+    return result;
   }
     
   private final int windBackCase(final String token, int result, final AtomicBoolean interrupted) {
@@ -229,5 +232,66 @@ public final class Index implements RAFSerializable<Index> {
   public IndexInfo getIndexInfo() {
     return new DictionaryInfo.IndexInfo(shortName, sortedIndexEntries.size(), mainTokenCount);
   }
+  
+  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;
+    for (int i = 0; i < searchTokens.size(); ++i) {
+      if (interrupted.get()) { return null; }
+      final String normalized = normalizeToken(searchTokens.get(i));
+      // Normalize them all.
+      searchTokens.set(i, normalized);
+      if (searchToken == null || normalized.length() > searchToken.length()) {
+        searchToken = normalized;
+      }
+    }
+    
+    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);
+    for (final RowMatchType rowMatchType : RowMatchType.values()) {
+      matches.put(rowMatchType, new LinkedHashSet<RowBase>());
+    }
+    
+    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;
+      }
+      
+      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, normalizer, swapPairEntries);
+        if (matchType != RowMatchType.NO_MATCH) {
+          matches.get(matchType).add(row);
+        }
+      }
+    }
+    
+    for (final Set<RowBase> rows : matches.values()) {
+      result.addAll(rows);
+    }
+    
+    return result;
+  }
+
+  private String normalizeToken(final String searchToken) {
+    if (TransliteratorManager.init(null)) {
+      final Transliterator normalizer = normalizer();
+      return normalizer.transliterate(searchToken);
+    } else {
+      // Do our best since the Transliterators aren't up yet.
+      return searchToken.toLowerCase();
+    }
+  }
 
 }
\ No newline at end of file
index ebfd84a14f4fd9bbdafc62c4679b6f4ad51721f3..e836a5cf2fc4237b5dc4322f2e356fdc14097d03 100644 (file)
@@ -19,9 +19,11 @@ import java.io.PrintStream;
 import java.io.RandomAccessFile;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.regex.Pattern;
 
 import com.hughes.util.raf.RAFSerializable;
 import com.hughes.util.raf.RAFSerializer;
+import com.ibm.icu.text.Transliterator;
 
 public class PairEntry extends AbstractEntry implements RAFSerializable<PairEntry>, Comparable<PairEntry> {
   
@@ -97,6 +99,11 @@ public class PairEntry extends AbstractEntry implements RAFSerializable<PairEntr
         final Index index) {
       super(referenceIndex, thisRowIndex, index);
     }
+    
+    @Override
+    public String toString() {
+      return getRawText(false);
+    }
 
     public PairEntry getEntry() {
       return index.dict.pairEntries.get(referenceIndex);
@@ -116,6 +123,40 @@ public class PairEntry extends AbstractEntry implements RAFSerializable<PairEntr
       final PairEntry pairEntry = getEntry();
       return pairEntry.getRawText(compact);
     }
+
+    @Override
+    public RowMatchType matches(final List<String> searchTokens, 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()];
+      for (int i = 0; i < pairs.size(); ++i) {
+        pairSides[i] = normalizer.transform(pairs.get(i).get(side));
+      }
+      for (int i = searchTokens.size() - 1; i >= 0; --i) {
+        final String searchToken = searchTokens.get(i);
+        boolean found = false;
+        for (final String pairSide : pairSides) {
+          found |= pairSide.contains(searchToken);
+        }
+        if (!found) {
+          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()) {
+          return RowMatchType.ORDERED_MATCH;
+        }
+      }
+      return RowMatchType.BAG_OF_WORDS_MATCH;
+    }
   
   }
 
index 569fc000a0094007bfdd6a4500d17abcfb82fdca..e84eb7377bd780a514d61f705b0d3561ca53488b 100644 (file)
@@ -17,9 +17,11 @@ package com.hughes.android.dictionary.engine;
 import java.io.IOException;
 import java.io.PrintStream;
 import java.io.RandomAccessFile;
+import java.util.List;
 
 import com.hughes.util.IndexedObject;
 import com.hughes.util.raf.RAFListSerializer;
+import com.ibm.icu.text.Transliterator;
 
 public abstract class RowBase extends IndexedObject {
   /**
@@ -97,6 +99,8 @@ public abstract class RowBase extends IndexedObject {
   public abstract void print(PrintStream out);
 
   public abstract String getRawText(final boolean compact);
+  
+  public abstract RowMatchType matches(final List<String> searchTokens, final Transliterator normalizer, boolean swapPairEntries);
 
   // RowBase must manage "disk-based" polymorphism.  All other polymorphism is
   // dealt with in the normal manner.
@@ -134,5 +138,5 @@ public abstract class RowBase extends IndexedObject {
       raf.writeInt(t.referenceIndex);
     }
   }
-
+  
 }
diff --git a/src/com/hughes/android/dictionary/engine/RowMatchType.java b/src/com/hughes/android/dictionary/engine/RowMatchType.java
new file mode 100644 (file)
index 0000000..96ac4ad
--- /dev/null
@@ -0,0 +1,23 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.hughes.android.dictionary.engine;
+
+public enum RowMatchType {
+  
+  ORDERED_MATCH,
+  BAG_OF_WORDS_MATCH,
+  NO_MATCH
+
+}
index 8bef29482ec67c1cc65c2901f778829023a0e9d1..ec5bc3905627d4c1e3aef3eaf3ab3ab4d1e43bb9 100644 (file)
@@ -17,9 +17,11 @@ package com.hughes.android.dictionary.engine;
 import java.io.IOException;
 import java.io.PrintStream;
 import java.io.RandomAccessFile;
+import java.util.List;
 
 import com.hughes.util.raf.RAFSerializable;
 import com.hughes.util.raf.RAFSerializer;
+import com.ibm.icu.text.Transliterator;
 
 public class TextEntry extends AbstractEntry implements RAFSerializable<TextEntry> {
   
@@ -28,6 +30,7 @@ public class TextEntry extends AbstractEntry implements RAFSerializable<TextEntr
   public TextEntry(final Dictionary dictionary, final RandomAccessFile raf) throws IOException {
     super(dictionary, raf);
     text = raf.readUTF();
+    throw new RuntimeException();
   }
   @Override
   public void write(RandomAccessFile raf) throws IOException {
@@ -81,6 +84,11 @@ public class TextEntry extends AbstractEntry implements RAFSerializable<TextEntr
     public String getRawText(boolean compact) {
       return getEntry().text;
     }
+    
+    @Override
+    public RowMatchType matches(List<String> searchTokens, Transliterator normalizer, boolean swapPairEntries) {
+      return null;
+    }
   }
 
 
index adb4c583b06627de703f93b8b5d6e92033868c34..7b7736c46bc899289db2c0c36b45c71bc49e54a4 100644 (file)
@@ -17,6 +17,9 @@ package com.hughes.android.dictionary.engine;
 import java.io.IOException;
 import java.io.PrintStream;
 import java.io.RandomAccessFile;
+import java.util.List;
+
+import com.ibm.icu.text.Transliterator;
 
 public class TokenRow extends RowBase {
   
@@ -61,5 +64,10 @@ public class TokenRow extends RowBase {
     return getToken();
   }
 
+  @Override
+  public RowMatchType matches(List<String> searchTokens, Transliterator normalizer, boolean swapPairEntries) {
+    return RowMatchType.NO_MATCH;
+  }
+
 
 }