/*
 * @(#)ticker.java
 *
 */

import java.util.*;
import java.awt.*;
import java.awt.image.*;
import java.applet.*;
import java.io.*;
import java.net.*;

/**
 * A class to show a ticker-tape style display.
 *
 * @author 		Chris Cobb
 * @version 	5.10, 14 September 1998
 *
 * For a complete listing of available parameters, please visit
 * http://www.ccobb.org/tkapplet.html.
 *
 * Copyright (c) 1996-1998, Chris Cobb. All rights reserved.
 *
 */

final class tickertext extends Object
{
	public tickertext next;
	public boolean bInitialized;
	public int in;
	public int out;
	public int speed;
	public int nPause;
	public int nPreFlash;
	public int nFlash;
	public int nPostFlash;
	public int cxPixels;
	public int cyPixels;
	public int cxImage;
	public int cyImage;
	public int tz;
	public int sm;
	public int sw;
	public int sd;
	public int st;
	public int em;
	public int ew;
	public int ed;
	public int et;
	public int shift;
	public Color cr;
	public Font font;
	public Image imTicker;
	public String szString;
	public String szLink;
	public String szFrame;
	public boolean bReverseString;

	public static final int LEFTWARD = 0;
	public static final int RIGHTWARD = 1;
	public static final int UPWARD = 2;
	public static final int DOWNWARD = 3;
	public static final int XINWARD = 4;
	public static final int XOUTWARD = 5;
	public static final int YINWARD = 6;
	public static final int YOUTWARD = 7;
	public static final int XRINWARD = 8;
	public static final int XROUTWARD = 9;
	public static final int YRINWARD = 10;
	public static final int YROUTWARD = 11;
	public static final int NONE = 12;
	public static final int XLEFTWARD = 13;
	public static final int XRIGHTWARD = 14;
	public static final int YUPWARD = 15;
	public static final int YDOWNWARD = 16;

	protected tickertext()
	{
		next = null;
		bInitialized = false;
		in = LEFTWARD;
		out = LEFTWARD;
		speed = 100;
		nPause = 0;
		nPreFlash = 0;
		nFlash = 0;
		nPostFlash = 0;
		cxPixels = 0;
		cyPixels = 0;
		cxImage = 0;
		cyImage = 0;
		tz = 12345678;
		sm = 4;
		sw = 1;
		sd = 0;
		st = 7200;
		em = 10;
		ew = -1;
		ed = 0;
		et = 7200;
		shift = 3600;
		cr = null;
		font = null;
		imTicker = null;
		szString = null;
		szLink = null;
		szFrame = null;
		bReverseString = false;
	}
}

public class ticker extends Applet implements Runnable
{
	Thread kicker = null;
	Thread refresh = null;
	tickertext ttHead = null;
	tickertext ttTail = null;
	tickertext ttCur = null;
	boolean bWeHaveMouse = false;
	int speed = 50;
	int pause = 0;
	boolean bScrollRight = false;
	Font font = null;
	String szLink = null;
	String szFrame = null;
	String szFile = null;
	boolean bLoadingFile = false;
	boolean bErrorLoadingFile = false;
	boolean bReverseString = false;
	int tz = 12345678;
	int sm = 4;
	int sw = 1;
	int sd = 0;
	int st = 7200;
	int em = 10;
	int ew = -1;
	int ed = 0;
	int et = 7200;
	int shift = 3600;
	int sizeBorder = 6;
	int sizeStrongBorder = 1;
	int sizeLED = 4;
	long fileRefresh = 0;
	boolean bTimeToRefresh = false;
	Color crBk, crText, crBorder, crUnlit;
	Image imDisplay, imUnlit;

	/**
	 * Return applet information to browser.
	 */
	public String getAppletInfo()
	{
		return new String("ticker Version 5.10 by Chris Cobb");
	}

	/**
	 * Initialize the applet. Get attributes.
	 */
	public void init()
	{
		String param;

		initGlobals();

		param = getParameter("file");
		if (param != null)
			szFile = new String(param).replace('\\', '/');
		else
			initTextParams();

		param = getParameter("filerefresh");
		if (param != null)
		{
			fileRefresh = Long.valueOf(param).longValue();
			if (fileRefresh < 0)
				fileRefresh = 0;
			fileRefresh *= 1000;
		}
}

	/**
	 * Initialize the TEXTn parameters.
	 */
	private final void initTextParams()
	{
		int i;

		for (i = 1; ; i++)
		{
			Integer iParam = new Integer(i);
			String szTextParam = "text" + iParam.toString();
			String param = getParameter(szTextParam);

			if (param == null)
				break;

			initTickerText(param);
		}
	}

	/**
	 * Allocate and initialize a tickertext structure based on a string parameter.
	 */
	private final void initTickerText(String param)
	{
		tickertext tt = new tickertext();

		tt.speed = speed;
		tt.nPause = pause;
		tt.cr = crText;
		tt.szLink = szLink;
		tt.szFrame = szFrame;
		tt.tz = tz;
		tt.sm = sm;
		tt.sw = sw;
		tt.sd = sd;
		tt.st = st;
		tt.em = em;
		tt.ew = ew;
		tt.ed = ed;
		tt.et = et;
		tt.shift = shift;
		tt.font = font;
		tt.bReverseString = bReverseString;
		tt.szString = "";
		if (bScrollRight)
		{
			tt.in = tickertext.RIGHTWARD;
			tt.out = tickertext.RIGHTWARD;
		}
		else
		{
			tt.in = tickertext.LEFTWARD;
			tt.out = tickertext.LEFTWARD;
		}

		StringTokenizer st = new StringTokenizer(param, ";");

		while (st.hasMoreTokens())
		{
			String szToken = st.nextToken();
			int nColon = szToken.indexOf(':');
			String szName, szValue;

			if (nColon != -1)
			{
				szName = szToken.substring(0, nColon).toUpperCase();
				szValue = szToken.substring(nColon + 1);
				if (szName.compareTo("IN") == 0 || szName.compareTo("OUT") == 0)
				{
					int iFinal = -1;

					szValue = szValue.toUpperCase();
					if (szValue.compareTo("LEFTWARD") == 0)
						iFinal = tickertext.LEFTWARD;
					if (szValue.compareTo("RIGHTWARD") == 0)
						iFinal = tickertext.RIGHTWARD;
					else if (szValue.compareTo("UPWARD") == 0)
						iFinal = tickertext.UPWARD;
					else if (szValue.compareTo("DOWNWARD") == 0)
						iFinal = tickertext.DOWNWARD;
					else if (szValue.compareTo("XRIGHTWARD") == 0)
						iFinal = tickertext.XRIGHTWARD;
					else if (szValue.compareTo("XLEFTWARD") == 0)
						iFinal = tickertext.XLEFTWARD;
					else if (szValue.compareTo("YUPWARD") == 0)
						iFinal = tickertext.YUPWARD;
					else if (szValue.compareTo("YDOWNWARD") == 0)
						iFinal = tickertext.YDOWNWARD;
					else if (szValue.compareTo("YINWARD") == 0)
						iFinal = tickertext.YINWARD;
					else if (szValue.compareTo("YOUTWARD") == 0)
						iFinal = tickertext.YOUTWARD;
					else if (szValue.compareTo("XINWARD") == 0)
						iFinal = tickertext.XINWARD;
					else if (szValue.compareTo("XOUTWARD") == 0)
						iFinal = tickertext.XOUTWARD;
					else if (szValue.compareTo("YRINWARD") == 0)
						iFinal = tickertext.YRINWARD;
					else if (szValue.compareTo("YROUTWARD") == 0)
						iFinal = tickertext.YROUTWARD;
					else if (szValue.compareTo("XRINWARD") == 0)
						iFinal = tickertext.XRINWARD;
					else if (szValue.compareTo("XROUTWARD") == 0)
						iFinal = tickertext.XROUTWARD;
					else if (szValue.compareTo("NONE") == 0)
						iFinal = tickertext.NONE;

					if (iFinal != -1)
						if (szName.compareTo("IN") == 0)
							tt.in = iFinal;
						else
							tt.out = iFinal;
				}
				else if (szName.compareTo("COLOR") == 0)
					tt.cr = initColor(szValue);
				else if (szName.compareTo("ANCHOR") == 0)
					tt.szLink = new String(szValue);
				else if (szName.compareTo("FRAME") == 0)
					tt.szFrame = new String(szValue);
				else if (szName.compareTo("FONT") == 0)
					tt.font = initFont(szValue);
				else if (szName.compareTo("TIMEZONE") == 0)
				{
					int [] tzInfo = initTimeZone(szValue);

					tt.tz = tzInfo[0];
					tt.sm = tzInfo[1];
					tt.sw = tzInfo[2];
					tt.sd = tzInfo[3];
					tt.st = tzInfo[4];
					tt.em = tzInfo[5];
					tt.ew = tzInfo[6];
					tt.ed = tzInfo[7];
					tt.et = tzInfo[8];
					tt.shift = tzInfo[9];
				}
				else if (szName.compareTo("FLASH") == 0)
				{
					StringTokenizer stFlash = new StringTokenizer(szValue, ",");

					try
					{
						tt.nPreFlash = Integer.valueOf(stFlash.nextToken()).intValue();
						tt.nFlash = Integer.valueOf(stFlash.nextToken()).intValue();
						tt.nPostFlash = Integer.valueOf(stFlash.nextToken()).intValue();
					}
					catch(Exception e)
					{
						tt.nPreFlash = 0;
						tt.nFlash = 0;
						tt.nPostFlash = 0;
					}
					if (tt.nFlash == 0)
					{
						tt.nPreFlash = 0;
						tt.nPostFlash = 0;
					}
				}
				else if (szName.compareTo("SPEED") == 0)
					tt.speed = Integer.valueOf(szValue).intValue();
				else if (szName.compareTo("PAUSE") == 0)
					tt.nPause = Integer.valueOf(szValue).intValue();
				else if (szName.compareTo("STRING") == 0 || szName.compareTo("RSTRING") == 0)
				{
					while (st.hasMoreTokens())
						szValue += ";" + st.nextToken();
					tt.szString = szValue;
					tt.bReverseString = ((bReverseString && szName.compareTo("STRING") == 0) || (!bReverseString && szName.compareTo("RSTRING") == 0));
				}
			}
		}
		if (tt.nFlash != 0 && tt.nPause == 0)
			tt.nPause = speed;

		if (ttHead == null)
		{
			ttHead = tt;
			ttTail = tt;
		}
		else
		{
			ttTail.next = tt;
			ttTail = tt;
		}
	}

