/*
* javex.java - 19 Jan 1996 - Version 1.03
*
* Note: This source code is subject to perpetual refinement!
*
* Copyright 1996 by Bill Giel
*
* E-mail: rvdi@usa.nai.net
* WWW: http://www.nai.net/~rvdi/home.htm
*
*
* Revision 1.01 - revised hms class to calculate GMT. This permits the clock
* 10 Feb 96 to be used to display the time at any location by supplying
* a TIMEZONE parameter in the HTML call.
*
* Revision 1.02 - revised timezone to accept real numbers, for places like
* 11 Feb 96 India, with a 5.5 hour time difference. I learn something
* new everyday!
*
* Revision 1.03 - fixed loop in run() to exit if clockThread==null, rather
* than simple for(;;)
*
* Usage:
*
*
* Permission to use, copy, modify, and distribute this software
* and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and
* without fee is hereby granted, provided that any use properly credits
* the author, i.e. "Javex Clock courtesy of
* Bill Giel.
*
*
* 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.
*
* THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE
* CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE
* PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT
* NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE
* SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
* SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE
* PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES"). YOU WOULD
* HAVE TO BE OUT OF YOUR MIND TO USE THIS SOFTWARE IN A HIGH-RISK ENVIRONMENT
* AND, AS SUCH MAY BE THE CASE, THE AUTHOR SPECIFICALLY DISCLAIMS ANY EXPRESS
* OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES, INCLUDING BUT NOT
* LIMITED TO USE OR MISUSE OF THIS SOFTWARE RESULTING IN NUCLEAR EXPLOSIONS,
* GLOBAL WARMING OR OZONE-LAYER DEPLETION.
*/
import java.applet.Applet;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MediaTracker;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
class Hms extends Date {
// Note that localOffset is hours difference from GMT
// west of Greenwich meridian is positive, east is negative.
// i.e. New York City (Eastern Standard Time) is +5
// Eastern Daylight Time is +4
public Hms(double localOffset) {
super();
long tzOffset = getTimezoneOffset() * 60L * 1000L;
setTime(getTime() + tzOffset - (long) (localOffset * 3600.0 * 1000.0));
}
public double get_hours() {
return (double) super.getHours() + (double) getMinutes() / 60.0;
}
}
abstract class ClockHand {
protected int baseX[], baseY[];
protected int transX[], transY[];
protected int numberOfPoints;
public ClockHand(int originX, int originY, int length, int thickness,
int points) {
baseX = new int[points];
baseY = new int[points];
transX = new int[points];
transY = new int[points];
initiallizePoints(originX, originY, length, thickness);
numberOfPoints = points;
}
abstract protected void initiallizePoints(int originX, int originY,
int length, int thickness);
abstract public void draw(Color color, double angle, Graphics g);
protected void transform(double angle) {
for (int i = 0; i < numberOfPoints; i++) {
transX[i] = (int) ((baseX[0] - baseX[i]) * Math.cos(angle)
- (baseY[0] - baseY[i]) * Math.sin(angle) + baseX[0]);
transY[i] = (int) ((baseX[0] - baseX[i]) * Math.sin(angle)
+ (baseY[0] - baseY[i]) * Math.cos(angle) + baseY[0]);
}
}
}
class SweepHand extends ClockHand {
public SweepHand(int originX, int originY, int length, int points) {
super(originX, originY, length, 0, points);
}
protected void initiallizePoints(int originX, int originY, int length,
int unused) {
unused = unused; // We don't use the thickness parameter in this
// class
// This comes from habit to prevent compiler warning
// concerning unused arguments.
baseX[0] = originX;
baseY[0] = originY;
baseX[1] = originX;
baseY[1] = originY - length / 5;
baseX[2] = originX;
baseY[2] = originY + length;
}
public void draw(Color color, double angle, Graphics g) {
transform(angle);
g.setColor(color);
g.drawLine(transX[1], transY[1], transX[2], transY[2]);
}
}
class HmHand extends ClockHand {
public HmHand(int originX, int originY, int length, int thickness,
int points) {
super(originX, originY, length, thickness, points);
}
protected void initiallizePoints(int originX, int originY, int length,
int thickness) {
baseX[0] = originX;
baseY[0] = originY;
baseX[1] = baseX[0] - thickness / 2;
baseY[1] = baseY[0] + thickness / 2;
baseX[2] = baseX[1];
baseY[2] = baseY[0] + length - thickness;
baseX[3] = baseX[0];
baseY[3] = baseY[0] + length;
baseX[4] = baseX[0] + thickness / 2;
baseY[4] = baseY[2];
baseX[5] = baseX[4];
baseY[5] = baseY[1];
}
public void draw(Color color, double angle, Graphics g) {
transform(angle);
g.setColor(color);
g.fillPolygon(transX, transY, numberOfPoints);
}
}
public class Javex extends Applet implements Runnable {
// some DEFINE'd constants
static final int BACKGROUND = 0; // Background image index
static final int LOGO = 1; // Logo image index
static final String JAVEX = "JAVEX"; // Default text on clock face
static final double MINSEC = 0.104719755; // Radians per minute or second
static final double HOUR = 0.523598776; // Radians per hour
Thread clockThread = null;
// User options, see getParameterInfo(), below.
int width = 100;
int height = 100;
Color bgColor = new Color(0, 0, 0);
Color faceColor = new Color(0, 0, 0);
Color sweepColor = new Color(255, 0, 0);
Color minuteColor = new Color(192, 192, 192);
Color hourColor = new Color(255, 255, 255);
Color textColor = new Color(255, 255, 255);
Color caseColor = new Color(0, 0, 0);
Color trimColor = new Color(192, 192, 192);
String logoString = null;
Image images[] = new Image[2]; // Array to hold optional images
boolean isPainted = false; // Force painting on first update, if not
// painted
// Center point of clock
int x1, y1;
// Array of points for triangular icon at 12:00
int xPoints[] = new int[3], yPoints[] = new int[3];
// Class to hold time, with method to return (double)(hours + minutes/60)
Hms cur_time;
// The clock's seconds, minutes, and hours hands.
SweepHand sweep;
HmHand minuteHand, hourHand;
// The last parameters used to draw the hands.
double lastHour;
int lastMinute, lastSecond;
// The font used for text and date.
Font font;
// Offscreen image and device context, for buffered output.
Image offScrImage;
Graphics offScrGC;
// Use to test background image, if any.
MediaTracker tracker;
int minDimension; // Ensure a round clock if applet is not square.
int originX; // Upper left corner of a square enclosing the clock
int originY; // with respect to applet area.
double tzDifference = 0;
// Users' parameters - self-explanatory?
public String[][] getParameterInfo() {
String[][] info = {
{ "width", "int", "width of the applet, in pixels" },
{ "height", "int", "height of the applet, in pixels" },
{ "bgColor", "string",
"hex color triplet of the background, i.e. 000000 for black " },
{ "faceColor", "string",
"hex color triplet of clock face, i.e. 000000 for black " },
{ "sweepColor", "string",
"hex color triplet of seconds hand, i.e. FF0000 for red " },
{ "minuteColor", "string",
"hex color triplet of minutes hand, i.e. C0C0C0 for lt.gray " },
{ "hourColor", "string",
"hex color triplet of hours hand, i.e. FFFFFF for white " },
{ "textColor", "string",
"hex color triplet of numbers, etc., i.e. FFFFFF for white " },
{ "caseColor", "string",
"hex color triplet of case, i.e. 000000 for black " },
{ "trimColor", "string",
"hex color triplet of case outliners, i.e. C0C0C0 for lt.gray " },
{ "bgImageURL", "string",
"URL of background image, if any " },
{ "logoString", "string",
"Name to display on watch face " },
{ "logoImageURL", "string",
"URL of logo image to display on watch face " },
{ "timezone", "real",
"Timezone difference from GMT (decimal hours,+ West/- East)<0>" }, };
return info;
}
// Applet name, author and info lines
public String getAppletInfo() {
return "JAVEX 1.03 - analog clock applet by Bill Giel, 2 Mar 96\nhttp://www.nai.net/~rvdi/home.htm or rvdi@usa.nai.net";
}
void showURLerror(Exception e) {
String errorMsg = "JAVEX URL error: " + e;
showStatus(errorMsg);
System.err.println(errorMsg);
}
// This lets us create clocks of various sizes, but with the same
// proportions.
private int size(int percent) {
return (int) ((double) percent / 100.0 * (double) minDimension);
}
public void init() {
URL imagesURL[] = new URL[2];
String szImagesURL[] = new String[2];
tracker = new MediaTracker(this);
String paramString = getParameter("WIDTH");
if (paramString != null)
width = Integer.valueOf(paramString).intValue();
paramString = getParameter("HEIGHT");
if (paramString != null)
height = Integer.valueOf(paramString).intValue();
paramString = getParameter("TIMEZONE");
if (paramString != null)
tzDifference = Double.valueOf(paramString).doubleValue();
paramString = getParameter("BGCOLOR");
if (paramString != null)
bgColor = parseColorString(paramString);
paramString = getParameter("FACECOLOR");
if (paramString != null)
faceColor = parseColorString(paramString);
paramString = getParameter("SWEEPCOLOR");
if (paramString != null)
sweepColor = parseColorString(paramString);
paramString = getParameter("MINUTECOLOR");
if (paramString != null)
minuteColor = parseColorString(paramString);
paramString = getParameter("HOURCOLOR");
if (paramString != null)
hourColor = parseColorString(paramString);
paramString = getParameter("TEXTCOLOR");
if (paramString != null)
textColor = parseColorString(paramString);
paramString = getParameter("CASECOLOR");
if (paramString != null)
caseColor = parseColorString(paramString);
paramString = getParameter("TRIMCOLOR");
if (paramString != null)
trimColor = parseColorString(paramString);
logoString = getParameter("LOGOSTRING");
if (logoString == null)
logoString = JAVEX;
else if (logoString.length() > 8)
logoString = logoString.substring(0, 8); // Max 8 characters!
szImagesURL[BACKGROUND] = getParameter("BGIMAGEURL");
szImagesURL[LOGO] = getParameter("LOGOIMAGEURL");
for (int i = 0; i < 2; i++) {
if (szImagesURL[i] != null) {
try {
imagesURL[i] = new URL(getDocumentBase(), szImagesURL[i]);
} catch (MalformedURLException e) {
showURLerror(e);
imagesURL[i] = null;
images[i] = null;
}
if (imagesURL[i] != null) {
showStatus("Javex loading image: "
+ imagesURL[i].toString());
images[i] = getImage(imagesURL[i]);
if (images[i] != null)
tracker.addImage(images[i], i);
showStatus("");
}
if (images[i] != null)
try {
tracker.waitForID(i);
} catch (InterruptedException e) {
images[i] = null;
}
} else
images[i] = null;
}
cur_time = new Hms(tzDifference);
lastHour = -1.0;
lastMinute = -1;
lastSecond = -1;
x1 = width / 2;
y1 = height / 2;
minDimension = Math.min(width, height);
originX = (width - minDimension) / 2;
originY = (height - minDimension) / 2;
xPoints[1] = x1 - size(3);
xPoints[2] = x1 + size(3);
xPoints[0] = x1;
yPoints[1] = y1 - size(38);
yPoints[2] = y1 - size(38);
yPoints[0] = y1 - size(27);
sweep = new SweepHand(x1, y1, size(40), 3);
minuteHand = new HmHand(x1, y1, size(40), size(6), 6);
hourHand = new HmHand(x1, y1, size(25), size(8), 6);
font = new Font("TXT", Font.BOLD, size(10));
offScrImage = createImage(width, height);
offScrGC = offScrImage.getGraphics();
}
public void start() {
if (clockThread == null) {
clockThread = new Thread(this);
clockThread.start();
}
}
public void stop() {
if (null != clockThread) {
clockThread.stop();
clockThread = null;
}
}
private void drawHands(Graphics g) {
double angle;
int i, j;
int x, y;
angle = MINSEC * lastSecond;
sweep.draw(faceColor, angle, g);
if (cur_time.getMinutes() != lastMinute) {
minuteHand.draw(faceColor, MINSEC * lastMinute, g);
if (cur_time.get_hours() != lastHour)
hourHand.draw(faceColor, HOUR * lastHour, g);
}
g.setColor(textColor);
g.fillRect(originX + size(12), y1 - size(2), size(10), size(4));
g.fillRect(x1 - size(2), originY + minDimension - size(22), size(4),
size(10));
g.fillPolygon(xPoints, yPoints, 3);
for (i = 1; i < 12; i += 3)
for (j = i; j < i + 2; j++) {
x = (int) (x1 + Math.sin(HOUR * j) * size(35));
y = (int) (y1 - Math.cos(HOUR * j) * size(35));
g.fillOval(x - size(3), y - size(3), size(6), size(6));
}
// Set the font and get font info...
g.setFont(font);
FontMetrics fm = g.getFontMetrics();
// Paint our logo...
g.drawString(logoString, x1 - fm.stringWidth(logoString) / 2, y1
- size(12));
// Get the day of the month...
String day = Integer.toString(cur_time.getDate(), 10);
// Paint it...
g.drawString(day, originX + minDimension - size(14)
- fm.stringWidth(day), y1 + size(5));
// and put a box around it.
g.drawRect(originX + minDimension - size(14) - fm.stringWidth(day)
- size(2), y1 - size(5) - size(2), fm.stringWidth(day)
+ size(4), size(10) + size(4));
if (images[LOGO] != null) {
x = originX + (minDimension - images[LOGO].getWidth(this)) / 2;
y = y1
+ (minDimension / 2 - size(22) - images[LOGO]
.getHeight(this)) / 2;
if (x > 0 && y > 0)
offScrGC.drawImage(images[LOGO], x, y, this);
}
lastHour = cur_time.get_hours();
hourHand.draw(hourColor, HOUR * lastHour, g);
lastMinute = cur_time.getMinutes();
minuteHand.draw(minuteColor, MINSEC * lastMinute, g);
g.setColor(minuteColor);
g.fillOval(x1 - size(4), y1 - size(4), size(8), size(8));
g.setColor(sweepColor);
g.fillOval(x1 - size(3), y1 - size(3), size(6), size(6));
lastSecond = cur_time.getSeconds();
angle = MINSEC * lastSecond;
sweep.draw(sweepColor, angle, g);
g.setColor(trimColor);
g.fillOval(x1 - size(1), y1 - size(1), size(2), size(2));
}
private Color parseColorString(String colorString) {
if (colorString.length() == 6) {
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();
return new Color(R, G, B);
} else
return Color.lightGray;
}
public void run() {
// Let's not hog the system, now...
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
repaint();
while (null != clockThread) {
cur_time = new Hms(tzDifference);
repaint();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
System.exit(0);
}
public void paint(Graphics g) {
int i, x0, y0, x2, y2;
if (images[BACKGROUND] == null) {
offScrGC.setColor(bgColor);
offScrGC.fillRect(0, 0, width, height);
} else
offScrGC.drawImage(images[BACKGROUND], 0, 0, this);
offScrGC.setColor(caseColor);
// Shrink one pixel so we don't clip anything off...
offScrGC.fillOval(originX + 1, originY + 1, minDimension - 2,
minDimension - 2);
offScrGC.setColor(faceColor);
offScrGC.fillOval(originX + size(5), originY + size(5), minDimension
- size(10), minDimension - size(10));
offScrGC.setColor(trimColor);
offScrGC.drawOval(originX + 1, originY + 1, minDimension - 2,
minDimension - 2);
offScrGC.drawOval(originX + size(5), originY + size(5), minDimension
- size(10), minDimension - size(10));
offScrGC.setColor(textColor);
// Draw graduations, a longer index every fifth mark...
for (i = 0; i < 60; i++) {
if (i == 0 || (i >= 5 && i % 5 == 0)) {
x0 = (int) (x1 + size(40) * Math.sin(MINSEC * i));
y0 = (int) (y1 + size(40) * Math.cos(MINSEC * i));
} else {
x0 = (int) (x1 + size(42) * Math.sin(MINSEC * i));
y0 = (int) (y1 + size(42) * Math.cos(MINSEC * i));
}
x2 = (int) (x1 + size(44) * Math.sin(MINSEC * i));
y2 = (int) (y1 + size(44) * Math.cos(MINSEC * i));
offScrGC.drawLine(x0, y0, x2, y2);
}
drawHands(offScrGC);
g.drawImage(offScrImage, 0, 0, this);
isPainted = true;
}
public synchronized void update(Graphics g) {
if (!isPainted)
paint(g);
else {
drawHands(offScrGC);
g.drawImage(offScrImage, 0, 0, this);
}
}
}