/**
 File:      AnalogClockApplet.java
 Author:    Angus McIntyre <angus@pobox.com>
 Date:      22.05.96
 Updated:   17.03.98
 
 Simple Java applet to display an analog clock and update it continuously.
 
    HISTORY
    -------
    
	20.02.99	SLAM	Added call to 'verifyAppletLocation'.
    17.03.98	SLAM	Added documentation for 'time-zone' parameter.
    29.11.96    SLAM    Split out into separate files
    27.05.96    SLAM    Classes merged, bugs fixed
    26.05.96    SLAM    Analog clock implemented
    22.05.96    SLAM    First version implemented
    
    TO-DO
    -----
    
        * Add tick-marks (optionally) to outer edge of clock frame
        
        * Add numbers (optionally) to outer edge of clock frame
        
        * Implement better scheme for calculating hand position; one
          of the problems is that the hour hand points directly to the
          current hour, rather than pointing to somewhere intermediate
          between it and the next hour, the way that a real analog clock
          does. This makes the display unrealistic and hard to read.
          
    LEGAL
    -----
    
    This software is free. It can be used and modified in any way you 
    choose, but it may not be sold, either separately or as part of a 
    collection without explicit prior permission from the author. The 
    author assumes no liability for any loss, damage or mental or 
    physical trauma you may incur through use of or inability to use 
    this software. This disclaimer must appear on any modified or 
    unmodified version of the software in which the name of the author
    also appears.
    
**/

/* ----------------------------------------------------------------------
 *                              IMPORTS
 * ---------------------------------------------------------------------- */

import java.applet.*;
import java.awt.*;
import java.util.*;
import ClockApplet;

/* ----------------------------------------------------------------------
 *                              CLASSES
 * ---------------------------------------------------------------------- */

// Class:   AnalogClockApplet
//
// An alternative clock type, used for analog clocks. This actually
// caches the last hour, minute and second drawn, so as to reduce the
// amount of redrawing required.

public class AnalogClockApplet extends ClockApplet {

    // Constants
    
    static  final   int         margin = 5;

    // Defaults
    
    static  final   Color       default_hand_color = Color.black;
    static  final   Color       default_sweep_color = Color.red;
    static  final   Color       default_face_color = Color.white;
    static  final   Color       default_frame_color = Color.black;
    static  final   Color       default_background_color = Color.lightGray;

    // Instance variables
    
                    Color       hand_color;
                    Color       sweep_color;
                    Color       face_color;
                    Color       frame_color;
                    Color       background_color;
                    
                    Point       center;
                    int         diameter = 0;
                    int         last_hour, last_minute, last_second;
                    int         hour_range = 24;
                    int         hour_hand_length;
                    int         minute_hand_length;
                    int         second_hand_length;

    // Method:  getAppletInfo()
    //
    // Return information about the applet.

    public String getAppletInfo() {
        return "AnalogClockApplet 1.2 by Angus McIntyre <angus@pobox.com>";
    }

    // Method:  getParameterInfo()
    //
    // Return information about the applet's parameter options.
    
    public String[][] getParameterInfo() {
        String[][] info = {
			{"time-zone",			"int",		"TZ offset in hours from Greenwich"},
            {"hand-color",          "Color",    "color used for clock hands"},
            {"second-hand-color",   "Color",    "color used for second hand"},
            {"face-color",          "Color",    "color used for clockface"},
            {"frame-color",         "Color",    "color used for frame of clock"},
            {"background-color",    "Color",    "color used for background"},
            {"twenty-four-hour",    "boolean",  "use 24-hour clock - default to false"},
            {"show-seconds",        "boolean",  "show second hand - default to true"}
        };
    return info;
    }

    // Method:  init()
    //
    // Call the superclass method, read in some parameters, and then set
    // up the information that we need to draw the clock. Note that for
    // analog clocks, the default is a twelve-hour display, because
    // twenty-four-hour analog clocks look strange.
    //
    // The call to 'verifyAppletLocation' has been put in to guard against
    // people who link to classfiles stored on other people's servers
    // (intentionally or unintentionally stealing bandwidth). The argument
    // it takes is the URL of a place where the source and class files can
    // be downloaded. If you create your own modified version of this applet,
    // change this to point to your own page. If you don't intend to share
    // your code, then simply pass an empty string.
    
    public void init() {
        super.init();
        hand_color = parseColorParameter("hand-color",default_hand_color);
        sweep_color = parseColorParameter("second-hand-color",default_sweep_color);
        face_color = parseColorParameter("face-color",default_face_color);
        frame_color = parseColorParameter("frame-color",default_frame_color);
        background_color = parseColorParameter("background-color",default_background_color);
        twenty_four_hour_p = parseBooleanParameter("twenty-four-hour",false);
        
        // Set up internal numeric variables. Calculate the diameter of the
        // clockface, and its center. The lengths of the various hands are
        // also calculated here; change the decimal constants if you want.
        
        diameter = Math.min(bounds().width-(2*margin),
                            bounds().height-(2*margin));
        center = new Point(bounds().x + (diameter / 2) + margin,
                           bounds().y + (diameter / 2) + margin);
        hour_hand_length = (int) (diameter * 0.40);
        minute_hand_length = (int) (diameter * 0.45);
        second_hand_length = (int) (diameter * 0.45);
        
        // Set up the internal variables used to track time

        last_hour = date.getHours();
        last_minute = date.getMinutes();
        last_second = date.getSeconds();
        if (!twenty_four_hour_p) {
            hour_range = 12;
            if (last_hour > 12)
                last_hour -= 12;
        }
    }
    
