/*


 * javex.java - 19 Jan 1996 - Version 1.00


 *


 * Note: This source code is subject to perpetual refinement!


 *


 * Copyright 1996 by Bill Giel


 *


 * E-mail: rvdi@usa.nai.net


 * WWW: http://www.nai.net/~rvdi/home.htm


 *


 *


 * Permission to use, copy, modify, and distribute this software


 * and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and


 * without fee is hereby granted, provided that any use properly credits


 * the author, i.e. "Javex Clock courtesy of <A HREF="mailto:rvdi@usa.nai.net">


 * Bill Giel</A>.


 * 


 * 


 * THE AUTHOR MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY


 * OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED


 * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A


 * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. THE AUTHOR SHALL NOT BE LIABLE


 * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR


 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.


 * 


 * THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE


 * CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE


 * PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT


 * NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE


 * SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE


 * SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE


 * PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES"). YOU WOULD


 * HAVE TO BE OUT OF YOUR MIND TO USE THIS SOFTWARE IN A HIGH-RISK ENVIRONMENT


 * AND, AS SUCH MAY BE THE CASE,  THE AUTHOR SPECIFICALLY DISCLAIMS ANY EXPRESS


 * OR IMPLIED WARRANTY OF FITNESS FOR HIGH RISK ACTIVITIES, INCLUDING BUT NOT


 * LIMITED TO USE OR MISUSE OF THIS SOFTWARE RESULTING IN NUCLEAR EXPLOSIONS,


 * GLOBAL WARMING OR OZONE-LAYER DEPLETION.


 */











import java.awt.*;


import java.applet.*;


import java.lang.*;


import java.util.*;


import java.net.*;





class hms extends Date


{


    public hms(){super();}





    public double get_hours()


    {


        return (double)super.getHours()+(double)getMinutes()/60.0;


    }


}





abstract class clockHand


{


    protected int baseX[], baseY[];


    protected int transX[],transY[];


    protected int numberOfPoints;





    public clockHand(int originX, int originY, int length,int thickness,int points){


        baseX= new int[points]; baseY=new int[points];


        transX= new int[points]; transY=new int[points];


        initiallizePoints(originX,originY,length,thickness);


        numberOfPoints=points;


    }





    abstract protected void initiallizePoints(  int originX,


                                                int originY,


                                                int length,


                                                int thickness);


                                                


    abstract public void draw(Color color, double angle, Graphics g);





    protected void transform(double angle)


    {


        for(int i=0;i<numberOfPoints;i++){


            transX[i]=(int)(    (baseX[0]-baseX[i]) * Math.cos(angle) -


                                (baseY[0]-baseY[i]) * Math.sin(angle) +


                                 baseX[0]);


                                 


            transY[i]=(int)(    (baseX[0]-baseX[i]) * Math.sin(angle) +


                                (baseY[0]-baseY[i]) * Math.cos(angle) +


                                 baseY[0]);


        }


    }


}





class sweepHand extends clockHand


{


    public sweepHand(int originX,int originY, int length, int points)


    {


        super(originX,originY,length,0,points);


	}





    protected void initiallizePoints(int originX,int originY, int length, int unused)


    {


        unused=unused;  //We don't use the thickness parameter in this class


                        //This comes from habit to prevent compiler warning


                        //concerning unused arguments.


        


        baseX[0]=originX; baseY[0]=originY;


        baseX[1]=originX; baseY[1]=originY-length/5;


        baseX[2]=originX; baseY[2]=originY+length;


    }





    public void draw(Color color, double angle, Graphics g)


    {


        transform(angle);


        g.setColor(color);


        g.drawLine(transX[1],transY[1],transX[2],transY[2]);


    }	


}





class hmHand extends clockHand


{


    public hmHand(int originX,int originY, int length,int thickness, int points){


        super(originX,originY,length,thickness,points);


    }	





    protected void initiallizePoints(   int originX,


                                        int originY,


                                        int length,


                                        int thickness)


    {


        baseX[0]=originX;


        baseY[0]=originY;


        


        baseX[1]=baseX[0]-thickness/2;


        baseY[1]=baseY[0]+thickness/2;


		


        baseX[2]=baseX[1];


        baseY[2]=baseY[0]+length- thickness;


		


        baseX[3]=baseX[0];


        baseY[3]=baseY[0]+length;


		


        baseX[4]=baseX[0]+thickness/2;


        baseY[4]=baseY[2];


		


        baseX[5]=baseX[4];


        baseY[5]=baseY[1];


    }





