package org.madore.android.unicodeMap;

import java.lang.ref.SoftReference;
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.net.Uri;
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;
import android.app.ListActivity;
import android.app.Dialog;
import android.app.ProgressDialog;

public final class UnicodeMapActivity extends ListActivity {

    protected static void textMakeSelectableIfPossible(TextView text) {
	// .setTextIsSelectable() method is only available on Android
	// Honeycomb (API 11) and later.
	try {
	    java.lang.reflect.Method m = TextView.class.getMethod("setTextIsSelectable", new Class[] { boolean.class });
	    m.invoke(text, true);
	} catch (Exception e) {
	    // Ignore
	}
    }

    protected UnicodeDatabase db;
    protected ListView lv;
    protected ClipboardManager cmgr;

    protected static abstract class Display {
	protected String title;
	protected SoftReference<List<UnicodeDisplayable>> listCacheRef;
	public Display(String title) { this.title = title; }
	public final String getTitle() { return this.title; }
	public void setListCache(List<UnicodeDisplayable> list) {
	    this.listCacheRef = new SoftReference<List<UnicodeDisplayable>>(list);
	}
	public List<UnicodeDisplayable> getListCache() {
	    if ( this.listCacheRef != null )
		return this.listCacheRef.get();
	    else
		return null;
	}
    }
    protected static class RootDisplay extends Display {
	public RootDisplay() { super(null); }
    }
    protected static class RangeDisplay extends Display {
	protected int from;  protected int to;  protected int limit;
	public RangeDisplay(String title, int from, int to, int limit) {
	    super(title);
	    this.from = from;  this.to = to;  this.limit = limit;
	}
	public RangeDisplay(UnicodeRangeable rng, int limit) {
	    super(rng.getTitle());
	    this.from = rng.getFrom();  this.to = rng.getTo();  this.limit = limit;
	}
	public int getFrom() { return this.from; }
	public int getTo() { return this.to; }
	public int getLimit() { return this.limit; }
    }
    protected static class SearchDisplay extends Display {
	protected String like;  protected int limit;
	protected int sizeHint;
	public SearchDisplay(String title, String like, int limit) {
	    super(title);
	    this.like = like;  this.limit = limit;
	    this.sizeHint = 128;
	}
	public void setSizeHint(int hint) { this.sizeHint = hint; }
	public String getLike() { return this.like; }
	public int getLimit() { return this.limit; }
	public int getSizeHint() { return this.sizeHint; }
    }
    protected static class DecodeDisplay extends Display {
	protected String coded;
	public DecodeDisplay(String title, String coded) {
	    super(title);
	    this.coded = coded;
	}
	public String getCoded() { return this.coded; }
    }

    protected static class DisplayAndPosition {
	protected Display disp;
	protected int selPosition;  protected int yOffset;
	public DisplayAndPosition(Display disp,
				  int selPosition, int yOffset) {
	    this.disp = disp;
	    this.selPosition = selPosition;
	    this.yOffset = yOffset;
	}
	public Display getDisp() { return this.disp; }
	public int getSelPosition() { return this.selPosition; }
	public int getYOffset() { return this.yOffset; }
    }

    protected Display curDisp;
    protected final List<DisplayAndPosition> dispHistory
	= new ArrayList<DisplayAndPosition>(10);

    protected EditText textForm;

    protected void launchPopulation() {
	final ProgressDialog progress = new ProgressDialog(this);
	progress.setOwnerActivity(this);
	progress.setTitle(R.string.loading_database_title);
	progress.setMessage(getResources().getText(R.string.loading_database_msg));
	progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
	progress.setCancelable(false);
	progress.show();
	final Handler handler = new Handler() {
		public void handleMessage(Message msg) {
		    int done = msg.getData().getInt("done");
		    int total = msg.getData().getInt("total");
		    progress.setMax(total);
		    progress.setProgress(done);
		    if ( done >= total )
			progress.dismiss();
		}
	    };
	final Thread thr = new Thread() {
		public void run() {
		    final UnicodeDatabase writeDb
			= new UnicodeDatabase(UnicodeMapActivity.this);
		    writeDb.populate(handler);
		}
	    };
	thr.start();
    }

