components/visual-panels/core/src/java/util/com/oracle/solaris/vp/util/swing/time/AnalogClock.java
changeset 3553 f1d133b09a8c
parent 3552 077ebe3d0d24
child 3554 ef58713bafc4
equal deleted inserted replaced
3552:077ebe3d0d24 3553:f1d133b09a8c
     1 /*
       
     2  * CDDL HEADER START
       
     3  *
       
     4  * The contents of this file are subject to the terms of the
       
     5  * Common Development and Distribution License (the "License").
       
     6  * You may not use this file except in compliance with the License.
       
     7  *
       
     8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
       
     9  * or http://www.opensolaris.org/os/licensing.
       
    10  * See the License for the specific language governing permissions
       
    11  * and limitations under the License.
       
    12  *
       
    13  * When distributing Covered Code, include this CDDL HEADER in each
       
    14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
       
    15  * If applicable, add the following below this CDDL HEADER, with the
       
    16  * fields enclosed by brackets "[]" replaced with your own identifying
       
    17  * information: Portions Copyright [yyyy] [name of copyright owner]
       
    18  *
       
    19  * CDDL HEADER END
       
    20  */
       
    21 
       
    22 /*
       
    23  * Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
       
    24  */
       
    25 
       
    26 package com.oracle.solaris.vp.util.swing.time;
       
    27 
       
    28 import java.awt.*;
       
    29 import java.awt.event.*;
       
    30 import java.awt.geom.*;
       
    31 import java.awt.image.BufferedImage;
       
    32 import java.beans.*;
       
    33 import java.util.Calendar;
       
    34 import javax.swing.*;
       
    35 import org.jdesktop.animation.timing.*;
       
    36 import com.oracle.solaris.vp.util.misc.finder.Finder;
       
    37 import com.oracle.solaris.vp.util.swing.*;
       
    38 
       
    39 @SuppressWarnings({"serial"})
       
    40 public class AnalogClock extends JPanel implements MouseListener,
       
    41     MouseMotionListener, TimingTarget {
       
    42 
       
    43     //
       
    44     // Static data
       
    45     //
       
    46 
       
    47     private static final int _DIAMETER = 150;
       
    48 
       
    49     //
       
    50     // Instance data
       
    51     //
       
    52 
       
    53     private TimeModel model;
       
    54     private TimeSelectionModel selModel;
       
    55 
       
    56     private PropertyChangeListener timeListener =
       
    57 	new PropertyChangeListener() {
       
    58 	    @Override
       
    59 	    public void propertyChange(PropertyChangeEvent event) {
       
    60 		timeChanged();
       
    61 	    }
       
    62 	};
       
    63 
       
    64     // Cached images to speed up repaints
       
    65     private BufferedImage baseImage;
       
    66     private BufferedImage timeImage;
       
    67     private BufferedImage centerImage;
       
    68 
       
    69     private int frameWidth;
       
    70     private Point centerPoint;
       
    71     private Point mousePoint;
       
    72     private boolean interactive;
       
    73     private boolean useToolTips;
       
    74     private SunIcon sunIcon = new SunIcon();
       
    75     private MoonIcon moonIcon = new MoonIcon();
       
    76     private StarIcon starIcon = new StarIcon();
       
    77     private Shape amPm;
       
    78     private Point hourPoint;
       
    79     private Point minPoint;
       
    80     private Point secPoint;
       
    81 
       
    82     private Animator amPmAnimator = new Animator(300, this);
       
    83     private float amPmAnimOffset;
       
    84     private int amPmAnimFactor;
       
    85 
       
    86     // UI prefs
       
    87     private Color centerColor = Color.black;
       
    88     private Color frameColor = ColorUtil.darker(
       
    89 	UIManager.getColor("Panel.background"), .2f);
       
    90     private Color faceColor = Color.white;
       
    91     private Color hourHandColor = Color.black;
       
    92     private Color hourMarkColor = Color.black;
       
    93     private float lightAngle = (float)Math.toRadians(122);
       
    94     private Color minuteHandColor = Color.black;
       
    95     private Color minuteMarkColor = Color.gray;
       
    96     private Color secondHandColor = Color.red.darker();
       
    97     private Color highlightColor = new Color(0, 0, 204);
       
    98     private Color amColor = new Color(148, 188, 246);
       
    99     private Color pmColor = new Color(0, 0, 78);
       
   100 
       
   101     //
       
   102     // Constructors
       
   103     //
       
   104 
       
   105     /**
       
   106      * Constructs an {@code AnalogClock} for the given model.
       
   107      */
       
   108     public AnalogClock(TimeModel model) {
       
   109 	setTimeModel(model);
       
   110 	setTimeSelectionModel(new SimpleTimeSelectionModel());
       
   111 	setOpaque(false);
       
   112 	setPreferredDiameter(_DIAMETER);
       
   113     }
       
   114 
       
   115     /**
       
   116      * Constructs an {@code AnalogClock} with a zero {@link
       
   117      * TimeModel#getTimeOffset offset} from the system clock.
       
   118      */
       
   119     public AnalogClock() {
       
   120 	this(new SimpleTimeModel());
       
   121     }
       
   122 
       
   123     //
       
   124     // MouseListener methods
       
   125     //
       
   126 
       
   127     @Override
       
   128     public void mouseClicked(MouseEvent e) {
       
   129 	int selectedField = selModel.getSelectedField();
       
   130 	if (selectedField == Calendar.AM_PM && GUIUtil.isUnmodifiedClick(e, 1))
       
   131 	{
       
   132 	    amPmAnimOffset = (float)Math.PI;
       
   133 
       
   134 	    amPmAnimFactor = model.getCalendar().get(
       
   135 		selectedField) == Calendar.AM ? 1 : -1;
       
   136 
       
   137 	    // 43200000 = 1000 * 60 * 60 * 12 = milliseconds in 12-hour change
       
   138 	    long delta = amPmAnimFactor * 43200000;
       
   139 	    model.setTimeOffset(model.getTimeOffset() + delta);
       
   140 
       
   141 	    amPmAnimator.start();
       
   142 	}
       
   143     }
       
   144 
       
   145     @Override
       
   146     public void mouseEntered(MouseEvent e) {
       
   147     }
       
   148 
       
   149     @Override
       
   150     public void mouseExited(MouseEvent e) {
       
   151 	mousePoint = null;
       
   152 	repaint();
       
   153     }
       
   154 
       
   155     @Override
       
   156     public void mousePressed(MouseEvent e) {
       
   157 	mousePoint = null;
       
   158 	repaint();
       
   159     }
       
   160 
       
   161     @Override
       
   162     public void mouseReleased(MouseEvent e) {
       
   163 	mouseMoved(e);
       
   164     }
       
   165 
       
   166     //
       
   167     // MouseMotionListener methods
       
   168     //
       
   169 
       
   170     @Override
       
   171     public void mouseDragged(MouseEvent e) {
       
   172 	int selectedField = selModel.getSelectedField();
       
   173 	if (selectedField != Calendar.HOUR &&
       
   174 	    selectedField != Calendar.MINUTE &&
       
   175 	    selectedField != Calendar.SECOND) {
       
   176 	    return;
       
   177 	}
       
   178 
       
   179 	Point centerPoint = this.centerPoint;
       
   180 	Point dragPoint = e.getPoint();
       
   181 	int dY = centerPoint.y - dragPoint.y;
       
   182 	int dX = dragPoint.x - centerPoint.x;
       
   183 	float angle = (float)Math.atan2(dY, dX);
       
   184 
       
   185 	int newVal;
       
   186 	int period;
       
   187 
       
   188 	if (selectedField == Calendar.HOUR) {
       
   189 	    newVal = (int)radiansToHour(angle);
       
   190 	    period = 12;
       
   191 	} else {
       
   192 	    newVal = (int)radiansToMin(angle);
       
   193 	    period = 60;
       
   194 	}
       
   195 
       
   196 	Calendar cal = model.getCalendar();
       
   197 	int curVal = cal.get(selectedField);
       
   198 	int factor = (newVal - curVal + period) % period;
       
   199 	if (factor >= period / 2) {
       
   200 	    factor -= period;
       
   201 	}
       
   202 
       
   203         long timeOffset = model.getTimeOffset() + factor *
       
   204 	    timeFieldToMillis(selectedField);
       
   205 	model.setTimeOffset(timeOffset);
       
   206     }
       
   207 
       
   208     @Override
       
   209     public void mouseMoved(MouseEvent e) {
       
   210 	mousePoint = e.getPoint();
       
   211 	repaint();
       
   212     }
       
   213 
       
   214     //
       
   215     // TimingTarget methods
       
   216     //
       
   217 
       
   218     @Override
       
   219     public void begin() {
       
   220     }
       
   221 
       
   222     @Override
       
   223     public void end() {
       
   224     }
       
   225 
       
   226     @Override
       
   227     public void repeat() {
       
   228     }
       
   229 
       
   230     @Override
       
   231     public void timingEvent(float fraction) {
       
   232 	amPmAnimOffset = amPmAnimFactor * (float)Math.PI * (1 - fraction);
       
   233 	timeImage = null;
       
   234 	repaint();
       
   235     }
       
   236 
       
   237     //
       
   238     // JComponent methods
       
   239     //
       
   240 
       
   241     @Override
       
   242     protected void paintComponent(Graphics g1) {
       
   243 	super.paintComponent(g1);
       
   244 
       
   245 	Graphics2D g = (Graphics2D)g1;
       
   246 	g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
       
   247 	    RenderingHints.VALUE_ANTIALIAS_ON);
       
   248 
       
   249 	Insets insets = getInsets();
       
   250 	int width = getWidth() - insets.left - insets.right;
       
   251 	int height = getHeight() - insets.top - insets.bottom;
       
   252 
       
   253 	if (width <= 0 || height <= 0) {
       
   254 	    return;
       
   255 	}
       
   256 
       
   257 	// TimeModel is square
       
   258 	int diameter = Math.min(width, height);
       
   259 
       
   260 	int diff = height - width;
       
   261 	int hDiff = diff / 2;
       
   262 	if (diff > 0) {
       
   263 	    diameter = width;
       
   264 	    insets.top += hDiff;
       
   265 	    insets.bottom += diff - hDiff;
       
   266 	} else if (diff < 0) {
       
   267 	    diameter = height;
       
   268 	    insets.left -= hDiff;
       
   269 	    insets.right -= diff - hDiff;
       
   270 	}
       
   271 
       
   272 	g.translate(insets.left, insets.top);
       
   273 
       
   274 	createBaseImage(diameter);
       
   275 	g.drawImage(baseImage, 0, 0, null);
       
   276 
       
   277 	g.translate(frameWidth, frameWidth);
       
   278 
       
   279 	diameter -= 2 * frameWidth;
       
   280 	int radius = Math.round(diameter/ 2f);
       
   281 
       
   282 	createTimeImage(diameter);
       
   283 	g.drawImage(timeImage, 0, 0, null);
       
   284 
       
   285 	Point centerPoint = new Point(radius, radius);
       
   286 	Point mousePoint = this.mousePoint;
       
   287 
       
   288 	if (mousePoint != null) {
       
   289 	    int selectedField = -1;
       
   290 	    mousePoint = (Point)mousePoint.clone();
       
   291 	    mousePoint.translate(-frameWidth - insets.left,
       
   292 		-frameWidth - insets.top);
       
   293 
       
   294 	    Line2D line = new Line2D.Float();
       
   295 
       
   296 	    line.setLine(centerPoint, hourPoint);
       
   297 	    double hourDist = line.ptSegDist(mousePoint);
       
   298 
       
   299 	    line.setLine(centerPoint, minPoint);
       
   300 	    double minDist = line.ptSegDist(mousePoint);
       
   301 
       
   302 	    line.setLine(centerPoint, secPoint);
       
   303 	    double secDist = line.ptSegDist(mousePoint);
       
   304 
       
   305 	    // Is the mouse close enough to a hand to call it selected?
       
   306 	    double closest = Math.min(hourDist, Math.min(minDist, secDist));
       
   307 	    if (closest / diameter <= .02f) {
       
   308 		if (hourDist == closest) {
       
   309 		    selectedField = Calendar.HOUR;
       
   310 		    drawHourHand(g, diameter, centerPoint, hourPoint);
       
   311 		} else if (minDist == closest) {
       
   312 		    selectedField = Calendar.MINUTE;
       
   313 		    drawMinuteHand(g, diameter, centerPoint, minPoint);
       
   314 		} else {
       
   315 		    selectedField = Calendar.SECOND;
       
   316 		    drawSecondHand(g, diameter, centerPoint, secPoint);
       
   317 		}
       
   318 	    } else if (amPm.contains(mousePoint)) {
       
   319 		selectedField = Calendar.AM_PM;
       
   320 	    }
       
   321 
       
   322 	    setSelectedField(selectedField);
       
   323 	}
       
   324 
       
   325 	createCenterImage(diameter);
       
   326 	g.drawImage(centerImage, 0, 0, null);
       
   327 
       
   328 	centerPoint.translate(frameWidth + insets.left,
       
   329 	    frameWidth + insets.top);
       
   330 
       
   331 	this.centerPoint = centerPoint;
       
   332 
       
   333 	g.translate(-frameWidth - insets.left, -frameWidth - insets.top);
       
   334     }
       
   335 
       
   336     //
       
   337     // AnalogClock methods
       
   338     //
       
   339 
       
   340     protected float amPmToRadians() {
       
   341 	Calendar cal = model.getCalendar();
       
   342 	int hour = cal.get(Calendar.HOUR);
       
   343 	int minute = cal.get(Calendar.MINUTE);
       
   344 	int second = cal.get(Calendar.SECOND);
       
   345 	int amPm = cal.get(Calendar.AM_PM);
       
   346 	return amPmToRadians(amPm, hour, minute, second);
       
   347     }
       
   348 
       
   349     protected Shape drawAmPm(Graphics2D g, int diameter) {
       
   350 	// Create shapes
       
   351 
       
   352 	float outerMargin = diameter * .17f;
       
   353 	float innerMargin = diameter * .105f;
       
   354 
       
   355 	diameter -= Math.round(2 * outerMargin);
       
   356 	float radius = diameter / 2f;
       
   357 
       
   358 	int offset = Math.round(outerMargin);
       
   359 	g.translate(offset, offset);
       
   360 
       
   361 	Arc2D.Float outer = new Arc2D.Float(0, 0,
       
   362 	    diameter, diameter, 0, 180, Arc2D.OPEN);
       
   363 
       
   364 	int innerDiam = Math.round(2 * innerMargin);
       
   365 	Arc2D.Float inner = new Arc2D.Float(radius - innerMargin,
       
   366 	    radius - innerMargin, innerDiam, innerDiam, 180, -180, Arc2D.OPEN);
       
   367 
       
   368 	Line2D.Float[] lines = {
       
   369 	    new Line2D.Float(outer.getEndPoint(), inner.getStartPoint()),
       
   370 	    new Line2D.Float(inner.getEndPoint(), outer.getStartPoint()),
       
   371 	};
       
   372 
       
   373 	GeneralPath window = new GeneralPath(outer);
       
   374 	window.append(lines[0], true);
       
   375 	window.append(inner, true);
       
   376 	window.append(lines[1], true);
       
   377 	window.closePath();
       
   378 
       
   379 	// Fill background
       
   380 
       
   381 	float angle = (float)Math.PI - amPmToRadians() - amPmAnimOffset;
       
   382 	float cos = (float)Math.cos(angle);
       
   383 	float sin = (float)Math.sin(angle);
       
   384 	float xDelta = radius * cos;
       
   385 	float yDelta = radius * sin;
       
   386 
       
   387 	g.setPaint(new GradientPaint(
       
   388 	    Math.round(radius + xDelta), Math.round(radius + yDelta), amColor,
       
   389 	    Math.round(radius - xDelta), Math.round(radius - yDelta), pmColor));
       
   390 	g.fill(window);
       
   391 
       
   392 	// Paint icons
       
   393 
       
   394 	Shape clip = g.getClip();
       
   395 	g.setClip(window);
       
   396 
       
   397 	float arcWidth = radius - innerMargin;
       
   398 	float midPointToCtr = innerMargin + arcWidth * .5f;
       
   399 	int sunIconDiam = (int)(arcWidth * .7f);
       
   400 	float sunIconRad = sunIconDiam / 2f;
       
   401 	xDelta = midPointToCtr * cos;
       
   402 	yDelta = midPointToCtr * sin;
       
   403 
       
   404 	sunIcon.setDiameter(sunIconDiam);
       
   405 	sunIcon.paintIcon(this, g, Math.round(radius + xDelta - sunIconRad),
       
   406 	    Math.round(radius + yDelta - sunIconRad));
       
   407 
       
   408 	int moonIconDiam = (int)(sunIconDiam * sunIcon.getOrbSizePercentage());
       
   409 	float moonIconRad = moonIconDiam / 2f;
       
   410 
       
   411 	moonIcon.setDiameter(moonIconDiam);
       
   412 	moonIcon.paintIcon(this, g, Math.round(radius - xDelta - moonIconRad),
       
   413 	    Math.round(radius - yDelta - moonIconRad));
       
   414 
       
   415 	int starIconDiam = (int)(moonIconDiam * .4f);
       
   416 	float starIconRad = starIconDiam / 2f;
       
   417 
       
   418 	starIcon.setDiameter(starIconDiam);
       
   419 
       
   420 	// Angle (degree) offset from moon, percent distance between arcs
       
   421 	float[] starData = {
       
   422 	    -48f, .55f,
       
   423 	    -30f, .22f,
       
   424 	    -25f, .7f,
       
   425 	    5f, .85f,
       
   426 	    20f, .25f,
       
   427 	    25f, .65f,
       
   428 	    45f, .35f,
       
   429 	};
       
   430 
       
   431 	for (int i = 0; i < starData.length; i += 2) {
       
   432 	    float starAngle = (float)Math.toRadians(starData[i]);
       
   433 	    float starDist = innerMargin + arcWidth * starData[i + 1];
       
   434 
       
   435 	    int x = Math.round(radius - starDist *
       
   436 		(float)Math.cos(angle + starAngle) - starIconRad);
       
   437 
       
   438 	    int y = Math.round(radius - starDist *
       
   439 		(float)Math.sin(angle + starAngle) - starIconRad);
       
   440 
       
   441 	    starIcon.paintIcon(this, g, x, y);
       
   442 	}
       
   443 
       
   444 	g.setClip(clip);
       
   445 
       
   446 	// Paint inset border
       
   447 
       
   448 	Stroke stroke = g.getStroke();
       
   449 	g.setStroke(new BasicStroke(diameter / 80));
       
   450 
       
   451 	cos = (float)Math.cos(lightAngle);
       
   452 	sin = (float)Math.sin(lightAngle);
       
   453 	xDelta = radius * cos;
       
   454 	yDelta = radius * sin;
       
   455 
       
   456 	Color color = Color.gray;
       
   457 	Color shadow = ColorUtil.darker(color, .25f);
       
   458 	Color light = ColorUtil.darker(Color.white, .25f);
       
   459 
       
   460 	g.setPaint(new GradientPaint(radius + xDelta, radius - yDelta, shadow,
       
   461 	    radius - xDelta, radius + yDelta, light, false));
       
   462 	g.draw(outer);
       
   463 
       
   464 	xDelta = innerMargin * cos;
       
   465 	yDelta = innerMargin * sin;
       
   466 
       
   467 	g.setPaint(new GradientPaint(radius + xDelta, radius - yDelta, light,
       
   468 	    radius - xDelta, radius + yDelta, shadow, false));
       
   469 	g.draw(inner);
       
   470 
       
   471 	float theta = lightAngle - (float)Math.PI / 2f;
       
   472 	float pct = (1 - (float)Math.cos(theta)) / 2f;
       
   473 
       
   474 	g.setColor(ColorUtil.blend(light, shadow, pct));
       
   475 	g.draw(lines[0]);
       
   476 	g.draw(lines[1]);
       
   477 
       
   478 	g.setStroke(stroke);
       
   479 	g.translate(-offset, -offset);
       
   480 
       
   481 	Shape shape = AffineTransform.getTranslateInstance(offset, offset).
       
   482 	    createTransformedShape(window);
       
   483 
       
   484 	return shape;
       
   485     }
       
   486 
       
   487     protected void drawCenterDot(Graphics2D g, int diameter) {
       
   488 	float radius = diameter / 2f;
       
   489 	int dotDiam = Math.round(diameter * .06f);
       
   490 	float dotRadius = dotDiam / 2f;
       
   491 	float pctFromCtr = .75f;
       
   492 	float distFromCtr = dotRadius * pctFromCtr;
       
   493 	float xCenter = radius + distFromCtr * (float)Math.cos(lightAngle);
       
   494 	float yCenter = radius - distFromCtr * (float)Math.sin(lightAngle);
       
   495 
       
   496 	Color focusColor = ColorUtil.lighter(centerColor, .65f);
       
   497 	float[] fractions = {0.0f, 1.0f};
       
   498 	Color[] colors = {focusColor, centerColor};
       
   499 	RadialGradientPaint paint = new RadialGradientPaint(
       
   500 	    xCenter, yCenter, dotRadius, fractions, colors,
       
   501 	    MultipleGradientPaint.CycleMethod.NO_CYCLE);
       
   502 	g.setPaint(paint);
       
   503 	int coord = Math.round(radius - dotRadius);
       
   504 	g.fillOval(coord, coord, dotDiam, dotDiam);
       
   505     }
       
   506 
       
   507     protected void drawFace(Graphics2D g, int diameter) {
       
   508 	g.setColor(faceColor);
       
   509 	g.fillOval(0, 0, diameter, diameter);
       
   510     }
       
   511 
       
   512     protected int drawFrame(Graphics2D g, int diameter) {
       
   513 	// Draw outline
       
   514 	int frameWidth = 0;
       
   515 	float radius = diameter / 2f;
       
   516 
       
   517 	float cos = (float)Math.cos(lightAngle);
       
   518 	float sin = (float)Math.sin(lightAngle);
       
   519 	float pctFromEdge = .67f;
       
   520 	float xStart = radius * (1 + cos);
       
   521 	float yStart = radius * (1 - sin);
       
   522 	float xEnd = radius * (1 + pctFromEdge * cos);
       
   523 	float yEnd = radius * (1 - pctFromEdge * sin);
       
   524 
       
   525 	g.setPaint(new GradientPaint(xStart, yStart,
       
   526 	    ColorUtil.darker(Color.white, .25f), xEnd, yEnd,
       
   527 	    ColorUtil.darker(frameColor, .25f), false));
       
   528 	g.fillOval(frameWidth, frameWidth, diameter, diameter);
       
   529 
       
   530 	// Draw outer edge
       
   531 	int gap = Math.round(diameter * .006f);
       
   532 	frameWidth += gap;
       
   533 	diameter -= 2 * gap;
       
   534 	radius = diameter / 2f;
       
   535 
       
   536 	xStart = radius * (1 + cos);
       
   537 	yStart = radius * (1 - sin);
       
   538 	xEnd = radius * (1 + pctFromEdge * cos);
       
   539 	yEnd = radius * (1 - pctFromEdge * sin);
       
   540 
       
   541 	g.setPaint(new GradientPaint(xStart, yStart, Color.white, xEnd, yEnd,
       
   542 	    frameColor, false));
       
   543 	g.fillOval(frameWidth, frameWidth, diameter, diameter);
       
   544 
       
   545 	// Draw inner edge
       
   546 	gap = Math.round(diameter * .025f);
       
   547 	frameWidth += gap;
       
   548 	diameter -= 2 * gap;
       
   549 	radius = diameter / 2f;
       
   550 
       
   551 	cos = (float)Math.cos(lightAngle + Math.PI);
       
   552 	sin = (float)Math.sin(lightAngle + Math.PI);
       
   553 	pctFromEdge = .5f;
       
   554 	xStart = radius * (1 + cos);
       
   555 	yStart = radius * (1 - sin);
       
   556 	xEnd = radius * (1 + pctFromEdge * cos);
       
   557 	yEnd = radius * (1 - pctFromEdge * sin);
       
   558 
       
   559 	g.setPaint(new GradientPaint(xStart, yStart,
       
   560 	    ColorUtil.lighter(frameColor, .55f), xEnd, yEnd, frameColor,
       
   561 	    false));
       
   562 	g.fillOval(frameWidth, frameWidth, diameter, diameter);
       
   563 
       
   564 	gap = Math.round(diameter * .011f);
       
   565 	frameWidth += gap;
       
   566 
       
   567 	return frameWidth;
       
   568     }
       
   569 
       
   570     protected void drawHand(Graphics2D g, Color color, float handWidth,
       
   571 	Point p1, Point p2) {
       
   572 
       
   573 	Stroke stroke = g.getStroke();
       
   574 	g.setStroke(new BasicStroke(handWidth, BasicStroke.CAP_ROUND,
       
   575 	    BasicStroke.JOIN_MITER));
       
   576 
       
   577 	g.setColor(color);
       
   578 	g.drawLine(p1.x, p1.y, p2.x, p2.y);
       
   579 
       
   580 	g.setStroke(stroke);
       
   581     }
       
   582 
       
   583     protected Point drawHand(Graphics2D g, Color color, float handWidth,
       
   584 	float handLength, float angle, int diameter) {
       
   585 
       
   586 	float radius = diameter / 2f;
       
   587 	float cos = (float)Math.cos(angle);
       
   588 	float sin = (float)Math.sin(angle);
       
   589 	int x = Math.round(radius + handLength * cos);
       
   590 	int y = Math.round(radius - handLength * sin);
       
   591 
       
   592 	// Shadow
       
   593 	float offset = diameter * .02f;
       
   594 	cos = (float)Math.cos(lightAngle + Math.PI);
       
   595 	sin = (float)Math.sin(lightAngle + Math.PI);
       
   596 	int xOffset = Math.round(offset * cos);
       
   597 	int yOffset = -Math.round(offset * sin);
       
   598 
       
   599 	drawHand(g, ColorUtil.alpha(Color.black, .2f), handWidth,
       
   600 	    new Point((int)radius + xOffset, (int)radius + yOffset),
       
   601 	    new Point(x + xOffset, y + yOffset));
       
   602 
       
   603 	// Hand
       
   604 	Point p = new Point(x, y);
       
   605 	drawHand(g, color, handWidth, new Point((int)radius, (int)radius), p);
       
   606 
       
   607 	return p;
       
   608     }
       
   609 
       
   610     protected void drawHourHand(Graphics2D g, int diameter, Point p1,
       
   611 	Point p2) {
       
   612 	drawHand(g, highlightColor, diameter * .02f, p1, p2);
       
   613     }
       
   614 
       
   615     protected Point drawHourHand(Graphics2D g, int diameter) {
       
   616 	return drawHand(g, hourHandColor, diameter * .02f, diameter * .275f,
       
   617 	    hourToRadians(), diameter);
       
   618     }
       
   619 
       
   620     protected void drawHourMarks(Graphics2D g, int diameter) {
       
   621 	float markWidth = diameter * .015f;
       
   622 	float markLength = diameter * .04f;
       
   623 	float pctFromCtr = .9f;
       
   624 	float radius = diameter / 2f;
       
   625 
       
   626 	Stroke stroke = g.getStroke();
       
   627 	g.setStroke(new BasicStroke(markWidth, BasicStroke.CAP_ROUND,
       
   628 	    BasicStroke.JOIN_MITER));
       
   629 	g.setColor(hourMarkColor);
       
   630 
       
   631 	float angleConst = (float)Math.PI / 6f;
       
   632 	float distFromCtrStart = radius * pctFromCtr;
       
   633 	float distFromCtrEnd = distFromCtrStart - markLength;
       
   634 
       
   635 	for (int i = 0; i < 12; i++) {
       
   636 	    float angle = i * angleConst;
       
   637 	    float cos = (float)Math.cos(angle);
       
   638 	    float sin = (float)Math.sin(angle);
       
   639 
       
   640 	    int xStart = Math.round(radius + distFromCtrStart * cos);
       
   641 	    int yStart = Math.round(radius - distFromCtrStart * sin);
       
   642 
       
   643 	    int xEnd = Math.round(radius + distFromCtrEnd * cos);
       
   644 	    int yEnd = Math.round(radius - distFromCtrEnd * sin);
       
   645 
       
   646 	    g.drawLine(xStart, yStart, xEnd, yEnd);
       
   647 	}
       
   648 
       
   649 	g.setStroke(stroke);
       
   650     }
       
   651 
       
   652     protected void drawMinuteHand(Graphics2D g, int diameter, Point p1,
       
   653 	Point p2) {
       
   654 	drawHand(g, highlightColor, diameter * .02f, p1, p2);
       
   655     }
       
   656 
       
   657     protected Point drawMinuteHand(Graphics2D g, int diameter) {
       
   658 	return drawHand(g, minuteHandColor, diameter * .02f, diameter * .405f,
       
   659 	    minToRadians(), diameter);
       
   660     }
       
   661 
       
   662     protected void drawMinuteMarks(Graphics2D g, int diameter) {
       
   663 	float markWidth = diameter * .009f;
       
   664 	float markLength = diameter * .015f;
       
   665 	float pctFromCtr = .9f;
       
   666 	float radius = diameter / 2f;
       
   667 
       
   668 	Stroke stroke = g.getStroke();
       
   669 	g.setStroke(new BasicStroke(markWidth, BasicStroke.CAP_ROUND,
       
   670 	    BasicStroke.JOIN_MITER));
       
   671 	g.setColor(minuteMarkColor);
       
   672 
       
   673 	float angleConst = (float)Math.PI / 30f;
       
   674 	float distFromCtrStart = radius * pctFromCtr;
       
   675 	float distFromCtrEnd = distFromCtrStart - markLength;
       
   676 
       
   677 	for (int i = 0; i < 60; i++) {
       
   678 	    if (i % 5 != 0) {
       
   679 		float angle = i * angleConst;
       
   680 		float cos = (float)Math.cos(angle);
       
   681 		float sin = (float)Math.sin(angle);
       
   682 
       
   683 		int xStart = Math.round(radius + distFromCtrStart * cos);
       
   684 		int yStart = Math.round(radius - distFromCtrStart * sin);
       
   685 
       
   686 		int xEnd = Math.round(radius + distFromCtrEnd * cos);
       
   687 		int yEnd = Math.round(radius - distFromCtrEnd * sin);
       
   688 
       
   689 		g.drawLine(xStart, yStart, xEnd, yEnd);
       
   690 	    }
       
   691 	}
       
   692 
       
   693 	g.setStroke(stroke);
       
   694     }
       
   695 
       
   696     protected void drawSecondHand(Graphics2D g, int diameter, Point p1,
       
   697 	Point p2) {
       
   698 	drawHand(g, highlightColor, diameter * .011f, p1, p2);
       
   699     }
       
   700 
       
   701     protected Point drawSecondHand(Graphics2D g, int diameter) {
       
   702 	return drawHand(g, secondHandColor, diameter * .011f,
       
   703 	    diameter * .405f, secToRadians(), diameter);
       
   704     }
       
   705 
       
   706     protected void drawShadow(Graphics2D g, int diameter) {
       
   707 	// This puts the shadow around the entire face, which also looks nice
       
   708 	// float offset = 0f;
       
   709 	// float[] fractions = {0f, .95f, 1f};
       
   710 
       
   711 	float radius = diameter / 2f;
       
   712 	Color shadow = Color.black;
       
   713 	Color focus = ColorUtil.alpha(Color.black, 0f);
       
   714 	float[] fractions = {0f, .94f, 1f};
       
   715 	Color[] colors = {focus, focus, shadow};
       
   716 
       
   717 	float cos = (float)Math.cos(lightAngle + Math.PI);
       
   718 	float sin = (float)Math.sin(lightAngle + Math.PI);
       
   719 	float offset = radius * .02f;
       
   720 	float xOffset = offset * cos;
       
   721 	float yOffset = -offset * sin;
       
   722 	RadialGradientPaint paint = new RadialGradientPaint(radius + xOffset,
       
   723 	    radius + yOffset, radius + offset, fractions, colors,
       
   724 	    MultipleGradientPaint.CycleMethod.NO_CYCLE);
       
   725 
       
   726 	g.setPaint(paint);
       
   727 	g.fillOval(0, 0, diameter, diameter);
       
   728     }
       
   729 
       
   730     /**
       
   731      * Gets the color at the AM end of the AM-PM gradient.
       
   732      */
       
   733     public Color getAmColor() {
       
   734 	return amColor;
       
   735     }
       
   736 
       
   737     /**
       
   738      * Gets the {@code Animator} the controls the animation of the interactive
       
   739      * change from AM to PM and back.
       
   740      */
       
   741     public Animator getAmPmAnimator() {
       
   742 	return amPmAnimator;
       
   743     }
       
   744 
       
   745     /**
       
   746      * Gets the color of the center dial of the clock.
       
   747      */
       
   748     public Color getCenterColor() {
       
   749 	return centerColor;
       
   750     }
       
   751 
       
   752     /**
       
   753      * Gets the color of the face of the clock.
       
   754      */
       
   755     public Color getFaceColor() {
       
   756 	return faceColor;
       
   757     }
       
   758 
       
   759     /**
       
   760      * Gets the color of the outer frame of the clock.
       
   761      */
       
   762     public Color getFrameColor() {
       
   763 	return frameColor;
       
   764     }
       
   765 
       
   766     /**
       
   767      * Gets the color to use when highlighting a hand.
       
   768      */
       
   769     public Color getHighlightColor() {
       
   770 	return highlightColor;
       
   771     }
       
   772 
       
   773     public Color getHourHandColor() {
       
   774 	return hourHandColor;
       
   775     }
       
   776 
       
   777     /**
       
   778      * Gets the color of the hour mark on the clock face.
       
   779      */
       
   780     public Color getHourMarkColor() {
       
   781 	return hourMarkColor;
       
   782     }
       
   783 
       
   784     /**
       
   785      * Gets the angle of the light, in radians, with 0 at three o'clock, shining
       
   786      * onto the clock.
       
   787      */
       
   788     public float getLightAngle() {
       
   789 	return lightAngle;
       
   790     }
       
   791 
       
   792     public Color getMinuteHandColor() {
       
   793 	return minuteHandColor;
       
   794     }
       
   795 
       
   796     /**
       
   797      * Gets the color of the minute mark on the clock face.
       
   798      */
       
   799     public Color getMinuteMarkColor() {
       
   800 	return minuteMarkColor;
       
   801     }
       
   802 
       
   803     /**
       
   804      * Gets the color at the PM end of the AM-PM gradient.
       
   805      */
       
   806     public Color getPmColor() {
       
   807 	return pmColor;
       
   808     }
       
   809 
       
   810     public Color getSecondHandColor() {
       
   811 	return secondHandColor;
       
   812     }
       
   813 
       
   814     public TimeModel getTimeModel() {
       
   815 	return model;
       
   816     }
       
   817 
       
   818     public TimeSelectionModel getTimeSelectionModel() {
       
   819 	return selModel;
       
   820     }
       
   821 
       
   822     /**
       
   823      * Gets whether to use instructional tooltips over the interactive portions
       
   824      * of the clock.
       
   825      */
       
   826     public boolean getUseToolTips() {
       
   827 	return useToolTips;
       
   828     }
       
   829 
       
   830     protected float hourToRadians() {
       
   831 	Calendar cal = model.getCalendar();
       
   832 	int hour = cal.get(Calendar.HOUR);
       
   833 	int minute = cal.get(Calendar.MINUTE);
       
   834 	int second = cal.get(Calendar.SECOND);
       
   835 	return hourToRadians(hour, minute, second);
       
   836     }
       
   837 
       
   838     /**
       
   839      * Gets whether this {@code AnalogClock}'s time can be set by dragging the
       
   840      * hands.
       
   841      */
       
   842     public boolean isInteractive() {
       
   843 	return interactive;
       
   844     }
       
   845 
       
   846     protected float minToRadians() {
       
   847 	Calendar cal = model.getCalendar();
       
   848 	int minute = cal.get(Calendar.MINUTE);
       
   849 	int second = cal.get(Calendar.SECOND);
       
   850 	return minToRadians(minute, second);
       
   851     }
       
   852 
       
   853     protected float secToRadians() {
       
   854 	Calendar cal = model.getCalendar();
       
   855 	int second = cal.get(Calendar.SECOND);
       
   856 	return secToRadians(second);
       
   857     }
       
   858 
       
   859     /**
       
   860      * Sets the color at the AM end of the AM-PM gradient.
       
   861      */
       
   862     public void setAmColor(Color amColor) {
       
   863 	this.amColor = amColor;
       
   864 	repaint();
       
   865     }
       
   866 
       
   867     /**
       
   868      * Sets the color of the center dial of the clock.
       
   869      */
       
   870     public void setCenterColor(Color centerColor) {
       
   871 	this.centerColor = centerColor;
       
   872 	repaint();
       
   873     }
       
   874 
       
   875     /**
       
   876      * Sets the preferred widht and height of this {@code AnalogClock} to {@code
       
   877      * diameter}.
       
   878      */
       
   879     public void setPreferredDiameter(int diameter) {
       
   880 	setPreferredSize(new Dimension(diameter, diameter));
       
   881     }
       
   882 
       
   883     /**
       
   884      * Sets the color of the face of the clock.
       
   885      */
       
   886     public void setFaceColor(Color faceColor) {
       
   887 	this.faceColor = faceColor;
       
   888 	baseImage = null;
       
   889 	repaint();
       
   890     }
       
   891 
       
   892     /**
       
   893      * Sets the color of the outer frame of the clock.
       
   894      */
       
   895     public void setFrameColor(Color frameColor) {
       
   896 	this.frameColor = frameColor;
       
   897 	baseImage = null;
       
   898 	repaint();
       
   899     }
       
   900 
       
   901     /**
       
   902      * Sets the color to use when highlighting a hand.
       
   903      */
       
   904     public void setHighlightColor(Color highlightColor) {
       
   905 	this.highlightColor = highlightColor;
       
   906     }
       
   907 
       
   908     public void setHourHandColor(Color hourHandColor) {
       
   909 	this.hourHandColor = hourHandColor;
       
   910 	repaint();
       
   911     }
       
   912 
       
   913     /**
       
   914      * Sets the color of the hour mark on the clock face.
       
   915      */
       
   916     public void setHourMarkColor(Color hourMarkColor) {
       
   917 	this.hourMarkColor = hourMarkColor;
       
   918 	baseImage = null;
       
   919 	repaint();
       
   920     }
       
   921 
       
   922     /**
       
   923      * Sets whether this {@code AnalogClock}'s time can be set by dragging the
       
   924      * hands.
       
   925      */
       
   926     public void setInteractive(boolean interactive) {
       
   927 	if (this.interactive != interactive) {
       
   928 	    this.interactive = interactive;
       
   929 	    if (interactive) {
       
   930 		addMouseListener(this);
       
   931 		addMouseMotionListener(this);
       
   932 	    } else {
       
   933 		removeMouseListener(this);
       
   934 		removeMouseMotionListener(this);
       
   935 		mousePoint = null;
       
   936 	    }
       
   937 	}
       
   938     }
       
   939 
       
   940     /**
       
   941      * Sets the angle of the light, in radians, with 0 at three o'clock, shining
       
   942      * onto the clock.
       
   943      */
       
   944     public void setLightAngle(float lightAngle) {
       
   945 	this.lightAngle = lightAngle;
       
   946 	baseImage = null;
       
   947 	timeImage = null;
       
   948 	centerImage = null;
       
   949 	repaint();
       
   950     }
       
   951 
       
   952     public void setMinuteHandColor(Color minuteHandColor) {
       
   953 	this.minuteHandColor = minuteHandColor;
       
   954 	repaint();
       
   955     }
       
   956 
       
   957     /**
       
   958      * Sets the color of the minute mark on the clock face.
       
   959      */
       
   960     public void setMinuteMarkColor(Color minuteMarkColor) {
       
   961 	this.minuteMarkColor = minuteMarkColor;
       
   962 	baseImage = null;
       
   963 	repaint();
       
   964     }
       
   965 
       
   966     /**
       
   967      * Sets the color at the PM end of the AM-PM gradient.
       
   968      */
       
   969     public void setPmColor(Color pmColor) {
       
   970 	this.pmColor = pmColor;
       
   971 	repaint();
       
   972     }
       
   973 
       
   974     public void setSecondHandColor(Color secondHandColor) {
       
   975 	this.secondHandColor = secondHandColor;
       
   976 	repaint();
       
   977     }
       
   978 
       
   979     public void setTimeModel(TimeModel model) {
       
   980 	if (this.model != model) {
       
   981 	    if (this.model != null) {
       
   982 		this.model.removePropertyChangeListener(timeListener);
       
   983 	    }
       
   984 
       
   985 	    model.addPropertyChangeListener(TimeModel.PROPERTY_TIME,
       
   986 		timeListener);
       
   987 
       
   988 	    this.model = model;
       
   989 	    timeChanged();
       
   990 	}
       
   991     }
       
   992 
       
   993     public void setTimeSelectionModel(TimeSelectionModel selModel) {
       
   994 	this.selModel = selModel;
       
   995     }
       
   996 
       
   997     /**
       
   998      * Sets whether to use instructional tooltips over the interactive portions
       
   999      * of the clock.
       
  1000      */
       
  1001     public void setUseToolTips(boolean useToolTips) {
       
  1002 	if (this.useToolTips != useToolTips) {
       
  1003 	    this.useToolTips = useToolTips;
       
  1004 	    setToolTipText();
       
  1005 	}
       
  1006     }
       
  1007 
       
  1008     //
       
  1009     // Private methods
       
  1010     //
       
  1011 
       
  1012     private void createBaseImage(int diameter) {
       
  1013 	if (baseImage == null || baseImage.getWidth() != diameter) {
       
  1014 	    baseImage = new BufferedImage(diameter, diameter,
       
  1015 		BufferedImage.TYPE_INT_ARGB);
       
  1016 
       
  1017 	    Graphics2D g = baseImage.createGraphics();
       
  1018 	    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
       
  1019 		RenderingHints.VALUE_ANTIALIAS_ON);
       
  1020 
       
  1021 	    frameWidth = drawFrame(g, diameter);
       
  1022 	    diameter -= 2 * frameWidth;
       
  1023 
       
  1024 	    g.translate(frameWidth, frameWidth);
       
  1025 
       
  1026 	    drawFace(g, diameter);
       
  1027 	    drawHourMarks(g, diameter);
       
  1028 	    drawMinuteMarks(g, diameter);
       
  1029 	    drawShadow(g, diameter);
       
  1030 
       
  1031 	    g.translate(-frameWidth, -frameWidth);
       
  1032 	}
       
  1033     }
       
  1034 
       
  1035     private void createCenterImage(int diameter) {
       
  1036 	if (centerImage == null || centerImage.getWidth() != diameter) {
       
  1037 	    centerImage = new BufferedImage(diameter, diameter,
       
  1038 		BufferedImage.TYPE_INT_ARGB);
       
  1039 
       
  1040 	    Graphics2D g = centerImage.createGraphics();
       
  1041 	    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
       
  1042 		RenderingHints.VALUE_ANTIALIAS_ON);
       
  1043 
       
  1044 	    drawCenterDot(g, diameter);
       
  1045 	}
       
  1046     }
       
  1047 
       
  1048     private void createTimeImage(int diameter) {
       
  1049 	if (timeImage == null || timeImage.getWidth() != diameter) {
       
  1050 	    timeImage = new BufferedImage(diameter, diameter,
       
  1051 		BufferedImage.TYPE_INT_ARGB);
       
  1052 
       
  1053 	    Graphics2D g = timeImage.createGraphics();
       
  1054 	    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
       
  1055 		RenderingHints.VALUE_ANTIALIAS_ON);
       
  1056 
       
  1057 	    amPm = drawAmPm(g, diameter);
       
  1058 	    hourPoint = drawHourHand(g, diameter);
       
  1059 	    minPoint = drawMinuteHand(g, diameter);
       
  1060 	    secPoint = drawSecondHand(g, diameter);
       
  1061 	}
       
  1062     }
       
  1063 
       
  1064     private void setSelectedField(int selectedField) {
       
  1065 	setCursor(selectedField == -1 ? null :
       
  1066 	    Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
       
  1067 	selModel.setSelectedField(selectedField);
       
  1068 
       
  1069 	setToolTipText();
       
  1070     }
       
  1071 
       
  1072     private void setToolTipText() {
       
  1073 	String resource = null;
       
  1074 
       
  1075 	if (useToolTips) {
       
  1076 	    switch (selModel.getSelectedField()) {
       
  1077 		case Calendar.AM_PM:
       
  1078 		    resource = "time.tooltip.ampm";
       
  1079 		    break;
       
  1080 
       
  1081 		case Calendar.HOUR:
       
  1082 		    resource = "time.tooltip.hour";
       
  1083 		    break;
       
  1084 
       
  1085 		case Calendar.MINUTE:
       
  1086 		    resource = "time.tooltip.minute";
       
  1087 		    break;
       
  1088 
       
  1089 		case Calendar.SECOND:
       
  1090 		    resource = "time.tooltip.second";
       
  1091 		    break;
       
  1092 	    }
       
  1093 	}
       
  1094 
       
  1095 	setToolTipText(resource == null ? null : Finder.getString(resource));
       
  1096     }
       
  1097 
       
  1098     private void timeChanged() {
       
  1099 	timeImage = null;
       
  1100 	repaint();
       
  1101     }
       
  1102 
       
  1103     //
       
  1104     // Static methods
       
  1105     //
       
  1106 
       
  1107     public static float amPmToRadians(int amPm, int hour, int minute,
       
  1108 	int second) {
       
  1109 
       
  1110 	hour = (hour % 12) + (amPm == Calendar.PM ? 12 : 0);
       
  1111 	return (6 - (hour + minute / 60f + second / 3600f)) * (float)Math.PI /
       
  1112 	    12f;
       
  1113     }
       
  1114 
       
  1115     public static float hourToRadians(int hour, int minute, int second) {
       
  1116 	return (15 - (hour + minute / 60f + second / 3600f)) * (float)Math.PI /
       
  1117 	    6f;
       
  1118     }
       
  1119 
       
  1120     public static float minToRadians(int minute, int second) {
       
  1121 	return (75 - (minute + second / 60f)) * (float)Math.PI / 30f;
       
  1122     }
       
  1123 
       
  1124     public static float radiansToHour(float radians) {
       
  1125 	float value = (-6 * radians / (float)Math.PI) + 3;
       
  1126 	if (value < 1) {
       
  1127 	    value += 12;
       
  1128 	}
       
  1129 	return value;
       
  1130     }
       
  1131 
       
  1132     public static float radiansToMin(float radians) {
       
  1133 	float value = (-30 * radians / (float)Math.PI) + 15;
       
  1134 	if (value < 0) {
       
  1135 	    value += 60;
       
  1136 	}
       
  1137 	return value;
       
  1138     }
       
  1139 
       
  1140     public static float secToRadians(int second) {
       
  1141 	return (75 - second) * (float)Math.PI / 30f;
       
  1142     }
       
  1143 
       
  1144     /**
       
  1145      * Converts {@code Calendar} time fields to milliseconds.
       
  1146      *
       
  1147      * @param	    calendarField
       
  1148      *		    {@code Calendar.MILLISECOND},
       
  1149      *		    {@code Calendar.SECOND},
       
  1150      *		    {@code Calendar.MINUTE},
       
  1151      *		    {@code Calendar.HOUR},
       
  1152      *		    {@code Calendar.HOUR_OF_DAY}, or
       
  1153      *		    {@code Calendar.AM_PM},
       
  1154      *
       
  1155      * @return	    the number of milliseconds in a unit of the given field, or
       
  1156      *		    zero if {@code calendarField} is not any of the allowed
       
  1157      *		    fields
       
  1158      */
       
  1159     public static long timeFieldToMillis(int calendarField) {
       
  1160 	long millis = 0;
       
  1161 	switch (calendarField) {
       
  1162 	    case Calendar.MILLISECOND:
       
  1163 		millis = 1;
       
  1164 		break;
       
  1165 
       
  1166 	    case Calendar.SECOND:
       
  1167 		millis = 1000;
       
  1168 		break;
       
  1169 
       
  1170 	    case Calendar.MINUTE:
       
  1171 		millis = 60000;
       
  1172 		break;
       
  1173 
       
  1174 	    case Calendar.HOUR:
       
  1175 	    case Calendar.HOUR_OF_DAY:
       
  1176 		millis = 3600000;
       
  1177 		break;
       
  1178 
       
  1179 	    case Calendar.AM_PM:
       
  1180 		millis = 43200000;
       
  1181 		break;
       
  1182 	}
       
  1183 
       
  1184 	return millis;
       
  1185     }
       
  1186 
       
  1187     public static void main(String args[]) {
       
  1188 	AnalogClock clock = new AnalogClock();
       
  1189 	clock.setInteractive(true);
       
  1190 	clock.setPreferredDiameter(300);
       
  1191 
       
  1192 	TimeModelUpdater updater = new TimeModelUpdater(clock.getTimeModel());
       
  1193 	updater.start();
       
  1194 
       
  1195 	JFrame frame = new JFrame();
       
  1196 	Container c = frame.getContentPane();
       
  1197 	c.setLayout(new BorderLayout());
       
  1198 	c.add(clock, BorderLayout.CENTER);
       
  1199 	frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
       
  1200 	frame.pack();
       
  1201 	frame.setVisible(true);
       
  1202     }
       
  1203 }