/* File: StopWatch.java Author: Peter Loeb Date: 9/6/99 This program is a demo. The idea is to have a computerized stop-watch complete with start, stop, and reset buttons. The "state" variable tells us what is the current state of the program. It can be in one of three states: INIT, RUNNING, or STOP. I use two variables, "offset" and "disp" to make sense of the timer. The disp is "running" total of mS and the offset is used to keep track of when we started. Here is the logic: At init (or reset) both offset and disp are set to zero (0). This is done in the set_clock method. At start, we must set the offset. The disp may not be zero, since the user is allowed to do: start stop start stop etc. The offset is the current time (in mS) minus the disp. This is done in the start_clock method. When the timer pops, we must set the disp. We do this by subtracting the offset from the current time. This is done in the on_wakeup method. */ package com.palserv.stpwtch; import java.applet.*; import java.awt.*; import java.awt.event.*; /** This is the applet which implements the stop-watch @author Peter Loeb, Sept 1999 */ public class StopWatch extends Applet { long offset; long disp; int state; int INIT = 1; int RUNNING = 2; int STOP = 3; Label displab = new Label("00:00:00"); Button b_start = new Button("Start"); Button b_stop = new Button("Stop"); Button b_reset = new Button("Reset"); Panel p0 = new Panel(); Panel p1 = new Panel(); MyTimer mt; /** This method allows the applet to be used as an application. */ public static void main(String args[]) { StopWatch applet = new StopWatch(); Frame aFrame = new Frame("Stop Watch"); aFrame.addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } } ); aFrame.add(applet, BorderLayout.CENTER); aFrame.setSize(200,100); applet.init(); aFrame.setVisible(true); } public void init() { /* the primary layout is a GridLayout with one column and two rows. each row will get a panel */ setLayout(new GridLayout(2,1)); /* add the first panel (top). it will contain the "display" of the numbers. this panel will use a FlowLayout so that the one thing in it will be centered. */ add(p0); /* add panel */ p0.setLayout(new FlowLayout()); p0.add(displab); /* add the "display" */ /* set the font of the display so that the numbers are somewhat bigger than the default */ displab.setFont(new Font("Helvetica", Font.PLAIN, 18)); /* add the second panel. this will contain the three buttons (start, stop, and reset). this panel will also use a FlowLayout so that the buttons will be centered. Each button will add an action listener as an inner class. These inner class action listeners will implement the logic for the button. */ add(p1); /* add the panel */ p1.setLayout(new FlowLayout()); p1.add(b_start); /* add start button */ p1.add(b_stop); /* add stop button */ p1.add(b_reset); /* add reset button */ /* add the "start" inner class action listener */ b_start.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent ae) { if (state != RUNNING) { /* ignore if running */ set_state(RUNNING); /* set to running */ start_clock(); /* start the clock */ } } } ); /* add the "stop" inner class action listener */ b_stop.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent ae) { if (state == RUNNING) { /* ignore if not running */ set_state(STOP); /* set to stop */ stop_clock(); /* stop clock */ } } } ); /* add the "reset" inner class action listener */ b_reset.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent ae) { if (state == STOP) { /* ignore if not stop */ startup(); /* do startup routine */ } } } ); /* this code looks a little weird, but is correct. There is a call to startup() a few lines above, which seems to be followed by another call to startup() right after this comment. We are in the "init" routine which is called once to initialize an applet. In the statement before the comment, we add a listener to the "reset" button. When the user hits the button, the specified action will take place. Thus the call to startup() before the comment is only called when the reset button is hit (and the state is STOP). The call to startup() after the comment is called during initialization. */ startup(); } void startup() { /* This is the initialization routine. It is called from the init method and it is called when the user hits the "reset" button. */ set_state(INIT); /* set state to INIT */ stop_clock(); /* stop the clock */ set_clock(); /* set the clock to zero */ set_label(disp); /* display "00:00:00" */ } void set_state(int st) { /* this routine (along with get_state) encapsulates the "state" variable. it is not really necessary in a simple app like this, but it is good form, so I did it anyway. So there! */ state = st; } int get_state() { /* see the comments for set_state, above */ return state; } void start_clock() { /* this routine starts the timer. see the comments in class MyTimer */ /* set the "offset" */ offset = System.currentTimeMillis() - disp; mt = new MyTimer(this); /* create a timer */ mt.start(); /* invoke the "run" method of MyTimer */ } void stop_clock() { /* "kill" the timer This is already accomplished by setting state, which SHOULD be done before calling this routine. */ } void set_clock() { /* set both variables, offset and disp to 0. */ offset = 0; disp = 0; } void on_wakeup() { /* this routine is called from the timer routine. see the comments in class MyTimer. */ /* set the "disp" */ disp = System.currentTimeMillis() - offset; /* display the "disp" */ set_label(disp); } void set_label(long d) { /* this routine takes a number of mS ("d") and displays it in the form MM:SS:HH, where: MM is the number of minutes elapsed SS is the number of seconds HH is the number of hundredths of seconds */ long lm, ls, lh; String m, s, h; Long tm, ts, th; ls = d / 1000; /* number of whole seconds */ lh = (d - (ls * 1000)) / 10; /* remainder for hundredths */ lm = ls / 60; /* number of minutes */ ls = ls - (lm * 60); /* number of seconds */ /* convert from long to Long */ ts = new Long(ls); th = new Long(lh); tm = new Long(lm); /* convert from Long to string */ h = new String(th.toString()); s = new String(ts.toString()); m = new String(tm.toString()); /* display in label */ displab.setText(sfrmt(m) + ":" + sfrmt(s) + ":" + sfrmt(h)); } String sfrmt(String s) { /* this method is used in the set_label routine to insure that each of the MM, SS, and HH strings are exactly two characters long with the right number of leading zeros */ int l; l = s.length(); if (l == 2) { return s; } if (l < 2) { if (l == 1) { return "0" + s; } if (l == 0) { return "00"; } } // l > 2 return s.substring(l - 2 - 1); } } class MyTimer extends Thread { /* This class implements a timer. An instance of this class is created when the user hits the start button. It wakes up every 50 mS and calls the on_wakeup method of the StopWatch class. In order to call the on_wakeup routine of the StopWatch class, I needed a pointer to the StopWatch class. This is accomplished in the contructor, which takes one argument, a StopWatch object. In the start_clock method of StopWatch, an instance of MyTimer is created with a pointer ("this") to the current StopWatch object. It is picked up in the constructor and used to set the variable "sw". This is then used to allow us to invoke the get_state and on_wakeup methods of StopWatch. */ StopWatch sw; int RUNNING = 2; /* the constructor */ MyTimer(StopWatch s) { sw = s; } public void run() { /* this method is invoked from the start_clock method of StopWatch when it sends a message to the "start" method (of Thread). We loop as long as the state is RUNNING. It will change when the user hits the stop button. Within the loop, we invoke the on_wakeup method of StopWatch. Then we sleep for 50 ms. The net result is to call on_wakup 20 times per second. The on_wakeup routine simply calculates the "disp" value based on the current time and the "offset", and then displays this value in the "displab" Label. */ while (sw.get_state() == RUNNING) { sw.on_wakeup(); try { sleep(50); } catch(InterruptedException ie) { System.err.println(ie); } } } }