components/visual-panels/core/src/java/util/com/oracle/solaris/vp/util/swing/time/AnalogClock.java
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
*/
package com.oracle.solaris.vp.util.swing.time;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.beans.*;
import java.util.Calendar;
import javax.swing.*;
import org.jdesktop.animation.timing.*;
import com.oracle.solaris.vp.util.misc.finder.Finder;
import com.oracle.solaris.vp.util.swing.*;
@SuppressWarnings({"serial"})
public class AnalogClock extends JPanel implements MouseListener,
MouseMotionListener, TimingTarget {
//
// Static data
//
private static final int _DIAMETER = 150;
//
// Instance data
//
private TimeModel model;
private TimeSelectionModel selModel;
private PropertyChangeListener timeListener =
new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent event) {
timeChanged();
}
};
// Cached images to speed up repaints
private BufferedImage baseImage;
private BufferedImage timeImage;
private BufferedImage centerImage;
private int frameWidth;
private Point centerPoint;
private Point mousePoint;
private boolean interactive;
private boolean useToolTips;
private SunIcon sunIcon = new SunIcon();
private MoonIcon moonIcon = new MoonIcon();
private StarIcon starIcon = new StarIcon();
private Shape amPm;
private Point hourPoint;
private Point minPoint;
private Point secPoint;
private Animator amPmAnimator = new Animator(300, this);
private float amPmAnimOffset;
private int amPmAnimFactor;
// UI prefs
private Color centerColor = Color.black;
private Color frameColor = ColorUtil.darker(
UIManager.getColor("Panel.background"), .2f);
private Color faceColor = Color.white;
private Color hourHandColor = Color.black;
private Color hourMarkColor = Color.black;
private float lightAngle = (float)Math.toRadians(122);
private Color minuteHandColor = Color.black;
private Color minuteMarkColor = Color.gray;
private Color secondHandColor = Color.red.darker();
private Color highlightColor = new Color(0, 0, 204);
private Color amColor = new Color(148, 188, 246);
private Color pmColor = new Color(0, 0, 78);
//
// Constructors
//
/**
* Constructs an {@code AnalogClock} for the given model.
*/
public AnalogClock(TimeModel model) {
setTimeModel(model);
setTimeSelectionModel(new SimpleTimeSelectionModel());
setOpaque(false);
setPreferredDiameter(_DIAMETER);
}
/**
* Constructs an {@code AnalogClock} with a zero {@link
* TimeModel#getTimeOffset offset} from the system clock.
*/
public AnalogClock() {
this(new SimpleTimeModel());
}
//
// MouseListener methods
//
@Override
public void mouseClicked(MouseEvent e) {
int selectedField = selModel.getSelectedField();
if (selectedField == Calendar.AM_PM && GUIUtil.isUnmodifiedClick(e, 1))
{
amPmAnimOffset = (float)Math.PI;
amPmAnimFactor = model.getCalendar().get(
selectedField) == Calendar.AM ? 1 : -1;
// 43200000 = 1000 * 60 * 60 * 12 = milliseconds in 12-hour change
long delta = amPmAnimFactor * 43200000;
model.setTimeOffset(model.getTimeOffset() + delta);
amPmAnimator.start();
}
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
mousePoint = null;
repaint();
}
@Override
public void mousePressed(MouseEvent e) {
mousePoint = null;
repaint();
}
@Override
public void mouseReleased(MouseEvent e) {
mouseMoved(e);
}
//
// MouseMotionListener methods
//
@Override
public void mouseDragged(MouseEvent e) {
int selectedField = selModel.getSelectedField();
if (selectedField != Calendar.HOUR &&
selectedField != Calendar.MINUTE &&
selectedField != Calendar.SECOND) {
return;
}
Point centerPoint = this.centerPoint;
Point dragPoint = e.getPoint();
int dY = centerPoint.y - dragPoint.y;
int dX = dragPoint.x - centerPoint.x;
float angle = (float)Math.atan2(dY, dX);
int newVal;
int period;
if (selectedField == Calendar.HOUR) {
newVal = (int)radiansToHour(angle);
period = 12;
} else {
newVal = (int)radiansToMin(angle);
period = 60;
}
Calendar cal = model.getCalendar();
int curVal = cal.get(selectedField);
int factor = (newVal - curVal + period) % period;
if (factor >= period / 2) {
factor -= period;
}
long timeOffset = model.getTimeOffset() + factor *
timeFieldToMillis(selectedField);
model.setTimeOffset(timeOffset);
}
@Override
public void mouseMoved(MouseEvent e) {
mousePoint = e.getPoint();
repaint();
}
//
// TimingTarget methods
//
@Override
public void begin() {
}
@Override
public void end() {
}
@Override
public void repeat() {
}
@Override
public void timingEvent(float fraction) {
amPmAnimOffset = amPmAnimFactor * (float)Math.PI * (1 - fraction);
timeImage = null;
repaint();
}
//
// JComponent methods
//
@Override
protected void paintComponent(Graphics g1) {
super.paintComponent(g1);
Graphics2D g = (Graphics2D)g1;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Insets insets = getInsets();
int width = getWidth() - insets.left - insets.right;
int height = getHeight() - insets.top - insets.bottom;
if (width <= 0 || height <= 0) {
return;
}
// TimeModel is square
int diameter = Math.min(width, height);
int diff = height - width;
int hDiff = diff / 2;
if (diff > 0) {
diameter = width;
insets.top += hDiff;
insets.bottom += diff - hDiff;
} else if (diff < 0) {
diameter = height;
insets.left -= hDiff;
insets.right -= diff - hDiff;
}
g.translate(insets.left, insets.top);
createBaseImage(diameter);
g.drawImage(baseImage, 0, 0, null);
g.translate(frameWidth, frameWidth);
diameter -= 2 * frameWidth;
int radius = Math.round(diameter/ 2f);
createTimeImage(diameter);
g.drawImage(timeImage, 0, 0, null);
Point centerPoint = new Point(radius, radius);
Point mousePoint = this.mousePoint;
if (mousePoint != null) {
int selectedField = -1;
mousePoint = (Point)mousePoint.clone();
mousePoint.translate(-frameWidth - insets.left,
-frameWidth - insets.top);
Line2D line = new Line2D.Float();
line.setLine(centerPoint, hourPoint);
double hourDist = line.ptSegDist(mousePoint);
line.setLine(centerPoint, minPoint);
double minDist = line.ptSegDist(mousePoint);
line.setLine(centerPoint, secPoint);
double secDist = line.ptSegDist(mousePoint);
// Is the mouse close enough to a hand to call it selected?
double closest = Math.min(hourDist, Math.min(minDist, secDist));
if (closest / diameter <= .02f) {
if (hourDist == closest) {
selectedField = Calendar.HOUR;
drawHourHand(g, diameter, centerPoint, hourPoint);
} else if (minDist == closest) {
selectedField = Calendar.MINUTE;
drawMinuteHand(g, diameter, centerPoint, minPoint);
} else {
selectedField = Calendar.SECOND;
drawSecondHand(g, diameter, centerPoint, secPoint);
}
} else if (amPm.contains(mousePoint)) {
selectedField = Calendar.AM_PM;
}
setSelectedField(selectedField);
}
createCenterImage(diameter);
g.drawImage(centerImage, 0, 0, null);
centerPoint.translate(frameWidth + insets.left,
frameWidth + insets.top);
this.centerPoint = centerPoint;
g.translate(-frameWidth - insets.left, -frameWidth - insets.top);
}
//
// AnalogClock methods
//
protected float amPmToRadians() {
Calendar cal = model.getCalendar();
int hour = cal.get(Calendar.HOUR);
int minute = cal.get(Calendar.MINUTE);
int second = cal.get(Calendar.SECOND);
int amPm = cal.get(Calendar.AM_PM);
return amPmToRadians(amPm, hour, minute, second);
}
protected Shape drawAmPm(Graphics2D g, int diameter) {
// Create shapes
float outerMargin = diameter * .17f;
float innerMargin = diameter * .105f;
diameter -= Math.round(2 * outerMargin);
float radius = diameter / 2f;
int offset = Math.round(outerMargin);
g.translate(offset, offset);
Arc2D.Float outer = new Arc2D.Float(0, 0,
diameter, diameter, 0, 180, Arc2D.OPEN);
int innerDiam = Math.round(2 * innerMargin);
Arc2D.Float inner = new Arc2D.Float(radius - innerMargin,
radius - innerMargin, innerDiam, innerDiam, 180, -180, Arc2D.OPEN);
Line2D.Float[] lines = {
new Line2D.Float(outer.getEndPoint(), inner.getStartPoint()),
new Line2D.Float(inner.getEndPoint(), outer.getStartPoint()),
};
GeneralPath window = new GeneralPath(outer);
window.append(lines[0], true);
window.append(inner, true);
window.append(lines[1], true);
window.closePath();
// Fill background
float angle = (float)Math.PI - amPmToRadians() - amPmAnimOffset;
float cos = (float)Math.cos(angle);
float sin = (float)Math.sin(angle);
float xDelta = radius * cos;
float yDelta = radius * sin;
g.setPaint(new GradientPaint(
Math.round(radius + xDelta), Math.round(radius + yDelta), amColor,
Math.round(radius - xDelta), Math.round(radius - yDelta), pmColor));
g.fill(window);
// Paint icons
Shape clip = g.getClip();
g.setClip(window);
float arcWidth = radius - innerMargin;
float midPointToCtr = innerMargin + arcWidth * .5f;
int sunIconDiam = (int)(arcWidth * .7f);
float sunIconRad = sunIconDiam / 2f;
xDelta = midPointToCtr * cos;
yDelta = midPointToCtr * sin;
sunIcon.setDiameter(sunIconDiam);
sunIcon.paintIcon(this, g, Math.round(radius + xDelta - sunIconRad),
Math.round(radius + yDelta - sunIconRad));
int moonIconDiam = (int)(sunIconDiam * sunIcon.getOrbSizePercentage());
float moonIconRad = moonIconDiam / 2f;
moonIcon.setDiameter(moonIconDiam);
moonIcon.paintIcon(this, g, Math.round(radius - xDelta - moonIconRad),
Math.round(radius - yDelta - moonIconRad));
int starIconDiam = (int)(moonIconDiam * .4f);
float starIconRad = starIconDiam / 2f;
starIcon.setDiameter(starIconDiam);
// Angle (degree) offset from moon, percent distance between arcs
float[] starData = {
-48f, .55f,
-30f, .22f,
-25f, .7f,
5f, .85f,
20f, .25f,
25f, .65f,
45f, .35f,
};
for (int i = 0; i < starData.length; i += 2) {
float starAngle = (float)Math.toRadians(starData[i]);
float starDist = innerMargin + arcWidth * starData[i + 1];
int x = Math.round(radius - starDist *
(float)Math.cos(angle + starAngle) - starIconRad);
int y = Math.round(radius - starDist *
(float)Math.sin(angle + starAngle) - starIconRad);
starIcon.paintIcon(this, g, x, y);
}
g.setClip(clip);
// Paint inset border
Stroke stroke = g.getStroke();
g.setStroke(new BasicStroke(diameter / 80));
cos = (float)Math.cos(lightAngle);
sin = (float)Math.sin(lightAngle);
xDelta = radius * cos;
yDelta = radius * sin;
Color color = Color.gray;
Color shadow = ColorUtil.darker(color, .25f);
Color light = ColorUtil.darker(Color.white, .25f);
g.setPaint(new GradientPaint(radius + xDelta, radius - yDelta, shadow,
radius - xDelta, radius + yDelta, light, false));
g.draw(outer);
xDelta = innerMargin * cos;
yDelta = innerMargin * sin;
g.setPaint(new GradientPaint(radius + xDelta, radius - yDelta, light,
radius - xDelta, radius + yDelta, shadow, false));
g.draw(inner);
float theta = lightAngle - (float)Math.PI / 2f;
float pct = (1 - (float)Math.cos(theta)) / 2f;
g.setColor(ColorUtil.blend(light, shadow, pct));
g.draw(lines[0]);
g.draw(lines[1]);
g.setStroke(stroke);
g.translate(-offset, -offset);
Shape shape = AffineTransform.getTranslateInstance(offset, offset).
createTransformedShape(window);
return shape;
}
protected void drawCenterDot(Graphics2D g, int diameter) {
float radius = diameter / 2f;
int dotDiam = Math.round(diameter * .06f);
float dotRadius = dotDiam / 2f;
float pctFromCtr = .75f;
float distFromCtr = dotRadius * pctFromCtr;
float xCenter = radius + distFromCtr * (float)Math.cos(lightAngle);
float yCenter = radius - distFromCtr * (float)Math.sin(lightAngle);
Color focusColor = ColorUtil.lighter(centerColor, .65f);
float[] fractions = {0.0f, 1.0f};
Color[] colors = {focusColor, centerColor};
RadialGradientPaint paint = new RadialGradientPaint(
xCenter, yCenter, dotRadius, fractions, colors,
MultipleGradientPaint.CycleMethod.NO_CYCLE);
g.setPaint(paint);
int coord = Math.round(radius - dotRadius);
g.fillOval(coord, coord, dotDiam, dotDiam);
}
protected void drawFace(Graphics2D g, int diameter) {
g.setColor(faceColor);
g.fillOval(0, 0, diameter, diameter);
}
protected int drawFrame(Graphics2D g, int diameter) {
// Draw outline
int frameWidth = 0;
float radius = diameter / 2f;
float cos = (float)Math.cos(lightAngle);
float sin = (float)Math.sin(lightAngle);
float pctFromEdge = .67f;
float xStart = radius * (1 + cos);
float yStart = radius * (1 - sin);
float xEnd = radius * (1 + pctFromEdge * cos);
float yEnd = radius * (1 - pctFromEdge * sin);
g.setPaint(new GradientPaint(xStart, yStart,
ColorUtil.darker(Color.white, .25f), xEnd, yEnd,
ColorUtil.darker(frameColor, .25f), false));
g.fillOval(frameWidth, frameWidth, diameter, diameter);
// Draw outer edge
int gap = Math.round(diameter * .006f);
frameWidth += gap;
diameter -= 2 * gap;
radius = diameter / 2f;
xStart = radius * (1 + cos);
yStart = radius * (1 - sin);
xEnd = radius * (1 + pctFromEdge * cos);
yEnd = radius * (1 - pctFromEdge * sin);
g.setPaint(new GradientPaint(xStart, yStart, Color.white, xEnd, yEnd,
frameColor, false));
g.fillOval(frameWidth, frameWidth, diameter, diameter);
// Draw inner edge
gap = Math.round(diameter * .025f);
frameWidth += gap;
diameter -= 2 * gap;
radius = diameter / 2f;
cos = (float)Math.cos(lightAngle + Math.PI);
sin = (float)Math.sin(lightAngle + Math.PI);
pctFromEdge = .5f;
xStart = radius * (1 + cos);
yStart = radius * (1 - sin);
xEnd = radius * (1 + pctFromEdge * cos);
yEnd = radius * (1 - pctFromEdge * sin);
g.setPaint(new GradientPaint(xStart, yStart,
ColorUtil.lighter(frameColor, .55f), xEnd, yEnd, frameColor,
false));
g.fillOval(frameWidth, frameWidth, diameter, diameter);
gap = Math.round(diameter * .011f);
frameWidth += gap;
return frameWidth;
}
protected void drawHand(Graphics2D g, Color color, float handWidth,
Point p1, Point p2) {
Stroke stroke = g.getStroke();
g.setStroke(new BasicStroke(handWidth, BasicStroke.CAP_ROUND,
BasicStroke.JOIN_MITER));
g.setColor(color);
g.drawLine(p1.x, p1.y, p2.x, p2.y);
g.setStroke(stroke);
}
protected Point drawHand(Graphics2D g, Color color, float handWidth,
float handLength, float angle, int diameter) {
float radius = diameter / 2f;
float cos = (float)Math.cos(angle);
float sin = (float)Math.sin(angle);
int x = Math.round(radius + handLength * cos);
int y = Math.round(radius - handLength * sin);
// Shadow
float offset = diameter * .02f;
cos = (float)Math.cos(lightAngle + Math.PI);
sin = (float)Math.sin(lightAngle + Math.PI);
int xOffset = Math.round(offset * cos);
int yOffset = -Math.round(offset * sin);
drawHand(g, ColorUtil.alpha(Color.black, .2f), handWidth,
new Point((int)radius + xOffset, (int)radius + yOffset),
new Point(x + xOffset, y + yOffset));
// Hand
Point p = new Point(x, y);
drawHand(g, color, handWidth, new Point((int)radius, (int)radius), p);
return p;
}
protected void drawHourHand(Graphics2D g, int diameter, Point p1,
Point p2) {
drawHand(g, highlightColor, diameter * .02f, p1, p2);
}
protected Point drawHourHand(Graphics2D g, int diameter) {
return drawHand(g, hourHandColor, diameter * .02f, diameter * .275f,
hourToRadians(), diameter);
}
protected void drawHourMarks(Graphics2D g, int diameter) {
float markWidth = diameter * .015f;
float markLength = diameter * .04f;
float pctFromCtr = .9f;
float radius = diameter / 2f;
Stroke stroke = g.getStroke();
g.setStroke(new BasicStroke(markWidth, BasicStroke.CAP_ROUND,
BasicStroke.JOIN_MITER));
g.setColor(hourMarkColor);
float angleConst = (float)Math.PI / 6f;
float distFromCtrStart = radius * pctFromCtr;
float distFromCtrEnd = distFromCtrStart - markLength;
for (int i = 0; i < 12; i++) {
float angle = i * angleConst;
float cos = (float)Math.cos(angle);
float sin = (float)Math.sin(angle);
int xStart = Math.round(radius + distFromCtrStart * cos);
int yStart = Math.round(radius - distFromCtrStart * sin);
int xEnd = Math.round(radius + distFromCtrEnd * cos);
int yEnd = Math.round(radius - distFromCtrEnd * sin);
g.drawLine(xStart, yStart, xEnd, yEnd);
}
g.setStroke(stroke);
}
protected void drawMinuteHand(Graphics2D g, int diameter, Point p1,
Point p2) {
drawHand(g, highlightColor, diameter * .02f, p1, p2);
}
protected Point drawMinuteHand(Graphics2D g, int diameter) {
return drawHand(g, minuteHandColor, diameter * .02f, diameter * .405f,
minToRadians(), diameter);
}
protected void drawMinuteMarks(Graphics2D g, int diameter) {
float markWidth = diameter * .009f;
float markLength = diameter * .015f;
float pctFromCtr = .9f;
float radius = diameter / 2f;
Stroke stroke = g.getStroke();
g.setStroke(new BasicStroke(markWidth, BasicStroke.CAP_ROUND,
BasicStroke.JOIN_MITER));
g.setColor(minuteMarkColor);
float angleConst = (float)Math.PI / 30f;
float distFromCtrStart = radius * pctFromCtr;
float distFromCtrEnd = distFromCtrStart - markLength;
for (int i = 0; i < 60; i++) {
if (i % 5 != 0) {
float angle = i * angleConst;
float cos = (float)Math.cos(angle);
float sin = (float)Math.sin(angle);
int xStart = Math.round(radius + distFromCtrStart * cos);
int yStart = Math.round(radius - distFromCtrStart * sin);
int xEnd = Math.round(radius + distFromCtrEnd * cos);
int yEnd = Math.round(radius - distFromCtrEnd * sin);
g.drawLine(xStart, yStart, xEnd, yEnd);
}
}
g.setStroke(stroke);
}
protected void drawSecondHand(Graphics2D g, int diameter, Point p1,
Point p2) {
drawHand(g, highlightColor, diameter * .011f, p1, p2);
}
protected Point drawSecondHand(Graphics2D g, int diameter) {
return drawHand(g, secondHandColor, diameter * .011f,
diameter * .405f, secToRadians(), diameter);
}
protected void drawShadow(Graphics2D g, int diameter) {
// This puts the shadow around the entire face, which also looks nice
// float offset = 0f;
// float[] fractions = {0f, .95f, 1f};
float radius = diameter / 2f;
Color shadow = Color.black;
Color focus = ColorUtil.alpha(Color.black, 0f);
float[] fractions = {0f, .94f, 1f};
Color[] colors = {focus, focus, shadow};
float cos = (float)Math.cos(lightAngle + Math.PI);
float sin = (float)Math.sin(lightAngle + Math.PI);
float offset = radius * .02f;
float xOffset = offset * cos;
float yOffset = -offset * sin;
RadialGradientPaint paint = new RadialGradientPaint(radius + xOffset,
radius + yOffset, radius + offset, fractions, colors,
MultipleGradientPaint.CycleMethod.NO_CYCLE);
g.setPaint(paint);
g.fillOval(0, 0, diameter, diameter);
}
/**
* Gets the color at the AM end of the AM-PM gradient.
*/
public Color getAmColor() {
return amColor;
}
/**
* Gets the {@code Animator} the controls the animation of the interactive
* change from AM to PM and back.
*/
public Animator getAmPmAnimator() {
return amPmAnimator;
}
/**
* Gets the color of the center dial of the clock.
*/
public Color getCenterColor() {
return centerColor;
}
/**
* Gets the color of the face of the clock.
*/
public Color getFaceColor() {
return faceColor;
}
/**
* Gets the color of the outer frame of the clock.
*/
public Color getFrameColor() {
return frameColor;
}
/**
* Gets the color to use when highlighting a hand.
*/
public Color getHighlightColor() {
return highlightColor;
}
public Color getHourHandColor() {
return hourHandColor;
}
/**
* Gets the color of the hour mark on the clock face.
*/
public Color getHourMarkColor() {
return hourMarkColor;
}
/**
* Gets the angle of the light, in radians, with 0 at three o'clock, shining
* onto the clock.
*/
public float getLightAngle() {
return lightAngle;
}
public Color getMinuteHandColor() {
return minuteHandColor;
}
/**
* Gets the color of the minute mark on the clock face.
*/
public Color getMinuteMarkColor() {
return minuteMarkColor;
}
/**
* Gets the color at the PM end of the AM-PM gradient.
*/
public Color getPmColor() {
return pmColor;
}
public Color getSecondHandColor() {
return secondHandColor;
}
public TimeModel getTimeModel() {
return model;
}
public TimeSelectionModel getTimeSelectionModel() {
return selModel;
}
/**
* Gets whether to use instructional tooltips over the interactive portions
* of the clock.
*/
public boolean getUseToolTips() {
return useToolTips;
}
protected float hourToRadians() {
Calendar cal = model.getCalendar();
int hour = cal.get(Calendar.HOUR);
int minute = cal.get(Calendar.MINUTE);
int second = cal.get(Calendar.SECOND);
return hourToRadians(hour, minute, second);
}
/**
* Gets whether this {@code AnalogClock}'s time can be set by dragging the
* hands.
*/
public boolean isInteractive() {
return interactive;
}
protected float minToRadians() {
Calendar cal = model.getCalendar();
int minute = cal.get(Calendar.MINUTE);
int second = cal.get(Calendar.SECOND);
return minToRadians(minute, second);
}
protected float secToRadians() {
Calendar cal = model.getCalendar();
int second = cal.get(Calendar.SECOND);
return secToRadians(second);
}
/**
* Sets the color at the AM end of the AM-PM gradient.
*/
public void setAmColor(Color amColor) {
this.amColor = amColor;
repaint();
}
/**
* Sets the color of the center dial of the clock.
*/
public void setCenterColor(Color centerColor) {
this.centerColor = centerColor;
repaint();
}
/**
* Sets the preferred widht and height of this {@code AnalogClock} to {@code
* diameter}.
*/
public void setPreferredDiameter(int diameter) {
setPreferredSize(new Dimension(diameter, diameter));
}
/**
* Sets the color of the face of the clock.
*/
public void setFaceColor(Color faceColor) {
this.faceColor = faceColor;
baseImage = null;
repaint();
}
/**
* Sets the color of the outer frame of the clock.
*/
public void setFrameColor(Color frameColor) {
this.frameColor = frameColor;
baseImage = null;
repaint();
}
/**
* Sets the color to use when highlighting a hand.
*/
public void setHighlightColor(Color highlightColor) {
this.highlightColor = highlightColor;
}
public void setHourHandColor(Color hourHandColor) {
this.hourHandColor = hourHandColor;
repaint();
}
/**
* Sets the color of the hour mark on the clock face.
*/
public void setHourMarkColor(Color hourMarkColor) {
this.hourMarkColor = hourMarkColor;
baseImage = null;
repaint();
}
/**
* Sets whether this {@code AnalogClock}'s time can be set by dragging the
* hands.
*/
public void setInteractive(boolean interactive) {
if (this.interactive != interactive) {
this.interactive = interactive;
if (interactive) {
addMouseListener(this);
addMouseMotionListener(this);
} else {
removeMouseListener(this);
removeMouseMotionListener(this);
mousePoint = null;
}
}
}
/**
* Sets the angle of the light, in radians, with 0 at three o'clock, shining
* onto the clock.
*/
public void setLightAngle(float lightAngle) {
this.lightAngle = lightAngle;
baseImage = null;
timeImage = null;
centerImage = null;
repaint();
}
public void setMinuteHandColor(Color minuteHandColor) {
this.minuteHandColor = minuteHandColor;
repaint();
}
/**
* Sets the color of the minute mark on the clock face.
*/
public void setMinuteMarkColor(Color minuteMarkColor) {
this.minuteMarkColor = minuteMarkColor;
baseImage = null;
repaint();
}
/**
* Sets the color at the PM end of the AM-PM gradient.
*/
public void setPmColor(Color pmColor) {
this.pmColor = pmColor;
repaint();
}
public void setSecondHandColor(Color secondHandColor) {
this.secondHandColor = secondHandColor;
repaint();
}
public void setTimeModel(TimeModel model) {
if (this.model != model) {
if (this.model != null) {
this.model.removePropertyChangeListener(timeListener);
}
model.addPropertyChangeListener(TimeModel.PROPERTY_TIME,
timeListener);
this.model = model;
timeChanged();
}
}
public void setTimeSelectionModel(TimeSelectionModel selModel) {
this.selModel = selModel;
}
/**
* Sets whether to use instructional tooltips over the interactive portions
* of the clock.
*/
public void setUseToolTips(boolean useToolTips) {
if (this.useToolTips != useToolTips) {
this.useToolTips = useToolTips;
setToolTipText();
}
}
//
// Private methods
//
private void createBaseImage(int diameter) {
if (baseImage == null || baseImage.getWidth() != diameter) {
baseImage = new BufferedImage(diameter, diameter,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = baseImage.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
frameWidth = drawFrame(g, diameter);
diameter -= 2 * frameWidth;
g.translate(frameWidth, frameWidth);
drawFace(g, diameter);
drawHourMarks(g, diameter);
drawMinuteMarks(g, diameter);
drawShadow(g, diameter);
g.translate(-frameWidth, -frameWidth);
}
}
private void createCenterImage(int diameter) {
if (centerImage == null || centerImage.getWidth() != diameter) {
centerImage = new BufferedImage(diameter, diameter,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = centerImage.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
drawCenterDot(g, diameter);
}
}
private void createTimeImage(int diameter) {
if (timeImage == null || timeImage.getWidth() != diameter) {
timeImage = new BufferedImage(diameter, diameter,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = timeImage.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
amPm = drawAmPm(g, diameter);
hourPoint = drawHourHand(g, diameter);
minPoint = drawMinuteHand(g, diameter);
secPoint = drawSecondHand(g, diameter);
}
}
private void setSelectedField(int selectedField) {
setCursor(selectedField == -1 ? null :
Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
selModel.setSelectedField(selectedField);
setToolTipText();
}
private void setToolTipText() {
String resource = null;
if (useToolTips) {
switch (selModel.getSelectedField()) {
case Calendar.AM_PM:
resource = "time.tooltip.ampm";
break;
case Calendar.HOUR:
resource = "time.tooltip.hour";
break;
case Calendar.MINUTE:
resource = "time.tooltip.minute";
break;
case Calendar.SECOND:
resource = "time.tooltip.second";
break;
}
}
setToolTipText(resource == null ? null : Finder.getString(resource));
}
private void timeChanged() {
timeImage = null;
repaint();
}
//
// Static methods
//
public static float amPmToRadians(int amPm, int hour, int minute,
int second) {
hour = (hour % 12) + (amPm == Calendar.PM ? 12 : 0);
return (6 - (hour + minute / 60f + second / 3600f)) * (float)Math.PI /
12f;
}
public static float hourToRadians(int hour, int minute, int second) {
return (15 - (hour + minute / 60f + second / 3600f)) * (float)Math.PI /
6f;
}
public static float minToRadians(int minute, int second) {
return (75 - (minute + second / 60f)) * (float)Math.PI / 30f;
}
public static float radiansToHour(float radians) {
float value = (-6 * radians / (float)Math.PI) + 3;
if (value < 1) {
value += 12;
}
return value;
}
public static float radiansToMin(float radians) {
float value = (-30 * radians / (float)Math.PI) + 15;
if (value < 0) {
value += 60;
}
return value;
}
public static float secToRadians(int second) {
return (75 - second) * (float)Math.PI / 30f;
}
/**
* Converts {@code Calendar} time fields to milliseconds.
*
* @param calendarField
* {@code Calendar.MILLISECOND},
* {@code Calendar.SECOND},
* {@code Calendar.MINUTE},
* {@code Calendar.HOUR},
* {@code Calendar.HOUR_OF_DAY}, or
* {@code Calendar.AM_PM},
*
* @return the number of milliseconds in a unit of the given field, or
* zero if {@code calendarField} is not any of the allowed
* fields
*/
public static long timeFieldToMillis(int calendarField) {
long millis = 0;
switch (calendarField) {
case Calendar.MILLISECOND:
millis = 1;
break;
case Calendar.SECOND:
millis = 1000;
break;
case Calendar.MINUTE:
millis = 60000;
break;
case Calendar.HOUR:
case Calendar.HOUR_OF_DAY:
millis = 3600000;
break;
case Calendar.AM_PM:
millis = 43200000;
break;
}
return millis;
}
public static void main(String args[]) {
AnalogClock clock = new AnalogClock();
clock.setInteractive(true);
clock.setPreferredDiameter(300);
TimeModelUpdater updater = new TimeModelUpdater(clock.getTimeModel());
updater.start();
JFrame frame = new JFrame();
Container c = frame.getContentPane();
c.setLayout(new BorderLayout());
c.add(clock, BorderLayout.CENTER);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
}