/* * HollywoodText.java - 30 May 1996 - Version 1.0 * * Copyright 1997 by Bill Giel/Moondog Software * * E-mail: bgiel@ct2.nai.net * WWW: http://w3.nai.net/~rvdi/bgiel/bill.htm * * * A LICENSE to use, copy, modify, and distribute this software * and its documentation for NON-COMMERCIAL purposes and without * fee is hereby granted, provided that any use properly credits * the author. * * * THE AUTHOR MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY * OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE, OR NON-INFRINGEMENT. THE AUTHOR SHALL NOT BE LIABLE FOR ANY * DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING * THIS SOFTWARE OR ITS DERIVATIVES. * */ import java.awt.*; import java.applet.*; import java.net.*; import java.util.*; import java.io.*; //An applet class to display text in a fashion similar to //computer terminals that we see only in the movies :-) public class HollywoodText extends Applet implements Runnable { final String MOREMESSAGE = "More..."; Thread kicker; Vector script = new Vector(); Image image; boolean threadSuspended, isRunning; //Configurable stuff... int width, height; Color bgcolor, fgcolor; AudioClip sound; int cpause, lpause, spause, lspace, fontsize, cursor, indent, initx, inity, maxy; boolean loop; Image bgimage; MediaTracker tracker; String file; public String[][] getParameterInfo() { String[][] info = { {"width", "int", "width of the applet, in pixels"}, {"height", "int", "height of the applet, in pixels"}, {"bgcolor", "String", "RGB hex triplet for 'screen' background "}, {"fgcolor", "String", "RGB hex triplet for 'screen' foreground "}, {"sound", "String", "Sound clip file, relative to codebase "}, {"cpause", "int", "Delay between characters <25ms>"}, {"lpause", "int", "Delay between lines <250ms>"}, {"spause", "int", "Delay between repeats <500ms>"}, {"loop", "int", "Non-zero causes applet to loop <1>"}, {"script", "String", "Script file to display, relative to codebase "}, {"lspace", "int", "Line spacing <15>"}, {"fontsize", "int", "Font size <12>"}, {"cursor", "int", "Width of \"Cursor\" <3>"}, {"indent", "int", "Indent value for text display in pixels <15>"}, {"initx","int", "X-coord of \"Screen\" origin in applet panel <0>"}, {"inity","int", "Y-coord of \"Screen\" origin in applet panel <0>"}, {"bgimage", "String", "Background image file, relative to codebase "}, {"maxy","int","Lower limit of text display, zero uses applet height <0>"} }; return info; } public String getAppletInfo() { return("HollywoodText Applet, Copyright 1997 by William Giel."); } public void init() { super.init(); width=size().width; height=size().height; String param = getParameter("bgcolor"); bgcolor = parseColorString(param, Color.black); param = getParameter("fgcolor"); fgcolor = parseColorString(param, Color.green); param = getParameter("sound"); sound = getClip(param); param = getParameter("cpause"); cpause = getInt(param,25); param = getParameter("lpause"); lpause = getInt(param,250); param = getParameter("spause"); spause = getInt(param,500); param = getParameter("loop"); loop = (getInt(param,1) > 0)? true : false; param = getParameter("lspace"); lspace = getInt(param,15); param = getParameter("fontsize"); fontsize = getInt(param,12); param = getParameter("cursor"); cursor = getInt(param,3); param = getParameter("indent"); indent = getInt(param,0); param = getParameter("initx"); initx = getInt(param,0); indent += initx; param = getParameter("inity"); inity = getInt(param,0); param = getParameter("maxy"); maxy = getInt(param,height); param = getParameter("bgimage"); if(null != param){ try{ bgimage = getImage(getCodeBase(),param); tracker = new MediaTracker(this); tracker.addImage(bgimage,0); }catch(Exception e){ bgimage = null; } } file = getParameter("script"); if(null == file){ System.out.println("HollywoodText: No script was specified."); return; } } public void start() { if(null == kicker){ kicker=new Thread(this); kicker.start(); } } public void stop() { if(null != kicker){ kicker.stop(); kicker=null; } } private AudioClip getClip(String s) { AudioClip clip = null; try{ URL url = new URL(getCodeBase(),s); clip = getAudioClip(url); }catch(Exception e){ System.out.println(e); clip=null; } return clip; } private Color parseColorString(String colorString, Color dflt) { Color color; try{ colorString = colorString.replace('#',' ').trim(); int R = Integer.valueOf(colorString.substring(0,2),16).intValue(); int G = Integer.valueOf(colorString.substring(2,4),16).intValue(); int B = Integer.valueOf(colorString.substring(4,6),16).intValue(); color = new Color(R,G,B); }catch(Exception e){ color = dflt; } return color; } private int getInt(String s, int dflt) { int val; try{ val = Integer.parseInt(s); }catch(Exception e){ val = dflt; } return val; } private void getScript() { String textline; try{ URLConnection urlConnection = new URL(getCodeBase(),file).openConnection(); //In JDK 1.1, this should be replaced with BufferedReader.readLine, however //the following works with both 1.02 and 1.1... DataInputStream in = new DataInputStream(urlConnection.getInputStream()); while(null != (textline = in.readLine())){ script.addElement(textline.trim()); } }catch (Exception e){ script.removeAllElements(); script.addElement("Could not retrieve script."); } } private void repaintScreen(Graphics g) { //We call this to refresh our buffered image //prior to typing text onto it... if(null == bgimage){ g.setColor(bgcolor); g.fillRect(0, 0, width, height); } else{ g.drawImage(bgimage,0,0,this); } } private int displayString(Graphics g, String string, int y, int w, int ascent) { //Step through the string, display each character with the sound effect //Paint a cursor as we do, and sleep for the character pause interval //in between each character. // //Return the Y coord for the next line of text. for(int j = 0; j < string.length(); j++){ if(null != sound) sound.play(); g.setColor(fgcolor); g.drawString(string.substring(j,j+1),indent + (j)*w, y); g.fillRect(indent + (j+1)*w, y-ascent, cursor, fontsize); try{ kicker.sleep(cpause); }catch(InterruptedException e){} g.setColor(bgcolor); g.fillRect(indent + (j+1)*w, y-ascent, cursor, fontsize); repaint(); } return y+lspace; } public void run() { //Wait for a background image, if any... if(null != bgimage){ try{ tracker.waitForAll(); }catch(Exception e){ bgimage = null; } } //Create our buffered image image= createImage(width, height); //Get the graphics context for the buffered image Graphics g = image.getGraphics(); //Put it up... repaint(); //Now, get the script (if necessary.) if(script.isEmpty()) getScript(); //Set a teletype font, and get some font metrics g.setFont(new Font("Courier", Font.PLAIN, fontsize)); FontMetrics fm = g.getFontMetrics(); int w = fm.stringWidth("A"); int ascent = fm.getMaxAscent(); //We've got our image and file, so we can now set isRunning to true. //This is used to see if we are ready to respond to mouseDown events //(see below) isRunning=true; while(kicker != null){ repaintScreen(g); int Y = inity + lspace; for(int i = 0; i < script.size(); i++){ String string = (String)script.elementAt(i); //Handle a NEWLINE "tag" if(string.toLowerCase().equals("")){ if(Y != inity + lspace) Y += lspace; } //Handle a NEWPAGE "tag" else if(string.toLowerCase().equals("")){ repaintScreen(g); Y = inity + lspace; } //Handle a PAUSE "tag" (parse out the value) and sleep //for the requested duration else if(string.toLowerCase().startsWith(""); start++; try{ pause=Integer.parseInt(string.substring(start,end)); if(pause == 0){ threadSuspended=true; kicker.suspend(); } else{ kicker.sleep(pause); } }catch(Exception e){} } //Otherwise, handle a message line. else{ if(string.toLowerCase().equals("")){ string = MOREMESSAGE; } Y = displayString(g, string, Y, w, ascent); //Are we near enough to the bottom of the display to //show a "More..." (and is it appropriate, i.e. is //there more text to display?) if((Y + 2*lspace) > maxy && i+1 < script.size()){ Y+=lspace; string=MOREMESSAGE; displayString(g, string, Y, w, ascent); } //Did we just display "More..."? if(string.equals(MOREMESSAGE)){ //This will force a new page after //the thread resumes Y += maxy; threadSuspended=true; kicker.suspend(); } //Otherwise, if we simply displayed a text line, //sleep for the line pause interval. else{ try{ kicker.sleep(lpause); }catch(InterruptedException e){} } } //Do we need to start a new "Page?" if(Y > maxy){ repaintScreen(g); Y = inity + lspace; } } //Is unattended looping enabled? If it is, sleep for //a screen pause interval. if(loop){ try{ kicker.sleep(spause); }catch(InterruptedException e){} } //Otherwise, suspend until we get a mouseDown event else{ threadSuspended=true; kicker.suspend(); } } } public synchronized boolean mouseDown(java.awt.Event evt, int x, int y) { //Only respond to mouseDown if the applet has its image and file //and is running... if(isRunning){ if (threadSuspended) { kicker.resume(); } else { kicker.suspend(); } threadSuspended = !threadSuspended; } return true; } public void paint(Graphics g) { //If we've got an image, draw it... if(null != image) g.drawImage(image,0,0,this); //Otherwise, paint the applet panel the specified //background color... else{ g.setColor(bgcolor); g.fillRect(0,0,size().width,size().height); } } public void update(Graphics g) { //Override update to call our paint method //to eliminate flickering. paint(g); } }