    protected void saveDisplay(View selView, int selPosition) {
	lv.clearTextFilter();
	final int yOffset;
	if ( selView != null )
	    yOffset = selView.getTop();
	else
	    yOffset = 0;
	dispHistory.add(new DisplayAndPosition(curDisp, selPosition, yOffset));
    }

    protected void saveDisplay() {
	lv.clearTextFilter();
	int position = lv.getSelectedItemPosition();
	int position0 = lv.getFirstVisiblePosition();
	if ( position == AdapterView.INVALID_POSITION )
	    position = position0;
	View view = lv.getChildAt(position-position0);
	final int yOffset;
	if ( view != null )
	    yOffset = view.getTop();
	else
	    yOffset = 0;
	dispHistory.add(new DisplayAndPosition(curDisp, position, yOffset));
    }

    protected void doDisplay(Display disp, boolean isNew) {
	curDisp = disp;
	List<UnicodeDisplayable> list = disp.getListCache();
	UnicodeArrayAdapter adapter;
	if ( list != null ) {
	    android.util.Log.v("UnicodeMapActivity", "list was retrieved from its cache");
	    adapter = new UnicodeArrayAdapter(this, list);
	    setListAdapter(adapter);
	} else if ( disp instanceof RootDisplay ) {
	    list = new ArrayList<UnicodeDisplayable>(UnicodeCharacter.Range.values().length);
	    // list.add(new UnicodeRangeable() {
	    // 	    public int getFrom() { return 0x4DF8; }
	    // 	    public int getTo() { return 0x4E10; }
	    // 	    public String getDescr() { return "TEST"; }
	    // 	    public String getTitle() { return "TEST"; }
	    // 	});
	    for ( UnicodeCharacter.Range rng : UnicodeCharacter.Range.values() )
		if ( rng != UnicodeCharacter.Range.HIGH_SURROGATES
		     && rng != UnicodeCharacter.Range.HIGH_PRIVATE_USE_SURROGATES
		     && rng != UnicodeCharacter.Range.LOW_SURROGATES
		     && rng != UnicodeCharacter.Range.PRIVATE_USE_AREA
		     && rng != UnicodeCharacter.Range.SUPPLEMENTARY_PRIVATE_USE_AREA_A
		     && rng != UnicodeCharacter.Range.SUPPLEMENTARY_PRIVATE_USE_AREA_B )
		    list.add(rng);
	    disp.setListCache(list);
	    adapter = new UnicodeArrayAdapter(this, list);
	    setListAdapter(adapter);
	} else if ( disp instanceof RangeDisplay ) {
	    int from = ((RangeDisplay)disp).getFrom();
	    int to = ((RangeDisplay)disp).getTo();
	    int count = db.countRange(from,to);
	    if ( count > 1024 ) {
		list = new ArrayList<UnicodeDisplayable>(count/128 + 1);
		int from0 = from&(~127);
		int to0 = ((to-1)|127)+1;
		for ( int base=from0 ; base<to0 ; base+=128 ) {
		    final int from1 = Math.max(from,base);
		    final int to1 = Math.min(to,base+128);
		    final String descr
			= String.format("%04X\u2013%04X", from1, to1-1);
		    final String title
			= String.format("%s (%s)", disp.getTitle(), descr);
		    list.add(new UnicodeRangeable() {
			    public int getFrom() { return from1; }
			    public int getTo() { return to1; }
			    public String getDescr() { return descr; }
			    public String getTitle() { return title; }
			});
		}
	    } else {
		list = new ArrayList<UnicodeDisplayable>(count);
		for ( UnicodeCharacter ch : db.getRange(from,to) )
		    list.add(ch);
	    }
	    disp.setListCache(list);
	    adapter = new UnicodeArrayAdapter(this, list);
	    setListAdapter(adapter);
	} else if ( disp instanceof SearchDisplay ) {
	    String s = ((SearchDisplay)disp).getLike();
	    int limit = ((SearchDisplay)disp).getLimit();
	    int sizeHint = ((SearchDisplay)disp).getSizeHint();
	    list = new ArrayList<UnicodeDisplayable>(sizeHint);
	    for ( UnicodeCharacter ch : db.searchNames(s,limit+1) )
		list.add(ch);
	    disp.setListCache(list);
	    int size = list.size();
	    ((SearchDisplay)disp).setSizeHint(size);
	    boolean overflowed = ( size > limit );
	    if ( overflowed )
		list.remove(list.size()-1);
	    adapter = new UnicodeArrayAdapter(this, list);
	    setListAdapter(adapter);
	    if ( overflowed && isNew ) {
		String str = getResources().getString(R.string.list_too_long);
		Toast.makeText(UnicodeMapActivity.this,
			       String.format(str, limit),
			       Toast.LENGTH_SHORT).show();
	    }
	} else if ( disp instanceof DecodeDisplay ) {
	    String coded = ((DecodeDisplay)disp).getCoded();
	    list = new ArrayList<UnicodeDisplayable>(coded.length());
	    for ( int i=0 ; i<coded.length() ; i++ ) {
		int codePoint = coded.codePointAt(i);
		if ( codePoint >= 0x10000 )
		    i++;
		UnicodeCharacter ch = db.getSingle(codePoint);
		if ( ch == null ) {
		    String name;  UnicodeCharacter.Category category;
		    if ( codePoint < 0x0020 || ( codePoint >= 0x007f
						 && codePoint < 0x00a0 ) ) {
			name = String.format("<control-%04X>", codePoint);
			category = UnicodeCharacter.Category.CONTROL;
		    } else if ( UnicodeCharacter.Range.HIGH_SURROGATES.belongs(codePoint)
				|| UnicodeCharacter.Range.HIGH_PRIVATE_USE_SURROGATES.belongs(codePoint)
				|| UnicodeCharacter.Range.LOW_SURROGATES.belongs(codePoint) ) {
			name = String.format("<surrogate-%04X>", codePoint);
			category = UnicodeCharacter.Category.SURROGATE;
		    } else if ( UnicodeCharacter.Range.PRIVATE_USE_AREA.belongs(codePoint)
				|| UnicodeCharacter.Range.SUPPLEMENTARY_PRIVATE_USE_AREA_A.belongs(codePoint)
				|| UnicodeCharacter.Range.SUPPLEMENTARY_PRIVATE_USE_AREA_B.belongs(codePoint) ) {
			name = String.format("<private-use-%04X>", codePoint);
			category = UnicodeCharacter.Category.PRIVATE_USE;
		    } else {
			name = String.format("<noncharacter-%04X>", codePoint);
			category = UnicodeCharacter.Category.UNASSIGNED;
		    }
		    ch = new UnicodeCharacter(codePoint, name, category,
					      false);
		}
		list.add(ch);
	    }
	    disp.setListCache(list);
	    adapter = new UnicodeArrayAdapter(this, list);
	    setListAdapter(adapter);
	} else
	    throw new AssertionError("unknown UnicodeMapActivity.Display");
	String title = disp.getTitle();
	if ( title != null )
	    setTitle(String.format(getResources().getString(R.string.app_name_spec), title));
	else
	    setTitle(getResources().getString(R.string.app_name));
    }

