/* * TextScroll.java 2.8.3 15/12/98 * * Copyright (c) 1997, 1998 by Kevin Swan. All rights reserved. * * I reserve all rights to this software. You may use it and * distribute it freely, provided you do not remove this header * and attribute partial credit to the author, Kevin Swan. * * 24 Feb 1998 Added directives to catch up to the Java 1.1 version. * 27 Feb 1998 Added setCenter() directive. * Modified the supportedDirective() to use an array. * Released as version 2.2. * 27 Feb 1998 Added setBold() and setItalic() directives. * Added an element, inset, for the x coordinate * of text to permit an inset. * Added directive setInset() to set the inset. * Released as version 2.3. * 31 Mar 1998 Modified to permit complete URLs in the "data" * parameter of the applet. * Released as version 2.4. * 01 Apr 1998 Added setURL() directive. * Added version and credit information to init() method. * Released as version 2.5. * 05 Apr 1998 Modified so that if a URL has been set using setURL(), * then when the user moves their mouse over the applet, * the target URL is displayed in the status bar, instead * of "Click to start/stop the applet." If no URL is set, * it still displays "Click to ..." * 14 Apr 1998 Modified to display applet info in status bar instead of * "Click to ..." if no URL is set. * Released as version 2.5.2. * 14 Apr 1998 Modified to display messages while loading data, makes * it a little more professional looking. * Added the DataLoader class for asynchronous text data * loading. * Added the DirectiveManager class to decrease * responsibility of the main class, and to work towards * automatic text wrapping. * Added the LineWrapManager class to take care of the * line wrapping. * Released as version 2.6. * 16 Apr 1998 Modified to permit specification of a target frame as * well as a target URL. * Released as version 2.7. * 20 Apr 1998 Did some major internal rewriting to correct the scrolling * algorithm. It is now more "correct." Also lengthened * the offscreen canvas to support font sizes of up to 200 * pixels. * Added ability to refresh data from server. * Expanded documentation. * Released first version offered as Java 1.0 only. * Released as version 2.8. * 21 Apr 1998 Added ability to alter the loading/error colors through * tags. * Modified to notify DataLoader thread when start() and * stop() are called. * Released as version 2.8.1. * 04 Jun 1998 Added ability to disable automatic line wrapping by * including a tag. * Was not officially released yet. * 06 Jun 1998 Modified setURL () to handle commas in valid URLs. * Released as version 2.8.2. * 15 Dec 1998 Made some major modifications to allow a different * colored panel on the left side to be displayed, * similar to some other scrollers. * Released as version 2.8.3. * 12 Jul 2004 Fixed a bug that caused a NullPointerException when the * data refreshing failed. */ /* * Commented out for unbundled distribution. * * package kevin.applets.textscroll; */ import java.util.Vector; import java.applet.*; import java.awt.*; import java.net.*; import java.io.*; /** * This applet is displays a two-paned scrolling text window. * The separate panes are right next to each other, but can * have different color schemes, to allow for a headline on * the left, with content on the right. It is very simple in * design, but allows for a great deal of configuration. The * user can specify multiple parameters with the * <PARAM> tags, such as the following: * *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Name * * Description * * Example *
leftfontfaceThe style of Font to use in the left * pane. It must be a type supported by Java. If * an invalid font face is specified, * SansSerif will be used. * SansSerif is also the default font face. * Permitted values are: *
    *
  • SansSerif *
  • Serif *
  • Monospaced *
*

  *

* * <PARAM NAME="leftfontface" * VALUE="SansSerif"> * *
rightfontfaceThe style of Font to use in the right * pane. It must be a type supported by Java. If * an invalid font face is specified, * SansSerif will be used. * SansSerif is also the default font face. * Permitted values are: *
    *
  • SansSerif *
  • Serif *
  • Monospaced *
*

  *

* * <PARAM NAME="rightfontface" * VALUE="SansSerif"> * *
fontfaceThe style of Font to use in the right * pane. This is the same as rightfontface, and was * left in for backwards compatibility. * * * <PARAM NAME="fontface" * VALUE="SansSerif"> * *
leftfontsizeThe size of Font to use in the left * pane. If an invalid size is specified, the default * will be used. The default font size is 10. * * * <PARAM NAME="leftfontsize" * VALUE="12"> * *
rightfontsizeThe size of Font to use in the right * pane. If an invalid size is specified, the default * will be used. The default font size is 10. * * * <PARAM NAME="rightfontsize" * VALUE="12"> * *
fontsizeThe size of Font to use in the right * pane. This is the same as rightfontsize, and was * left in for backwards compatibility. * * * <PARAM NAME="fontsize" * VALUE="12"> * *
speedThe speed of scrolling to use. Valid values are all numbers * from TextScroll.MIN_SPEED (1) to * TextScroll.MAX_SPEED (100), with * TextScroll.MIN_SPEED being the slowest and * TextScroll.MAX_SPEED being the fastest. The * default is TextScroll.DEFAULT_SPEED (70). * * * <PARAM NAME="speed" VALUE="65"> * *
dataThe name of the datafile to use. * In versions 2.4 and above, you can use a complete URL in this * parameter if you wish. If the value of this parameter starts * with "http", it tries to create a new URL from the * string. Otherwise, it treats it as a relative URL from the * location of the HTML document containing the applet. * * * <PARAM NAME="data" VALUE="scroll.txt"> * *
leftforegroundThe foreground color for the left pane of the applet. * This will be the color of the text in the left pane. * The default is white. It must be a trio of integer * values which make up a legal RGB color value. Each * integer value must be between 0 and 255, inclusively. * The example sets the foreground color to a shade of red. * * * <PARAM NAME="leftforeground" * VALUE="144, 32, 32"> * *
rightforegroundThe foreground color for the right pane of the applet. * This will be the color of the text in the right pane. * The default is white. It must be a trio of integer * values which make up a legal RGB color value. Each * integer value must be between 0 and 255, inclusively. * The example sets the foreground color to a shade of red. * * * <PARAM NAME="rightforeground" * VALUE="144, 32, 32"> * *
foregroundThe foreground color for the right pane of the applet. * This will be the color of the text in the right pane. * This is the same as rightforeground, and was left in * for backwards compatibility. * * * <PARAM NAME="foreground" * VALUE="144, 32, 32"> * *
leftbackgroundThe background color for the left pane of the applet. * The default is black. It must be a trio of integer * values which make up a legal RGB color value. Each * integer value must be between 0 and 255, inclusively. * The example sets the background color to a light blue. * * * <PARAM NAME="leftbackground" * VALUE="224, 224, 255"> * *
rightbackgroundThe background color for the right pane of the applet. * The default is black. It must be a trio of integer * values which make up a legal RGB color value. Each * integer value must be between 0 and 255, inclusively. * The example sets the background color to a light blue. * * * <PARAM NAME="rightbackground" * VALUE="224, 224, 255"> * *
backgroundThe background color for the right pane of the applet. * This is the same as rightbackground, and was left in * for backwards compatibility. * * * <PARAM NAME="background" * VALUE="224, 224, 255"> * *
refreshThis indicates that the applet should re-request the data * from the server after the text has been repeated * n times, where n is the integer argument to * this parameter. The example to the right will reload the * text data after it has completely displayed 3 times. Note * that this is simply when it starts to load the data * again. The data will most likely not be available * instantaneously, so it will display the current data again * before replacing it with the new data. This means it might * actually display 4 or even 5 times before you'll actually see * the new data. The benefit of this is that your data will not * be changed in the middle of a presentation, it will only be * updated at the end. * * * <PARAM NAME="refresh" VALUE="3"> * *
fgloadcolorThis lets you specify what the applet should use for a * foreground color while it is loading data. The default * is blue. This is the color the "Loading data * ..." string will be displayed in. You generally won't * have to use this unless you are really concerned about it * fitting into your page during loading. The default is blue. * It must be a trio of integer values which make up a legal RGB * color value. Each integer value must be between 0 and 255, * inclusively. The example sets the foreground color to green. * * * <PARAM NAME="fgloadcolor" VALUE="32, 144, 32"> * *
bgloadcolorThis lets you specify what the applet should use for a background * color while it is loading data. The default is light blue. * It must be a trio of integer values which make up a legal RGB * color value. Each integer value must be between 0 and 255, * inclusively. The example sets the background color to a light * green. * * * <PARAM NAME="background" VALUE="224, 255, 224"> * *
wraptextThis lets you specify whether the applet should apply * automatic line wrapping or not. An argument of * true will apply line wrapping, an argument of * false will wrap the lines as they appear in * the data file. This feature was included for users who * want tight control of where lines are wrapped. * * * <PARAM NAME="wraptext" VALUE="false"> * *
* * *

Note that most of the parameters can be specified in the text file * itself, with the exception of the name of the text file, and the text * wrapping. The text wrapping is performed when the data is loaded. Thus, * the only mandatory <PARAM> tag is the data * tag. Indeed, this is the only one I usually specify, since the data * file changes most of the other values several times during the * presentation. * *

If the user clicks on the scrolling text, the scrolling will stop. * If the user clicks again, it will start again, in the same place. * The exception is if a URL is set. If a URL is set, then when the user * clicks on it, that URL will be loaded. * *

More complete documentation can be found at this applet's homepage * at * http://kombat.org/Programming/TextScroll. * * @version 2.8.4 12 Jul 2004 * @author Kevin Swan, kombat@kombat.org */ public class TextScroll extends Applet implements Runnable { /** * The current version of this applet. */ public static final String VERSION = "2.8.4"; /** * A constant representing the maximum number of milliseconds * the applet may sleep. This is the maximum number a user may * specify as the speed value, and would represent a sleep time * of MIN_SPEED milliseconds. */ public static final int MAX_SPEED = 100; /** * A constant representing the minimum number of milliseconds * the applet may sleep. This is the minimum number a user may * specify as the speed value, and would represent a sleep time * of MAX_SPEED milliseconds. */ public static final int MIN_SPEED = 1; /** * A constant representing the default number of milliseconds * the applet should sleep. This value is used for the speed * if the user specifies an invalid value, or none at all. */ public static final int DEFAULT_SPEED = 70; /** * This is the color to use for a background while the data is * loading. Default is light blue. */ public static final int BG_LOAD_COLOR = (224 << 16) | (224 << 8) | 255; /** * This is the color to draw the information in while the data is * loading. Default is dark blue. */ public static final int FG_LOAD_COLOR = (32 << 16) | (32 << 8) | 144; /** * This is the color to use for a background if an error occurs. * Default is light red. */ public static final int BG_ERROR_COLOR = (255 << 16) | (224 << 8) | 224; /** * This is the color to draw the information in if an error occurs. * Default is dark red. */ public static final int FG_ERROR_COLOR = (144 << 16) | (32 << 8) | 32; /** * A constant indicating the text data is ready. */ public static final int T_READY = 1; /** * A constant used to indicate the text is loading. */ public static final int T_LOADING = 2; /** * A constant used to indicate the data is being formatted. */ public static final int T_FORMATTING = 4; /** * A constant used to indicate an error occured in loading the text. */ public static final int T_NO_DATA_SPECIFIED = 8; /** * A constant used to indicate an error occured in loading the text. */ public static final int T_LOAD_ERROR = 16; /** This is the String displayed when no URL is set. */ public static final String STATUS_MSG = "TextScroll -- Version " + TextScroll.VERSION; /** The default Font face to use ("SansSerif.") */ public static final String DEFAULT_FONT_FACE = "SansSerif"; /** The default Font size to use (12). */ public static final int DEFAULT_FONT_SIZE = 12; /** The default width of the left panel (0). */ public static final int DEFAULT_LEFT_WIDTH = 0; /** The Image to create and use for buffering. */ private Image buffImage; /** The Graphics object from the buffer Image that we can draw on. */ private Graphics buffGraphics; /** The height and width of the buffer canvas. */ private int buffHeight, buffWidth; /** The Colors to use. */ private Color leftBackgroundColor, leftForegroundColor, rightBackgroundColor, rightForegroundColor, bgLoadColor, fgLoadColor; /** The initial font. This is used for refreshing the data. */ private Font initialFont; /** The Font to use in the left pane. */ private Font leftFont; /** The Font to use in the right pane. */ private Font rightFont; /** The size of the Font to use in the left pane. */ private int leftFontSize; /** The size of the Font to use in the right pane. */ private int rightFontSize; /** * The height in pixels of the font we're using. This is important * when determining where to draw the next line of text. */ private int fontHeight; /** The name of the file to load. */ private String fileName; /** The URL of the data file to load. */ private URL dataFileURL; /** The height and width of the applet/clipping window. */ private int width, height; /** The width of the left panel. */ private int leftWidth = TextScroll.DEFAULT_LEFT_WIDTH; /** Whether we should center the text in the left pane or not. */ private boolean leftCenter = false; /** Whether we should center the text in the right pane or not. */ private boolean rightCenter = false; /** * The speed to scroll at. Can be from MIN_SPEED to MAX_SPEED. * Default is DEFAULT_SPEED. */ private int speed; /** * This is the target URL the applet should load if the user * clicks on the applet. */ private URL targetURL = null; /** * This flag indicates whether or not the apply should apply * automatic line wrapping to the data. Default is to apply * line wrapping. */ private boolean shouldWrapText = true; /** * This is the frame to display the contents of the target URL in * when the applet is clicked on. If this is null, then the content * will be displayed in the current frame. */ private String targetFrame = null; /** The x coordinate to start drawing text at. */ private int inset = 3; /** boolean to flag if the Applet is currently running. */ private boolean running; /** * boolean to flag whether or not we should display * the message in the left panel this time. This is * set to true when the setLeftText directive is * invoked, and reset to false when the painting * routine actually gets around to displaying the * text. */ private boolean showLeftText = false; /** The text to display in the left panel. */ private String leftText = ""; /** * This is how many pixels to allow between lines of text. */ private int lineSpacing = 5; /** The actual text to display, one line per entry. */ private String[] text; /** The "window" we're displaying in, used for scrolling. */ private int frame = 16; /** The current line to append when we have to add another line. */ private int currLine = 0; /** * Keeps a count of how many times we've displayed the data * completely. */ private int iteration = 0; /** * This value specifies how many times to display the data before * refreshing from the server. Values <= 0 indicate not to refresh * at all. */ private int refreshValue = 1; /** Thread to control the scrolling. */ private Thread scroller; /** * This is an undocumented element to allow greater control over * the positioning of the animation. This is how many pixels to * move the text when scrolling. The default is 1. */ private int offset = 1; /** * This variable remembers whether the mouse is inside the applet or * not. This is important because if it *is*, and we had a URL set, * then that URL is displayed in the status bar. If the mouse stays * inside the applet, and the target URL changes, that should be * reflected in the status bar. So, when setURL() is called, it must * check this flag to see if it should update the status bar. */ private boolean mouseInside = false; /** * This flag monitors whether all the necessary data is here yet or not. */ private int status; /** * The Object responsible for retrieving the text data. */ private DataLoader loader = null; /** * This flag lets us know that we've tried to refresh the data. */ private boolean reloaded = true; /** * The Object responsible for managing directives. */ private DirectiveManager directiveManager = null; /** * Called during initialization. This is where we check all run-time * flags set by the <PARAM> tags, and load the * text. We will also prepare the text buffer. */ public void init () { this.status = TextScroll.T_LOADING; this.text = null; this.directiveManager = new DirectiveManager (this); /* Print version info. */ System.err.println ("TextScroll v" + TextScroll.VERSION + "\nCopyright (C) 1998, 2004 Kevin Swan, kombat@kombat.org"); running = false; String param = null; /* * Get the dimensions of the applet. The buffer image will be exactly * as wide as the applet, but will be 200 pixels taller, supporting * a maximum font size of 200. */ this.width = this.size ().width; this.height = this.size ().height; this.buffWidth = this.width; this.buffHeight = this.height + 200; /* * Get the width of the left text area. */ param = getParameter ("leftwidth"); if (param == null) this.leftWidth = TextScroll.DEFAULT_LEFT_WIDTH; else try { this.leftWidth = Integer.parseInt (param); } catch (NumberFormatException nfe) { this.leftWidth = TextScroll.DEFAULT_LEFT_WIDTH; } /* * Get the color parameters for the text areas. */ param = getParameter ("leftforeground"); if (param == null) leftForegroundColor = Color.black; else leftForegroundColor = getColorFromString (param); if (leftForegroundColor == null) leftForegroundColor = Color.black; param = getParameter ("leftbackground"); if (param == null) leftBackgroundColor = Color.white; else leftBackgroundColor = getColorFromString (param); if (leftBackgroundColor == null) leftBackgroundColor = Color.white; param = getParameter ("rightforeground"); if (param == null) rightForegroundColor = Color.black; else rightForegroundColor = getColorFromString (param); if (rightForegroundColor == null) rightForegroundColor = Color.black; param = getParameter ("rightbackground"); if (param == null) rightBackgroundColor = Color.white; else rightBackgroundColor = getColorFromString (param); if (rightBackgroundColor == null) rightBackgroundColor = Color.white; param = getParameter ("foreground"); if (param == null) rightForegroundColor = Color.black; else rightForegroundColor = getColorFromString (param); if (rightForegroundColor == null) rightForegroundColor = Color.black; param = getParameter ("background"); if (param == null) rightBackgroundColor = Color.white; else rightBackgroundColor = getColorFromString (param); if (rightBackgroundColor == null) rightBackgroundColor = Color.white; param = getParameter ("fgloadcolor"); if (param == null) fgLoadColor = new Color (TextScroll.FG_LOAD_COLOR); else fgLoadColor = getColorFromString (param); if (fgLoadColor == null) fgLoadColor = new Color (TextScroll.FG_LOAD_COLOR); param = getParameter ("bgloadcolor"); if (param == null) bgLoadColor = new Color (TextScroll.BG_LOAD_COLOR); else bgLoadColor = getColorFromString (param); if (bgLoadColor == null) bgLoadColor = new Color (TextScroll.BG_LOAD_COLOR); /* * Get the desired Font information. */ param = getParameter ("leftfontsize"); if (param == null) this.leftFontSize = TextScroll.DEFAULT_FONT_SIZE; else try { this.leftFontSize = Integer.parseInt (param); } catch (NumberFormatException nfe) { this.leftFontSize = TextScroll.DEFAULT_FONT_SIZE; } param = getParameter ("rightfontsize"); if (param == null) this.rightFontSize = TextScroll.DEFAULT_FONT_SIZE; else try { this.rightFontSize = Integer.parseInt (param); } catch (NumberFormatException nfe) { this.rightFontSize = TextScroll.DEFAULT_FONT_SIZE; } param = getParameter ("fontsize"); if (param == null) this.rightFontSize = TextScroll.DEFAULT_FONT_SIZE; else try { this.rightFontSize = Integer.parseInt (param); } catch (NumberFormatException nfe) { this.rightFontSize = TextScroll.DEFAULT_FONT_SIZE; } String face = null; param = getParameter ("leftfontface"); if (param == null) face = TextScroll.DEFAULT_FONT_FACE; else face = param; this.leftFont = new Font (face, Font.PLAIN, this.leftFontSize); param = getParameter ("rightfontface"); if (param == null) face = TextScroll.DEFAULT_FONT_FACE; else face = param; this.rightFont = new Font (face, Font.PLAIN, this.rightFontSize); this.initialFont = new Font (face, Font.PLAIN, this.rightFontSize); FontMetrics fm = this.getToolkit ().getFontMetrics (this.rightFont); this.fontHeight = fm.getMaxAscent () + fm.getMaxDescent () + fm.getLeading (); frame = this.lineSpacing + this.fontHeight; param = getParameter ("fontface"); if (param == null) face = TextScroll.DEFAULT_FONT_FACE; else face = param; this.rightFont = new Font (face, Font.PLAIN, this.rightFontSize); this.initialFont = new Font (face, Font.PLAIN, this.rightFontSize); fm = this.getToolkit ().getFontMetrics (this.rightFont); this.fontHeight = fm.getMaxAscent () + fm.getMaxDescent () + fm.getLeading (); frame = this.lineSpacing + this.fontHeight; /* * Set the name of the file to load. */ param = getParameter ("data"); this.dataFileURL = null; if (param == null) this.status = TextScroll.T_NO_DATA_SPECIFIED; else { try { if (!param.startsWith ("http://")) this.dataFileURL = new URL (getDocumentBase (), param); else this.dataFileURL = new URL (param); } catch (MalformedURLException mue) { this.status = TextScroll.T_LOAD_ERROR; } } /* * Check if the user wants line wrapping or not. */ param = getParameter ("wraptext"); if (param != null) this.shouldWrapText = (new Boolean (param)).booleanValue (); /* Look for a different offset. */ param = getParameter ("offset"); if (param == null) this.offset = 1; else try { this.offset = Integer.parseInt (param); } catch (NumberFormatException nfe) { this.offset = 1; } /* Set the speed information. */ param = getParameter ("speed"); if (param == null) this.speed = TextScroll.DEFAULT_SPEED; else try { this.speed = Integer.parseInt (param); } catch (NumberFormatException nfe) { this.speed = TextScroll.DEFAULT_SPEED; } if (this.speed > TextScroll.MAX_SPEED || this.speed < TextScroll.MIN_SPEED) this.speed = TextScroll.DEFAULT_SPEED; /* Set the refresh information. */ param = getParameter ("refresh"); if (param == null) this.refreshValue = -1; else try { this.refreshValue = Integer.parseInt (param); } catch (NumberFormatException nfe) { this.refreshValue = -1; } this.resize (this.width, this.height); /* * buffGraphics is what we will use to draw on our buffImage, the * offscreen canvas we will use to write the text to before clipping * it and copying it to the active Graphics object in paint (). * We will support a maximum font size of 200. */ this.buffImage = this.createImage (this.buffWidth, this.buffHeight); this.buffGraphics = this.buffImage.getGraphics (); /* Prepare the offscreen buffer. */ this.buffGraphics.setColor (this.leftBackgroundColor); this.buffGraphics.fillRect (0, 0, this.leftWidth, this.buffHeight); this.buffGraphics.setColor (this.rightBackgroundColor); this.buffGraphics.fillRect ( this.leftWidth, 0, this.buffWidth, this.buffHeight); // this.buffGraphics.setColor (this.rightForegroundColor); // this.buffGraphics.setFont (this.rightFont); loadData (this.dataFileURL); } /* init () */ /** * buffGraphics is what we will use to draw on our buffImage, the * offscreen canvas we will use to write the text to before clipping * it and copying it to the active Graphics object in paint (). * We will support a maximum font size of 200. * * @return The Graphics object we will draw * on for a buffer. */ private Graphics getBuffGraphics () { if (this.buffGraphics == null) { this.buffImage = this.createImage (this.buffWidth, this.buffHeight); this.buffGraphics = this.buffImage.getGraphics (); } return this.buffGraphics; } /** * This method attempts to convert a String numeric * representation of a color into an actual Color object. * It expects the given String to be in the format * "red,green,blue" * * @param rgb A comma-separated RGB numeric representation of a * color, passed as a String. * * @return A Color object represented by the given RGB value, * if the values are legal. If an error occurs, return * null. */ public static Color getColorFromString (String rgb) { int red, green, blue; red = green = blue = 0; if (rgb == null) return null; try{ red = Integer.parseInt ((rgb.substring (0, rgb.indexOf (","))).trim ()); green = Integer.parseInt ((rgb.substring(rgb.indexOf (",") + 1, rgb.lastIndexOf (","))).trim ()); blue = Integer.parseInt ((rgb.substring(rgb.lastIndexOf (",") + 1)).trim ()); } catch (NumberFormatException nfe) { return null; } try { return new Color(red, green, blue); } catch (IllegalArgumentException iae) { return null; } } /* getColorFromString () */ /** * The implementation of the run () method, as required by * implementing the Runnable interface. This method performs * the actual animation. */ public void run () { while (!this.isReady ()) { if (this.getStatus () > TextScroll.T_LOADING) { repaint (); return; } try { scroller.sleep (200); } catch (InterruptedException ie) { return; } } while (running) { /* Move the area upwards offset pixels. */ this.getBuffGraphics ().copyArea ( 0, offset, this.buffWidth, this.height + this.fontHeight + this.lineSpacing, 0, 0 - offset); frame -= offset; /* The frame tells us when its time to draw another line of text. */ if (frame < 0) { /* Get the next line in the source file. */ String line = this.text [this.currLine]; this.currLine++; /* * If we've hit the end of the presentation, reset the line * variable, increment the iteration variable, and test if * we should refresh our data. */ if (this.currLine == this.text.length) { this.currLine = 0; this.iteration++; /* See if we should refresh our data. */ if ((this.refreshValue > 0) && (this.iteration >= this.refreshValue)) { /* * First, we should check to see if we've already tried to refresh * the data. If we have, and its ready now, replace the data. */ if (this.reloaded) { if (this.isReady ()) { String[] tmpArr = this.loader.getData (); if (this.loader.errorOccurred ()) { this.loader = null; System.err.println ( " Error reading data file, creating new dataloader ***"); this.loadData (this.dataFileURL); } else { this.text = new String [tmpArr.length]; System.arraycopy (tmpArr, 0, this.text, 0, tmpArr.length); this.iteration = 0; this.reloaded = false; } } } else /* If we haven't started refreshing it yet, do it now. */ this.refreshData (); } } /* * If the line is a method directive, try to service it. */ if (line.startsWith ("^^")) if (!this.directiveManager.performDirective (line)) System.err.println ("Illegal directive call:\n\t" + line); else continue; /* Treat the line as normal. */ /** * If we have a left message set, and we haven't * displayed it yet, now is the time to display it. */ FontMetrics fm = this.getToolkit ().getFontMetrics (this.leftFont); if (this.showLeftText) { this.getBuffGraphics ().setColor (this.leftForegroundColor); this.getBuffGraphics ().setFont (this.leftFont); if (!this.leftCenter) { this.getBuffGraphics ().drawString ( leftText, 0, this.height + this.lineSpacing + fm.getMaxAscent ()); } else { /* * We must center the text. We will do this in 3 steps: * 1. Find the length of the text. * 2. Calculate what x value to start drawing the text at. * 3. Draw the text. */ int length = fm.stringWidth (leftText); int start = (this.leftWidth / 2) - (length / 2); start = (start < 0) ? 0 : start; this.getBuffGraphics ().drawString ( leftText, start, this.height + this.lineSpacing + fm.getMaxAscent ()); } this.getBuffGraphics ().setColor (this.rightForegroundColor); this.showLeftText = false; } this.getBuffGraphics ().setColor (this.rightForegroundColor); this.getBuffGraphics ().setFont (this.rightFont); fm = this.getToolkit ().getFontMetrics (this.rightFont); frame = this.lineSpacing + this.fontHeight; if (!this.rightCenter) { this.getBuffGraphics ().drawString ( line, this.leftWidth + this.inset, this.height + this.lineSpacing + fm.getMaxAscent ()); } else { /* * We must center the text. We will do this in 3 steps: * 1. Find the length of the text. * 2. Calculate what x value to start drawing the text at. * 3. Draw the text. */ int length = fm.stringWidth (line); int start = ((this.width - this.leftWidth) / 2) - (length / 2); start = (start < this.leftWidth) ? this.leftWidth : start; this.getBuffGraphics ().drawString ( line, start + this.leftWidth, this.height + this.lineSpacing + fm.getMaxAscent ()); } } try { scroller.sleep (TextScroll.MAX_SPEED + 1 - this.speed); } catch (InterruptedException ie) { return; } this.repaint (); } return; } /* run () */ /** * This method reloads the text data file from the * server. Note that by the time this method is * called, it will probably take some time to * completely load, and we can't really replace the * whole data array in the middle of a display. We'll * simply refresh the DataLoader. The * next time we get to the end of the presentation, * we'll check if the data is ready. But, we do have * to note somehow that we tried to refresh it. We'll * set a flag. */ private void refreshData () { this.loader.refresh (); this.reloaded = true; } /** * This method loads data from a URL into * a DataLoader. The applet should * periodically check its DataLoader to see * if the data is ready. It is given the URL of the text * file to read. * * @param url The URL of the text data file to load. */ private void loadData (URL url) { /* If it is null, just ignore it. */ if (url == null) return; else this.loader = new DataLoader ( url, this.initialFont, this.width - this.leftWidth, this.shouldWrapText); return; } /** * Called to paint the screen. */ public void paint (Graphics g) { if (this.status > TextScroll.T_READY) this.displayStatus (g); else g.drawImage (this.buffImage, 0, 0, this); } /** * Called to update the screen. */ public void update (Graphics g) { if (this.status > TextScroll.T_READY) this.displayStatus (g); else g.drawImage (this.buffImage, 0, 0, this); } /** * Called to print the appropriate status information if the applet * is not able to begin scrolling yet. * * @param g The Graphics object to draw on. */ private void displayStatus (Graphics g) { if (this.status == TextScroll.T_READY) return; g.setFont (new Font ("SansSerif", Font.PLAIN, 12)); switch (this.getStatus ()) { case (TextScroll.T_LOADING): g.setColor (this.bgLoadColor); g.fillRect (0, 0, this.width, this.height); g.setColor (this.fgLoadColor); g.drawString ("Loading data ...", 5, 15); return; case (TextScroll.T_FORMATTING): g.setColor (this.bgLoadColor); g.fillRect (0, 0, this.width, this.height); g.setColor (this.fgLoadColor); g.drawString ("Formatting data ...", 5, 15); return; case (TextScroll.T_NO_DATA_SPECIFIED): g.setColor (new Color (TextScroll.BG_ERROR_COLOR)); g.fillRect (0, 0, this.width, this.height); g.setColor (new Color (TextScroll.FG_ERROR_COLOR)); g.drawString ("No \"data\" parameter specified.", 5, 15); return; case (TextScroll.T_LOAD_ERROR): g.setColor (new Color (TextScroll.BG_ERROR_COLOR)); g.fillRect (0, 0, this.width, this.height); g.setColor (new Color (TextScroll.FG_ERROR_COLOR)); g.drawString ("Couldn't read specified text file.", 5, 15); return; } } /* displayStatus () */ /** * Called to start this applet. If the applet has been running * before, we will pick up where we left off. */ public void start () { running = true; (scroller = new Thread (this)).start (); if (this.loader != null) this.loader.start (); else System.err.println ("ERROR: DataLoader reference was lost."); } /** * Stop the applet. */ public void stop () { running = false; if (this.scroller != null) this.scroller.stop (); if (this.loader != null) this.loader.stop (); } /** * If the Applet is running, stop it. If it is stopped, restart it. */ private void toggle () { if (this.running) this.stop (); else this.start (); } /** * Method called when the mouse pointer enters the applet. * * @param e The actual event * @param x The x coordinate the event occurred at. * @param y The y coordinate the event occurred at. * * @return true if the event is handled, false otherwise. */ public boolean mouseEnter (Event e, int x, int y) { this.mouseInside = true; if (this.getURL () == null) this.showStatus (TextScroll.STATUS_MSG); else this.showStatus (this.getURL ().toString ()); return true; } /** * Method called when the mouse pointer leaves the applet. * * @param e The actual event * @param x The x coordinate the event occurred at. * @param y The y coordinate the event occurred at. * * @return true if the event is handled, false otherwise. */ public boolean mouseExit (Event e, int x, int y) { this.mouseInside = false; this.showStatus (""); return true; } /** * Method called when the user clicks. * * @param e The actual event. * @param x The x coordinate the even occurred at. * @param y The y coordinate the even occurred at. * * @return true if the event is handled, false otherwise. */ public boolean mouseDown (Event me, int x, int y) { if (this.getURL () == null) this.toggle (); else try { if (this.getAppletContext () != null) { if (this.targetFrame == null) this.getAppletContext ().showDocument (this.getURL ()); else this.getAppletContext ().showDocument (this.getURL (), this.targetFrame); } } catch (Exception e) { this.toggle (); } return true; } /** * This method is used to determine whether the data is ready or not. * * @return An int value which is one of the constants: *

*/ public int getStatus () { return this.status; } /** * A method used to determine whether we're ready to start the animation * or not. * * @return true if all the necessary data has been loaded, * false otherwise. */ public boolean isReady () { if (this.text == null) { if (this.loader.dataReady ()) { String[] tmpArr = this.loader.getData (); this.text = new String [tmpArr.length]; System.arraycopy (tmpArr, 0, this.text, 0, tmpArr.length); this.status = TextScroll.T_READY; } else { if (this.loader.errorOccurred ()) this.status = TextScroll.T_LOAD_ERROR; else this.status = TextScroll.T_LOADING; } } return ((this.status & TextScroll.T_READY) == TextScroll.T_READY); } /** * This method is used to specify whether the text in the * left pane should be centered or not. The String argument * must either be "true" or "false" The * comparison is case-insensitive. * * @param boolStr A String. Must be "true" to specify * that all further text in the left pane should * be centered, or "false" to specify * normal formatting (left align). Any other * values are ignored, and a quiet message is * printed to stderr. */ public void setLeftCenter (String boolStr) { if (boolStr.equalsIgnoreCase ("true")) this.leftCenter = true; else if (boolStr.equalsIgnoreCase ("false")) this.leftCenter = false; else System.err.println ( "setLeftCenter directive ignored - invalid argument: " + boolStr); } /** * This method is used to specify whether the text in the * right pane should be centered or not. The String argument * must either be "true" or "false" The * comparison is case-insensitive. * * @param boolStr A String. Must be "true" to specify * that all further text in the right pane should * be centered, or "false" to specify * normal formatting (left align). Any other * values are ignored, and a quiet message is * printed to stderr. */ public void setRightCenter (String boolStr) { if (boolStr.equalsIgnoreCase ("true")) this.rightCenter = true; else if (boolStr.equalsIgnoreCase ("false")) this.rightCenter = false; else System.err.println ( "setRightCenter directive ignored - invalid argument: " + boolStr); } /** * This method is used to specify whether the text in the * right pane should be centered or not. The String argument * must either be "true" or "false" The * comparison is case-insensitive. This method is left in * for compatibility reasons. * * @param boolStr A String. Must be "true" to specify * that all further text in the right pane should * be centered, or "false" to specify * normal formatting (left align). Any other * values are ignored, and a quiet message is * printed to stderr. */ public void setCenter (String boolStr) { if (boolStr.equalsIgnoreCase ("true")) this.rightCenter = true; else if (boolStr.equalsIgnoreCase ("false")) this.rightCenter = false; else System.err.println ( "setCenter directive ignored - invalid argument: " + boolStr); } /** * This method is used to turn bold font styling for the * left pane on or off. It expects a single String argument * which must be either "true" or "false". * The comparison is case-insensitive. * * @param boolStr A String. Must be "true" to specify * that all further text displayed in the left * pane should be bold, or "false" to * specify non-bold styling. Any other values are * ignored, and a quiet message is printed to stderr. */ public void setLeftBold (String boolStr) { int style = this.leftFont.getStyle (); if (boolStr.equalsIgnoreCase ("true")) style = style | Font.BOLD; else if (boolStr.equalsIgnoreCase ("false")) style = style & ~Font.BOLD; else { System.err.println ("setLeftBold directive ignored - invalid argument: " + boolStr); return; } this.leftFont = new Font (this.leftFont.getName (), style, this.leftFontSize); } /** * This method is used to turn bold font styling for the * right pane on or off. It expects a single String argument * which must be either "true" or "false". * The comparison is case-insensitive. * * @param boolStr A String. Must be "true" to specify * that all further text displayed in the right * pane should be bold, or "false" to * specify non-bold styling. Any other values are * ignored, and a quiet message is printed to stderr. */ public void setRightBold (String boolStr) { int style = this.rightFont.getStyle (); if (boolStr.equalsIgnoreCase ("true")) style = style | Font.BOLD; else if (boolStr.equalsIgnoreCase ("false")) style = style & ~Font.BOLD; else { System.err.println ( "Error: Invalid int arg for setBold or setRightBold: " + boolStr); return; } this.rightFont = new Font (this.rightFont.getName (), style, this.rightFontSize); FontMetrics fm = this.getToolkit ().getFontMetrics (this.rightFont); this.fontHeight = fm.getMaxAscent () + fm.getMaxDescent () + fm.getLeading (); this.getBuffGraphics ().setFont (this.rightFont); } /** * This method is used to turn bold font styling for the * right pane on or off. It expects a single String argument * which must be either "true" or "false". * The comparison is case-insensitive. This method is left * in for compatibility purposes. * * @param boolStr A String. Must be "true" to specify * that all further text displayed in the right * pane should be bold, or "false" to * specify non-bold styling. Any other values are * ignored, and a quiet message is printed to stderr. */ public void setBold (String boolStr) { this.setRightBold (boolStr); } /** * This method is used to turn italic font styling for the * left pane on or off. It expects a single String argument * which must be either "true" or "false". * The comparison is case-insensitive. * * @param boolStr A String. Must be "true" to specify * that all further text displayed in the left * pane should be italic, or "false" to * specify non-italic styling. Any other values * are ignored, and a quiet message is printed to * stderr. */ public void setLeftItalic (String boolStr) { int style = this.leftFont.getStyle (); if (boolStr.equalsIgnoreCase ("true")) style = style | Font.ITALIC; else if (boolStr.equalsIgnoreCase ("false")) style = style & ~Font.ITALIC; else { System.err.println ( "Error: Invalid int arg for setLeftItalic: " + boolStr); return; } this.leftFont = new Font (this.leftFont.getName (), style, this.leftFontSize); } /** * This method is used to turn italic font styling for the * right pane on or off. It expects a single String argument * which must be either "true" or "false". * The comparison is case-insensitive. * * @param boolStr A String. Must be "true" to specify * that all further text displayed in the right * pane should be italic, or "false" to * specify non-italic styling. Any other values * are ignored, and a quiet message is printed to * stderr. */ public void setRightItalic (String boolStr) { int style = this.rightFont.getStyle (); if (boolStr.equalsIgnoreCase ("true")) style = style | Font.ITALIC; else if (boolStr.equalsIgnoreCase ("false")) style = style & ~Font.ITALIC; else { System.err.println ( "Error: Invalid int arg for setItalic or setRightItalic: " + boolStr); return; } this.rightFont = new Font (this.rightFont.getName (), style, this.rightFontSize); FontMetrics fm = this.getToolkit ().getFontMetrics (this.rightFont); this.fontHeight = fm.getMaxAscent () + fm.getMaxDescent () + fm.getLeading (); this.getBuffGraphics ().setFont (this.rightFont); } /** * This method is used to turn italic font styling for the * right pane on or off. It expects a single String argument * which must be either "true" or "false". * The comparison is case-insensitive. This method is left * in for compatibility purposes. * * @param boolStr A String. Must be "true" to specify * that all further text displayed in the right * pane should be italic, or "false" to * specify non-italic styling. Any other values * are ignored, and a quiet message is printed to * stderr. */ public void setItalic (String boolStr) { this.setRightItalic (boolStr); } /** * This method is used to set the inset value. The inset is the * x coordinate where the text will be drawn at. The default is 3. * The argument must be a String object which can be parsed to an * int. * * @param insetStr A String which can be parsed to an int. */ public void setInset (String insetStr) { int val; try { val = Integer.parseInt (insetStr); } catch (NumberFormatException nfe) { System.err.println ("setInset directive ignored - invalid argument: " + insetStr + " (Expected a number)"); return; } this.inset = val; } /** * This method sets the currently active URL to the given URL, * such that if the user clicks anywhere in the applet area * after this method has been invoked, it will load the named * URL, unless URLString is "null" * (case insensitive), in which case clicking will simply * result in toggling scrolling. * *

Note that in versions 2.7 and greater, the user can specify * a target frame for the URL to be displayed in as well. The * frame name must be specified, followed by a comma, followed * by the target URL. * *

This method was modified in version 2.8.2 to be more * intelligent in determining if the user really wants a frame * or not. Some URLs contain commas. This method now only * treats it as a frame if there is a comma before the first * "http://" string. * * @param URLString The URL to load. If the string is only a URL, * then the page will be loaded in the current * frame. If it consists of a string, followed * by a comma, followed by another string, the * first string will be treated as the target * frame name, and the second will be treated * as the target URL. If URLString * is "null." (case insensitive), * then the target URL and the target frame are * both set to null. */ public void setURL (String URLString) { /* * They might want a target frame, see if they've specified one. */ if (URLString == null) { this.setURL ((URL) null); this.setTargetFrame (null); } /* * If the URL string contains a comma, and that comma appears * before the http://, then we have a target frame. Otherwise, * just treat the whole string as a single URL. */ if (URLString.indexOf (',') >= 0) if (URLString.indexOf (',') < URLString.indexOf ("http://")) { String targetFrame = URLString.substring (0, URLString.indexOf (',')); String targetURL = URLString.substring (URLString.indexOf (',') + 1, URLString.length ()); this.setURL (targetURL); this.setTargetFrame (targetFrame); return; } this.setTargetFrame (null); if (URLString.equalsIgnoreCase ("null")) { this.setURL ((URL) null); this.setTargetFrame (null); } else try { this.setURL (new URL (URLString)); } catch (MalformedURLException mue) { System.err.println ("setURL directive ignored - invalid argument: " + URLString + "\n(Unsetting clickable link)"); this.setURL ((URL) null); } } /** * This method sets the name of the target frame to display the * target URL in. If targetFrame is null, then the * target URLs will be displayed in the current browser frame. * * @param targetFrame A String name of the target * frame to display the contents of the target * URL in when the applet is clicked on. */ public void setTargetFrame (String targetFrame) { if (targetFrame != null) { targetFrame = targetFrame.trim (); if (targetFrame.equalsIgnoreCase ("null")) targetFrame = null; } this.targetFrame = targetFrame; } /** * An accessor method to get the current value of the target frame. * * @return The current target frame. */ public String getTargetFrame () { return this.targetFrame; } /** * This method is used to set the target URL to an actual * URL object. It is used by the mouse event * handler. * * @param url The URL to load in the page if * the user clicks on the applet. */ public void setURL (URL url) { this.targetURL = url; if (this.mouseInside) if (this.targetURL == null) this.showStatus (TextScroll.STATUS_MSG); else this.showStatus (this.targetURL.toString ()); } /** * Display the given string in the left panel. * * @param msg The string to display in the left * panel. */ public void setLeftText (String msg) { this.leftText = msg; this.showLeftText = true; } /** * Answer the currently set target URL. * * @return The current URL target. */ public URL getURL () { return this.targetURL; } /** * Answer a little blurb about this applet. * * @return info about this applet. */ public String getAppletInfo () { return "TextScroll Version " + VERSION + " Copyright (C) 1998, 2004 by Kevin Swan, kombat@kombat.org"; } /** * Answer information about legal parameters. * * @return info about the parameters. */ public String[][] getParameterInfo () { String[][] pinfo = { { "fontface", "Serif, SansSerif, Monospaced", "The font to use" }, { "fontsize", "integer", "The size of font to use" }, { "speed", TextScroll.MIN_SPEED + " - " + TextScroll.MAX_SPEED, "Scroll speed" }, { "data", "String", "Name of text file to display" }, { "foreground", "rrr,ggg,bbb", "RGB value to use for foreground color" }, { "background", "rrr,ggg,bbb", "RGB value to use for background color" } }; return pinfo; } /** * Sets the left foreground color to the given RGB String. * * @param rgb a String representing a comma-seperated RGB value. */ public void setLeftForegroundColor (String rgb) { Color color = getColorFromString (rgb); if (color != null) this.leftForegroundColor = color; this.getBuffGraphics ().setColor (this.leftForegroundColor); } /** * Sets the left background color to the given RGB String. * * @param rgb a String representing a comma-seperated RGB value. */ public void setLeftBackgroundColor (String rgb) { Color color = getColorFromString (rgb); if (color != null) leftBackgroundColor = color; this.getBuffGraphics ().setColor (this.leftBackgroundColor); this.getBuffGraphics ().fillRect (0, 0, this.leftWidth, this.buffHeight); this.getBuffGraphics ().setColor (this.rightForegroundColor); } /** * Sets the right foreground color to the given RGB String. * * @param rgb a String representing a comma-seperated RGB value. */ public void setRightForegroundColor (String rgb) { Color color = getColorFromString (rgb); if (color != null) this.rightForegroundColor = color; this.getBuffGraphics ().setColor (this.rightForegroundColor); } /** * Sets the right foreground color to the given RGB String. * This method is left in for backwards compatibility. * * @param rgb a String representing a comma-seperated RGB value. */ public void setForegroundColor (String rgb) { this.setRightForegroundColor (rgb); } /** * Sets the right background color to the given RGB String. * * @param rgb a String representing a comma-seperated RGB value. */ public void setRightBackgroundColor (String rgb) { Color color = getColorFromString (rgb); if (color != null) rightBackgroundColor = color; this.getBuffGraphics ().setColor (this.rightBackgroundColor); this.getBuffGraphics ().fillRect (this.leftWidth, 0, this.buffWidth, this.buffHeight); this.getBuffGraphics ().setColor (this.rightForegroundColor); } /** * Sets the right background color to the given RGB String. * This method is left in for backwards compatibility. * * @param rgb a String representing a comma-seperated RGB value. */ public void setBackgroundColor (String rgb) { this.setRightBackgroundColor (rgb); } /** * Try and pause for the given number of milliseconds. Note * that the time is given as a String. * * @param time a String that should be able to be converted to an * Integer. */ public void pause (String timeStr) { Integer time; try { time = Integer.valueOf (timeStr); } catch (NumberFormatException nfe) { System.err.println ( "Error: Invalid integer specified for pause (): \"" + timeStr + "\""); return; } try { this.scroller.sleep (time.intValue ()); } catch (InterruptedException ie) { } return; } /** * This is simply a way for the user to call toggle () * as a directive. This was done to relieve confusion, so the user * can simply use pause () with or without an argument * to cause the applet to pause scrolling. If it is called with * no arguments, this method is called, and the scrolling stops * until the user clicks the text area. * */ public void pause () { this.toggle (); } /** * Sets the speed of the applet to the given value. Note that the * speed value should be an integer in the form of a String, between * MIN_SPEED and MAX_SPEED. If an invalid value is specified, * the speed value is left at its current setting. * * @param speedStr a String representing the integer speed to use for * this applet. */ public void setSpeed (String speedStr) { Integer speed; try { speed = Integer.valueOf (speedStr); } catch (NumberFormatException nfe) { System.err.println ( "Error: Invalid integer specified for setSpeed (): \"" + speedStr + "\""); return; } if ((speed.intValue () <= TextScroll.MAX_SPEED) && (speed.intValue () >= TextScroll.MIN_SPEED)) this.speed = speed.intValue (); return; } /** * This method allows the user to change fonts in the middle of * the scrolling. It sets the font to use for the left pane. * * @param face The String name of the new * Font to use in the left pane. */ public void setLeftFontFace (String face) { this.leftFont = new Font (face, this.leftFont.getStyle (), this.leftFontSize); } /** * This method allows the user to change fonts in the middle of * the scrolling. It sets the font to use for the right pane. * * @param face The String name of the new * Font to use in the right pane. */ public void setRightFontFace (String face) { this.rightFont = new Font (face, this.rightFont.getStyle (), this.rightFontSize); FontMetrics fm = this.getToolkit ().getFontMetrics (this.rightFont); this.fontHeight = fm.getMaxAscent () + fm.getMaxDescent () + fm.getLeading (); this.getBuffGraphics ().setFont (this.rightFont); } /** * This method allows the user to change fonts in the middle of * the scrolling. It sets the font to use for the right pane. * This method is left in for backwards compatibility. * * @param face The String name of the new * Font to use in the right pane. */ public void setFontFace (String face) { this.setRightFontFace (face); } /** * This method allows the user to change font size * in the middle of the scrolling. It sets the * size of the font to use in the left pane. * * @param sizeStr The String representing * an integer size of the Font to use * in the left pane. */ public void setLeftFontSize (String sizeStr) { int size; try { size = Integer.parseInt (sizeStr); } catch (NumberFormatException nfe) { System.err.println ( "Error: Invalid integer specified for setLeftFontSize (): \"" + sizeStr + "\""); return; } this.leftFontSize = size; this.leftFont = new Font (this.leftFont.getName(), this.leftFont.getStyle (), this.leftFontSize); } /** * This method allows the user to change font size * in the middle of the scrolling. It sets the * size of the font to use in the right pane. * * @param sizeStr The String representing * an integer size of the Font to use * in the right pane. */ public void setRightFontSize (String sizeStr) { int size; try { size = Integer.parseInt (sizeStr); } catch (NumberFormatException nfe) { System.err.println ( "Error: Invalid arg for setFontSize or setRightFontSize (): \"" + sizeStr + "\""); return; } this.rightFontSize = size; this.rightFont = new Font (this.rightFont.getName(), this.rightFont.getStyle (), this.rightFontSize); FontMetrics fm = this.getToolkit ().getFontMetrics (this.rightFont); this.fontHeight = fm.getMaxAscent () + fm.getMaxDescent () + fm.getLeading (); this.getBuffGraphics ().setFont (this.rightFont); } /** * This method allows the user to change font size * in the middle of the scrolling. It sets the * size of the font to use in the right pane. * It is left in for backwards compatibility. * * @param sizeStr The String representing * an integer size of the Font to use * in the right pane. */ public void setFontSize (String sizeStr) { this.setRightFontSize (sizeStr); } }