    public void draw(Color color,double angle, Graphics g)


    {


        transform(angle);


        g.setColor(color);


        g.fillPolygon(transX,transY,numberOfPoints);


    }


}





public class javex extends Applet implements Runnable


{


    //some DEFINE'd constants


    static final int BACKGROUND=0;              //Background image index


    static final int LOGO=1;                    //Logo image index


    static final String JAVEX="JAVEX";          //Default text on clock face


    static final double MINSEC=0.104719755;     //Radians per minute or second


    static final double HOUR=0.523598776;       //Radians per hour





    Thread clockThread = null;





    //User options, see getParameterInfo(), below.


    int width = 100;


    int height = 100;


    Color bgColor = new Color(0,0,0);


    Color faceColor = new Color(0,0,0);


    Color sweepColor = new Color(255,0,0);


    Color minuteColor = new Color (192,192,192);


    Color hourColor = new Color (255,255,255);


    Color textColor = new Color (255,255,255);


    Color caseColor = new Color (0,0,0);


    Color trimColor = new Color (192,192,192);


    String logoString=null;


    


    Image images[] = new Image[2]; //Array to hold optional images





    boolean isPainted=false; //Force painting on first update, if not painted





    //Center point of clock


    int x1,y1;





    //Array of points for triangular icon at 12:00


    int xPoints[]=new int[3], yPoints[]=new int[3];





    //Class to hold time, with method to return (double)(hours + minutes/60)


    hms cur_time;





    //The clock's seconds, minutes, and hours hands.


    sweepHand sweep;


    hmHand  minuteHand,


            hourHand;





    //The last parameters used to draw the hands.


    double lastHour;


    int lastMinute,lastSecond;





    //The font used for text and date.


    Font font;





    //Offscreen image and device context, for buffered output.


    Image offScrImage;


    Graphics offScrGC;





    // Use to test background image, if any.


    MediaTracker tracker;





   


    int minDimension;   // Ensure a round clock if applet is not square.


    int originX;        // Upper left corner of a square enclosing the clock


    int originY;        // with respect to applet area.


    





    //Users' parameters - self-explanatory?


    public String[][] getParameterInfo()


    {


        String[][] info = {


            {"width",       "int",      "width of the applet, in pixels"},


            {"height",      "int",      "height of the applet, in pixels"},


            {"bgColor",     "string",   "hex color triplet of the background, i.e. 000000 for black <black>"},


            {"faceColor",   "string",   "hex color triplet of clock face, i.e. 000000 for black <black>"},


            {"sweepColor",  "string",   "hex color triplet of seconds hand, i.e. FF0000 for red <red>"},


            {"minuteColor", "string",   "hex color triplet of minutes hand, i.e. C0C0C0 for lt.gray <lt.gray>"},


            {"hourColor",   "string",   "hex color triplet of hours hand, i.e. FFFFFF for white <white>"},


            {"textColor",   "string",   "hex color triplet of numbers, etc., i.e. FFFFFF for white <white>"},


            {"caseColor",   "string",   "hex color triplet of case, i.e. 000000 for black <black>"},


            {"trimColor",   "string",   "hex color triplet of case outliners, i.e. C0C0C0 for lt.gray <lt.gray>"}, 


            {"bgImageURL",  "string",   "URL of background image, if any <null>"},


            {"logoString",  "string",   "Name to display on watch face <JAVEX>"},


            {"logoImageURL","string",   "URL of logo image to display on watch face <null>"},


        };


        return info;


    }





    //Applet name, author and info lines


    public String getAppletInfo()


    {


        return "JAVEX - analog clock applet by Bill Giel, 17 Jan 96\nhttp://www.nai.net/~rvdi/home.htm  or  rvdi@usa.nai.net";


    }





    void showURLerror(Exception e)


    {


        String errorMsg = "JAVEX URL error: "+e;


        showStatus(errorMsg);


        System.err.println(errorMsg);


    }





    // This lets us create clocks of various sizes, but with the same


    // proportions.


    private int size(int percent)


    {


        return (int)((double)percent/100.0 * (double)minDimension);


    }





    public void init()


