import java.util.*; import java.awt.*; public class DemoApplet extends BufferedApplet { int W=0, H, shiftState=0, dx, dy, r, clickPage=-1, page=0, border; Font font = new Font("Courier", Font.BOLD, 20); Font labelFont = new Font("TimesRoman", Font.PLAIN, 14); Font patternFont, smallerFont; Color darkInk = new Color(150,150,150); Color faintInk = new Color(200,200,200); Color veryFaintInk = new Color(220,220,220); Color unselectedColor = new Color(230,230,255); Color selectedColor = new Color(255,230,250); Color pressedColor = new Color(220,210,230); boolean isMouseDown = false; int terseness = 1; boolean isClickingPatternRect = false; boolean isControlState = false; AlphaDraw z = new AlphaDraw(); TextEditor text = new TextEditor(); Rectangle R, tersenessRect, pageRects[]; Polygon dot; Graphics g; String fileName = ""; int panX = 0, panY = 0; public void render(Graphics g) { this.g = g; if (W == 0) { load(page); setPitch(40); pageRects = new Rectangle[20]; } if (W != bounds().width) { W = bounds().width; H = bounds().height; border = W / 25; text.setBounds(W - border, H); tersenessRect = new Rectangle(0,H-40,80,20); int w = (W-border) / 20; for (int i = 0 ; i < pageRects.length ; i++) pageRects[i] = new Rectangle(w*i,H-20,w,20); } g.setColor(Color.white); g.fillRect(0,0,W,H); g.setColor(veryFaintInk); int parity = 0; for (int y = 0 ; y < H ; y += dy) { for (int x = parity*dx/2 ; x < W ; x += dx) fillDot(x - panX,y - panY); parity = 1 - parity; } g.setColor(Color.black); if (isDrawing && terseness>0) drawLetterPattern(g); for (int i = 0 ; i < n-2 ; i += 2) g.drawLine(xy[i],xy[i+1],xy[i+2],xy[i+3]); g.setFont(patternFont); for (int i = 0 ; i < nw ; i++) drawLetterIcon(cToIcon(wayC[i]), wayX[i], wayY[i], true); g.setFont(patternFont); g.setColor(darkInk); g.drawString("" + dx, W-border-25,14); g.setFont(labelFont); g.drawString(fileName, W-border-5 - TextEditor.stringWidth(g, fileName), H-25); g.setFont(font); text.render(g); g.setColor(faintInk); g.fillRect(W - border, 0, border, H); g.setColor(darkInk); fillTriangle(W - 3*border/4, H/2 - border, W - border/2, H/2 - 3*border/2, W - border/4, H/2 - border); fillTriangle(W - 3*border/4, H/2 + border, W - border/2, H/2 + 3*border/2, W - border/4, H/2 + border); drawButton(tersenessRect, "=> " + (terseness==0 ? "TERSE" : terseness==1 ? "WORDY" : "EXPERT"), false, isClickingPatternRect); for (int i = 0 ; i < pageRects.length ; i++) drawButton(pageRects[i], "" + (i+1), i == page, i == clickPage); } void drawButton(Rectangle R,String label,boolean isSelected,boolean isDown) { g.setColor(isSelected ? selectedColor : unselectedColor); g.fill3DRect(R.x,R.y,R.width,R.height, ! isDown); g.setColor(Color.black); g.setFont(labelFont); drawString(label, R.x + R.width/2, R.y + 15); } int X3[] = new int[3], Y3[] = new int[3]; void fillTriangle(int x0, int y0, int x1, int y1, int x2, int y2) { X3[0] = x0; X3[1] = x1; X3[2] = x2; Y3[0] = y0; Y3[1] = y1; Y3[2] = y2; g.fillPolygon(X3,Y3,3); } void setPitch(int p) { if (p >= 10) { xCenter = yCenter = nw = n = 0; dx = p; dy = (int)(dx * Math.sqrt(3) / 2); r = (int)(.23 * dx); patternFont = new Font("Courier", Font.PLAIN, 5*r/3); smallerFont = new Font("Courier", Font.PLAIN, r); damage = true; for (int i = 0 ; i < 6 ; i++) { double theta = 2 * Math.PI * i / 6; DX[i] = (int)(r * Math.sin(theta) + .5); DY[i] = (int)(r * Math.cos(theta) + .5); } dot = new Polygon(DX,DY,6); } } boolean onDot(int x, int y) { y += 201*dy/2; x += 201*dx/2; if (y / dy % 2 == 1) x += dx/2; x = x % dx - dx/2; y = y % dy - dy/2; return dot.inside(x - xToDot(x,y), y - yToDot(x,y)); } int xy[] = new int[10000]; int n = 0; int xDown = -1, yDown = -1; int xCenter, yCenter; boolean isSelecting = false; boolean isChangingPitch = false; boolean isDrawing = false; int nw = 0, wayX[] = new int[50], wayY[] = new int[50], wayC[] = new int[50]; public boolean mouseDown(Event e, int x, int y) { isMouseDown = true; xDown = x; yDown = y; nw = n = 0; for (clickPage = pageRects.length-1 ; clickPage >= 0 ; clickPage--) if (pageRects[clickPage].inside(x,y)) break; if ( clickPage < 0 && ! (isClickingPatternRect = tersenessRect.inside(x,y)) && ! (isChangingPitch = x > W - border) ) { g.setFont(font); if (isSelecting = text.isOverText(x,y)) text.select(xDown, yDown, x, y); else { if (onDot(x,y)) startStroke(x,y); xy[n++] = x; xy[n++] = y; } } damage = true; return true; } void startStroke(int x, int y) { isDrawing = true; nw = 0; wayX[nw ] = xCenter = xToDot(x,y); wayY[nw ] = yCenter = yToDot(x,y); wayC[nw++] = -1; panX = panY = 0; mx = x; my = y; } int xToDot(int x, int y) { x -= panX; y -= panY; x += dx/2; y += dy/2; return y / dy % 2 == 0 ? x / dx * dx : (x + dx/2) / dx * dx - dx/2; } int yToDot(int x, int y) { y += dy/2; return y / dy * dy; } int n0, n1; int xTravel, yTravel, heading = -1; int mx, my; public boolean mouseDrag(Event e, int x, int y) { if (clickPage >= 0) ; else if (isClickingPatternRect) ; else if (isChangingPitch) { if (y > yDown + border/2) { setPitch(dx - 1); yDown = y; } else if (y < yDown - border/2) { setPitch(dx + 1); yDown = y; } } else if (isSelecting) { g.setFont(font); text.select(xDown, yDown, x, y); } else if (nw == 0 && ! onDot(x,y)) ; else { if (nw == 0 && onDot(x,y)) startStroke(x,y); boolean wasOnDot = onDot(xy[n-2],xy[n-1]); boolean onDot = onDot(x,y); if (wasOnDot && ! onDot) { n0 = n-2; xTravel = yTravel = 0; heading = -1; } if (! wasOnDot && ! onDot) { xTravel += x-xCenter; yTravel += y-yCenter; int X = 4 * xTravel / (n-n0), Y = 4 * yTravel / (n-n0); int RR = X*X + Y*Y; if (heading == -1 && RR >= .4 * dx * dx) { double angle = 6.5 - 6 * Math.atan2(X,Y) / Math.PI; heading = (int)angle % 12; if (heading % 2 == 0 && RR < .9 * dx * dx) heading = -1; } } if (! wasOnDot && onDot) { int xc = xToDot(x,y); int yc = yToDot(x,y); recognize(xCenter,yCenter, xc,yc); wayX[nw ] = xCenter = xc; wayY[nw ] = yCenter = yc; wayC[nw++] = ch; //panX += x - mx; //panY += y - my; mx = x; my = y; heading = -1; } xy[n++] = x; xy[n++] = y; } damage = true; return true; } public boolean mouseUp(Event e, int x, int y) { if (clickPage >= 0) { save(page); load(page = clickPage); clickPage = -1; damage = true; return true; } if (isClickingPatternRect) { isClickingPatternRect = false; terseness = (terseness + 1) % 3; damage = true; return true; } isChangingPitch = false; isMouseDown = false; isDrawing = false; heading = -1; if (isSelecting) { g.setFont(font); if (! text.hasSelection()) text.placeCursor(xDown,yDown); isSelecting = false; n = 0; damage = true; return true; } if (ch > ' ' && ch < 128 || nw > 0 && ! onDot(x,y)) enterTextChar(' '); damage = true; return true; } int roundX(int x, int y) { return x + 100*dx - xCenter + dx/2; } int roundY(int x, int y) { return y + 100*dy - yCenter + dy/2; } int p[] = new int[1000]; int ch = 0; void recognize(int x0,int y0, int x1,int y1) { int rr = 0; for (int i = 0 ; i < n-n0 ; i += 2) { int x = xy[n0+i]-xy[n0], y = xy[n0+i+1]-xy[n0+1]; rr = Math.max(rr, x*x + y*y); } if (rr < r * r) return; int DX = x1 - x0, DY = y1 - y0; int R = (int)Math.sqrt(DX * DX + DY * DY); int j = R < dx/2 ? 0 : R < 3*dx/2 ? 1 : R < 11*dx/6 ? 2 : 3; for (int i = 0 ; i < n-n0 ; i++) p[i] = xy[n0 + i]; ch = z.recognize(p, n-n0, j, shiftState != 0); int c = ch; if (isControlState) c -= 'a'-1; isControlState = false; switch (c) { case -1: // DO NOTHING break; case AlphaDraw.SHIFT: // SHIFT shiftState = (shiftState + 1) % 3; break; case AlphaDraw.CNTRL: // CONTROL isControlState = true; break; case 'C' - '@': // CONTROL C text.copy(); break; case 'F' - '@': // CONTROL F - SET FILE NAME setFileName(text.getCutBuffer()); break; case 'P' - '@': // CONTROL P - PRINT for (int p = 0 ; p < 20 ; p++) { load(p); String s = text.toString(); if (s.length() > 1) { System.out.println(":" + fileName + ":" + p); System.out.print(s); } } load(page); break; case 'T' - '@': // CONTROL T - IS PATTERN VERBOSE terseness = (terseness + 1) % 3; damage = true; break; case 'V' - '@': // CONTROL V text.paste(); break; case 'X' - '@': // CONTROL X text.cut(); break; case AlphaDraw.SEND: // SEND System.out.println("SEND"); break; case AlphaDraw.END: // END System.out.println("END"); break; case AlphaDraw.DELWD: // DELETE WORD text.deleteWord(); break; default: if (c == '\b' && text.hasSelection()) text.cut(); else enterTextChar(c); break; } ch = ch >= 'A' && ch <= 'Z' ? ch+'a'-'A' : ch; } void setFileName(String s) { for (int i = 0 ; i < s.length() ; i++) if (TextEditor.isSpace(s.charAt(i))) s = s.substring(0,i) + "_" + s.substring(i+1,s.length()); save(page); fileName = s; load(page); damage = true; } int cToIcon(int c) { switch (c) { case -1: break; case '\b': c = 'B'; break; case AlphaDraw.SHIFT: // SHIFT c = 'C'; break; case 27: c = 'E'; break; case AlphaDraw.CNTRL: // CONTROL c = 'K'; break; case '\n': c = 'N'; break; case AlphaDraw.DELWD: // DELETE WORD c = 'W'; default: if (c < ' ') c += 'a'-1; break; } return c; } void enterTextChar(int c) { text.enterText("" + (char)c); if (shiftState == 1) shiftState = 0; damage = true; } public boolean keyUp(Event e, int key) { enterTextChar(key); return true; } String CH[] = {"aeiotB", "bflpux", "ycgmrv", "dhnswz", " jkqNC", "579A13", "4680K2", "\\>]}E(", "= 0) { int c = CH[n].charAt((i+J[n])%6); int x = (int)(xCenter+JU[n]*C+JV[n]*S), y = (int)(yCenter-JV[n]*C+JU[n]*S); drawLetterIcon(c, x, y, false); } } else { if (i == heading/2 && K[n] >= 0) { int c = CH[n].charAt((i+K[n])%6); int x = (int)(xCenter+KU[n]*C+KV[n]*S), y = (int)(yCenter-KV[n]*C+KU[n]*S); drawLetterIcon(c, x, y, false); } } } } int DX[] = new int[6], DY[] = new int[6]; int X[] = new int[6], Y[] = new int[6]; void drawDot(int x, int y) { for (int i = 0 ; i < 6 ; i++) { X[i] = x + DX[i]; Y[i] = y + DY[i]; } g.drawPolygon(X,Y,6); } void fillDot(int x, int y) { for (int i = 0 ; i < 6 ; i++) { X[i] = x + DX[i]; Y[i] = y + DY[i]; } g.fillPolygon(X,Y,6); } void drawDot(int x, int y, boolean selected, boolean pressed) { g.setColor(pressed ? pressedColor : selected ? selectedColor : unselectedColor); fillDot(x,y); g.setColor(faintInk); drawDot(x,y); if (pressed) { g.setColor(Color.black); for (int i = 2 ; i < 5 ; i++) g.drawLine(x+DX[i], y+DY[i], x+DX[i+1], y+DY[i+1]); } else { g.setColor(darkInk); for (int i = 2 ; i < 5 ; i++) { int d = (i == 2 ? 1 : 0); g.drawLine(x-DX[i]+d, y-DY[i]+d, x-DX[i+1], y-DY[i+1]); } } } void drawLetterIcon(int c, int x, int y, boolean isSelected) { int u = xToDot(x,y) - x - panX; int v = yToDot(x,y) - y - panY; boolean isOnDot = u*u + v*v < r*r/2; int du = x - xCenter, dv = y - yCenter; if (isOnDot || du*du + dv*dv > 2*dx*dx) { boolean pressed = isDrawing && dot.inside(xy[n-2]-x, xy[n-1]-y); drawDot(x+u,y+v, isSelected, pressed); } Font font = isOnDot ? patternFont : smallerFont; if (isOnDot) { x += u; y += v; } g.setFont(font); g.setColor(Color.black); int R = isOnDot ? r/2 : r/4 + 1; switch (c) { case -1: break; case 'A': g.setFont(smallerFont); drawString("ALT", x, y + r/2); g.setFont(font); break; case 'B': fillTriangle(x-R,y, x+R,y-R, x+R,y+R); break; case 'C': fillTriangle(x-R,y+R, x,y-R, x+R,y+R); break; case 'E': g.setFont(smallerFont); drawString("ESC", x, y + r/2); g.setFont(font); break; case 'K': g.setFont(smallerFont); drawString("CTL", x, y + r/2); g.setFont(font); break; case 'N': fillTriangle(x-R+1,y-R+1, x+1,y+R+1, x+R+1,y-R+1); break; case 'W': fillTriangle(x-3*R/2,y, x+3*R/2,y-R, x+3*R/2,y+R); break; default: if (! isSelected && shiftState != 0) c = z.shift(c); drawString("" + (char)c, x, y + 2*r/3-1); break; } } void drawString(String s, int x, int y) { g.drawString(s, x - TextEditor.stringWidth(g,s)/2, y); } boolean isAlpha(int c) { return c >= 'a' && c <= 'z'; } void load(int page) { text.clear(); String s = DB.get("pen" + fileName + page); if (s.equals("no results\n") || s.equals("\n")) s = ""; for (int i = 0 ; i < s.length() ; i++) { int c = s.charAt(i); if (c == '\\') switch (c = s.charAt(++i)) { case 'n': c = '\n'; break; } text.enterChar(c); } } void save(int page) { String s = text.toString(), t = ""; for (int i = 0 ; i < s.length()-1 ; i++) { int c = s.charAt(i); switch (c) { case '\n': t += (char)'\\'; c = 'n'; break; } t += (char)c; } DB.set("pen" + fileName + page, t); } }