プログラム (Java)

import java.awt.*;
import java.applet.Applet;
import java.net.*;
import java.io.*;

public class PitecanDictionary extends Applet {
	PitecanCanvas pc;
	DynamicTextField dtf;
	Searcher searcher;
	BGProcess bgprocess;
	SoftKeyboardPanel skp;

	public void init(){
		searcher = new Searcher(getDocumentBase().getHost(),5555);
		pc = new PitecanCanvas(searcher);
		dtf = new DynamicTextField(searcher,pc);
		skp = new SoftKeyboardPanel(dtf,pc);
		bgprocess = new BGProcess(searcher,pc);
		new Thread(bgprocess).start();
		setLayout(new BorderLayout());
		add("North",skp);
		add("South",pc);
		add("Center",dtf);
	}
}

class Searcher extends Object {
	Socket socket;
	DataInputStream in;
	DataOutputStream out;
	int nmatch;
	String matchedWords[];
	String searchpat;
	int ambig;

	public Searcher(String server, int port){
		try {
			socket = new Socket(server, port);
			in = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
			out = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
		} catch (IOException e) {
			System.err.println("Can't create a socket");
			System.exit(0); // Appletの時でも大丈夫??
		}
	}
	int search(int a, String pat){
		try {
			searchpat = pat;
			ambig = a;
			out.writeBytes("S"+String.valueOf(ambig)+pat+" \n");
			out.flush();
			nmatch = Integer.parseInt(in.readLine().trim());
			matchedWords = new String[nmatch];
			return nmatch;
		} catch (IOException e) {
			System.out.println("Search Failed!");
			return -1;
		}
	}

	String getEntry(int index){
		String s;
		try {
			out.writeBytes("E"+String.valueOf(index)+"\n");
			out.flush();
			return (matchedWords[index] = in.readLine().trim());
		} catch (IOException e) {
			System.err.println("Can't get a word entry");
			return "";
		}
	}

	void getEntry(int start, int end, int skip){
		String s;
		// 一度に複数のエントリを読む
		try {
			int i,se;
			out.writeBytes("E"+String.valueOf(start)+
					","+String.valueOf(end)+
					","+String.valueOf(skip)+"\n");
			out.flush();
			while(true){
				s = in.readLine();
				if(s.length() > 0) break;
			}
			for(i=start;(se = s.indexOf(9)) > 0;i+=skip){
				matchedWords[i] = s.substring(0,se);
				s = s.substring(se+1);
			}
		}catch (IOException e) {
			System.err.println("Can't get a word entry");
		}
	}
}

/*
	canvasの状態から適当に判断してバックグラウンドで検索を行ない続ける。
	結果が使えそうだったらsearcherに反映する。
*/
class BGProcess extends Object implements Runnable {
	Searcher searcher;
	PitecanCanvas pc;
	int wordindex, wordgap;
	int start, end;
	int ambig;
	String searchpat;

	public BGProcess(Searcher fgs, PitecanCanvas p){
		searcher = fgs;
		pc = p;
	}

	public void run(){
		int i;
		boolean required;
		while(true){
			required = false;
			synchronized(pc.sync){
				required = ((wordgap != pc.wordgap) ||
					(wordindex != pc.wordindex) ||
					(start != pc.start) ||
					(end != pc.end) ||
					(ambig != searcher.ambig) ||
					searchpat.compareTo(searcher.searchpat) != 0 ||
					pc.changed );
				wordgap = pc.wordgap;
				wordindex = pc.wordindex;
				start = pc.start;
				end = pc.end;
				pc.changed = false;	
				ambig = searcher.ambig;
				searchpat = searcher.searchpat;
				if(required){
					// ここで通信/計算に時間がかかる
					if(pc.changed == false){
						searcher.getEntry(start,end,wordgap);
					}
					pc.repaint();
				}
				else {
					// プリフェッチ
					if(wordgap % 2 == 0){ // ズーム対応
						int wg2;
						wg2 = wordgap/2;
						required = false;
						for(i=start; i<=end; i+=wg2){
							if(searcher.matchedWords[i]==null){
								required = true;
								break;
							}
						}
						if(required){
							searcher.getEntry(start,end,wg2);
							pc.repaint();
						}
					}
					// 下向きスクロール対応
					int ss;
					for(ss=start,i=0; i<8 && ss-wordgap >= 0; i++, ss-=wordgap);
					required = false;
					for(i=ss; iend; i-=wordgap){
						if(searcher.matchedWords[i]==null){
							required = true;
							break;
						}
					}
					if(required){
						searcher.getEntry(end+wordgap,ee,wordgap);
					}
				}
			}
			if(pc.changed == false){
				try {
					Thread.currentThread().sleep(10);
				}catch(InterruptedException e){};
			}
		}
	}
}