    {


        URL imagesURL[] = new URL[2];


        String szImagesURL[] = new String[2];


        tracker = new MediaTracker(this);


				


        String paramString    = getParameter( "WIDTH"  );


        if( paramString != null )


            width = Integer.valueOf(paramString).intValue();


			


        paramString   = getParameter( "HEIGHT" );


        if( paramString != null )


            height = Integer.valueOf(paramString).intValue();


			


        paramString    = getParameter( "BGCOLOR");


        if( paramString != null )


            bgColor=parseColorString(paramString);


			


        paramString    = getParameter( "FACECOLOR");


        if( paramString != null )


            faceColor=parseColorString(paramString);


			


        paramString    = getParameter( "SWEEPCOLOR");


        if( paramString != null )


            sweepColor=parseColorString(paramString);


			


        paramString    = getParameter( "MINUTECOLOR");


        if( paramString != null )


            minuteColor=parseColorString(paramString);





        paramString    = getParameter( "HOURCOLOR");


        if( paramString != null )


            hourColor=parseColorString(paramString);				





        paramString    = getParameter( "TEXTCOLOR");


        if( paramString != null )


            textColor=parseColorString(paramString);





        paramString    = getParameter( "CASECOLOR");


        if( paramString != null )


            caseColor=parseColorString(paramString);





        paramString    = getParameter( "TRIMCOLOR");


        if( paramString != null )


            trimColor=parseColorString(paramString);





        logoString  = getParameter( "LOGOSTRING");


        if( logoString == null )


            logoString=JAVEX;


        else if(logoString.length() > 8)


            logoString= logoString.substring(0,8); //Max 8 characters!


		


        szImagesURL[BACKGROUND]  = getParameter("BGIMAGEURL");


        szImagesURL[LOGO] = getParameter("LOGOIMAGEURL");





        for(int i=0; i<2; i++){


            if(szImagesURL[i] != null){


                try{


                    imagesURL[i]=new URL(getDocumentBase(),szImagesURL[i]);


                } catch (MalformedURLException e)


                    {


                        showURLerror(e);


                        imagesURL[i]=null;


                        images[i]=null;


                    }


                if(imagesURL[i] != null){


                    showStatus("Javex loading image: " + imagesURL[i].toString());


                    images[i]=getImage(imagesURL[i]);


                    if(images[i] != null)


                        tracker.addImage(images[i],i);	


                    showStatus("");						


                }


                if(images[i] != null)


                    try{


                        tracker.waitForID(i);


                    }catch (InterruptedException e)


                        {


                            images[i]=null;


                        }


            }


            else images[i]=null;


        }


		


        cur_time=new hms();


        lastHour=-1.0;


        lastMinute=-1;


        lastSecond=-1;





        x1=width/2;


        y1=height/2;





        minDimension= Math.min(width, height);


        originX=(width-minDimension)/2;


        originY=(height-minDimension)/2;





        xPoints[1]=x1-size(3); xPoints[2]=x1+size(3); xPoints[0]=x1;


        yPoints[1]=y1-size(38);yPoints[2]=y1-size(38); yPoints[0]=y1-size(27);





        sweep=new sweepHand(x1,y1,size(40),3);


        minuteHand=new hmHand(x1,y1,size(40),size(6),6);


        hourHand=new hmHand(x1,y1,size(25),size(8),6);





        font=new Font("TXT",Font.BOLD,size(10));





        offScrImage = createImage(width,height);


        offScrGC = offScrImage.getGraphics();


    }





    public void start()


    {


        if(clockThread == null){


            clockThread = new Thread(this);


            clockThread.start();


        }


    }





    public void stop()


    {


        clockThread.stop();


        clockThread=null;


    }


    


    private void drawHands(Graphics g)


