2012年7月8日日曜日

xclock (もどき) on windows (xclock in java applet)

私は xclock が大好きである。
いつでもデスクトップの右肩に表示していてほしいくらい大好きだ。
(参考:昔の記事「"Startup Applications" で立ち上げた xclock を "Always on Top" かつ "Always on Visible Workspace" にしたい。 -> devilspie を導入した」)

しかし、windows用のxclockを見つけることができなかった。
窓の社などでwindows用のアナログ時計が公開されているのは知っているけれど、xclock ほど可憐なものは見つけられなかった (あくまでも私の主観です、美しい時計のもたくさんあります)。

自分で windows 用のプログラムを書くのは(環境整備が)ちょっと大変そうだったので、お手軽にJava appletを書くことにした。

まずは、xclock を大きく表示してスクリーンショットで撮って、針の形とか目盛の大きさとかをgimpで拡大して読み取って、いろいろ計算して、、、完成した。


左側が自作のjava applet、右側が本物のxclock

「完成品のソースコード」は自由に参考にしてかまいません。ただし、コードの品質は著作者は一切保証しませんし、コード使用によるすべての責任は使用者、引用者本人が負うものとします。

(上記は、一応のために書きましたが、java なので sandbox 内で実行されますし、通信するプログラムでもありません。読んでいただければ安全だと納得できると思います。)  

完成品のソースコード
(幅がはみ出ちゃったけど気にしない。)
Clock.java

// Clock.java  (quantum, 2012)
// Xclock-like java applet.

import java.applet.Applet;
import java.awt.*;
import java.lang.Math;
import java.util.*; // Date, Formatter
import java.io.*;
import javax.imageio.ImageIO;

public class Clock extends Applet{
    Image offimage;
    Graphics offgraphics;
    int size=300;
    Calendar cal1=Calendar.getInstance();    

    Tics tics1 = new Tics(size);
    Hand lh1=new Hand(size,true);
    Hand sh1=new Hand(size,false);
    Day d1=new Day(size);
    Image hatoimage;
    public Clock(){
        try{
            hatoimage=ImageIO.read(new File("hato.png"));
        }catch(Exception e){
            e.printStackTrace();
            hatoimage = null;
        }
    }
    public void paint(Graphics g){
        tics1.paint(g);
        sh1.paint(g);
        lh1.paint(g);
        d1.paint(g);
    }
    public void update(Graphics g){
        int w = getSize().width, h=getSize().height;
        //      if(offimage == null){
        offimage=createImage(w,h);
        offgraphics=offimage.getGraphics();
        offgraphics.setColor(getBackground());
        offgraphics.fillRect(0,0,w,h);
        paint(offgraphics);             
        //      }
        if(cal1.get(Calendar.MINUTE)==0 && cal1.get(Calendar.SECOND)<10 && hatoimage != null){
            try{
                //              offimage=ImageIO.read(new File("hato.png"));
                offimage=hatoimage;
                g.drawImage(offimage, 0,0, this);                       
            }catch(Exception e){
                e.printStackTrace();
                offimage = null;
            }
        }
        g.drawImage(offimage, 0, 0, this);
    }
        
    public void start() { (new Thread(new MyRun())).start();}
    class MyRun implements Runnable{
        public void run(){
            while(true){
                try{Thread.sleep(500);}catch(Exception e){}
                cal1=Calendar.getInstance();
                sh1.setdirection((cal1.get(Calendar.HOUR_OF_DAY)%12)/12.0
                                 +cal1.get(Calendar.MINUTE)/12.0/60.0);
                lh1.setdirection(cal1.get(Calendar.MINUTE)/60.0
                                 +cal1.get(Calendar.SECOND)/60.0/60.0);
                d1.setcal(cal1);
                repaint();
            }
        }
    }

