/*
* AnalogClock.java v1.0 Jan 8, 1997
*
* Copyright (c) 1997 H.J. Tsai, Inc. All Rights Reserved.
*
* Permission to use, copy, modify, and distribute this software
* and its documentation for any purposes and without
* fee is hereby granted provided that this copyright notice
* appears in all copies.
*
* H.J. Tsai 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. H.J. Tsai SHALL NOT BE LIABLE FOR
* ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
* DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
*
* Version 1.0 Jan 8 1996 Initial version
*/
import java.lang.Math;
import java.util.*;
import java.awt.*;
import java.applet.*;
public class AnalogClock extends Clock {
static final private String clockname = "AnalogClock 97";
static final private String clocksmith = "Clocksmith: H.J. Tsai (c) 1997";
static final private int SHOW_MARK_ALL = 1;
static final private int SHOW_MARK_LARGE = 2;
static final private int SHOW_MARK_NONE = 3;
private int showMark = SHOW_MARK_ALL;
private Color clrMark = Color.black;
private boolean showRoundFrame = true;
private int roundFrameWidth = 5;
private Color clrRoundFrame = Color.red;
private Color clrHandHour = Color.blue;
private Color clrHandMinute = Color.blue;
private Color clrHandSecond = Color.red;
private Color clrCenterDot = Color.black;
private Color clrClockFace = Color.yellow;
private Color clrClockBackground = #ffffee;
// the lengths of tails of the hands
// tails are the other ends of the clock hands (closer to the center)
private int lenHourTail = 8;
private int lenMinuteTail = 10;
private int lenSecondTail = 16;
// widths of the clock hands (distance from the center to the tip of the side)
private int widthHour = 5;
private int widthMinute = 4;
private int widthSecond = 3;
private int radiusCenterDot = 2;
private boolean counterClockwise = false;
private boolean showFrame = true;
private Color clrFrame = Color.black;
private int frameThickness = 5;
private int frameStyle = Rect.RS_NORMAL;
private int outBevel = 0;
private int outBevelStyle = Rect.RS_3DRAISED;
private int inBevel = 0;
private int inBevelStyle = Rect.RS_3DINSET;
// program options
protected boolean showZoneTime = true; /// false;
protected long tzdiff = 0L; // diff between local and target zone in ms
protected int tztarget; // diff between GMT and target zone in minutes
// internal variables
private RectFrame rectFrame;
private Rect rectClock;
private int radius;
private int xcenter;
private int ycenter;
private double xtrigtable[] = new double [61]; // work around netscape 3.0 bug
private double ytrigtable[] = new double [60];
private int lasts = -1;
private int lastm = -1;
private int lasth = -1;
private int xpoints[] = new int[4];
private int ypoints[] = new int[4];
// last hand positions on the screen
private Point slastp[] = new Point[4];
private Point mlastp[] = new Point[4];
private Point hlastp[] = new Point[4];
private Point p0 = new Point(0,0);
private Point p1 = new Point(0,0);
private Point p2 = new Point(0,0);
private Point p3 = new Point(0,0);
private Image memImage;
private boolean showCopyright = true;
private Point marks[] = new Point[60];
private Point marks2[] = new Point[60];
public String getAppletInfo() {
String info = clockname + " " + clocksmith;
return info;
}
public String [][] getParamterInfo() {
String info[][] = {
{"ShowFrame", "boolean", "option to display the frame"},
{"OutBevel", "integer", "Outer frame bevel width"},
{"OutBevelStyle", "String", "Outer frame bevel style"},
{"InBevel", "integer", "Inner Frame bevel width"},
{"InBevelStyle", "String", "Inner frame bevel style"},
{"FrameStyle", "String", "various styles for the frame"},
{"FrameThickness", "integer", "the thickness of the clock frame"},
{"FrameColor", "String", "color for the clock frame"},
};
return info;
}
//
// formula for ellipses
// x**2/a**2 + y**2/b**2 = 1;
// b = sqrt(a**2 - c**2);
//
// if (b > a)
// swap(a,b) a = b, b = a;
// x2/a2 = 1 - y2/b2
//
public void init() {
super.init();
double sintable[] = new double [8];
double costable[] = new double [8];
double delta = Math.PI * 2.0 / 60.0;
double angle = 0f;
for (int i = 0; i < 8; i++) {
sintable[i] = Math.sin(angle);
costable[i] = Math.cos(angle);
angle += delta;
}
// setup the trig table to have 0-59
// clock marker positions]
xtrigtable[0] = 0;
ytrigtable[0] = 1;
// 0-15
for (int i = 1; i <= 7; i++) {
xtrigtable[i] = sintable[i];
xtrigtable[7+i] = costable[8-i];
ytrigtable[i] = costable[i];
ytrigtable[7+i] = sintable[8-i];
}
// 16-59
for (int i=0; i < 15; i++) {
xtrigtable[15+i] = ytrigtable[i];
ytrigtable[15+i] = -xtrigtable[i];
xtrigtable[30+i] = -xtrigtable[i];
ytrigtable[30+i] = -ytrigtable[i];
xtrigtable[45+i] = -ytrigtable[i];
ytrigtable[45+i] = xtrigtable[i];
}
// xtrigtable[] and ytrigtable[] now has the standard
// (x,y) coordinates in the cartensian space
// where (0,0) is the origin and the radius is 1
// On the screen <0,0> is upper left hand corner,
// y values are the exact oppsite to what we have
// so negate them
for (int i = 0; i < 60; i++) {
ytrigtable[i] = -ytrigtable[i];
}
Rectangle r = bounds();
memImage = createImage(r.width, r.height);
// get program options
getProgramParams();
if (showZoneTime) {
super.setTimezoneDiff(tztarget);
}
// for the couter clock wise
if (counterClockwise)
for (int i = 0; i < 60; i++)
xtrigtable[i] = -xtrigtable[i];
// set up the size for the clock and its frame
if (showFrame) {
rectFrame = new RectFrame(r,
outBevel,
outBevelStyle,
frameThickness,
frameStyle,
clrFrame,
inBevel,
inBevelStyle);
rectClock = rectFrame.getInsideRect();
}
else {
rectClock = new Rect(r);
}
radius = Math.min(rectClock.width, rectClock.height);
radius /= 2;
// leave some room
radius -= roundFrameWidth;
xcenter = rectClock.x + rectClock.width / 2;
ycenter = rectClock.y + rectClock.height / 2;
for (int i = 0 ; i < 4; i++) {
slastp[i] = new Point(0,0);
mlastp[i] = new Point(0,0);
hlastp[i] = new Point(0,0);
}
for (int i = 0; i < 60; i++) {
marks[i] = new Point(0,0);
marks2[i] = new Point(0,0);
}
double a, b;
a = (rectClock.width-roundFrameWidth)/2.0;
b = (rectClock.height-roundFrameWidth)/2.0;
// make a,b smaller, so the marks will be inside
// the elipse, not on it
a -= 2;
b -= 2;
int x, y;
int x2 = 0;
int y2 = 0;
int r2;
//System.out.println("RectClock=" + rectClock);
//System.out.println("a="+a+",b="+b);
double longmark;
double shortmark;
if (a == b) {
longmark = 0.85;
shortmark = 0.95;
}
else {
longmark = 0.85;
shortmark = 0.95;
}
for (int i = 0; i <= 15; i++) {
r2 = radius;
// find the closest distance (radius)
// where <x,y> is still inside the elipse
do {
x = (int)(xtrigtable[i] * r2);
y = (int)(ytrigtable[i] * r2);
r2 += 2;
} while((x*x)/(a*a) + (y*y)/(b*b) < 0.95);
// we found it,
// find the coordinates for the marker
if (i % 5 == 0) {
x2 = (int) (xtrigtable[i] * r2 * longmark);
y2 = (int) (ytrigtable[i] * r2 * longmark);
}
else {
x2 = (int) (xtrigtable[i] * r2 * shortmark);
y2 = (int) (ytrigtable[i] * r2 * shortmark);
}
marks[i].x = x;
marks[i].y = y;
marks[30-i].x = x;
marks[30-i].y = -y;
marks[30+i].x = -x;
marks[30+i].y = -y;
marks[(60-i)%60].x = -x;
marks[(60-i)%60].y = y;
marks2[i].x = x2;
marks2[i].y = y2;
marks2[30-i].x = x2;
marks2[30-i].y = -y2;
marks2[30+i].x = -x2;
marks2[30+i].y = -y2;
marks2[(60-i)%60].x = -x2;
marks2[(60-i)%60].y = y2;
}
for (int i = 0; i <= 59; i++) {
marks[i].y += ycenter;
marks[i].x += xcenter;
marks2[i].x += xcenter;
marks2[i].y += ycenter;
}
}
/**
* Applet starts running
*/
public void start() {
show();
super.start();
}
/**
* Applet stops running
*/
public void stop() {
super.stop();
}
public void tick() {
repaint();
}
public void update(Graphics g) {
paint(g);
}
public void paint(Graphics g) {
if (showCopyright) {
showCopyright(g, clockname, clocksmith);
showCopyright = false;
return;
}
// update the clock in offscreen memory image
Graphics memg = memImage.getGraphics();
// clear old time
memg.setPaintMode();
Rectangle r = g.getClipRect();
memg.clipRect(r.x, r.y, r.width, r.height);
memg.setColor(clrClockBackground);
memg.fillRect(r.x, r.y, r.width, r.height);
if (showFrame)
drawFrame(memg);
if (showRoundFrame) {
drawRoundFrame(memg);
}
if (showMark != SHOW_MARK_NONE) {
drawMarks(memg);
}
int s = getDisplaySeconds();
int m = getDisplayMinutes();
int h = getDisplayHours();
//System.err.println("h="+h + " m="+m + " s=" + s);
// hour positions: 1-12 only
if (h >= 12)
h -= 12;
drawHand(memg, h*5 + m/12,
hlastp, clrHandHour, radius/2, lenHourTail, widthHour);
drawHand(memg, m, mlastp, clrHandMinute, radius*3/4, lenMinuteTail, widthMinute);
drawHand(memg, s, slastp, clrHandSecond, radius, lenSecondTail, widthSecond);
memg.setColor(clrCenterDot);
int circlesize = radiusCenterDot + radiusCenterDot; // * 2;
memg.fillOval(xcenter-radiusCenterDot,
ycenter-radiusCenterDot,
circlesize, circlesize);
// copy the offscreen image to the real screen
g.drawImage(memImage, 0, 0, null);
}
public void drawHand(Graphics g,
int t,
Point lastp[],
Color c,
int radius,
int tail,
int width) {
// new coordinate for the hand's tip
Point tip = new Point(0,0);
tip.x = (int)(xtrigtable[t] * radius + xcenter);
tip.y = (int)(ytrigtable[t] * radius + ycenter);
// new coordinate for the second's rear end and side points
p0.x = xcenter + (int)(xtrigtable[(t+30)%60] * tail);
p0.y = ycenter + (int)(ytrigtable[(t+30)%60] * tail);
p1.x = xcenter + (int)(xtrigtable[(t+15)%60] * width);
p1.y = ycenter + (int)(ytrigtable[(t+15)%60] * width);
p2.x = xcenter + (int)(xtrigtable[(t+45)%60] * width);
p2.y = ycenter + (int)(ytrigtable[(t+45)%60] * width);
g.setColor(c);
xpoints[0] = p0.x;
ypoints[0] = p0.y;
xpoints[1] = p1.x;
ypoints[1] = p1.y;
xpoints[2] = tip.x;
ypoints[2] = tip.y;
xpoints[3] = p2.x;
ypoints[3] = p2.y;
g.fillPolygon(xpoints, ypoints, 4);
}
public void drawFrame(Graphics g) {
rectFrame.paint(g);
}
public void drawRoundFrame(Graphics g) {
g.setColor(clrRoundFrame);
for (int i = 0; i < roundFrameWidth; i++)
g.drawArc(rectClock.x+i,
rectClock.y+i,
rectClock.width-i*2,
rectClock.height-i*2,
0, 360);
g.setColor(clrClockFace);
g.fillArc(rectClock.x+roundFrameWidth,
rectClock.y+roundFrameWidth,
rectClock.width-roundFrameWidth*2,
rectClock.height-roundFrameWidth*2,
0, 360);
}
public void drawMarks(Graphics g) {
int x0, y0;
int x1, y1;
double d;
g.setColor(clrMark);
if (showMark == SHOW_MARK_NONE)
return;
if (showMark == SHOW_MARK_ALL) {
for (int i = 0; i < 60; i++) {
g.drawLine(marks[i].x, marks[i].y,
marks2[i].x, marks2[i].y);
}
}
else { // show large marks only
for (int i = 0; i < 60; i += 5) {
g.drawLine(marks[i].x, marks[i].y,
marks2[i].x, marks2[i].y);
}
}
}
/**
* Gets program pamameters
*/
protected void getProgramParams() {
// get program parameters
String arg;
arg = getParameter("Author");
if (arg != null && arg.equalsIgnoreCase("hjtsai@cargobay.com"))
showCopyright = false;
else
showCopyright = true;
// TimeZone desired in minutes
arg = getParameter("TimeZone");
if (arg == null)
showZoneTime = false;
else {
try {
tztarget = Integer.parseInt(arg);
// System.err.println("param timezone=" + tztarget);
showZoneTime = true;
} catch (NumberFormatException e) {
showZoneTime = false;
}
}
// show second
arg = getParameter("CounterClockwise");
if (arg != null)
counterClockwise = arg.equalsIgnoreCase("yes")
|| arg.equalsIgnoreCase("true");
arg = getParameter("ShowMark");
if (arg.equalsIgnoreCase("none"))
showMark = SHOW_MARK_NONE;
else if (arg.equalsIgnoreCase("large"))
showMark = SHOW_MARK_LARGE;
else
showMark = SHOW_MARK_ALL;
// Round Frame Color
arg = getParameter("MarkColor");
if (arg != null)
clrMark = ColorValue.s2color(arg, Color.black);
arg = getParameter("ShowRoundFrame");
if (arg != null)
showRoundFrame = arg.equalsIgnoreCase("yes")
|| arg.equalsIgnoreCase("true");
roundFrameWidth = getNumParam("roundFrameWidth", 4);
if (roundFrameWidth < 1)
roundFrameWidth = 1;
// Round Frame Color
arg = getParameter("RoundFrameColor");
if (arg != null)
clrRoundFrame = ColorValue.s2color(arg, Color.black);
// clock face color
arg = getParameter("ClockFaceColor");
if (arg != null)
clrClockFace = ColorValue.s2color(arg, Color.yellow);
// BackgroundColor
arg = getParameter("BackgroundColor");
if (arg != null)
clrClockBackground = ColorValue.s2color(arg, Color.lightGray);
// Hand Colors
arg = getParameter("HourHandColor");
if (arg != null)
clrHandHour = ColorValue.s2color(arg, Color.blue);
arg = getParameter("MinuteHandColor");
if (arg != null)
clrHandMinute = ColorValue.s2color(arg, Color.blue);
arg = getParameter("SecondHandColor");
if (arg != null)
clrHandSecond = ColorValue.s2color(arg, Color.red);
// hand sizes
lenHourTail = getNumParam("LengthHourTail", 8);
if (lenHourTail < 1)
lenHourTail = 8;
lenMinuteTail = getNumParam("LengthMinuteTail", 12);
if (lenMinuteTail < 1)
lenMinuteTail = 12;
lenSecondTail = getNumParam("LengthSecondTail", 16);
if (lenSecondTail < 1)
lenSecondTail = 16;
// hand fatness
widthHour = getNumParam("HourWaistSize", 5);
if (widthHour < 1)
widthHour = 5;
widthMinute = getNumParam("MinuteWaistSize", 4);
if (widthMinute < 1)
widthMinute = 4;
widthSecond = getNumParam("SecondWaistSize", 3);
if (widthSecond < 1)
widthSecond = 3;
// center circle color
arg = getParameter("CenterDotColor");
if (arg != null)
clrCenterDot = ColorValue.s2color(arg, Color.black);
// center dot size
radiusCenterDot = getNumParam("CenterDotRadius", 2);
if (radiusCenterDot < 0)
radiusCenterDot = 2;
// show clock frame
arg = getParameter("ShowFrame");
if (arg != null)
showFrame = arg.equalsIgnoreCase("yes")
|| arg.equalsIgnoreCase("true");
// clock frame color
arg = getParameter("FrameColor");
if (arg != null)
clrFrame = ColorValue.s2color(arg, Color.black);
// get clock frame thickness
frameThickness = getNumParam("FrameThickness", 4);
if (frameThickness < 1)
frameThickness = 1;
}
/**
* get the specified numeric programeter param, negative number now allowed
*/
public int getNumParam(String param, int defaultvalue) {
String arg = getParameter(param);
int i = 0;
if (arg == null)
i = defaultvalue;
else {
try {
i = Integer.parseInt(arg);
} catch (NumberFormatException e) {
i = defaultvalue;
}
}
if (i < 0)
i = 0;
return i;
}
/**
* Displays a copyright statement
*/
public void showCopyright(Graphics g, String clockname, String author) {
Rectangle r = bounds();
// clear background
Color c = Color.lightGray;
g.setColor(c);
g.fillRect(r.x, r.y, r.width, r.height);
// draw a frame
RectFrame rectFrame = new RectFrame(r,
2,
Rect.RS_3DRAISED,
3,
Rect.RS_NORMAL,
Color.lightGray,
2,
Rect.RS_ETCHEDIN);
rectFrame.paint(g);
// create the marquee for displaying copyright
Rectangle newr = new Rectangle(r.x+7,r.y+7, r.width-14, r.height-14);
g.clipRect(newr.x, newr.y, newr.width, newr.height);
// the amount=1 and delay=5 generate fast and smooth scrolling
// unfortuntely, Netscape Navigator V3.0 Java VM seems unable
// to handle short delay (delay=4 is too fast)
Marquee marquee = new Marquee(
newr,
1, // loop
5,// 1, // amount
100, // 5, // delay
Marquee.LEFT, // direction
Marquee.SCROLL, // behavior
clockname+" "+author,
Color.lightGray, // background
Color.darkGray, // foreground
this);
Thread mthread = new Thread(marquee);
mthread.start();
// wait until the marquee thread ends
while (mthread.isAlive()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
mthread.stop();
mthread = null;
}
}