class DynamicTextField extends TextField {
	Searcher searcher;
	PitecanCanvas pc;
	String searchText = "";

	public DynamicTextField(Searcher s, PitecanCanvas pitecancanvas){
		setFont(new Font("Helvetica",Font.PLAIN,18));
		searcher = s;
		pc = pitecancanvas;
		searcher.search(0,"");
		pc.reset();
	}

	public void search(){
		String s;
		s = getText();
		if(s.compareTo(searchText) != 0){
			int n;
			searchText = s;
			for(int ambig=0; ambig<=4; ambig++){
				n = searcher.search(ambig,searchText);
				if(n > 0) break;
			}
			pc.reset();
		}
	}

	public boolean keyUp(Event e, int key) {
		synchronized(pc.sync){
			search();
			pc.recalc();
			pc.repaint();
			pc.changed = true;
		}
		return false; // 上にイベントをわたす
	}
	public Dimension minimumSize() {
		return new Dimension(300,40);
	}
	public Dimension preferredSize() {
		return minimumSize();
	}
}

class PitecanCanvas extends Canvas {
	Searcher searcher;
	int dispx, dispy;	// 表示座標
	int orgdispx, orgdispy;	// マウスを押しはじめた時の表示座標
	int startx, starty;	// マウスを押しはじめた時の座標
	int mousex, mousey;	// マウスの座標
	static final double MAXSCALE = 14.0;
	static final double DOUBLEX = 20.0;
	double scalefactor;
	int intscale;
	int wordgap = 1;
	double dispgap = 1.0;
	int wordindex;
	Font font;
	Color color;
	int start, end;
	int height, width;

	static Object sync = new Object();
	boolean changed = false;

	public PitecanCanvas(){
	}
	public PitecanCanvas(Searcher s){
		searcher = s;
		font = new Font("Helvetica",Font.PLAIN,18);
		color = new Color(0x0000ff);
		reset();
	}
	public void reset(){
		int nmatch = searcher.nmatch;
		wordindex = nmatch / 2;
		scalefactor = Math.log((double)nmatch)/Math.log(2.0)-3.2;
		if(scalefactor < 0.0) scalefactor = 0.0;
		dispx = (int)((MAXSCALE-scalefactor)*DOUBLEX);
		dispy = 170;
		repaint();
	}
	private int pow2(int x) {
		 return 1 << (x > 0 ? x : 0);
	}
	public void recalc(){
		String s;
		double p;
		int i;
		height = size().height;
		width = size().width;

		intscale = (int)scalefactor;
		wordgap = pow2(intscale);
		dispgap = Math.pow(2.0, -3.2 - scalefactor + (float)intscale) * (double)(height);
		p = dispy;
		start = wordindex;
		while(start >= 0 && p <= (double)(height) - 10.0){
			start -= wordgap;
			p += dispgap;
		}
		start += wordgap;
		p = dispy;
		end = wordindex;
		while(end < searcher.nmatch && p >= 0.0){
			end += wordgap;
			p -= dispgap;
		}
		end -= wordgap;
	}
	public void paint(Graphics g){
		String s;
		double p;
		int i;

		recalc();

		g.setFont(font);
		g.setColor(color);

		p = dispy;
		i = wordindex;
		while(i >= 0 && p <= (double)(height) - 10.0){
			s = searcher.matchedWords[i];
			if(s != null)
				g.drawString(s,10+dispx,height-(10+(int)p));
			i -= wordgap;
			p += dispgap;
		}
		p = dispy;
		i = wordindex;
		while(i < searcher.nmatch && p >= 0.0){
			s = searcher.matchedWords[i];
			if(s != null)
				g.drawString(s,10+dispx,height-(10+(int)p));
			i += wordgap;
			p -= dispgap;
		}
	}

	public boolean mouseDrag(Event e,int x,int y){
		mousex = x;
		mousey = size().height-y;
		dispx = orgdispx + (mousex - startx);
		dispy = orgdispy + (mousey - starty);
		scalefactor = MAXSCALE - ((float)dispx)/DOUBLEX;
		if(scalefactor < 0.0) scalefactor = 0.0;
		repaint();
		return true;
	}

