import java.awt.*; import java.util.StringTokenizer; import java.applet.Applet; // The Ticker class displays a ticker tape of text that scrolls across the // screen in a loop. When the text scrolls completely off one end it // reappears on the other end. The text, font, foreground and background can // be set via parameters. The font is specified by fontname, fontsize, // fontweight and fontslant. The colors may be entered by name or by // rgb values. To avoid flickering, the text is first drawn into an // offscreen image and the image is then displayed all at once. public class Ticker extends Applet implements Runnable { // The init() method initializes the Ticker. It sets the text, // font, foreground and background. The background is defaulted to pink, // the foreground to black, and the font to Helvetica. public void init() { tape = "Java by Example: The Java Book for Programmers"; String param = getParameter("text"); if (param != null) { tape = param; } // Get the user's font parameters, with 12 point Helvetica // font as the default. textFont = getFont("Helvetica", Font.PLAIN, 12); // We use the font height and font descent to draw the text // centered in the scrolling area. The width is used to // determine how far to scroll before starting over. FontMetrics metrics = getFontMetrics(textFont); textHeight = metrics.getHeight(); textDescent = metrics.getDescent(); textWidth = metrics.stringWidth(tape); // Get the colors specified by the user with black as the default // foreground and pink as the default background. getColors(Color.black, Color.pink); // Initialize the size, number of scrolling steps and the offscreen // image used to draw the text. setupTape(); } // The start() method create a new thread to scroll the text and // starts it. public void start() { scrollThread = new Thread(this); scrollThread.start(); } // The stop() method stops the scrolling thread. public void stop() { scrollThread.stop(); } // The Ticker update() method just calls paint() since we are covering the // background with our offscreen image. public void update(Graphics g) { paint(g); } // The paint() method first checks if the Ticker has grown wider or // taller. If it has grown taller, it is resized back to the right // height for the font. If it has grown wider, setupTape() will // create a new wider offscreen image and reset the number of steps // required to scroll all the way across. // // After checking for resize, paint() draws the string into the // offscreen image at the current scroll position then draws the // image on the display. public void paint(Graphics g) { // Adjust for resize if necessary. setupTape(); // Draw the string into the offscreen image. Graphics offg = offscreenImage.getGraphics(); offg.setColor(getBackground()); offg.fillRect(0, 0, size().width - (RECTMARGIN * 2), textHeight); offg.setColor(getForeground()); offg.setFont(textFont); // previousWidth is the width of the displayed area in pixels. // The text starts at the right edge of the Ticker at a position // previousWidth pixels from the left. Each time it is repainted // it is drawn one pixel farther to the left. offg.drawString(tape, previousWidth - tapeIndex, textHeight - textDescent); // totalTapeSteps is set to the width of the display area + // the width of the text. After the text has scrolled the // width of the display area it is at the left end of the // display and it must scroll its own width to complete // one traversal. tapeIndex = (tapeIndex + 1) % totalTapeSteps; // Draw a 3D rectangle just within the display area for a border. g.draw3DRect(RECTMARGIN, RECTMARGIN, size().width - RECTMARGIN, size().height - RECTMARGIN, false); // Draw the offscreen image within the rectangle. g.drawImage(offscreenImage, RECTMARGIN + 1, RECTMARGIN + TEXTMARGIN + 1, this); } // The run() method just pauses briefly between repaints. public void run() { while (true) { try { scrollThread.sleep(SCROLLPAUSE); } catch (InterruptedException e) { } repaint(); } } // The getColors() method retrieves color parameters from the // user for foreground and background colors. If either is // unspecified, the arguments are used as defaults. private void getColors(Color foreground, Color background) { // Get either a named foreground color or an rgb value. String param = getParameter("foreground"); if (param != null) { setForeground(lookupColor(param, foreground)); } else { param = getParameter("foreground-rgb"); if (param != null) { setForeground(lookupRGBColor(param, foreground)); } else { // no parameter, use the default. setForeground(foreground); } } // Get either a named background color or an rgb value. param = getParameter("background"); if (param != null) { setBackground(lookupColor(param, background)); } else { param = getParameter("background-rgb"); if (param != null) { setBackground(lookupRGBColor(param, background)); } else { // no parameter, use the default. setBackground(background); } } } // The lookupColor() method looks up a color by name. All the // predefined colors in the Color class are available except // the shades of gray. The "colors" array holds { "colorname", color } // pairs. If the name argument does not match any color in the // array, the defaultColor argument is used. private Color lookupColor(String name, Color defaultColor) { for (int i = 0; i < colors.length; i++) { if (name.equals(colors[i][0])) { return (Color)colors[i][1]; } } return defaultColor; } // The lookupRGBColor() method takes a string that is expected to // have the form: "rval,gval,bval" where rval, gval and bval are // integers between 0 and 255. If the string matches this format, // a color is returned with the specified rgb values; otherwise, // defaultColor is returned. // // The Java StringTokenizer class is used to split the "description" // argument into separate red, green, and blue values. private Color lookupRGBColor(String description, Color defaultColor) { try { // If any error occurs trying to parse the string, return // the default color. Possible errors are: not enough tokens, // and malformed integers. StringTokenizer tokens = new StringTokenizer(description, ","); int red = Integer.parseInt(tokens.nextToken()); int green = Integer.parseInt(tokens.nextToken()); int blue = Integer.parseInt(tokens.nextToken()); // ignore any extra stuff return new Color(red, green, blue); } catch (Exception e) { return defaultColor; } } // The getFont() method retrieves user specified font parameters. // Any parameter not set by the user is replaced by the corresponding // default argument. The "style" argument subsumes the "fontweight" // and "fontslant" parameters. private Font getFont(String name, int style, int pointSize) { String param = getParameter("fontname"); if (param != null) { name = param; } // Since the Java font model conflates slant and weight into style // we need to keep track of whether or not any style parameters // have been specified. If "fontweight" was specified, then // "fontslant" should be "or'd" with it; otherwise, the "fontslant" // should completely replace the default. boolean styleSpecified = false; param = getParameter("fontweight"); if (param != null) { styleSpecified = true; if (param.equals("bold") || param.equals("BOLD")) { style = Font.BOLD; } else { // Since the only two weights are "bold" and "plain", // we'll set the style to "plain" if it isn't bold. style = Font.PLAIN; } } param = getParameter("fontslant"); if (param != null) { if (param.equals("italic") || param.equals("ITALIC")) { if (styleSpecified) { style |= Font.ITALIC; } else { style = Font.ITALIC; } } else if (!styleSpecified) { // Since the only two slants are "italic" and "plain", // we'll set the style to "plain" if it isn't italic. style = Font.PLAIN; } } param = getParameter("fontsize"); if (param != null) { pointSize = Integer.parseInt(param); } return new Font(name, style, pointSize); } // The setupTape() method adjusts the state of the Ticker after a resize. // If the height has changed, it is reset to the appropriate height for // the selected font. If the width has changed, the number of steps // required for a complete scroll traversal is reset and a new offscreen // image is created to draw into. private void setupTape() { Dimension dim = size(); // resize() does nothing if the size hasn't changed. resize(dim.width, textHeight + (TEXTMARGIN * 2) + (RECTMARGIN * 2)); // If the width hasn't changed, we're done. if (dim.width == previousWidth) { return; } // Save the width to compare against next time. previousWidth = dim.width; // Reset the tape to the beginning. tapeIndex = 0; // The total steps required for a scroll traversal is the width // of the display area + the width of the text. totalTapeSteps = dim.width + textWidth; // If there is already an existing offscreen image, destroy it. if (offscreenImage != null) { offscreenImage.flush(); } // Create a new image to fit the current display area. offscreenImage = createImage(dim.width - (RECTMARGIN * 2), textHeight); } // Constants. private static final int TEXTMARGIN = 3; private static final int RECTMARGIN = 1; private static final int SCROLLPAUSE = 15; private Thread scrollThread; // Thread that scrolls the text. private String tape; // The text being displayed. private Image offscreenImage = null; // The offscreen image we write // text into before displaying it. private Font textFont; // The displayed font. private int tapeIndex; // The current index into the text. private int totalTapeSteps; // The number of steps required to // to traverse a complete scroll. private int textWidth; // The width of the text in pixels. private int textHeight; // The height of the text in pixels. private int textDescent; // How far the text goes below the // font's baseline in pixels. private int previousWidth = -1; // How wide the Ticker was the last // time we drew it. // Colors indexed by name. Used by lookupColor to select a color // value. private Object[][] colors = { { "white", Color.white }, { "WHITE", Color.white }, { "gray", Color.gray }, { "GRAY", Color.gray }, { "black", Color.black }, { "BLACK", Color.black }, { "red", Color.red }, { "RED", Color.red }, { "pink", Color.pink }, { "PINK", Color.pink }, { "orange", Color.orange }, { "ORANGE", Color.orange }, { "yellow", Color.yellow }, { "YELLOW", Color.yellow }, { "green", Color.green }, { "GREEN", Color.green }, { "magenta", Color.magenta }, { "MAGENTA", Color.magenta }, { "cyan", Color.cyan }, { "CYAN", Color.cyan }, { "blue", Color.blue }, { "BLUE", Color.blue } }; }