    static class Tics{  
        double theta,R,d,l,size;
        int[] x=new int[4];
        int[] y=new int[4];
        double[] xd=new double[4];
        double[] yd=new double[4];
        public Tics (double s){
            size=s;R=size*360.0/800.0;d=size/200.0;
        }
        public void paint(Graphics g){
            g.setColor(Color.black);
            for(int i=0;i<60;++i){
                theta=i/60.0*2*Math.PI;
                l=size*36.0/800.0/2.0;
                if((i%5)==0){l=2*l;}
                xd[0]=R*Math.sin(theta)-d/2.0*Math.cos(theta); 
                xd[1]=R*Math.sin(theta)+d/2.0*Math.cos(theta); 
                xd[2]=R*Math.sin(theta)+d/2.0*Math.cos(theta)-l*Math.sin(theta);
                xd[3]=R*Math.sin(theta)-d/2.0*Math.cos(theta)-l*Math.sin(theta);
                
                yd[0]=R*Math.cos(theta)+d/2.0*Math.sin(theta); 
                yd[1]=R*Math.cos(theta)-d/2.0*Math.sin(theta); 
                yd[2]=R*Math.cos(theta)-d/2.0*Math.sin(theta)-l*Math.cos(theta);
                yd[3]=R*Math.cos(theta)+d/2.0*Math.sin(theta)-l*Math.cos(theta);
                
                for(int j=0;j<4;++j){
                    x[j]=(int)Math.round(xd[j]+size/2.0);
                    y[j]=(int)Math.round(-yd[j]+size/2.0);
                }
                g.fillPolygon(x,y,4);
                                //              s=String.format("%d,%d,%d,%d",y[0],y[1],y[2],y[3]);
                //              g.drawString(s,30,330);
            }
        }
    }

    static class Hand{
        double size,direction,phi,r1,r2;
        boolean islong;
        int[] x=new int[3];
        int[] y=new int[3];
        double[] xd=new double[3];
        double[] yd=new double[3];
        public Hand(double s,boolean l){
            size=s; islong=l;direction=0;
            if(l==true){
                phi=134.4/180.0*Math.PI;r1=277.0/800.0;r2=34.3/800.0;
            }else{
                phi=134.4/180.0*Math.PI;r1=152.4/800.0;r2=35.4/800.0;
            }
        }
        public void setdirection(double d){
            direction=2*Math.PI*d;
        }
        public void paint(Graphics g){
            xd[0]=r1*size*Math.sin(direction);
            yd[0]=r1*size*Math.cos(direction);
            xd[1]=r2*size*Math.sin(direction+phi);
            yd[1]=r2*size*Math.cos(direction+phi);
            xd[2]=r2*size*Math.sin(direction-phi);
            yd[2]=r2*size*Math.cos(direction-phi);
            for(int j=0;j<3;++j){
                x[j]=(int)Math.round(xd[j]+size/2.0);
                y[j]=(int)Math.round(-yd[j]+size/2.0);
            }
            g.fillPolygon(x,y,3);       
        }
    }
    static class Day{
        double size;
        Calendar cal;
        public Day(double s){
            size=s;
            cal=Calendar.getInstance();
        }
        public void setcal(Calendar c){
            cal=c;
        }
        public void paint(Graphics g){
            g.setFont(new Font("Helvetica",Font.BOLD,(int)(size*0.08)));
            String s=String.format("%tm/%td %ta",cal,cal,cal);
            g.drawString(s,(int)(size*0.25),(int)(size*0.75));
        }
    }
}

Clock.html
<html>
<head><title>sample</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8"></meta>
</head>
<body>
<applet code="Clock.class" width=300 height=300></applet>
</body>
</html>

使い方
1. xclock用のディレクトリを作る。
2. 上記のソースコードをClock.java、Clock.htmlとして保存する。300 x 300 の鳩の絵を hato.png として保存する。
3. Clock.javaをコンパイルする。
4. hato.png のあるディレクトリでアプレットビューアでClock.htmlを実行する。

注意
正時に鳩の絵が表示されるのが嫌な人は、hato.png を置かなくて大丈夫です。

日本語版の windows などの日本語環境だと、曜日の表示が日本語になります。英語がいい人は、Lang=C appletviwer Clock.htmlとすればいいはずです。たぶん。

こんな汚いソースコードは嫌じゃ!と言う人は、自分で直してください。  

参考文献
教養学部の学生だったの頃の、久野先生の講義の資料
Java の APIドキュメント