    {


	


        double angle;


        int i,j;


        int x,y;


	


        angle=MINSEC * lastSecond;


        sweep.draw(faceColor, angle, g);





        if(cur_time.getMinutes() != lastMinute){


            minuteHand.draw(faceColor,MINSEC*lastMinute,g);


            if(cur_time.get_hours() != lastHour)


                hourHand.draw(faceColor,HOUR*lastHour,g);


        }





        g.setColor(textColor);


        g.fillRect(originX+size(12),y1-size(2),size(10),size(4));


        g.fillRect(x1-size(2),originY + minDimension-size(22),size(4),size(10));


        g.fillPolygon( xPoints, yPoints, 3);


        for(i=1;i<12;i+=3)


            for(j=i;j<i+2;j++){


                x=(int)(x1+Math.sin(HOUR*j)*size(35));


                y=(int)(y1-Math.cos(HOUR*j)*size(35));


                g.fillOval(x-size(3),y-size(3),size(6),size(6));


            }





        //Set the font and get font info...


        g.setFont(font);


        FontMetrics fm=g.getFontMetrics();





        //Paint our logo...


        g.drawString(logoString,x1-fm.stringWidth(logoString)/2,y1-size(12));





        //Get the day of the month...


        String day=Integer.toString(cur_time.getDate(),10);


        


        //Paint it...


        g.drawString(   day,


                        originX + minDimension-size(14)-fm.stringWidth(day),


                        y1+size(5));





        //and put a box around it.                        


        g.drawRect( originX + minDimension-size(14)-fm.stringWidth(day)-size(2),


                    y1-size(5)-size(2),


                    fm.stringWidth(day)+size(4),


                    size(10)+size(4));





        if(images[LOGO] != null){


            x = originX + (minDimension-images[LOGO].getWidth(this))/2;


            y = y1 + (minDimension/2 - size(22) - images[LOGO].getHeight(this))/2;


            if(x > 0 && y > 0)


                offScrGC.drawImage(images[LOGO], x, y, this);


        }





        lastHour=cur_time.get_hours();


        hourHand.draw(hourColor,HOUR*lastHour,g);


        


        lastMinute=cur_time.getMinutes();


        minuteHand.draw(minuteColor,MINSEC*lastMinute,g);		





        g.setColor(minuteColor);


        g.fillOval(x1-size(4),y1-size(4),size(8),size(8));


        g.setColor(sweepColor);


        g.fillOval(x1-size(3),y1-size(3),size(6),size(6));





        lastSecond=cur_time.getSeconds();		


        angle=MINSEC*lastSecond;


        sweep.draw(sweepColor, angle,g);





        g.setColor(trimColor);


        g.fillOval(x1-size(1),y1-size(1),size(2),size(2));		


    }





    private Color parseColorString(String colorString)


    {


        if(colorString.length()==6){


            int R = Integer.valueOf(colorString.substring(0,2),16).intValue();


            int G = Integer.valueOf(colorString.substring(2,4),16).intValue();


            int B = Integer.valueOf(colorString.substring(4,6),16).intValue();


            return new Color(R,G,B);


        }


        else return Color.lightGray;


    }


    


    public void run()


    {


        //Let's not hog the system, now...


        Thread.currentThread().setPriority(Thread.MIN_PRIORITY);


        


        repaint();


        for(;;){


            cur_time=new hms();


            repaint();


            try{


                Thread.sleep(1000);


            } catch (InterruptedException e) {}			


        }


    }





    public void paint(Graphics g)


    {


        int i,x0,y0,x2,y2;





        if(images[BACKGROUND] == null){


            offScrGC.setColor(bgColor);


            offScrGC.fillRect(0,0,width,height);


        }


        else


            offScrGC.drawImage(images[BACKGROUND], 0, 0, this);





        offScrGC.setColor(caseColor);





        //Shrink one pixel so we don't clip anything off...


        offScrGC.fillOval(  originX+1,


                            originY+1,


                            minDimension-2,


                            minDimension-2);


		


        offScrGC.setColor(faceColor);


        offScrGC.fillOval(  originX + size(5),


                            originY + size(5),


                            minDimension - size(10),


                            minDimension - size(10));


							


        offScrGC.setColor(trimColor);


        offScrGC.drawOval(  originX+1,


                            originY+1,


                            minDimension-2,


                            minDimension-2);


							


        offScrGC.drawOval(  originX + size(5),


                            originY + size(5),


                            minDimension - size(10),


                            minDimension - size(10));


							


        offScrGC.setColor(textColor);





        //Draw graduations, a longer index every fifth mark...


        for(i=0;i<60;i++){


            if(i==0 || (i>=5 && i%5 == 0)){


                x0=(int)(x1+size(40)*Math.sin(MINSEC*i));


                y0=(int)(y1+size(40)*Math.cos(MINSEC*i));


            }


            else{


                x0=(int)(x1+size(42)*Math.sin(MINSEC*i));


                y0=(int)(y1+size(42)*Math.cos(MINSEC*i));


            }


            x2=(int)(x1+size(44)*Math.sin(MINSEC*i));


            y2=(int)(y1+size(44)*Math.cos(MINSEC*i));


            offScrGC.drawLine(x0,y0,x2,y2);


        }


		


        drawHands(offScrGC);


        g.drawImage(offScrImage,0,0,this);





        isPainted=true;


    }





    public synchronized void update(Graphics g)


    {


        if(!isPainted)


            paint(g);


        else{


            drawHands(offScrGC);


            g.drawImage(offScrImage,0,0,this);


        }


    }


}


