/* * LineFinder * Copyright (C) 2001 Scott Maxwell * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * You can reach me at maxwell@ScottMaxwell.org. */ import java.applet.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.*; import java.util.zip.*; import java.net.*; class LineAwareTextArea extends TextArea { //ed1 private final static int NUM_LINES = 39339; private final static int NUM_LINES = 48921; private final static int CHUNK = 1000; private int [] posns = new int [NUM_LINES / CHUNK + 1]; private LineFinder applet; public LineAwareTextArea(LineFinder a) { applet = a; } public void setText(String txt) { posns[0] = 0; for (int i = 1; i < posns.length; ++i) posns[i] = findPos(txt, i * CHUNK, posns[i - 1]); super.setText(txt); } public void scrollToLine(int line) { // This will allow using line number 0 to go to the top of the // file -- but that's OK by me. if ((line < 0) || (line > NUM_LINES)) { applet.setStatus("Line number " + line + " is out of range."); return; } long before = System.currentTimeMillis(); applet.beginBusy("Seeking line " + line + " ..."); scrollToPos(1 + findPos(getText(), line, posns[line / CHUNK])); long after = System.currentTimeMillis(); applet.endBusy("Found line " + line + " in " + ((after - before) / 1000) + " seconds"); } private void scrollToPos(int pos) { // I don't know why, but this sometimes doesn't work if you do // it only once (jdk 1.1.7 on Linux). setCaretPosition(pos); setCaretPosition(pos); } private int findPos(String txt, int line, int startFrom) { String find = "\n" + line + " "; return txt.indexOf(find, startFrom); } } class Filler extends Thread { private static final long UPDATE_FREQ = 1000; // In msec. private static boolean allRead = false; private long prevUpdate = System.currentTimeMillis(); private LineFinder applet; private TextArea display; String filename = "/mnt/cdrom/lckc_code"; // String codeUrl = "http://www.ScottMaxwell.org/lckc_code.gz"; String codeUrl = "http://home.pacbell.net/s-max/smorg/lckc/lckc_code.gz"; public Filler(LineFinder a, TextArea ta, String file, String url) { applet = a; display = ta; if (file != null) filename = file; if (url != null) codeUrl = url; } public void run() { if (allRead) { System.out.println("Filler.run() called after all code read."); return; } String base = "Reading LCKC source code"; applet.beginBusy(base + " ..."); //ed1 final int sz = 1312112; final int sz = 1631569; try { InputStreamReader in; if (filename.equals("")) { URL url = new URL(codeUrl); in = new InputStreamReader( new GZIPInputStream( new BufferedInputStream(url.openStream()))); } else in = new InputStreamReader(new FileInputStream(filename)); int totalRead = 0; int nRead; char [] buf = new char [4096]; // Larger != (much) faster. String contents; StringBuffer sbuf = new StringBuffer(sz); while ((nRead = in.read(buf, 0, buf.length)) != -1) { yield(); totalRead += nRead; sbuf.append(buf, 0, nRead); long now = System.currentTimeMillis(); if ((now - prevUpdate) >= UPDATE_FREQ) { prevUpdate = now; applet.setStatus(base + ": " + totalRead + " / " + sz + ": " + (100 * totalRead / sz) + "% read"); } } allRead = true; contents = new String(sbuf); display.setText(contents); } catch (FileNotFoundException e) { System.out.println(e); } catch (IOException e) { System.out.println(e); } applet.endBusy("All done reading; enjoy"); } } // And now, the Applet itself. public class LineFinder extends Applet { private final static int defaultW = 512; private final static int defaultH = 512; private static long busyCount = 0; private boolean initialized = false; private TextField lineTxt = new TextField("", 5); private LineAwareTextArea display = new LineAwareTextArea(this); private Label statusLbl = new Label("OK"); String filename = null, url = null; // Ctor. public LineFinder() { // Running as an applet, so don't try to read from a file. filename = ""; } public LineFinder(String file, String u) { filename = file; url = u; } // One-time applet initialization. public void init() { // I've heard that some browsers may (incorrectly) invoke this // method more than once. So guard against that. if (initialized) return; initialized = true; // Set up the user interface. This uses the default // FlowLayout, creating a full-width Panel for each row of // items. // First row. setLayout(new BorderLayout()); Panel p = new Panel(); p.add(new Label("Type line number and press Enter:")); p.add(lineTxt); add(p, BorderLayout.NORTH); // Second row. add(display, BorderLayout.CENTER); display.setFont(new Font("Courier", Font.PLAIN, 12)); display.setEditable(false); add(statusLbl, BorderLayout.SOUTH); lineTxt.addActionListener(new MyActionListener(display)); repaint(); } public void stop() { } public void start() { } boolean started = false; public void paint(Graphics g) { if (!started) { started = true; new Filler(this, display, filename, url).run(); } super.paint(g); } public void beginBusy(String msg) { setStatus(msg); if (++busyCount == 1) { Cursor c = new Cursor(Cursor.WAIT_CURSOR); setCursor(c); display.setCursor(c); } } public void endBusy(String msg) { if ((busyCount > 0) && (--busyCount == 0)) { Cursor c = new Cursor(Cursor.DEFAULT_CURSOR); setCursor(c); display.setCursor(c); } setStatus(msg); } public void setStatus(String status) { statusLbl.setText(status); } // In case we're not running in in a Web browser. public static void main(String args []) { int w = defaultW; int h = defaultH; String filename = null; String url = null; Properties props = System.getProperties(); filename = (String) props.get("file"); url = (String) props.get("url"); String sw = (String) props.get("width"); String sh = (String) props.get("height"); if (sw != null) w = (new Integer(sw)).intValue(); if (sh != null) h = (new Integer(sh)).intValue(); LineFinderFrame frame = new LineFinderFrame(w, h, filename, url); } // Should be able to use an anonymous inner class defined at the // addActionListener() call, but that runs afoul of a bug (?) in // the Netscape JDK. private static class MyActionListener implements ActionListener { public MyActionListener(LineAwareTextArea d) { disp = d; } // Contrary to the JDK documentation, the action events appear // to be generated only when the user hits Enter. public void actionPerformed(ActionEvent e) { final TextField source = (TextField) e.getSource(); final String txt = source.getText(); try { disp.scrollToLine(new Integer(txt).intValue()); } catch (NumberFormatException e2) // E.g., "". { System.out.println(e); } } private final LineAwareTextArea disp; }; } // Only used when the LineFinder applet is not running in a Web browser. class LineFinderFrame extends Frame { public LineFinderFrame(int w, int h, String filename, String url) { super("LCKC LineFinder"); setSize(w, h); // Approximately; will change later. LineFinder lf = new LineFinder(filename, url); //add(lf, BorderLayout.CENTER); add(lf); lf.init(); lf.start(); enableEvents(Event.WINDOW_DESTROY); show(); } // Cleaner than a WindowListener with lots of empty methods. protected void processWindowEvent(WindowEvent e) { if (e.getID() == Event.WINDOW_DESTROY) System.exit(0); super.processWindowEvent(e); } }