	/**
	 * Initialize a color value passed in as a parameter.
	 */
	private final Color initColor(String param)
	{
		StringTokenizer st = new StringTokenizer(param, ",");
		int r = 0, g = 0, b = 0;

		try
		{
			r = Integer.valueOf(st.nextToken()).intValue();
			g = Integer.valueOf(st.nextToken()).intValue();
			b = Integer.valueOf(st.nextToken()).intValue();
		}
		catch(Exception e)
		{
		}

		return(new Color(r, g, b));
	}

	/**
	 * Initialize a Font passed in as a parameter.
	 */
	private final Font initFont(String param)
	{
		String szFaceName = "Dialog";
		int nStyle = Font.PLAIN;
		int nSize = 12;

		if (param != null)
		{
			StringTokenizer st = new StringTokenizer(param, ",");

			try
			{
				szFaceName = st.nextToken();

				StringTokenizer stStyle = new StringTokenizer(st.nextToken(), "|");

				nStyle = 0;
				while (stStyle.hasMoreTokens())
				{
					String szStyle = stStyle.nextToken();

					if (szStyle.equalsIgnoreCase("PLAIN"))
						nStyle |= Font.PLAIN;
					else if (szStyle.equalsIgnoreCase("BOLD"))
						nStyle |= Font.BOLD;
					else if (szStyle.equalsIgnoreCase("ITALIC"))
						nStyle |= Font.ITALIC;
				}

				nSize = Integer.valueOf(st.nextToken()).intValue();
			}
			catch(Exception e)
			{
				szFaceName = "Dialog";
				nStyle = Font.PLAIN;
				nSize = 8;
			}
		}

		return(new Font(szFaceName, nStyle, nSize));
	}

	/**
	 * Parse time zone information and return the information in an array.
	 *
	 * The input time zone information is specified as follows. All entries in brackets are optional, but if they are specified, the brackets are not to be included.
	 *
	 * [N][+/-]tz[,sm,sw,sd,st,em,ew,ed,et,shift]
	 *
	 * "N" indicates not to apply daylight savings time rules.
	 * "+/-" indicates either a positive or negative time zone offset. If neither is specified, a positive offset is assumed.
	 * "tz" indicates the time zone offset from Greenwich Mean Time (GMT). This number is specified as "hh[:mm]".
	 * "sm" indicates the month (from 1-12) in which daylight savings time starts.
	 * "sw" indicates the week of the month in which daylight savings time starts. It may be from 1 to 4 to indicate the week from the beginning of the month, or -1 to -4 to indicate the week from the ending of the month. It may also be 0, which affects the "sd" parameter (see comments below for the "sd" parameter).
	 * "sd" indicates the day of the week (0 for Sunday through 6 for Saturday) in which daylight savings time starts. If the "sw" parameter is 0, this number is interpreted as the day of the month (1-31) in which daylight savings time starts.
	 * "st" indicates the time on which daylight savings time starts. This number is specified as "hh[:mm]".
	 * "em" indicates the month (from 1-12) in which daylight savings time ends.
	 * "ew" indicates the week of the month in which daylight savings time ends. It may be from 1 to 4 to indicate the week from the beginning of the month, or -1 to -4 to indicate the week from the ending of the month. It may also be 0, which affects the "ed" parameter (see comments below for the "ed" parameter).
	 * "ed" indicates the day of the week (0 for Sunday through 6 for Saturday) in which daylight savings time ends. If the "ew" parameter is 0, this number is interpreted as the day of the month (1-31) in which daylight savings time ends.
	 * "et" indicates the time on which daylight savings time ends. This number is specified as "hh[:mm]".
	 * "shift" indicates the amount of time to shift for daylight savings time. This number is specified as "hh[:mm]".
	 *
	 * If "[+/-]tz" is specified by itself, the United States daylight savings time rules are applied.
	 */
	 private final int [] initTimeZone(String param)
	 {
	 	int [] rtn = { 12345678, 4, 1, 0, 7200, 10, -1, 0, 7200, 3600 };

		if (param != null && param.length() > 0)
		{
			int tzp = rtn[0];
			int smp = rtn[1];
			int swp = rtn[2];
			int sdp = rtn[3];
			int stp = rtn[4];
			int emp = rtn[5];
			int ewp = rtn[6];
			int edp = rtn[7];
			int etp = rtn[8];
			int shiftp = rtn[9];

			try
			{
				boolean negate = false;
				String timeParam;
				int comma;

				if (Character.toUpperCase(param.charAt(0)) == 'N')
				{
					shiftp = 0;
					param = param.substring(1);
				}
				if (param.charAt(0) == '+')
					param = param.substring(1);
				else if (param.charAt(0) == '-')
				{
					negate = true;
					param = param.substring(1);
				}

				timeParam = parseTimeZoneParam(param);
				if (timeParam.length() < param.length())
					param = param.substring(timeParam.length() + 1);
				else
					param = "";
				tzp = parseTime(timeParam);
				if (tzp < 0 || tzp > 12 * 3600)
					throw new Exception();
				if (negate)
					tzp = -tzp;

				if (param.length() != 0)
				{
					timeParam = parseTimeZoneParam(param);
					param = param.substring(timeParam.length() + 1);
					smp = Integer.valueOf(timeParam).intValue();
					if (smp < 1 || smp > 12)
						throw new Exception();

					timeParam = parseTimeZoneParam(param);
					param = param.substring(timeParam.length() + 1);
					swp = Integer.valueOf(timeParam).intValue();
					if (swp < -4 || swp > 4)
						throw new Exception();

					timeParam = parseTimeZoneParam(param);
					param = param.substring(timeParam.length() + 1);
					sdp = Integer.valueOf(timeParam).intValue();
					if ((swp != 0 && (sdp < 0 || sdp > 6)) || (swp == 0 && (sdp < 1 || sdp > 31)))
						throw new Exception();

					timeParam = parseTimeZoneParam(param);
					param = param.substring(timeParam.length() + 1);
					stp = parseTime(timeParam);
					if (stp == -1)
						throw new Exception();

					timeParam = parseTimeZoneParam(param);
					param = param.substring(timeParam.length() + 1);
					emp = Integer.valueOf(timeParam).intValue();
					if (emp < 1 || emp > 12)
						throw new Exception();

					timeParam = parseTimeZoneParam(param);
					param = param.substring(timeParam.length() + 1);
					ewp = Integer.valueOf(timeParam).intValue();
					if (ewp < -4 || ewp > 4)
						throw new Exception();

					timeParam = parseTimeZoneParam(param);
					param = param.substring(timeParam.length() + 1);
					edp = Integer.valueOf(timeParam).intValue();
					if ((ewp != 0 && (edp < 0 || edp > 6)) || (ewp == 0 && (edp < 1 || edp > 31)))
						throw new Exception();

					timeParam = parseTimeZoneParam(param);
					param = param.substring(timeParam.length() + 1);
					etp = parseTime(timeParam);
					if (etp == -1)
						throw new Exception();

					timeParam = parseTimeZoneParam(param);
					shiftp = parseTime(timeParam);
					if (shiftp == -1)
						throw new Exception();
				}

				rtn[0] = tzp;
				rtn[1] = smp;
				rtn[2] = swp;
				rtn[3] = sdp;
				rtn[4] = stp;
				rtn[5] = emp;
				rtn[6] = ewp;
				rtn[7] = edp;
				rtn[8] = etp;
				rtn[9] = shiftp;
			}
			catch(Exception e)
			{
			}
		}

		return(rtn);
	 }