    final static int rangeLimit = 1024;

    protected class MapItemClickListener
	implements AdapterView.OnItemClickListener,
		   AdapterView.OnItemLongClickListener {
	public void onItemClick(AdapterView<?> parent, View view,
				int position, long id) {
	    UnicodeDisplayable it
		= (UnicodeDisplayable)parent.getItemAtPosition(position);
	    if ( it instanceof UnicodeCharacter )
		textForm.append(((UnicodeCharacter)it).getChar());
	    else if ( it instanceof UnicodeRangeable ) {
		Display newDisp = new RangeDisplay((UnicodeRangeable)it, rangeLimit);
		saveDisplay(view, position);
		doDisplay(newDisp, true);
	    } else
		throw new AssertionError("unknown UnicodeDisplayable");
	}
	public boolean onItemLongClick(AdapterView<?> parent, View view,
				       int position, long id) {
	    final UnicodeDisplayable it
		= (UnicodeDisplayable)parent.getItemAtPosition(position);
	    if ( it instanceof UnicodeCharacter ) {
		final UnicodeCharacter itch = (UnicodeCharacter)it;
		final Dialog dialog = new Dialog(UnicodeMapActivity.this);
		dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
		dialog.setOwnerActivity(UnicodeMapActivity.this);
		dialog.setContentView(R.layout.char_details);
		dialog.setCancelable(true);
		TextView text;
		text = (TextView) dialog.findViewById(R.id.detailsLabel);
		text.setText(itch.getLabel());
		textMakeSelectableIfPossible(text);
		text = (TextView) dialog.findViewById(R.id.encodingLabel);
		StringBuilder s = new StringBuilder();
		Formatter fmt = new Formatter(s);
		fmt.format("UTF-8:");
		byte[] bytes;
		bytes = UnicodeCharacter.toUtf8(itch.getChar());
		for ( int i=0 ; i<bytes.length ; i++ )
		    fmt.format(" 0x%02x", bytes[i]);
		fmt.format("\nUTF-16BE:");
		bytes = UnicodeCharacter.toUtf16(itch.getChar());
		for ( int i=0 ; i<bytes.length ; i++ )
		    fmt.format(" 0x%02x", bytes[i]);
		text.setText(new String(s));
		textMakeSelectableIfPossible(text);
		text = (TextView) dialog.findViewById(R.id.categoryLabel);
		text.setText(itch.getCategory().getDescr());
		final Button copyCharButton = (Button) dialog.findViewById(R.id.copyCharButton);
		copyCharButton.setOnClickListener(new View.OnClickListener() {
			public void onClick(View view) {
			    dialog.dismiss();
			    cmgr.setText(itch.getChar());
			    Toast.makeText(UnicodeMapActivity.this,
					   R.string.copied_toast,
					   Toast.LENGTH_SHORT).show();
			}
		    });
		final Button appendCharButton = (Button) dialog.findViewById(R.id.appendCharButton);
		appendCharButton.setOnClickListener(new View.OnClickListener() {
			public void onClick(View view) {
			    dialog.dismiss();
			    textForm.append(itch.getChar());
			}
		    });
		final Button copyNameButton = (Button) dialog.findViewById(R.id.copyNameButton);
		copyNameButton.setOnClickListener(new View.OnClickListener() {
			public void onClick(View view) {
			    dialog.dismiss();
			    cmgr.setText(itch.getLabel());
			    Toast.makeText(UnicodeMapActivity.this,
					   R.string.copied_toast,
					   Toast.LENGTH_SHORT).show();
			}
		    });
		if ( UnicodeCharacter.Range.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A.belongs(itch)
		     || UnicodeCharacter.Range.CJK_UNIFIED_IDEOGRAPHS.belongs(itch)
		     || UnicodeCharacter.Range.CJK_COMPATIBILITY_IDEOGRAPHS.belongs(itch)
		     || UnicodeCharacter.Range.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B.belongs(itch)
		     || UnicodeCharacter.Range.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_C.belongs(itch)
		     || UnicodeCharacter.Range.CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT.belongs(itch) ) {
		    Button btn = new Button(UnicodeMapActivity.this);
		    btn.setText(R.string.unihan_lookup);
		    ((LinearLayout) dialog.findViewById(R.id.detailsLayout))
			.addView(btn,
				 ViewGroup.LayoutParams.FILL_PARENT,
				 ViewGroup.LayoutParams.WRAP_CONTENT);
		    final String tgt
			= String.format("http://www.unicode.org/cgi-bin/GetUnihanData.pl?codepoint=%04X",
					itch.getCodePoint());
		    btn.setOnClickListener(new View.OnClickListener() {
			    public void onClick(View view) {
				Intent intent
				    = new Intent(Intent.ACTION_VIEW,
						 Uri.parse(tgt));
				startActivity(intent);
			    }
			});
		}
		dialog.show();
		return true;
	    } else if ( it instanceof UnicodeRangeable ) {
		UnicodeRangeable itr = (UnicodeRangeable)it;
		Dialog dialog = new Dialog(UnicodeMapActivity.this);
		dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
		dialog.setOwnerActivity(UnicodeMapActivity.this);
		dialog.setContentView(R.layout.range_details);
		dialog.setCancelable(true);
		TextView text;
		text = (TextView) dialog.findViewById(R.id.rangeDescrLabel);
		text.setText(itr.getDescr());
		textMakeSelectableIfPossible(text);
		text = (TextView) dialog.findViewById(R.id.rangeRangeLabel);
		StringBuilder s = new StringBuilder();
		Formatter fmt = new Formatter(s);
		if ( itr instanceof UnicodeCharacter.Range )
		    fmt.format("%04X\u2013%04X (%d slots, %d chars)",
			       itr.getFrom(), itr.getTo()-1,
			       itr.getTo()-itr.getFrom(),
			       db.countRange(itr.getFrom(), itr.getTo()));
		else
		    fmt.format("(%d slots, %d chars)",
			       itr.getTo()-itr.getFrom(),
			       db.countRange(itr.getFrom(), itr.getTo()));
		text.setText(new String(s));
		textMakeSelectableIfPossible(text);
		dialog.show();
		return true;
	    } else
		throw new AssertionError("unknown UnicodeDisplayable");
	}
    }

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
	setContentView(R.layout.main_layout);
	db = new UnicodeDatabase(this);
	final boolean needPopulate = db.needPopulate();
	if ( needPopulate )
	    launchPopulation();
	lv = getListView();
	cmgr = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);
	Object testament = getLastNonConfigurationInstance();
	if ( testament != null ) {
	    @SuppressWarnings("unchecked")
	    List<DisplayAndPosition> oldDispHistory = (List<DisplayAndPosition>)testament;
	    dispHistory.clear();
	    dispHistory.addAll(oldDispHistory);
	    assert(dispHistory.size() > 0);
	    DisplayAndPosition saved = dispHistory.remove(dispHistory.size()-1);
	    doDisplay(saved.getDisp(), false);
	    lv.setSelectionFromTop(saved.getSelPosition(), saved.getYOffset());
	} else
	    doDisplay(new RootDisplay(), true);
	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(UnicodeMapActivity.this,
				   R.string.copied_toast,
				   Toast.LENGTH_SHORT).show();
		}
	    });
	lv.setTextFilterEnabled(true);
	MapItemClickListener listener = new MapItemClickListener();
	lv.setOnItemClickListener(listener);
	lv.setOnItemLongClickListener(listener);
    }

    @Override
    public void onDestroy() {
	db.close();
	db = null;
	super.onDestroy();
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
	saveDisplay();
	return dispHistory;
    }

    final static int searchLimit = 1000;

    protected void doSearch(String s) {
	s = s.replaceAll("\\*", "%");
	s = s.replaceAll("^\\s*", "");
	s = s.replaceAll("\\s*$", "");
	Display newDisp = new SearchDisplay(getResources().getString(R.string.search_results),
					    s, searchLimit);
	saveDisplay();
	doDisplay(newDisp, true);
    }

    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();
    }

    protected void doGo(String s) {
	try {
	    s = s.replaceAll("\\s+", "");
	    int code = Integer.parseInt(s, 16);
	    if ( code < 0 || code >= 0x110000 )
		throw new NumberFormatException();
	    int codeFrom = Math.max((code&~31)-32, 0);
	    int codeTo = Math.min((code|31)+32, 0x110000);
	    String str = String.format("%04X\u2013%04X", codeFrom, codeTo-1);
	    Display newDisp
		= new RangeDisplay(str, codeFrom, codeTo, rangeLimit);
	    saveDisplay();
	    doDisplay(newDisp, true);
	    int dichoFrom = 0;
	    int dichoTo = Math.min(codeTo-codeFrom, lv.getAdapter().getCount());
	    int dicho = Math.max(Math.min(code-codeFrom, dichoTo-1), 0);
	    LOOP:
	    while ( true ) {
		if ( dichoTo <= dichoFrom+1 )
		    break LOOP;
		UnicodeCharacter chx
		    = (UnicodeCharacter) lv.getAdapter().getItem(dicho);
		int codex = chx.getCodePoint();
		if ( codex == code )
		    break LOOP;
		else if ( codex > code ) {
		    dichoTo = dicho;
		    dicho = (dichoFrom+dichoTo)/2;
		} else if ( codex < code ) {
		    dichoFrom = dicho;
		    dicho = (dichoFrom+dichoTo)/2;
		}
	    }
	    lv.setSelectionFromTop(dicho, 0);
	    if ( db.getSingle(code) == null )
		Toast.makeText(UnicodeMapActivity.this,
			       R.string.no_character,
			       Toast.LENGTH_SHORT).show();
	} catch (NumberFormatException e) {
	    Toast.makeText(UnicodeMapActivity.this,
			   R.string.invalid_codepoint,
			   Toast.LENGTH_SHORT).show();
	}
    }

    protected void queryGo() {
	final Dialog dialog = new Dialog(UnicodeMapActivity.this);
	// dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
	dialog.setTitle(R.string.go_title);
	dialog.setOwnerActivity(this);
	dialog.setContentView(R.layout.go_dialog);
	dialog.setCancelable(true);
	// dialog.getWindow().setLayout(ViewGroup.LayoutParams.FILL_PARENT,
	// 			     ViewGroup.LayoutParams.WRAP_CONTENT);
	final EditText textForm
	    = (EditText) dialog.findViewById(R.id.goForm);
	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) ) {
			    doGo(s);
			    dialog.dismiss();
			}
			return true;
		    }
		    return false;
		}
	    });
	final Button btn = (Button) dialog.findViewById(R.id.goButton);
	btn.setOnClickListener(new View.OnClickListener() {
		public void onClick(View view) {
		    String s = textForm.getText().toString();
		    if ( ! Pattern.matches("^\\s*$", s) )
			doGo(s);
		    dialog.dismiss();
		}
	    });
	dialog.show();
    }

    protected void doDecode(String s) {
	Display newDisp = new DecodeDisplay(getResources().getString(R.string.decode_results),
					    s);
	saveDisplay();
	doDisplay(newDisp, true);
    }

    protected void queryDecode() {
	final Dialog dialog = new Dialog(UnicodeMapActivity.this);
	// dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
	dialog.setTitle(R.string.decode_title);
	dialog.setOwnerActivity(this);
	dialog.setContentView(R.layout.decode_dialog);
	dialog.setCancelable(true);
	dialog.getWindow().setLayout(ViewGroup.LayoutParams.FILL_PARENT,
				     ViewGroup.LayoutParams.WRAP_CONTENT);
	final EditText textForm
	    = (EditText) dialog.findViewById(R.id.decodeString);
	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) ) {
			    doDecode(s);
			    dialog.dismiss();
			}
			return true;
		    }
		    return false;
		}
	    });
	final Button btn = (Button) dialog.findViewById(R.id.decodeButton);
	btn.setOnClickListener(new View.OnClickListener() {
		public void onClick(View view) {
		    String s = textForm.getText().toString();
		    if ( ! Pattern.matches("^\\s*$", s) )
			doDecode(s);
		    dialog.dismiss();
		}
	    });
	dialog.show();
    }

    protected void displayAbout() {
	final Dialog dialog = new Dialog(UnicodeMapActivity.this);
	dialog.setTitle(R.string.about_title);
	dialog.setOwnerActivity(this);
	dialog.setContentView(R.layout.about_layout);
	dialog.setCancelable(true);
	Button btn;
	btn = (Button) dialog.findViewById(R.id.aboutOk);
	btn.setOnClickListener(new View.OnClickListener() {
		public void onClick(View view) {
		    dialog.dismiss();
		}
	    });
	btn = (Button) dialog.findViewById(R.id.aboutMore);
	btn.setOnClickListener(new View.OnClickListener() {
		public void onClick(View view) {
		    dialog.dismiss();
		    Intent intent
			= new Intent(Intent.ACTION_VIEW,
				     Uri.parse("http://www.madore.org/~david/programs/UnicodeMap.html"));
		    startActivity(intent);
		}
	    });
	dialog.show();
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
	if ( ( keyCode == KeyEvent.KEYCODE_BACK ) && dispHistory.size() > 0 ) {
	    DisplayAndPosition saved = dispHistory.remove(dispHistory.size()-1);
	    doDisplay(saved.getDisp(), false);
	    lv.setSelectionFromTop(saved.getSelPosition(), saved.getYOffset());
	    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.menuGo:
	    queryGo();
	    return true;
	case R.id.menuSearch:
	    querySearch();
	    return true;
	case R.id.menuDecode:
	    queryDecode();
	    return true;
	case R.id.menuAbout:
	    displayAbout();
	    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;
    }

}
