From 221e994358da9d5e18fe490a19389093dfe4e755 Mon Sep 17 00:00:00 2001
From: "David A. Madore" <david+git@madore.org>
Date: Tue, 27 Apr 2010 02:13:49 +0200
Subject: Implement a search mechanism.

---
 res/layout/main_layout.xml                         |   2 +-
 res/layout/search_dialog.xml                       |  24 +++++
 res/menu/options_menu.xml                          |   5 +
 res/values/strings.xml                             |   5 +
 .../madore/android/unicodeMap/UnicodeDatabase.java |  12 ++-
 .../android/unicodeMap/UnicodeMapActivity.java     | 109 ++++++++++++++++++++-
 6 files changed, 151 insertions(+), 6 deletions(-)
 create mode 100644 res/layout/search_dialog.xml
 create mode 100644 res/menu/options_menu.xml

diff --git a/res/layout/main_layout.xml b/res/layout/main_layout.xml
index 571a2dc..89480d7 100644
--- a/res/layout/main_layout.xml
+++ b/res/layout/main_layout.xml
@@ -21,7 +21,7 @@
 	android:layout_width="0dip"
 	android:layout_height="wrap_content"
 	android:layout_weight="1" />
-    <Button android:id="@+id/button"
+    <Button android:id="@+id/copyButton"
 	android:layout_width="wrap_content"
 	android:layout_height="fill_parent"
 	android:text="@string/copy_button" />
diff --git a/res/layout/search_dialog.xml b/res/layout/search_dialog.xml
new file mode 100644
index 0000000..7c7be92
--- /dev/null
+++ b/res/layout/search_dialog.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:padding="10dp"
+    android:orientation="vertical">
+  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+      android:layout_width="fill_parent"
+      android:layout_height="wrap_content"
+      android:orientation="horizontal">
+    <EditText android:id="@+id/searchTerm"
+        android:layout_width="0dip"
+        android:layout_height="wrap_content"
+        android:layout_weight="1" />
+    <Button android:id="@+id/searchButton"
+        android:layout_width="wrap_content"
+        android:layout_height="fill_parent"
+        android:text="@string/search_button" />
+  </LinearLayout>
+  <TextView
+      android:layout_width="fill_parent"
+      android:layout_height="wrap_content"
+      android:text="@string/search_hint" />
+</LinearLayout>
diff --git a/res/menu/options_menu.xml b/res/menu/options_menu.xml
new file mode 100644
index 0000000..a187813
--- /dev/null
+++ b/res/menu/options_menu.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:id="@+id/menuSearch"
+      android:title="@string/menu_search" />
+</menu>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 605a255..b77be4a 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -6,4 +6,9 @@
     <string name="copy_button">Copy</string>
     <string name="copied_toast">Copied to clipboard</string>
     <string name="empty_list">(Empty!)</string>
+    <string name="menu_search">Search</string>
+    <string name="search_title">Search in character names</string>
+    <string name="search_button">Search</string>
+    <string name="search_hint">Case is insensitive and matches whole string; use a % sign as wildcard (e.g.: "latin%letter r%")</string>
+    <string name="list_too_long">Only %d first results shown</string>
 </resources>
diff --git a/src/org/madore/android/unicodeMap/UnicodeDatabase.java b/src/org/madore/android/unicodeMap/UnicodeDatabase.java
index b81c99a..84efb63 100644
--- a/src/org/madore/android/unicodeMap/UnicodeDatabase.java
+++ b/src/org/madore/android/unicodeMap/UnicodeDatabase.java
@@ -217,7 +217,17 @@ public class UnicodeDatabase {
 		       "id >= ? AND id < ?",
 		       new String[] { Integer.toString(from),
 				      Integer.toString(to) },
-		       null, null, null, null);
+		       null, null, "id", null);
+	return new CursorIterable(c);
+    }
+
+    public Iterable<UnicodeCharacter> searchNames(String like, int limit) {
+	final Cursor c
+	    = db.query(UNICODE_TABLE_NAME, queryColumns,
+		       "name LIKE ?",
+		       new String[] { like },
+		       null, null, "id",
+		       (limit>0)?Integer.toString(limit):null);
 	return new CursorIterable(c);
     }
 
diff --git a/src/org/madore/android/unicodeMap/UnicodeMapActivity.java b/src/org/madore/android/unicodeMap/UnicodeMapActivity.java
index 1b43d9d..74dca91 100644
--- a/src/org/madore/android/unicodeMap/UnicodeMapActivity.java
+++ b/src/org/madore/android/unicodeMap/UnicodeMapActivity.java
@@ -4,13 +4,18 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.ArrayList;
 import java.util.Formatter;
+import java.util.regex.Pattern;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.Window;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MenuInflater;
 import android.view.KeyEvent;
 import android.widget.*;
 import android.text.ClipboardManager;
@@ -83,7 +88,10 @@ public final class UnicodeMapActivity extends ListActivity {
 					      list);
 		stack.add(adapter);
 		positionStack.add(position);
