/* * Reader.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.*; import java.io.*; import java.lang.*; public class Reader extends MIDlet implements CommandListener { private List contentsList; private Form titleForm, helpForm; private int oldPosition, position; private Command exitCommand, prevCommand, nextCommand, infoCommand; private Command contentsCommand, helpCommand; private SpeedWidget sw; private ResourceWordStream rws; private boolean showTitlePage = false; private boolean showingSpeedWidget = false; static final String readerVersion = "0.7.0"; static final String helpMessage = "MBook Reader is a speed reader, " + "which might take some getting used to.\n" + "\n" + "There are two reading modes, " + "SCAN and READ, which are toggled " + "using the FIRE command.\n" + "\n" + "SCAN <=> mode:\n" + "* UP/DOWN skip 5%,\n" + "* LEFT/RIGHT scan.\n" + "\n" + "READ >>> mode:\n" + "* UP/DOWN adjusts speed,\n" + "* LEFT skips back 2.5sec,\n" + "* RIGHT does nothing (but can " + "be used to keep your phone awake)."; public Reader() throws IOException { loadMbook("/mbook-index.txt"); exitCommand = new Command("Exit", Command.OK, 1); contentsCommand = new Command("Contents", Command.OK, 1); infoCommand = new Command("Title Page", Command.OK, 1); prevCommand = new Command("Prev", Command.OK, 1); nextCommand = new Command("Next", Command.OK, 1); helpCommand = new Command("Help", Command.OK, 1); /* Table of Contents */ contentsList = new List("Contents", List.IMPLICIT); for (int i = 0; i < indexList.size(); i++) { Vector chapter = (Vector)indexList.elementAt(i); contentsList.append ((String)chapter.elementAt(0), null); } contentsList.setTicker(new Ticker(infoTitle)); contentsList.addCommand(exitCommand); contentsList.addCommand(infoCommand); contentsList.addCommand(helpCommand); contentsList.setCommandListener(this); /* Book information */ titleForm = new Form("Title Page"); titleForm.setTicker(new Ticker(infoTitle)); titleForm.append(new StringItem("by ", infoAuthor)); titleForm.append(new StringItem("url:\n", infoURL)); if (infoLicense != null) { titleForm.append (new StringItem("license:\n", infoLicense)); } titleForm.addCommand(exitCommand); titleForm.addCommand(contentsCommand); titleForm.setCommandListener(this); /* Help */ helpForm = new Form("MBook Reader"); helpForm.append(new StringItem("version: ", readerVersion)); helpForm.append(new StringItem(null, helpMessage)); helpForm.addCommand(exitCommand); helpForm.addCommand(contentsCommand); helpForm.addCommand(infoCommand); helpForm.setCommandListener(this); /* Initialize rws with the first chapter. */ oldPosition = 0; position = 0; Vector chapter = (Vector)indexList.elementAt(0); rws = new ResourceWordStream ((String)chapter.elementAt(0), "/" + (String)chapter.elementAt(1)); /* Build the SpeedWidget */ sw = new SpeedWidget(rws); sw.addCommand(exitCommand); sw.addCommand(nextCommand); sw.addCommand(prevCommand); sw.addCommand(contentsCommand); sw.addCommand(infoCommand); sw.addCommand(helpCommand); sw.setCommandListener(this); showTitlePage = true; } public void startApp() { if (showingSpeedWidget) { sw.unpause(); } else if (showTitlePage) { Display.getDisplay(this).setCurrent(titleForm); } else { Display.getDisplay(this).setCurrent(contentsList); } } public void pauseApp() { if (showingSpeedWidget) { sw.pause(); } } public void destroyApp (boolean unconditional) {} public void displaySW () { if (position != oldPosition) { Vector chapter = (Vector)indexList.elementAt(position); rws.setStream ((String)chapter.elementAt(0), "/" + (String)chapter.elementAt(1)); oldPosition = position; } showingSpeedWidget = true; Display.getDisplay(this).setCurrent(sw); } public void commandAction (Command c, Displayable s) { if (c == exitCommand) { notifyDestroyed(); } else if (c == contentsCommand) { showTitlePage = false; if (showingSpeedWidget) { showingSpeedWidget = false; sw.pause(); } Display.getDisplay(this).setCurrent(contentsList); } else if (c == helpCommand) { if (showingSpeedWidget) { showingSpeedWidget = false; sw.pause(); } Display.getDisplay(this).setCurrent(helpForm); } else if (c == infoCommand) { if (showingSpeedWidget) { showingSpeedWidget = false; sw.pause(); } Display.getDisplay(this).setCurrent(titleForm); } else if (c == prevCommand) { if (position > 0) { position --; contentsList.setSelectedIndex (position, true); displaySW(); } } else if (c == nextCommand) { if (position + 1 < indexList.size()) { position ++; contentsList.setSelectedIndex (position, true); displaySW(); } } else if (c == List.SELECT_COMMAND) { position = contentsList.getSelectedIndex(); displaySW(); } } /* ============================================================= */ /* ============================================================= */ Vector mbook; Vector indexList; Vector infoList; String infoTitle, infoAuthor, infoURL, infoLicense; void malformed(String err) throws IOException { throw new IOException ("Malformed Mbook: " + err); } public void loadMbook(String id) throws IOException { mbook = parseStringList(id); if ((mbook.size() < 3) || !mbook.elementAt(0).equals("mbook") || !mbook.elementAt(1).equals("1.0")) { malformed("Expecting (mbook 1.0 ...)"); } /* * MBOOK := (mbook 1.0 T1 ... TN) * Tk := (name ...) * The only required tags are 'title' and 'author', 'url' * and 'index', all others are ignored. */ for (int i = 2; i < mbook.size(); i++) { Vector tag = (Vector)mbook.elementAt(i); if (tag.size() < 2) { continue; } Object tagName = tag.elementAt(0); Object tagBody = tag.elementAt(1); if (tagName.equals("title")) { if (!(tagBody instanceof String)) { malformed("Broken 'title' tag."); } infoTitle = (String)tagBody; } else if (tagName.equals("author")) { if (!(tagBody instanceof String)) { malformed("Broken 'author' tag."); } infoAuthor = (String)tagBody; } else if (tagName.equals("url")) { if (!(tagBody instanceof String)) { malformed("Broken 'url' tag."); } infoURL = (String)tagBody; } else if (tagName.equals("license")) { if (!(tagBody instanceof String)) { malformed("Broken 'license' tag."); } infoLicense = (String)tagBody; } else if (tagName.equals("index")) { if (!(tagBody instanceof Vector)) { malformed("Broken 'index' tag."); } indexList = (Vector)tagBody; } } if (infoTitle == null) { malformed("No 'title' tag."); } if (infoAuthor == null) { malformed("No 'author' tag."); } if (infoURL == null) { malformed("No 'url' tag."); } if (indexList == null) { malformed("No 'index' tag."); } Enumeration e = indexList.elements(); while (e.hasMoreElements()) { Object entry = e.nextElement(); if (!(entry instanceof Vector)) { malformed("Malformed 'index'."); } Vector chapter = (Vector)entry; if (! ((chapter.size() == 2) && (chapter.elementAt(0) instanceof String) && (chapter.elementAt(1) instanceof String)) ) { malformed("Malformed 'index'."); } } } /* ============================================================= */ /* ============================================================= */ private Vector parseStringList (String resourceId) throws IOException { InputStream is = getClass().getResourceAsStream(resourceId); if (is == null) { // FIXME throw new IOException(); } InputStreamReader isr = new InputStreamReader(is); int c; /* Scan forward for the start of the list. */ while ((c = isr.read()) != -1) { if ((c == '(') || (c == '{') || (c == '[')) { return parseStringList_r(c, isr); } } throw new IOException(); } /** * The recursive decent helper to parseStringList. */ private Vector parseStringList_r (int openDelimiter, InputStreamReader isr) throws IOException { Vector v = new Vector(); StringBuffer sb = null; int quote = 0; boolean escape = false; int closeDelimiter = ')'; switch (openDelimiter) { case '(': closeDelimiter = ')'; break; case '[': closeDelimiter = ']'; break; case '{': closeDelimiter = '}'; break; } int c; while ((c = isr.read()) != -1) { if (escape) { sb.append((char) c); escape = false; } else if (quote != 0) { if (c == '\\') { escape = true; } else if (c == quote) { quote = 0; } else { sb.append((char) c); } } else if ((c == '"') || (c == '\'')) { if (sb == null) { sb = new StringBuffer(); } quote = c; } else if ((c==' ')||(c=='\t')|| (c=='\n')||(c=='\r')) { if (sb != null) { v.addElement(sb.toString()); sb = null; } } else if ((c=='(')||(c=='[')||(c=='{')) { if (sb != null) { v.addElement(sb.toString()); sb = null; } v.addElement(parseStringList_r(c, isr)); } else if (c == closeDelimiter) { if (sb != null) { v.addElement(sb.toString()); sb = null; } v.trimToSize(); return v; } else { if (sb == null) { sb = new StringBuffer(); } sb.append((char) c); } } throw new IOException(); } /* ============================================================= */ /* ============================================================= */ }