/* * SpeedWidget.java * * Copyright (C) 2005 Crutcher Dunnavant * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ import javax.microedition.lcdui.*; import javax.microedition.midlet.*; import java.util.*; public final class SpeedWidget extends Canvas implements WordStreamWatcher { /** * Delay tuning constants. */ /** The granularity of the delay, in milliseconds. */ private static final int DSTEP = 20; /** The minimum delay value, in milliseconds. */ private static final int DMIN = 40; /** The maximum delay value, in milliseconds. */ private static final int DMAX = 500; /** The default delay value. */ private static final int DDEFAULT = 180; /** Percent bonus given to words with punctuation. */ private static final int PBONUS = 100; /** Handicap used in calculating character bonus. */ private static final int CHANDICAP = 4; /** Precent bonus given to word length. */ private static final int CBONUS = 8; /** Percent bonus given to the first word in a sentence. */ private static final int FBONUS = 75; /** * Display constants. */ /** Constant used for sizing the scroll bar. */ private static final int SCROLLBAR_HEIGHT = 5; /* pixels */ private String title; private WordStream ws; private int size; private int position; private String word; private String prev_word; static int cruising_delay = DDEFAULT; private int current_delay; private int word_delay; /** * State machine states. */ private int state; private static final int READING = 0; private static final int STOPPING = 1; private static final int SCANING = 2; private boolean key_latch; private Timer timer; public SpeedWidget(WordStream ws) { this.ws = ws; size = 0; position = 0; word_delay = 0; current_delay = 0; key_latch = false; state = SCANING; streamChange(true); ws.setWatcher(this); } /* ============================================================= */ public void kill () { pause(); ws.setWatcher(null); ws = null; } public void pause () { if (timer != null) { timer.cancel(); } } public void unpause () { rescheduleTick(); } /* ============================================================= */ /** Commands */ protected void keyPressed (int keyCode) { int action = getGameAction(keyCode); switch (state) { case SCANING: switch (action) { case FIRE: /* Read. */ if (!key_latch) { key_latch = true; state = READING; current_delay = DMAX; int d = (DMAX + DDEFAULT) / 2; if (cruising_delay <= d) { current_delay = d; } rescheduleTick(); } break; case UP: /* Skip backwards 5% */ changePosition(position - size / 20); break; case DOWN: /* Skip forwards 5% */ changePosition(position + size / 20); break; case LEFT: /* Scroll backwards. */ changePosition(position - 1); break; case RIGHT: /* Scroll forwards.. */ changePosition(position + 1); break; default: break; } break; case READING: switch (action) { case FIRE: /* Stop. */ if (!key_latch) { key_latch = true; state = STOPPING; rescheduleTick(); } break; case UP: /* Speed up. */ if (!key_latch) { key_latch = true; cruising_delay -= DSTEP; if (cruising_delay < DMIN) { cruising_delay = DMIN; } } break; case DOWN: /* Slow down. */ if (!key_latch) { key_latch = true; cruising_delay += DSTEP; if (cruising_delay > DMAX) { cruising_delay = DMAX; } } break; case LEFT: /* Skip backwards 2.5 seconds. */ changePosition(position - (2500 / current_delay)); break; case RIGHT: /* Read. (NOOP) */ break; default: break; } break; case STOPPING: /* Stop NOW. */ if (action == FIRE) { state = SCANING; rescheduleTick(); } break; } } protected void keyRepeated (int keyCode) { keyPressed(keyCode); } protected void keyReleased (int keyCode) { key_latch = false; } /* ============================================================= */ /** Tick */ public void streamChange(boolean reset) { int newSize = ws.getWordStreamSize(); if (newSize < size) { reset = true; } size = newSize; title = ws.getWordStreamTitle(); if (reset || word == null) { changePosition(0); } repaint(); } private boolean changePosition (int pos) { boolean bounds_fit; if (size == 0) { /** Case when the size is 0. */ position = 0; bounds_fit = false; prev_word = null; word = null; return bounds_fit; } else if (pos < 0) { position = 0; bounds_fit = false; } else if (pos >= size) { position = size - 1; bounds_fit = false; } else { position = pos; bounds_fit = true; } // System.err.println("[" + position + "]"); /** * Lookup the previous word (for use in delay calculation). */ prev_word = (position == 0) ? null : ws.getWordStreamWord(position - 1); word = ws.getWordStreamWord(position); recalulateWordDelay(); repaint(); return bounds_fit; } private void recalulateWordDelay() { word_delay = 0; if (word == null) { /* No word, means no word_delay. */ return; } /** delay long word. */ int l = word.length(); if (l > CHANDICAP) { word_delay += (l - CHANDICAP) * CBONUS * current_delay / 100; } /** delay word with punctuation. */ if ((word.indexOf('.') != -1) || (word.indexOf(',') != -1) || (word.indexOf('!') != -1) || (word.indexOf('?') != -1) || (word.indexOf(':') != -1) || (word.indexOf(';') != -1) || (word.indexOf('\'') != -1) || (word.indexOf('"') != -1) || (word.indexOf('(') != -1) || (word.indexOf(')') != -1) || (word.indexOf('{') != -1) || (word.indexOf('}') != -1)) { word_delay += PBONUS * current_delay / 100; } if (position > 0) { int pwl = prev_word.length(); if (pwl > 0) { char c = prev_word.charAt(pwl - 1); if ((c == '.') || (c == ':') || (c == ';')) { word_delay += FBONUS * current_delay / 100; } } } } public void tick() { switch (state) { case READING: if (changePosition(position + 1)) { if (current_delay > cruising_delay) { /* Speed up. */ current_delay -= 3 * DSTEP; if (current_delay < cruising_delay) { current_delay = cruising_delay; } } else if (current_delay < cruising_delay) { /* Slow down. */ current_delay += 3 * DSTEP; if (current_delay > cruising_delay) { current_delay = cruising_delay; } } } else { state = SCANING; } break; case STOPPING: if (changePosition(position + 1)) { current_delay *= 2; if (current_delay > DMAX) { state = SCANING; } } else { state = SCANING; } break; case SCANING: /* noop */ break; } rescheduleTick(); } public void rescheduleTick() { /* Kill existing timer, if present. */ if (timer != null) { timer.cancel(); } /* No ticks while SCANING. */ if (state == SCANING) { return; } /* create tick timer */ TimerTask tt = new TimerTask() { public void run() { tick(); } }; timer = new Timer(); timer.schedule(tt, current_delay + word_delay); } /* ============================================================= */ /** * Paint function. */ protected void paint (Graphics g) { int w, h; w = this.getWidth(); h = this.getHeight(); /** * First, blank the canvas with white. */ g.setGrayScale(255); g.fillRect(0, 0, w, h); /** * Now, switch the pen to black. */ g.setGrayScale(0); /** * Draw the Ttile. */ if (title != null) { g.drawString (title, 1, 0, Graphics.TOP | Graphics.LEFT); } /** * Draw the word. */ if (word != null) { g.drawString (word, w / 2, h / 2, Graphics.BASELINE | Graphics.HCENTER); } /** * Set the font for the status bar. */ g.setFont (Font.getFont (Font.FACE_PROPORTIONAL, Font.STYLE_PLAIN, Font.SIZE_SMALL)); /** * Paint the cruising_delay. */ g.drawString (Integer.toString(cruising_delay) + "ms", 0, h - SCROLLBAR_HEIGHT, Graphics.BOTTOM | Graphics.LEFT); /** * Paint the current mode. */ g.drawString ((state == SCANING) ? "<=>" : ">>>", w / 2, h - SCROLLBAR_HEIGHT, Graphics.BOTTOM | Graphics.HCENTER); /** * Paint the position. */ g.drawString (Integer.toString(position + 1), w, h - SCROLLBAR_HEIGHT, Graphics.BOTTOM | Graphics.RIGHT); /** * Paint the scroll bar. */ int SCROLLBAR_WIDTH; if (size != 0) { SCROLLBAR_WIDTH = (position + 1) * w / size; } else { SCROLLBAR_WIDTH = 0; } g.fillRect (0, h - SCROLLBAR_HEIGHT, SCROLLBAR_WIDTH, SCROLLBAR_HEIGHT); g.setGrayScale(200); g.fillRect (SCROLLBAR_WIDTH, h - SCROLLBAR_HEIGHT, w - SCROLLBAR_WIDTH, SCROLLBAR_HEIGHT); } }