	/**
	 * Parse the TimeZone parameter for the next substring (delimited by a comma).
	 */
	private final String parseTimeZoneParam(String param)
	{
		String rtn;

		try
		{
			int comma = param.indexOf(',');

			if (comma == -1)
				comma = param.length();
			rtn = param.substring(0, comma);
		}
		catch(Exception e)
		{
			rtn = new String();
		}

		return(rtn);
	}

	/**
	 * Parse a TimeZone substring, which should be in the format "hh[:mm]".
	 */
	private final int parseTime(String param)
	{
		StringTokenizer st = new StringTokenizer(param, ":");
		int h = 0, m = 0;
		int rtn = -1;

		try
		{
			h = Integer.valueOf(st.nextToken()).intValue();
			if (st.hasMoreTokens())
				m = Integer.valueOf(st.nextToken()).intValue();
			if (h >= 0 && h <= 23 && m >= 0 && m <= 59)
				rtn = (h * 60 + m) * 60;
		}
		catch(Exception e)
		{
			rtn = -1;
		}

		return(rtn);
	}

	/**
	 * Fix up a string, passed in as a parameter, to include date/time
	 * formats.
	 */
	private final String [] months = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" };
	private final String [] wkdays = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };

	private final String fixupString(String param, Date curDate)
	{
		String paramOut = new String();

		try
		{
			int beginIndex = 0;
			int endIndex = param.indexOf('%');

			while (endIndex != -1)
			{
				boolean bPoundFlag = false;
				String paramAppend;
				char ch;
				int i;

				paramOut += param.substring(beginIndex, endIndex++);
				ch = param.charAt(endIndex++);
				if (ch == '#')
				{
					bPoundFlag = true;
					ch = param.charAt(endIndex++);
				}

				switch(ch)
				{
					case 'a':
						paramAppend = wkdays[curDate.getDay()].substring(0, 3);
					break;

					case 'A':
						paramAppend = wkdays[curDate.getDay()];
					break;

					case 'b':
						paramAppend = months[curDate.getMonth()].substring(0, 3);
					break;

					case 'B':
						paramAppend = months[curDate.getMonth()];
					break;

					case 'c':
						if (!bPoundFlag)
							paramAppend = fixupString("%x %X", curDate);
						else
							paramAppend = fixupString("%#x %#X", curDate);
					break;

					case 'd':
						paramAppend = Integer.toString(curDate.getDate());
						if (!bPoundFlag && paramAppend.length() == 1)
							paramAppend = "0" + paramAppend;
					break;

					case 'H':
						paramAppend = Integer.toString(curDate.getHours());
						if (!bPoundFlag && paramAppend.length() == 1)
							paramAppend = "0" + paramAppend;
					break;

					case 'I':
						i = curDate.getHours() % 12;
						if (i == 0)
							i = 12;
						paramAppend = Integer.toString(i);
						if (!bPoundFlag && i < 10)
							paramAppend = "0" + paramAppend;
					break;

					case 'j':
						paramAppend = Long.toString(getDayOfYear(curDate));
						if (!bPoundFlag)
						{
							if (paramAppend.length() == 1)
								paramAppend = "0" + paramAppend;
							if (paramAppend.length() == 2)
								paramAppend = "0" + paramAppend;
						}
					break;

					case 'm':
						paramAppend = Integer.toString(curDate.getMonth() + 1);
						if (!bPoundFlag && paramAppend.length() == 1)
							paramAppend = "0" + paramAppend;
					break;

					case 'M':
						paramAppend = Integer.toString(curDate.getMinutes());
						if (!bPoundFlag && paramAppend.length() == 1)
							paramAppend = "0" + paramAppend;
					break;

					case 'p':
						paramAppend = (curDate.getHours() < 12) ? "AM" : "PM";
					break;

					case 'S':
						paramAppend = Integer.toString(curDate.getSeconds());
						if (!bPoundFlag && paramAppend.length() == 1)
							paramAppend = "0" + paramAppend;
					break;

					case 'U':
					{
						Date dateJan1 = new Date(curDate.getYear(), 0, 1);

						paramAppend = Long.toString((getDayOfYear(curDate) + (dateJan1.getDay() + 6) % 7) / 7);
						if (!bPoundFlag && paramAppend.length() == 1)
							paramAppend = "0" + paramAppend;
					}
					break;

					case 'w':
						paramAppend = Integer.toString(curDate.getDay());
					break;

					case 'W':
					{
						Date dateJan1 = new Date(curDate.getYear(), 0, 1);

						paramAppend = Long.toString((getDayOfYear(curDate) + (dateJan1.getDay() + 5) % 7) / 7);
						if (!bPoundFlag && paramAppend.length() == 1)
							paramAppend = "0" + paramAppend;
					}
					break;

					case 'x':
						if (!bPoundFlag)
							paramAppend = fixupString("%m/%d/%y", curDate);
						else
							paramAppend = fixupString("%A, %B %#d, %Y", curDate);
					break;

					case 'X':
						paramAppend = fixupString("%H:%M:%S", curDate);
					break;

					case 'y':
						paramAppend = Integer.toString(curDate.getYear());
						if (!bPoundFlag && paramAppend.length() == 1)
							paramAppend = "0" + paramAppend;
					break;

					case 'Y':
						paramAppend = Integer.toString(curDate.getYear() + 1900);
					break;

					case 'z':
					case 'Z':
						paramAppend = new String();
					break;

					case '%':
						paramAppend = "%";
					break;

					default:
						paramAppend = new String();
					break;
				}

				paramOut += paramAppend;
				beginIndex = endIndex;
				endIndex = param.indexOf('%', beginIndex);
			}

			paramOut += param.substring(beginIndex, param.length());
		}
		catch(Exception e)
		{
			paramOut = param;
		}

		return(paramOut);
	}

	/**
	 * Given a Date instance, calculate the number of days since the beginning
	 * of the year (1 - 366).
	 */
	private final long getDayOfYear(Date curDate)
	{
		return((curDate.UTC(curDate.getYear(), curDate.getMonth(), curDate.getDate(), 0, 0, 0) - curDate.UTC(curDate.getYear(), 0, 1, 0, 0, 0)) / 1000 / 60 / 60 / 24 + 1);
	}


	/**
	 * Load the file specified by the FILE= parameter.
	 */
	private final boolean loadFile()
	{
		URL codeBase = getCodeBase();
		Socket s = null;

		initGlobals();

		try
		{
			s = new Socket(codeBase.getHost(), 80);
		}
		catch(Exception e)
		{
			bErrorLoadingFile = true;
		}
		if (!bErrorLoadingFile)
		{
			DataInputStream is;
			DataOutputStream os;
			String getFileName, param;

			if (szFile.startsWith("/"))
				getFileName = szFile;
			else
			{
				getFileName = codeBase.getFile();
				if (!getFileName.endsWith("/"))
				{
					int index = getFileName.lastIndexOf("/");

					if (index >= 0)
						getFileName = codeBase.getFile().substring(0, index + 1);
					else
						getFileName = "/";
				}
				getFileName += szFile;
			}

			try
			{
				is = new DataInputStream(s.getInputStream());
				os = new DataOutputStream(s.getOutputStream());
				os.writeBytes("GET " + getFileName + "\n\n");
				for (param = is.readLine(); param != null; param = is.readLine())
				{
					int posDelim = param.indexOf('=');

					if (posDelim != -1 && posDelim < param.length() - 1)
					{
						String paramName = param.substring(0, posDelim);
						String paramValue = param.substring(posDelim + 1);

						if (paramName.equalsIgnoreCase("backcolor"))
							crBk = initColor(paramValue);
						else if (paramName.equalsIgnoreCase("litcolor"))
							crText = initColor(paramValue);
						else if (paramName.equalsIgnoreCase("unlitcolor"))
							crUnlit = initColor(paramValue);
						else if (paramName.equalsIgnoreCase("speed"))
							speed = Integer.valueOf(paramValue).intValue();
						else if (paramName.equalsIgnoreCase("pause"))
							pause = Integer.valueOf(paramValue).intValue();
						else if (paramName.equalsIgnoreCase("anchor"))
							szLink = new String(paramValue);
						else if (paramName.equalsIgnoreCase("frame"))
							szFrame = new String(paramValue);
						else if (paramName.equalsIgnoreCase("font"))
							font = initFont(paramValue);
						else if (paramName.equalsIgnoreCase("scrollright"))
							bScrollRight = (paramValue.compareTo("0") != 0);
						else if (paramName.equalsIgnoreCase("text"))
							initTickerText(paramValue);
						else if (paramName.equalsIgnoreCase("timezone"))
						{
							int [] tzInfo = initTimeZone(paramValue);

							tz = tzInfo[0];
							sm = tzInfo[1];
							sw = tzInfo[2];
							sd = tzInfo[3];
							st = tzInfo[4];
							em = tzInfo[5];
							ew = tzInfo[6];
							ed = tzInfo[7];
							et = tzInfo[8];
							shift = tzInfo[9];
						}
	                }
	            }
				is.close();
				os.close();
			}
			catch(Exception e)
			{
				bErrorLoadingFile = true;
			}
		}
		if (bErrorLoadingFile)
			repaint();

		return(!bErrorLoadingFile);
	}

	/**
	 * Initialize global variables
	 */
	private final void initGlobals()
	{
		String param;
		int [] tzInfo;

		ttHead = null;
		ttTail = null;
		ttCur = null;
		speed = 50;
		pause = 0;
		bScrollRight = false;
		font = initFont(null);
		szLink = null;
		szFrame = null;
		tz = 12345678;
		sm = 4;
		sw = 1;
		sd = 0;
		st = 7200;
		em = 10;
		ew = -1;
		ed = 0;
		et = 7200;
		shift = 3600;
		sizeBorder = 6;
		sizeStrongBorder = 1;
		sizeLED = 4;

		param = getParameter("backcolor");
		crBk = (param == null) ? Color.black : initColor(param);
		param = getParameter("litcolor");
		crText = (param == null) ? Color.red : initColor(param);
		param = getParameter("unlitcolor");
		crUnlit = (param == null) ? Color.darkGray : initColor(param);
		param = getParameter("bordercolor");
		crBorder = (param == null) ? Color.lightGray : initColor(param);
		param = getParameter("borderwidth");
		if (param != null)
			sizeBorder = Integer.valueOf(param).intValue();
		param = getParameter("strongborder");
		sizeStrongBorder = (param != null && param.compareTo("0") == 0) ? 0 : 1;
		param = getParameter("ledsize");
		if (param != null)
		{
			sizeLED = Integer.valueOf(param).intValue() + 1;
			if (sizeLED <= 1)
				sizeLED = 4;
		}
		param = getParameter("speed");
		if (param != null)
			speed = Integer.valueOf(param).intValue();
		param = getParameter("pause");
		if (param != null)
			pause = Integer.valueOf(param).intValue();
		param = getParameter("anchor");
		if (param != null)
			szLink = new String(param);
		param = getParameter("frame");
		if (param != null)
			szFrame = new String(param);
		param = getParameter("font");
		font = initFont(param);
		param = getParameter("scrollright");
		bScrollRight = (param != null && param.compareTo("0") != 0);
		param = getParameter("rstring");
		bReverseString = (param != null && param.compareTo("0") != 0);

		param = getParameter("timezone");
		tzInfo = initTimeZone(param);
		tz = tzInfo[0];
		sm = tzInfo[1];
		sw = tzInfo[2];
		sd = tzInfo[3];
		st = tzInfo[4];
		em = tzInfo[5];
		ew = tzInfo[6];
		ed = tzInfo[7];
		et = tzInfo[8];
		shift = tzInfo[9];
	}

	/**
	 * Handle mouse down events for potential links
	 */
	public boolean mouseDown(Event evt, int x, int y)
	{
		if (ttCur != null && ttCur.bInitialized && ttCur.szLink != null)
		{
			try
			{
				String szLink = new String(ttCur.szLink);
				URL url;

				if (!szLink.startsWith("http://"))
				{
					url = getCodeBase();
					szLink = url.getHost();
					if (!szLink.startsWith("http://"))
						szLink = "http://" + szLink;
					if (szLink.endsWith("/"))
						szLink = szLink.substring(0, szLink.length() - 1);
					if (!ttCur.szLink.startsWith("/"))
					{
						String szFile = url.getFile();

						if (!szFile.endsWith("/"))
						{
							int index = szFile.lastIndexOf("/");

							if (index >= 0)
								szFile = url.getFile().substring(0, index + 1);
							else
								szFile = "/";
						}
						szLink += szFile;
					}
					szLink += ttCur.szLink;
				}

				url = new URL(szLink);
				if (ttCur.szFrame == null)
					getAppletContext().showDocument(url);
				else
					getAppletContext().showDocument(url, ttCur.szFrame);
				getAppletContext().showStatus("");
			}
			catch (MalformedURLException e)
			{
			}
		}

		return(true);
	}

	/**
	 * Handle mouse enter events for potential links
	 */
	public boolean mouseEnter(Event evt, int x, int y)
	{
		if (ttCur != null && ttCur.bInitialized && ttCur.szLink != null)
			getAppletContext().showStatus("Shortcut to " + ttCur.szLink);

		bWeHaveMouse = true;

		return(true);
	}

	/**
	 * Handle mouse exit events for potential links
	 */
	public boolean mouseExit(Event evt, int x, int y)
	{
		if (ttCur != null && ttCur.bInitialized && ttCur.szLink != null)
			getAppletContext().showStatus("");

		bWeHaveMouse = false;

		return(true);
	}

	/**
	 * Run one of the threads. This method is called by class Thread.
	 * @see java.lang.Thread
	 */
	public void run()
	{
		if (Thread.currentThread() == refresh)
			runRefresh();
		else if (Thread.currentThread() == kicker)
			runKicker();
	}

	/**
	 * Run the file refresh thread.
	 */
	private final void runRefresh()
	{
		bTimeToRefresh = false;
		for ( ; ; )
		{
			try
			{
				Thread.sleep(fileRefresh);
			}
			catch (InterruptedException e)
			{
				break;
			}
			bTimeToRefresh = true;
		}
	}

	/**
	 * Run the image loop thread.
	 */
	private final void runKicker()
	{
		Thread.currentThread().setPriority(Thread.NORM_PRIORITY+1);
		
		Rectangle rec = bounds();
		int x, y;
		Graphics grUnlit;

		imDisplay = createImage(rec.width, rec.height);
		rec.width -= sizeBorder * 2;
		rec.height -= sizeBorder * 2;
		imUnlit = createImage(rec.width, rec.height);
		grUnlit = imUnlit.getGraphics();
		grUnlit.setColor(crBk);
		grUnlit.fillRect(0, 0, rec.width, rec.height);
		grUnlit.setColor(crUnlit);
		//
		// Under NT, if LEDSize is set to 2, fillOval won't draw anything.
		// We compensate here by calling FillRect instead.
		//
		if (sizeLED != 3)
		{
			for (y = 0; y < rec.height; y += sizeLED)
				for (x = 0; x < rec.width; x += sizeLED)
					grUnlit.fillOval(x, y, sizeLED - 1, sizeLED - 1);
		}
		else
		{
			for (y = 0; y < rec.height; y += sizeLED)
				for (x = 0; x < rec.width; x += sizeLED)
					grUnlit.fillRect(x, y, sizeLED - 1, sizeLED - 1);
		}
		grUnlit.dispose();

		rec = bounds();
		if (fileRefresh != 0)
		{
			ttHead = null;
			ttTail = null;
			ttCur = null;
		}

		if (szFile != null && ttHead == null)
		{
			bLoadingFile = true;
			repaint(10);
			if (!loadFile())
				return;
			bLoadingFile = false;
		}

		int phase = 1;
		int xFinal = 0;
		int yFinal = 0;
		int nSleep = 0;
		int nFlash = 0;
		int cxSpecial = 0;
		int cySpecial = 0;
		Date curDate = new Date();
		Date dstStartDate = new Date(0);
		Date dstEndDate = new Date(0);
		Graphics grDisplay;
		int cxDisplay, cyDisplay;

		x = 0;
		y = 0;
		grDisplay = imDisplay.getGraphics();
		if (sizeBorder != 0)
		{
			grDisplay.setColor(crBorder);
			grDisplay.fillRect(0, 0, rec.width, rec.height);
			if (sizeBorder > 1)
				grDisplay.draw3DRect(sizeStrongBorder, sizeStrongBorder, rec.width - 1 - 2 * sizeStrongBorder, rec.height - 1 - 2 * sizeStrongBorder, true);
			if (sizeBorder > 2)
				grDisplay.draw3DRect(sizeBorder - 1 - sizeStrongBorder, sizeBorder - 1 - sizeStrongBorder, rec.width - sizeBorder * 2 + 1 + 2 * sizeStrongBorder, rec.height - sizeBorder * 2 + 1 + 2 * sizeStrongBorder, false);
			if (sizeStrongBorder != 0)
			{
				grDisplay.setColor(Color.black);
				grDisplay.drawRect(0, 0, rec.width - 1, rec.height - 1);
				grDisplay.drawRect(sizeBorder - 1, sizeBorder - 1, rec.width - sizeBorder * 2 + 1, rec.height - sizeBorder * 2 + 1);
			}
		}
		grDisplay.setColor(crBk);
		grDisplay.fillRect(sizeBorder, sizeBorder, rec.width - sizeBorder * 2, rec.height - sizeBorder * 2);

		cxDisplay = rec.width - sizeBorder * 2;
		cyDisplay = rec.height - sizeBorder * 2;
		grDisplay.clipRect(sizeBorder, sizeBorder, cxDisplay, cyDisplay);
		grDisplay.translate(sizeBorder + 1, sizeBorder + 1);
		grDisplay.drawImage(imUnlit, 0, 0, this);
		grDisplay.dispose();
		repaint(10);

		ttCur = ttHead;

		while (ttCur != null && kicker != null)
		{
			if (ttCur.szString != null && phase == 1 && (!ttCur.bInitialized || ttCur.szString.indexOf('%') != -1))
			{
				curDate.setTime(System.currentTimeMillis());

				if (ttCur.tz != 12345678)
				{
					int offset1, offset2;

					curDate.setTime(curDate.getTime() + ttCur.tz * 1000);
					if (ttCur.shift != 0)
					{
						int year = curDate.getYear();

						dstStartDate = calcDSTDate(year, ttCur.sm, ttCur.sw, ttCur.sd, ttCur.st, curDate);
						dstEndDate = calcDSTDate(year, ttCur.em, ttCur.ew, ttCur.ed, ttCur.et, curDate);
						if (ttCur.em < ttCur.sm)
						{
							if (curDate.getTime() > dstEndDate.getTime())
								dstEndDate = calcDSTDate(year + 1, ttCur.em, ttCur.ew, ttCur.ed, ttCur.et, curDate);
							else
								dstStartDate = calcDSTDate(year - 1, ttCur.sm, ttCur.sw, ttCur.sd, ttCur.st, curDate);
						}
						if (curDate.getTime() >= dstStartDate.getTime() && curDate.getTime() + ttCur.shift * 1000 < dstEndDate.getTime())
							curDate.setTime(curDate.getTime() + ttCur.shift * 1000);
					}
					offset1 = calcTimezoneOffset(curDate);
					curDate.setTime(curDate.getTime() + offset1 * 60000);
					offset2 = calcTimezoneOffset(curDate);
					if (offset1 > offset2)
						curDate.setTime(curDate.getTime() - (offset1 - offset2) * 60000);
					else if (offset2 > offset1)
						curDate.setTime(curDate.getTime() + (offset2 - offset1) * 60000);
				}

				String szString = fixupString(ttCur.szString, curDate);
				Graphics grTicker;
				FontMetrics fm;
				int iDescent;

				if (ttCur.bReverseString)
				{
					String szTemp = "";
					int i;

					for (i = szString.length() - 1; i >= 0; i--)
						szTemp += szString.charAt(i);
					szString = szTemp;
				}

				Image imTemp = createImage(1, 1);
				Graphics grTemp = imTemp.getGraphics();

				grTemp.setFont(ttCur.font);
				fm = grTemp.getFontMetrics();
				ttCur.cxPixels = fm.stringWidth(szString);
				ttCur.cyPixels = fm.getHeight();
				iDescent = fm.getDescent();
				ttCur.cxImage = ttCur.cxPixels * sizeLED;
				ttCur.cyImage = ttCur.cyPixels * sizeLED;
				grTemp.dispose();

				ttCur.imTicker = createImage(ttCur.cxImage, ttCur.cyImage);
				grTicker = ttCur.imTicker.getGraphics();
				grTicker.setColor(crBk);
				grTicker.fillRect(0, 0, ttCur.cxImage, ttCur.cyImage);

				imTemp = createImage(ttCur.cxPixels, ttCur.cyPixels);
				grTemp = imTemp.getGraphics();
				grTemp.setColor(Color.black);
				grTemp.fillRect(0, 0, ttCur.cxPixels, ttCur.cyPixels);
				grTemp.setColor(Color.white);
				grTemp.setFont(ttCur.font);
				grTemp.drawString(szString, 0, ttCur.cyPixels - iDescent);
				grTemp.dispose();

				int [] pixels = new int[ttCur.cxPixels * ttCur.cyPixels];
				PixelGrabber pg = new PixelGrabber(imTemp, 0, 0, ttCur.cxPixels, ttCur.cyPixels, pixels, 0, ttCur.cxPixels);
				boolean bGrabbed = false;

				try
				{
					bGrabbed = pg.grabPixels();
				}
				catch (InterruptedException e)
				{
					bGrabbed = false;
				}

				if (bGrabbed)
				{
					//
					// Under NT, if LEDSize is set to 2, fillOval won't draw
					// anything. We compensate here by calling FillRect instead.
					//
					if (sizeLED != 3)
					{
						for (y = 0; y < ttCur.cyPixels; y++)
							for (x = 0; x < ttCur.cxPixels; x++)
							{
								grTicker.setColor((pixels[y * ttCur.cxPixels + x] == Color.black.getRGB()) ? crUnlit : ttCur.cr);
								grTicker.fillOval(x * sizeLED, y * sizeLED, sizeLED - 1, sizeLED - 1);
							}
					}
					else
					{
						for (y = 0; y < ttCur.cyPixels; y++)
							for (x = 0; x < ttCur.cxPixels; x++)
							{
								grTicker.setColor((pixels[y * ttCur.cxPixels + x] == Color.black.getRGB()) ? crUnlit : ttCur.cr);
								grTicker.fillRect(x * sizeLED, y * sizeLED, sizeLED - 1, sizeLED - 1);
							}
					}
				}

				grTicker.dispose();
				ttCur.bInitialized = true;
			}

			grDisplay = imDisplay.getGraphics();
			grDisplay.clipRect(sizeBorder, sizeBorder, cxDisplay, cyDisplay);
			grDisplay.translate(sizeBorder + 1, sizeBorder + 1);

			if (phase == 1)
			{
				if (bWeHaveMouse)
					getAppletContext().showStatus((ttCur.szLink != null) ? "Shortcut to " + ttCur.szLink : "");

				xFinal = (cxDisplay - ttCur.cxImage) / 2 / sizeLED * sizeLED;
				yFinal = (cyDisplay - ttCur.cyImage) / 2 / sizeLED * sizeLED;
				switch (ttCur.in)
				{
					case tickertext.RIGHTWARD:
						x = -ttCur.cxImage + sizeLED;
					break;

					case tickertext.UPWARD:
						y = cyDisplay / sizeLED * sizeLED - sizeLED;
					break;

					case tickertext.DOWNWARD:
						y = -ttCur.cyImage + sizeLED;
					break;

					case tickertext.XLEFTWARD:
						cxSpecial = sizeLED;
					break;

					case tickertext.XRIGHTWARD:
						cxSpecial = sizeLED;
					break;

					case tickertext.YUPWARD:
						cySpecial = sizeLED;
					break;

					case tickertext.YDOWNWARD:
						cySpecial = sizeLED;
					break;

					case tickertext.YOUTWARD:
						y = yFinal + ttCur.cyPixels / 2 * sizeLED;
						cySpecial = sizeLED * 2;
					break;

					case tickertext.YINWARD:
						cySpecial = sizeLED;
					break;

					case tickertext.XOUTWARD:
						x = xFinal + ttCur.cxPixels / 2 * sizeLED;
						cxSpecial = sizeLED * 2;
					break;

					case tickertext.XINWARD:
						cxSpecial = sizeLED;
					break;

					case tickertext.YROUTWARD:
						y = yFinal + ttCur.cyPixels / 2 * sizeLED;
						cySpecial = sizeLED;
					break;

					case tickertext.YRINWARD:
						y = ttCur.cyPixels / 2 * sizeLED;
						cySpecial = sizeLED;
					break;

					case tickertext.XROUTWARD:
						x = xFinal + ttCur.cxPixels / 2 * sizeLED;
						cxSpecial = sizeLED;
					break;

					case tickertext.XRINWARD:
						x = ttCur.cxPixels / 2 * sizeLED;
						cxSpecial = sizeLED;
					break;

					default:
						x = cxDisplay / sizeLED * sizeLED - sizeLED;
					break;
				}
				phase++;
			}

			if (phase == 2)
			{
				switch (ttCur.in)
				{
					case tickertext.RIGHTWARD:
						grDisplay.drawImage(ttCur.imTicker, x, yFinal, this);
						if (x > 0)
						{
							grDisplay.clipRect(0, 0, x, cyDisplay);
							grDisplay.drawImage(imUnlit, 0, 0, this);
						}
						x += sizeLED;
						if (x > xFinal)
							phase++;
					break;

					case tickertext.UPWARD:
						grDisplay.drawImage(imUnlit, 0, y, this);
						grDisplay.drawImage(ttCur.imTicker, xFinal, y, this);
						y -= sizeLED;
						if (y < yFinal)
							phase++;
					break;

					case tickertext.DOWNWARD:
						grDisplay.drawImage(imUnlit, 0, y, this);
						grDisplay.drawImage(ttCur.imTicker, xFinal, y, this);
						y += sizeLED;
						if (y > yFinal)
							phase++;
					break;

					case tickertext.XLEFTWARD:
						grDisplay.clipRect(cxDisplay / sizeLED * sizeLED - xFinal - cxSpecial, 0, xFinal + cxSpecial, cyDisplay);
						grDisplay.drawImage(ttCur.imTicker, xFinal, yFinal, this);
						cxSpecial += sizeLED;
						if (cxSpecial > ttCur.cxImage)
							phase++;
					break;

					case tickertext.XRIGHTWARD:
						grDisplay.clipRect(0, 0, xFinal + cxSpecial, cyDisplay);
						grDisplay.drawImage(ttCur.imTicker, xFinal, yFinal, this);
						cxSpecial += sizeLED;
						if (cxSpecial > ttCur.cxImage)
							phase++;
					break;

					case tickertext.YUPWARD:
						grDisplay.clipRect(0, cyDisplay / sizeLED * sizeLED - yFinal - cySpecial, cxDisplay, yFinal + cySpecial);
						grDisplay.drawImage(imUnlit, 0, 0, this);
						grDisplay.drawImage(ttCur.imTicker, xFinal, yFinal, this);
						cySpecial += sizeLED;
						if (cySpecial > ttCur.cyImage)
							phase++;
					break;

					case tickertext.YDOWNWARD:
						grDisplay.clipRect(0, 0, cxDisplay, yFinal + cySpecial);
						grDisplay.drawImage(imUnlit, 0, 0, this);
						grDisplay.drawImage(ttCur.imTicker, xFinal, yFinal, this);
						cySpecial += sizeLED;
						if (cySpecial > ttCur.cyImage)
							phase++;
					break;

					case tickertext.YOUTWARD:
						grDisplay.clipRect(0, y, cxDisplay, cySpecial);
						grDisplay.drawImage(imUnlit, 0, 0, this);
						grDisplay.drawImage(ttCur.imTicker, xFinal, yFinal, this);
						cySpecial += sizeLED * 2;
						y -= sizeLED;
						if (y < yFinal)
							phase++;
					break;

					case tickertext.YINWARD:
						grDisplay.clipRect(0, 0, cxDisplay, yFinal + cySpecial);
						grDisplay.drawImage(imUnlit, 0, 0, this);
						grDisplay.drawImage(ttCur.imTicker, xFinal, yFinal, this);
						grDisplay.dispose();
						grDisplay = imDisplay.getGraphics();
						grDisplay.clipRect(sizeBorder, sizeBorder, cxDisplay, cyDisplay);
						grDisplay.translate(sizeBorder + 1, sizeBorder + 1);
						grDisplay.clipRect(0, cyDisplay / sizeLED * sizeLED - yFinal - cySpecial, cxDisplay, yFinal + cySpecial);
						grDisplay.drawImage(imUnlit, 0, 0, this);
						grDisplay.drawImage(ttCur.imTicker, xFinal, yFinal, this);
						cySpecial += sizeLED;
						if (cySpecial > ttCur.cyImage / 2 + sizeLED)
							phase++;
					break;

					case tickertext.XOUTWARD:
						grDisplay.clipRect(x, 0, cxSpecial, cyDisplay);
						grDisplay.drawImage(imUnlit, 0, 0, this);
						grDisplay.drawImage(ttCur.imTicker, xFinal, yFinal, this);
						cxSpecial += sizeLED * 2;
						x -= sizeLED;
						if (x < xFinal)
							phase++;
					break;

					case tickertext.XINWARD:
						grDisplay.clipRect(0, 0, xFinal + cxSpecial, cyDisplay);
						grDisplay.drawImage(ttCur.imTicker, xFinal, yFinal, this);
						grDisplay.dispose();
						grDisplay = imDisplay.getGraphics();
						grDisplay.clipRect(sizeBorder, sizeBorder, cxDisplay, cyDisplay);
						grDisplay.translate(sizeBorder + 1, sizeBorder + 1);
						grDisplay.clipRect(cxDisplay / sizeLED * sizeLED - xFinal - cxSpecial, 0, xFinal + cxSpecial, cyDisplay);
						grDisplay.drawImage(ttCur.imTicker, xFinal, yFinal, this);
						cxSpecial += sizeLED;
						if (cxSpecial > ttCur.cxImage / 2 + sizeLED)
							phase++;
					break;

					case tickertext.YROUTWARD:
						grDisplay.clipRect(0, y - cySpecial, cxDisplay, cySpecial);
						grDisplay.drawImage(imUnlit, 0, 0, this);
						grDisplay.drawImage(ttCur.imTicker, xFinal, y - cySpecial, this);
						grDisplay.dispose();
						grDisplay = imDisplay.getGraphics();
						grDisplay.clipRect(sizeBorder, sizeBorder, cxDisplay, cyDisplay);
						grDisplay.translate(sizeBorder + 1, sizeBorder + 1);
						grDisplay.clipRect(0, y, cxDisplay, cySpecial);
						grDisplay.drawImage(imUnlit, 0, 0, this);
						grDisplay.drawImage(ttCur.imTicker, xFinal, y + cySpecial - ttCur.cyImage, this);
						cySpecial += sizeLED;
						if (cySpecial > ttCur.cyImage / 2 + sizeLED)
							phase++;
					break;

					case tickertext.YRINWARD:
						grDisplay.clipRect(0, 0, cxDisplay, cySpecial);
						grDisplay.drawImage(imUnlit, 0, 0, this);
						grDisplay.drawImage(ttCur.imTicker, xFinal, y + cySpecial - ttCur.cyImage, this);
						grDisplay.dispose();
						grDisplay = imDisplay.getGraphics();
						grDisplay.clipRect(sizeBorder, sizeBorder, cxDisplay, cyDisplay);
						grDisplay.translate(sizeBorder + 1, sizeBorder + 1);
						grDisplay.clipRect(0, cyDisplay / sizeLED * sizeLED - cySpecial, cxDisplay, cySpecial);
						grDisplay.drawImage(imUnlit, 0, 0, this);
						grDisplay.drawImage(ttCur.imTicker, xFinal, cyDisplay / sizeLED * sizeLED - cySpecial - y - sizeLED, this);
						cySpecial += sizeLED;
						if (cySpecial > cyDisplay / 2)
							phase++;
					break;

					case tickertext.XROUTWARD:
						grDisplay.clipRect(x - cxSpecial, 0, cxSpecial, cyDisplay);
						grDisplay.drawImage(imUnlit, 0, 0, this);
						grDisplay.drawImage(ttCur.imTicker, x - cxSpecial, yFinal, this);
						grDisplay.dispose();
						grDisplay = imDisplay.getGraphics();
						grDisplay.clipRect(sizeBorder, sizeBorder, cxDisplay, cyDisplay);
						grDisplay.translate(sizeBorder + 1, sizeBorder + 1);
						grDisplay.clipRect(x, 0, cxSpecial, cyDisplay);
						grDisplay.drawImage(imUnlit, 0, 0, this);
						grDisplay.drawImage(ttCur.imTicker, x + cxSpecial - ttCur.cxImage, yFinal, this);
						cxSpecial += sizeLED;
						if (cxSpecial > ttCur.cxImage / 2 + sizeLED)
							phase++;
					break;

					case tickertext.XRINWARD:
						grDisplay.clipRect(0, 0, cxSpecial, cyDisplay);
						grDisplay.drawImage(imUnlit, 0, 0, this);
						grDisplay.drawImage(ttCur.imTicker, x + cxSpecial - ttCur.cxImage, yFinal, this);
						grDisplay.dispose();
						grDisplay = imDisplay.getGraphics();
						grDisplay.clipRect(sizeBorder, sizeBorder, cxDisplay, cyDisplay);
						grDisplay.translate(sizeBorder + 1, sizeBorder + 1);
						grDisplay.clipRect(cxDisplay / sizeLED * sizeLED - cxSpecial, 0, cxSpecial, cyDisplay);
						grDisplay.drawImage(imUnlit, 0, 0, this);
						grDisplay.drawImage(ttCur.imTicker, cxDisplay / sizeLED * sizeLED - cxSpecial - x - sizeLED, yFinal, this);
						cxSpecial += sizeLED;
						if (cxSpecial > cxDisplay / 2)
							phase++;
					break;

					case tickertext.NONE:
						grDisplay.drawImage(ttCur.imTicker, xFinal, yFinal, this);
						phase++;
					break;

					default:
						grDisplay.drawImage(ttCur.imTicker, x, yFinal, this);
						if (x + ttCur.cxImage < cxDisplay / sizeLED * sizeLED)
						{
							grDisplay.clipRect(x + ttCur.cxImage, 0, cxDisplay - x, cyDisplay);
							grDisplay.drawImage(imUnlit, 0, 0, this);
						}
						x -= sizeLED;
						if (x < xFinal)
							phase++;
					break;
				}
			}

			if (phase == 3)
			{
				grDisplay.dispose();
				grDisplay = imDisplay.getGraphics();
				grDisplay.clipRect(sizeBorder, sizeBorder, cxDisplay, cyDisplay);
				grDisplay.translate(sizeBorder + 1, sizeBorder + 1);
				grDisplay.drawImage(imUnlit, 0, 0, this);
				grDisplay.drawImage(ttCur.imTicker, xFinal, yFinal, this);
				if (nSleep == 0 && ttCur.nPreFlash != 0)
					nSleep = ttCur.nPreFlash;
				else
				{
					nSleep = 0;
					phase++;
				}
			}

			if (phase == 4)
			{
				if (nSleep == 0)
				{
					if (ttCur.nPause == 0)
						nSleep = ttCur.speed;
					else if (ttCur.nFlash == 0)
						nSleep = ttCur.nPause;
					else
					{
						nSleep = ttCur.nPause / (ttCur.nFlash * 2);
						if (nSleep == 0)
							nSleep = 1;
						nFlash = 1;
					}
				}
				else if (nFlash == 0)
				{
					nSleep = 0;
					phase++;
				}
				else
				{
					if ((nFlash & 1) != 0)
						grDisplay.drawImage(imUnlit, 0, 0, this);
					else
						grDisplay.drawImage(ttCur.imTicker, xFinal, yFinal, this);
					nFlash++;
					if (nFlash > ttCur.nFlash * 2)
					{
						nSleep = 0;
						nFlash = 0;
						phase++;
					}
				}
			}

			if (phase == 5)
			{
				if (nSleep == 0 && ttCur.nPostFlash != 0)
					nSleep = ttCur.nPostFlash;
				else
				{
					nSleep = 0;
					phase++;
				}
			}

			if (phase == 6)
			{
				x = xFinal;
				y = yFinal;
				switch (ttCur.out)
				{
					case tickertext.RIGHTWARD:
						x += sizeLED;
						xFinal = cxDisplay / sizeLED * sizeLED;
					break;

					case tickertext.UPWARD:
						y -= sizeLED;
						yFinal = -ttCur.cyImage;
					break;

					case tickertext.DOWNWARD:
						y += sizeLED;
						yFinal = cyDisplay / sizeLED * sizeLED;
					break;

					case tickertext.XLEFTWARD:
						cxSpecial = sizeLED;
					break;

					case tickertext.XRIGHTWARD:
						cxSpecial = sizeLED;
					break;

					case tickertext.YUPWARD:
						cySpecial = sizeLED;
					break;

					case tickertext.YDOWNWARD:
						cySpecial = sizeLED;
					break;

					case tickertext.YOUTWARD:
						y += ttCur.cyPixels / 2 * sizeLED;
						cySpecial = sizeLED * 2;
					break;

					case tickertext.YINWARD:
						cySpecial = sizeLED;
					break;

					case tickertext.XOUTWARD:
						x += ttCur.cxPixels / 2 * sizeLED;
						cxSpecial = sizeLED * 2;
					break;

					case tickertext.XINWARD:
						cxSpecial = sizeLED;
					break;

					case tickertext.YROUTWARD:
						y += ttCur.cyPixels / 2 * sizeLED;
						cySpecial = sizeLED;
					break;

					case tickertext.YRINWARD:
						y = cyDisplay / 2 / sizeLED * sizeLED;
						cySpecial = sizeLED;
					break;

					case tickertext.XROUTWARD:
						x += ttCur.cxPixels / 2 * sizeLED;
						cxSpecial = sizeLED;
					break;

					case tickertext.XRINWARD:
						x = cxDisplay / 2 / sizeLED * sizeLED;
						cxSpecial = sizeLED;
					break;

					default:
						x -= sizeLED;
						xFinal = -ttCur.cxImage;
					break;
				}
				phase++;
			}

			if (phase == 7)
			{
				switch (ttCur.out)
				{
					case tickertext.RIGHTWARD:
						grDisplay.drawImage(ttCur.imTicker, x, yFinal, this);
						if (x > 0)
						{
							grDisplay.clipRect(0, 0, x, cyDisplay);
							grDisplay.drawImage(imUnlit, 0, 0, this);
						}
						x += sizeLED;
						if (x >= xFinal)
							phase++;
					break;

					case tickertext.UPWARD:
						grDisplay.drawImage(ttCur.imTicker, xFinal, y, this);
						grDisplay.drawImage(imUnlit, 0, y + ttCur.cyImage, this);
						y -= sizeLED;
						if (y <= yFinal)
							phase++;
					break;

					case tickertext.DOWNWARD:
						grDisplay.drawImage(ttCur.imTicker, xFinal, y, this);
						grDisplay.drawImage(imUnlit, 0, y - sizeLED - cyDisplay / sizeLED * sizeLED, this);
						y += sizeLED;
						if (y >= yFinal)
							phase++;
					break;

					case tickertext.XLEFTWARD:
						grDisplay.drawImage(imUnlit, xFinal + ttCur.cxImage - cxSpecial, 0, this);
						cxSpecial += sizeLED;
						if (cxSpecial > ttCur.cxImage)
							phase++;
					break;

					case tickertext.XRIGHTWARD:
						grDisplay.drawImage(imUnlit, xFinal + cxSpecial - cxDisplay / sizeLED * sizeLED, 0, this);
						cxSpecial += sizeLED;
						if (cxSpecial > ttCur.cxImage)
							phase++;
					break;

					case tickertext.YUPWARD:
						grDisplay.drawImage(imUnlit, 0, yFinal + ttCur.cyImage - cySpecial, this);
						cySpecial += sizeLED;
						if (cySpecial > ttCur.cyImage)
							phase++;
					break;

					case tickertext.YDOWNWARD:
						grDisplay.drawImage(imUnlit, 0, yFinal + cySpecial - cyDisplay / sizeLED * sizeLED, this);
						cySpecial += sizeLED;
						if (cySpecial > ttCur.cyImage)
							phase++;
					break;

					case tickertext.YOUTWARD:
						grDisplay.clipRect(0, y, cxDisplay, cySpecial);
						grDisplay.drawImage(imUnlit, 0, 0, this);
						cySpecial += sizeLED * 2;
						y -= sizeLED;
						if (y < yFinal)
							phase++;
					break;

					case tickertext.YINWARD:
						grDisplay.drawImage(imUnlit, 0, yFinal + cySpecial - cyDisplay / sizeLED * sizeLED, this);
						grDisplay.drawImage(imUnlit, 0, yFinal + ttCur.cyImage - cySpecial, this);
						cySpecial += sizeLED;
						if (cySpecial > ttCur.cyImage / 2 + sizeLED)
							phase++;
					break;

					case tickertext.XOUTWARD:
						grDisplay.clipRect(x, 0, cxSpecial, cyDisplay);
						grDisplay.drawImage(imUnlit, 0, 0, this);
						cxSpecial += sizeLED * 2;
						x -= sizeLED;
						if (x < xFinal)
							phase++;
					break;

					case tickertext.XINWARD:
						grDisplay.drawImage(imUnlit, xFinal + cxSpecial - cxDisplay / sizeLED * sizeLED, 0, this);
						grDisplay.drawImage(imUnlit, xFinal + ttCur.cxImage - cxSpecial, 0, this);
						cxSpecial += sizeLED;
						if (cxSpecial > ttCur.cxImage / 2 + sizeLED)
							phase++;
					break;

					case tickertext.YROUTWARD:
						grDisplay.copyArea(0, 0, cxDisplay, y, 0, -sizeLED);
						grDisplay.copyArea(0, y, cxDisplay, cyDisplay / sizeLED * sizeLED - y, 0, sizeLED);
						grDisplay.clipRect(0, y - cySpecial, cxDisplay, cySpecial * 2);
						grDisplay.drawImage(imUnlit, 0, 0, this);
						cySpecial += sizeLED;
						if (cySpecial > cyDisplay / 2 + sizeLED)
							phase++;
					break;

					case tickertext.YRINWARD:
						grDisplay.copyArea(0, 0, cxDisplay, y, 0, sizeLED);
						grDisplay.copyArea(0, y + sizeLED, cxDisplay, y - sizeLED, 0, -sizeLED);
						grDisplay.drawImage(imUnlit, 0, cySpecial - cyDisplay / sizeLED * sizeLED, this);
						grDisplay.drawImage(imUnlit, 0, cyDisplay / sizeLED * sizeLED - cySpecial, this);
						cySpecial += sizeLED;
						if (cySpecial > ttCur.cyPixels / 2 * sizeLED)
							phase++;
					break;

					case tickertext.XROUTWARD:
						grDisplay.copyArea(0, 0, x, cyDisplay, -sizeLED, 0);
						grDisplay.copyArea(x, 0, cxDisplay / sizeLED * sizeLED - x, cyDisplay, sizeLED, 0);
						grDisplay.clipRect(x - cxSpecial, 0, cxSpecial * 2, cyDisplay);
						grDisplay.drawImage(imUnlit, 0, 0, this);
						cxSpecial += sizeLED;
						if (cxSpecial > cxDisplay / 2 + sizeLED)
							phase++;
					break;

					case tickertext.XRINWARD:
						grDisplay.copyArea(0, 0, x, cyDisplay, sizeLED, 0);
						grDisplay.copyArea(x + sizeLED, 0, x - sizeLED, cyDisplay, -sizeLED, 0);
						grDisplay.drawImage(imUnlit, cxSpecial - cxDisplay / sizeLED * sizeLED, 0, this);
						grDisplay.drawImage(imUnlit, cxDisplay / sizeLED * sizeLED - cxSpecial, 0, this);
						cxSpecial += sizeLED;
						if (cxSpecial > ttCur.cxPixels / 2 * sizeLED)
							phase++;
					break;

					case tickertext.NONE:
						phase++;
					break;

					default:
						grDisplay.drawImage(ttCur.imTicker, x, yFinal, this);
						if (x + ttCur.cxImage < cxDisplay / sizeLED * sizeLED)
						{
							grDisplay.clipRect(x + ttCur.cxImage, 0, cxDisplay - x, cyDisplay);
							grDisplay.drawImage(imUnlit, 0, 0, this);
						}
						x -= sizeLED;
						if (x <= xFinal)
							phase++;
					break;
				}
			}

			if (phase == 8)
			{
				if (ttCur.out != tickertext.NONE)
				{
					grDisplay.dispose();
					grDisplay = imDisplay.getGraphics();
					grDisplay.clipRect(sizeBorder, sizeBorder, cxDisplay, cyDisplay);
					grDisplay.translate(sizeBorder + 1, sizeBorder + 1);
					grDisplay.drawImage(imUnlit, 0, 0, this);
				}
				if (!bTimeToRefresh)
					ttCur = (ttCur.next != null) ? ttCur.next : ttHead;
				else
				{
					ttHead = null;
					ttTail = null;
					ttCur = null;
					if (!loadFile())
						break;
					ttCur = ttHead;
					bTimeToRefresh = false;
				}
				phase = 1;
			}

			grDisplay.dispose();
			repaint(ttCur.speed / 5);

			try
			{
				Thread.sleep((nSleep != 0) ? nSleep : ttCur.speed);
			}
			catch (InterruptedException e)
			{
				break;
			}
		}
	}

	/**
	 * Calculate the date for Daylight Savings Time calculations.
	 */
	private final Date calcDSTDate(int year, int m, int w, int d, int t, Date curDate)
	{
		int hr = t / 3600;
		int min = (t % 3600) / 60;
		int sec = t % 60;
		int mon, rtnTimezoneOffset;
		Date rtnDate;

		mon = m - 1;
		if (w == 0)
			rtnDate = new Date(year, mon, d, hr, min, sec);
		else if (w > 0)
		{
			rtnDate = new Date(year, mon, 1, hr, min, sec);
			rtnDate.setTime(rtnDate.getTime() + (((7 + d - rtnDate.getDay()) % 7) + (w - 1) * 7) * 86400 * 1000);
		}
		else
		{
			rtnDate = new Date(year, mon + 1, 1, hr, min, sec);
			rtnDate.setTime(rtnDate.getTime() + (((7 + d - rtnDate.getDay()) % 7) - (-w * 7)) * 86400 * 1000);
		}
		rtnTimezoneOffset = calcTimezoneOffset(rtnDate);
		rtnDate.setTime(rtnDate.getTime() - rtnTimezoneOffset * 60000);
		if (calcTimezoneOffset(rtnDate) > rtnTimezoneOffset)
			rtnDate.setTime(rtnDate.getTime() - (calcTimezoneOffset(rtnDate) - rtnTimezoneOffset) * 60000);

		return(rtnDate);
	}

	/**
	 * Return the time zone offset for a given date. The value from Date.getTimezoneOffset is
	 * not always accurate, so this routine attempts to correct it.
	 */
	private final int calcTimezoneOffset(Date curDate)
	{
		int offset = curDate.getTimezoneOffset();

		if (offset >= 0)
			offset = (int)((offset + 15) / 30) * 30;
		else
			offset = (int)((offset - 15) / 30) * 30;

		return(offset);
	}

	public boolean imageUpdate(Image img, int flags, int x, int y, int w, int h)
	{
		repaint(50);
		return(true);
	}

	public void update(Graphics g)
	{
		paint(g);
	}

	/**
	 * Paint the current frame.
	 */
	public void paintAll(Graphics g)
	{
		paint(g);
	}

	public void paint(Graphics g)
	{
		if (!bLoadingFile)
			g.drawImage(imDisplay, 0, 0, this);
		else
		{
			FontMetrics fm = g.getFontMetrics();
			Rectangle rec = bounds();
			String szLoading = (!bErrorLoadingFile) ? "Loading text strings..." : "Error loading text strings!";

			g.setColor(Color.lightGray);
			g.fillRect(0, 0, rec.width, rec.height);
			g.setColor(Color.black);
			g.drawString(szLoading, (rec.width - fm.stringWidth(szLoading)) / 2, rec.height / 2);
		}
	}

	/**
	 * Start the applet by forking an animation thread.
	 */
	public void start()
	{
		if (kicker == null)
		{
			kicker = new Thread(this);
			kicker.start();
		}
		if (fileRefresh != 0 && refresh == null)
		{
			refresh = new Thread(this);
			refresh.start();
		}
	}

	/**
	 * Stop the applet. The thread will exit because kicker is set to null.
	 */
	public void stop()
	{
		if (kicker != null)
		{
			kicker.stop();
			kicker = null;
		}
		if (refresh != null)
		{
			refresh.stop();
			refresh = null;
		}
	}
}