-		positionYStack.add(view.getTop());
+		if ( view != null )
+		    positionYStack.add(view.getTop());
+		else
+		    positionYStack.add(0);
 		setListAdapter(adapter);
 	    } else
 		throw new AssertionError("unknown UnicodeDisplayable");
@@ -159,12 +167,12 @@ public final class UnicodeMapActivity extends ListActivity {
 	setListAdapter(rootAdapter);
 	final ClipboardManager cmgr
 	    = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);
-	final Button btn = (Button) findViewById(R.id.button);
+	final Button btn = (Button) findViewById(R.id.copyButton);
 	textForm = (EditText) findViewById(R.id.edit);
 	btn.setOnClickListener(new View.OnClickListener() {
 		public void onClick(View view) {
 		    cmgr.setText(textForm.getText());
-		    Toast.makeText(getApplicationContext(),
+		    Toast.makeText(UnicodeMapActivity.this,
 				   R.string.copied_toast,
 				   Toast.LENGTH_SHORT).show();
 		}
@@ -176,17 +184,110 @@ public final class UnicodeMapActivity extends ListActivity {
 	lv.setOnItemLongClickListener(listener);
     }
 
+    final static int searchLimit = 1000;
+
+    protected void doSearch(String s) {
+	List<UnicodeDisplayable> list
+	    = new ArrayList<UnicodeDisplayable>(128);
+	for ( UnicodeCharacter ch : db.searchNames(s,searchLimit+1) )
+	    list.add(ch);
+	boolean overflowed = ( list.size() > searchLimit );
+	if ( overflowed )
+	    list.remove(list.size()-1);
+	UnicodeArrayAdapter adapter
+	    = new UnicodeArrayAdapter(UnicodeMapActivity.this,
+				      list);
+	stack.add(adapter);
+	final ListView lv = getListView();
+	int position = lv.getSelectedItemPosition();
+	int position0 = lv.getFirstVisiblePosition();
+	if ( position == AdapterView.INVALID_POSITION )
+	    position = position0;
+	positionStack.add(position);
+	View view = lv.getChildAt(position-position0);
+	if ( view != null )
+	    positionYStack.add(view.getTop());
+	else
+	    positionYStack.add(0);
+	setListAdapter(adapter);
+	if ( overflowed ) {
+	    String str = getResources().getString(R.string.list_too_long);
+	    android.util.Log.e("UnicodeMapActivity", "format is: "+str);
+	    Toast.makeText(UnicodeMapActivity.this,
+			   String.format(str, searchLimit),
+			   Toast.LENGTH_SHORT).show();
+	}
+    }
+
+    protected void querySearch() {
+	final Dialog dialog = new Dialog(UnicodeMapActivity.this);
+	// dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+	dialog.setTitle(R.string.search_title);
+	dialog.setOwnerActivity(this);
+	dialog.setContentView(R.layout.search_dialog);
+	dialog.setCancelable(true);
+	dialog.getWindow().setLayout(ViewGroup.LayoutParams.FILL_PARENT,
+				     ViewGroup.LayoutParams.WRAP_CONTENT);
+	final EditText textForm
+	    = (EditText) dialog.findViewById(R.id.searchTerm);
+	textForm.setOnKeyListener(new View.OnKeyListener() {
+		public boolean onKey(View view, int keyCode, KeyEvent event) {
+		    if ( ( event.getAction() == KeyEvent.ACTION_DOWN )
+			 && ( keyCode == KeyEvent.KEYCODE_ENTER ) ) {
+			String s = textForm.getText().toString();
+			if ( ! Pattern.matches("^\\s*$", s) )
+			    doSearch(s);
+			dialog.dismiss();
+			return true;
+		    }
+		    return false;
+		}
+	    });
+	final Button btn = (Button) dialog.findViewById(R.id.searchButton);
+	btn.setOnClickListener(new View.OnClickListener() {
+		public void onClick(View view) {
+		    String s = textForm.getText().toString();
+		    if ( ! Pattern.matches("^\\s*$", s) ) {
+			doSearch(s);
+			dialog.dismiss();
+		    }
+		}
+	    });
+	dialog.show();
+    }
+
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
-	if ( ( keyCode == KeyEvent.KEYCODE_BACK) && stack.size() > 1 ) {
+	if ( ( keyCode == KeyEvent.KEYCODE_BACK ) && stack.size() > 1 ) {
 	    final ListView lv = getListView();
 	    stack.remove(stack.size()-1);
 	    setListAdapter(stack.get(stack.size()-1));
 	    lv.setSelectionFromTop(positionStack.remove(positionStack.size()-1),
 				   positionYStack.remove(positionYStack.size()-1));
 	    return true;
+	} else if ( keyCode == KeyEvent.KEYCODE_SEARCH ) {
+	    querySearch();
+	    return true;
 	}
 	return super.onKeyDown(keyCode, event);
     }
 
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+	switch ( item.getItemId() ) {
+	case R.id.menuSearch:
+	    querySearch();
+	    return true;
+	default:
+	    return super.onContextItemSelected(item);
+	}
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+	MenuInflater inflater = getMenuInflater();
+	inflater.inflate(R.menu.options_menu, menu);
+	return true;
+    }
+
 }
-- 
cgit v1.2.3