    // Method:  drawClock(Graphics)
    //
    // Draw the clock in the graphics environment provided. This involves
    // drawing the face and frame of the clock, and then drawing the hands
    // for the first time.
    
    public void drawClock (Graphics g) {
        super.drawClock(g);
        Rectangle bounds = bounds();
        
        // Fill in with the background color. I'd really like to be able
        // to get this from the browser, but they don't seem to be too
        // intelligent about this as a rule (for example, NetScape always
        // gives browser gray as the background color of the applet and
        // its parent, even if the BGCOLOR of the page is specified to be
        // something else).
        
        g.setColor(background_color);
        g.fillRect(bounds.x,bounds.y,bounds.width,bounds.height);
        
        // Draw the clock face
        
        g.setColor(face_color);
        g.fillOval(bounds.x+margin,bounds.y+margin,diameter,diameter);
        g.setColor(frame_color);
        g.drawOval(bounds.x+margin,bounds.y+margin,diameter,diameter);
        
        // Draw the hands
         
        drawClockHand(g,hand_color,hour_hand_length,last_hour,hour_range);
        drawClockHand(g,hand_color,minute_hand_length,last_minute,60);
        if (show_seconds_p)
            drawClockHand(g,sweep_color,second_hand_length,last_second,60);
    }
    
    // Method:  updateClock(Graphics)
    //
    // Call the superclass method to make sure that everything is ready
    // for drawing, then get the time information we need to draw.
    
    public void updateClock(Graphics g) {
        super.updateClock(g);
        int hour = date.getHours();
        int minute = date.getMinutes();
        int second = date.getSeconds();
        if (hour > hour_range)
            hour -= hour_range;
        
        // Drawing is actually done in 'reverse' order - second hand (if
        // shown) first, then minute hand, then hour hand. The reason for
        // this is that the second hand is more likely to wipe out the
        // hour hand as it moves on. If it does, the hour hand doesn't
        // get redrawn for a full second, which looks ugly. So we draw
        // the second hand first, and then draw the other hands over it.
        // The same applies with the minute hand.
        //
        // Each hand is checked against the last recorded position of the
        // hand. If it has moved on since it was last drawn, the old hand
        // is drawn over in the background color, and the hand is then
        // redrawn in the new position. This would actually be a prime
        // candidate for use of XOR drawing mode: unfortunately, NetScape
        // seems to do something a bit weird with XOR, so we simply use
        // standard mode and paint out the preceding item with the
        // background color. It seems to work equally well. 
        
        if (show_seconds_p) {
            if (second != last_second) {
                drawClockHand(g,face_color,second_hand_length,last_second,60);
                last_second = second;
            }
            drawClockHand(g,sweep_color,second_hand_length,second,60);
        }

        if (minute != last_minute) {
            drawClockHand(g,face_color,minute_hand_length,last_minute,60);
            last_minute = minute;
        }
        drawClockHand(g,hand_color,minute_hand_length,minute,60);

        if (hour != last_hour) {
            drawClockHand(g,face_color,hour_hand_length,last_hour,hour_range);
            last_hour = hour;
        }
        drawClockHand(g,hand_color,hour_hand_length,hour,hour_range);
    }
    
    // Method:  drawClockHand(Graphics,Color,int,int,int)
    //
    // Draw one of the hands of the clock. The method is passed not only
    // a graphics environment and a color, but also the length of the hand
    // to draw, and two other values called 'value' and 'range'. 'value' is
    // the time for that hand, e.g. if it were 8:45 and we were drawing
    // the hour hand, it would be '8'. 'range' is the range of possible
    // values: this will always be 60 for minutes and seconds, and either
    // 12 or 24 for hours, depending on whether the 24-hour clock is in
    // use. The combination of the value and range determines the angle
    // of the hand.
    
    protected void  drawClockHand(Graphics g, Color color, int length, 
                                  int value, int range) {
        Point tip = calculateHandTip(center.x,center.y,
                                     length,value,range);
        g.setColor(color);
        g.drawLine(center.x,center.y,tip.x,tip.y);
    }
    
    // Method:  calculateHandTip(int,int,int,int,int)
    //
    // Work out where the tip of the hand should be drawn, given the
    // position of the center of the clock, the length of the hand, and 
    // the value and range (as explained above). The calculation of the
    // angle involves an implicit conversion from degrees (which I like
    // thinking in) to radians (which Java expects). This could probably
    // be optimised. Ideally, in fact, we'd calculate everything in
    // advance and store it in an array for maximum speed - but this
    // amount of trigonometry probably isn't straining modern processors
    // too much.
    
    protected Point calculateHandTip(int x, int y, int length,
                                     int value, int range) {
        double  angle = (Math.PI * (((value * 360) / range) - 90)) / 180;
        return new Point(x + (int) (length * Math.cos(angle)),
                         y + (int) (length * Math.sin(angle)));
    }
}