	public boolean mouseDown(Event e,int x,int y){
		int d;
		mousex = startx = x;
		mousey = starty = size().height-y;
		d = (int)Math.floor((mousey - dispy)/dispgap);
		wordindex -= d * wordgap;
		wordindex = (wordindex < 0 ? 0 : wordindex >= searcher.nmatch ? searcher.nmatch-1 : wordindex);
		dispy += d * dispgap;
		orgdispx = dispx;
		orgdispy = dispy;
		return true;
	}
	public Dimension minimumSize() {
		return new Dimension(450,300);
	}
	public Dimension preferredSize() {
		return minimumSize();
	}
}


class SoftKeyboardPanel extends Panel {
	DynamicTextField tf;
	PitecanCanvas pc;
	protected void makebutton(String name,
		GridBagLayout gridbag,
		GridBagConstraints c){
		Button button = new Button(name);
		gridbag.setConstraints(button, c);
		add(button);
	}
	public SoftKeyboardPanel(){
		init();
	}
	public SoftKeyboardPanel(DynamicTextField textfield, PitecanCanvas pitecancanvas){
		tf = textfield;
		pc = pitecancanvas;
		init();
	}

	void init() {
		GridBagLayout gridbag = new GridBagLayout();
		GridBagConstraints c = new GridBagConstraints();
		setFont(new Font("Helvetica", Font.PLAIN, 14));
		setLayout(gridbag);
		c.fill = GridBagConstraints.BOTH;
		c.gridwidth = 1;
		c.weightx = 1.0;
		makebutton("Q", gridbag, c);
		makebutton("W", gridbag, c);
		makebutton("E", gridbag, c);
		makebutton("R", gridbag, c);
		makebutton("T", gridbag, c);
		makebutton("Y", gridbag, c);
		makebutton("U", gridbag, c);
		makebutton("I", gridbag, c);
		makebutton("O", gridbag, c);
		makebutton("P", gridbag, c);
		c.gridwidth = GridBagConstraints.REMAINDER; //end row
		makebutton(" ", gridbag, c);
		//
		c.gridwidth = 1;
		c.weightx = 1.0;
		c.weighty = 0.0;
		makebutton("A", gridbag, c);
		makebutton("S", gridbag, c);
		makebutton("D", gridbag, c);
		makebutton("F", gridbag, c);
		makebutton("G", gridbag, c);
		makebutton("H", gridbag, c);
		makebutton("J", gridbag, c);
		makebutton("K", gridbag, c);
		makebutton("L", gridbag, c);
		makebutton(";", gridbag, c);
		c.gridwidth = GridBagConstraints.REMAINDER; //end row
		makebutton(" ", gridbag, c);
		//
		c.gridwidth = 1;
		c.weightx = 1.0;
		makebutton("Z", gridbag, c);
		makebutton("X", gridbag, c);
		makebutton("C", gridbag, c);
		makebutton("V", gridbag, c);
		makebutton("B", gridbag, c);
		makebutton("N", gridbag, c);
		makebutton("M", gridbag, c);
		makebutton(",", gridbag, c);
		makebutton(".", gridbag, c);
		makebutton("/", gridbag, c);
		c.gridwidth = GridBagConstraints.REMAINDER; //end row
		makebutton(" ", gridbag, c);
		c.gridwidth = 9;
		c.weightx = 1.0;
		makebutton(" ", gridbag, c); //another row
		c.gridwidth = 1;
		makebutton("BS", gridbag, c);
		makebutton("CLR", gridbag, c);
	}
	public boolean action(Event e, Object o){
		String key = (String)o;
		String s;

		if(key.compareTo("CLR") == 0){
			s = "";
		}
		else {
			int start, end;
			s = tf.getText();
			start = tf.getSelectionStart();
			end =tf.getSelectionEnd();
			String s1 = s.substring(0,start);
			String s2 = s.substring(end,s.length());
			if(key.compareTo("BS") == 0){
				if(start == end){
					s = s.substring(0,s.length()-1);
				}
				else {
					s = s1+s2;
				}
			}
			else {
				s = s1+key.toLowerCase()+s2;
			}
		}
		tf.setText(s);
		synchronized(pc.sync){
			tf.search();
			pc.recalc();
			pc.repaint();
			pc.changed = true;
		}
		
		return false;
	}
	public Dimension minimumSize() {
		return new Dimension(200,100);
	}
	public Dimension preferredSize() {
		return minimumSize();
	}
}