home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 18 REXX
/
18-REXX.zip
/
netrexx.zip
/
NetRexx
/
Pinger.nrx
< prev
next >
Wrap
Text File
|
1997-11-27
|
29KB
|
722 lines
/* Pinger: a count-down ('kitchen') timer. */
/* ------------------------------------------------------------------ */
/* This is a sample stand-alone Java application with frame, menus, */
/* and a custom component that shows how to use images and graphics. */
/* This version needs Java 1.1 (or later) to run. */
/* */
/* When the timer ends, an audio clip is played. You'll only hear */
/* this if: */
/* -- you have a sound card or equivalent installed and working. */
/* -- you have speakers, headphone, etc. attached to the sound */
/* card, in range of your hearing, and switched on if need be. */
/* -- the file Pinger.au is available (this can be any sound clip */
/* in .au format, 0.5 seconds or less; a default is supplied). */
/* You can specify the name of an alternative soundclip file when the */
/* pinger is started. For example: java Pinger myclip.au */
/* ------------------------------------------------------------------ */
/* For the latest version, go to http://www2.hursley.ibm.com/netrexx */
/* ------------------------------------------------------------------ */
/* Mike Cowlishaw -- April 1996 - October 1997 */
options binary -- optional, for speed
import sun.audio. -- for sound clip player
/* ------------------------------------------------------------------ */
/* Pinger -- a stand-alone application for the Java Virtual Machine */
/* ------------------------------------------------------------------ */
class Pinger extends Frame-
adapter implements WindowListener, ActionListener
properties constant
defaultclip='Pinger.au' -- default soundclip file
properties private
-- active dialogs --
aboutdialog=Dialog
helpdialog =Dialog
-- menu selection objects --
menuhelp=MenuItem("Help")
menuabout=MenuItem("About")
/* The 'main' method is called when this class is started as an application */
/* The optional argument is the name of the sound clip file to use */
method main(s=String[]) static
Pinger("Pinger", Rexx(s)) -- make a Pinger, with title
/* The constructor/initializer for Pinger */
method Pinger(title, soundclip)
super(title) -- pass title to Frame
addWindowListener(this) -- we want window events
-- setup the logical structure of the frame
createmenus -- set up menubar
setLayout(BorderLayout()) -- we must have a layout
timer=PingerComponent() -- make new timer
add("Center", timer) -- add new timer image
this.pack -- recalculate the frame
-- position centre-screen
sizex=200; sizey=175
screen=Toolkit.getDefaultToolkit.getScreenSize
setBounds((screen.width-sizex)%2,(screen.height-sizey)%2, sizex, sizey)
this.show -- make us visible
timer.prime(soundclip) -- prime the audio system
/* Create menus and menu bar */
method createmenus
bar=MenuBar() -- create a MenuBar
-- Help drop-down menu --
help=Menu("Help") -- create a Menu
help.add(menuhelp)
-- help.addSeparator -- [how to add a separator]
help.add(menuabout)
bar.add(help) -- add the menu to the MenuBar
setMenuBar(bar) -- add the menubar to the Frame
-- say we want the MenuItem events to come to us
menuhelp.addActionListener(this)
menuabout.addActionListener(this)
/* Method for handling Button and MenuItem events */
method actionPerformed(a=ActionEvent)
source=a.getSource -- the sender object
if source<=MenuItem then -- .. from a MenuItem
select label menuitems
when source=menuhelp then do
if helpdialog=null then
helpdialog=PingerDialog(this, PingerDialog.HELP)
helpdialog.show
end
when source=menuabout then do
-- aboutdialog varies, so we always construct a new one
aboutdialog=PingerDialog(this, PingerDialog.ABOUT)
aboutdialog.show
end
end menuitems
/* windowClosing -- called when the window is closed.
We need to handle this to end the program. */
method windowClosing(e=WindowEvent)
exit
/* ------------------------------------------------------------------ */
/* PingerTime -- an object that holds a time, initially 00:00 */
/* ------------------------------------------------------------------ */
/* As an object, it can be protected for safe multiple-thread access. */
class PingerTime
properties public -- we're just a data receptacle
mm=0 -- minutes
ss=0 -- seconds
/* ------------------------------------------------------------------ */
/* PingerComponent -- a custom Pinger component */
/* ------------------------------------------------------------------ */
class PingerComponent extends Canvas- -- Canvas is a drawing area
adapter implements MouseListener, MouseMotionListener
properties constant
minmm=0; maxmm=99 -- bounds
minss=0; maxss=59 -- ..
properties private
-- Timing properties
current=PingerTime() -- current time [minutes and seconds]
timer =PingerTimer -- main timer, null unless started
spinner=PingerSpinner -- spin-button, non-null if spinning
-- Sound stuff
bleeper=PingerBleeper -- a bleeper object
-- Drawing and layout properties
shadow=Image -- shadow image
width=0; height=0 -- current picture dimensions
draw=Graphics -- 'context' where we can draw
background=Color.yellow -- guess what
badsize =boolean 1 -- too small
resizing=boolean 0 -- we are preparing new shadow
spinbutw=0 -- spin button width
spinbuth=10 -- spin button height [fixed]
controlw=0 -- control button width
controlh=24 -- control button height [fixed]
gap=5 -- margin [fixed]
timerect =Rectangle -- where time will go
timefont ="TimesRoman" -- font face for time
timepoints=0 -- pointsize for time
timeweight=Font.BOLD -- weight for time
timecol =Color.black -- color for time
timedi =boolean -- time needs redraw
buttons =6 -- number of buttons
but=Rectangle[buttons] -- button rectangles
-- 0/1 are Ups; 2/3 are Downs
-- 4/5 are reset/start
bcol=Color[buttons] -- button colors
btext=String[buttons] -- button labels
ben=boolean[buttons] -- button enabled
bup=boolean[buttons] -- button up
bdi=boolean[buttons] -- button dirty (needs redraw)
over=-1 -- button we are over [-1 is none]
/* Construct the component */
method PingerComponent
super()
addMouseListener(this) -- we want mouse events ..
addMouseMotionListener(this) -- .. and mouse movements
/* update -- called in reponse to repaint() */
-- We update our off-screen image here, to avoid embarrassment to the
-- AWT caused by asynchronous calls to paint()
method update(g=Graphics)
-- redraw areas that have changed since last repaint. We draw into
-- an off-screen image, later copied to the screen in a single call.
loop i=0 to 5 -- redraw dirty buttons
if bdi[i] then do
drawbutton(i)
bdi[i]=0
end
end i
if timedi then do
drawtime(timerect) -- redraw the time
timedi=0
end
paint(g)
/* paint -- called when the window needs to be resized or redrawn */
method paint(g=Graphics)
if resizing then return -- ignore paints while recalculating
if shadow=null | width<>getSize.width | height<>getSize.height then do
resizing=1
newsize
return
end
g.drawImage(shadow, 0, 0, this) -- copy to screen
/* newsize -- here when a new size detected */
method newsize
width=getSize.width; height=getSize.height
-- The very first time that we get here, there won't be an existing
-- Image, so we have to create it. It cannot be set up in advance,
-- as there's no physical context earlier.
shadow=createImage(width, height) -- need new image
draw=shadow.getGraphics -- for graphics
if height<gap*6+spinbuth*2+controlh*3/2 | width<50
then do label toosmall
badsize=1
text="Window too small"
draw.setColor(Color.white) -- warner
draw.fillRect(0, 0, width-1, height-1)
draw.setFont(Font("Helvetica", Font.PLAIN, 10))
fm=draw.getFontMetrics -- find out about font
w=fm.stringWidth(text) -- actual width
x=(width-w)%2 -- offset
y=(height-fm.getHeight)%2 -- raw offset
y=y+fm.getAscent+fm.getLeading -- adjust for whitespace
draw.setColor(Color.black)
draw.drawString(text, x, y)
end toosmall
else do label bigenough
badsize=0
draw.setColor(background) -- ok
draw.fillRect(0, 0, width-1, height-1)
spinbutw=(width-gap-gap)*4%9 -- button sizes
controlw=(width-gap*3)%2
-- Calculate button positions
but[0]=Rectangle(gap, gap, spinbutw, spinbuth)
but[1]=Rectangle(width-gap-spinbutw, gap, spinbutw, spinbuth)
lowspins=height-gap*3-1-controlh-spinbuth
but[2]=Rectangle(gap, lowspins, spinbutw, spinbuth)
but[3]=Rectangle(width-gap-spinbutw, lowspins, spinbutw, spinbuth)
lowconts=height-gap-controlh
but[4]=Rectangle(gap, lowconts, controlw, controlh)
but[5]=Rectangle(width-gap-controlw, lowconts, controlw, controlh)
-- Calculate time position and pointsize
timerect=Rectangle(gap, gap*2+spinbuth,-
width-gap*2, height-controlh-spinbuth*2-gap*6-1)
-- use 24 point for measuring, as an exact font almost certainly there
draw.setFont(Font(timefont, timeweight, 24)) -- measure font
fm=draw.getFontMetrics -- get metrics
x0=fm.stringWidth('0')
xc=fm.stringWidth(':')
y0=fm.getAscent
xpoints=timerect.width*24/(x0*4+xc*3)
ypoints=timerect.height*24/y0
if xpoints<ypoints then timepoints=int xpoints
else timepoints=int ypoints
-- Draw separator
draw.setColor(Color.lightGray)
sepy=lowspins+gap+spinbuth+1
draw.drawLine(0, sepy, width-1, sepy)
end bigenough
-- Set default state for buttons --
loop i=0 to buttons-1
select
when i<4 then do
btext[i]=""; bcol[i]=Color.orange.darker
end
when i=4 then do
btext[i]="Reset"; bcol[i]=Color.cyan.darker
end
when i=5 then do
btext[i]="Start"; bcol[i]=Color.cyan.darker
end
end
bup[i]=1; ben[i]=1; bdi[i]=1
end
-- Update the time and buttons
resizing=0 -- OK to use shadow, now
newtime -- this will refresh the display
/* Draw the time */
method drawtime(where=Rectangle)
if badsize then return
draw.setColor(background)
draw.fillRect(where.x, where.y, where.width-1, where.height-1)
draw.setFont(Font(timefont, timeweight, timepoints ))
fm=draw.getFontMetrics -- find out about font
y=(where.height-fm.getHeight)%2 -- raw offset
y=y+fm.getAscent+fm.getLeading -- adjust for whitespace
do protect current -- snapshot
mmtext=String current.mm
sstext=String current.ss
end
if sstext.length=1 then sstext='0'sstext
ctext=':'
draw.setColor(timecol)
-- centre the colon
cw=fm.stringWidth(ctext)
cx=(where.width-cw)%2
draw.drawString(ctext, where.x+cx, where.y+y)
-- right-align minutes
mw=fm.stringWidth(mmtext)
mx=spinbutw-mw -- shouldn't really use spinbutw
draw.drawString(mmtext, where.x+mx, where.y+y)
-- left-align seconds
-- sw=fm.stringWidth(sstext)
sx=where.width-spinbutw
draw.drawString(sstext, where.x+sx, where.y+y)
/* Draw a button, up or down, filling with colour */
method drawbutton(b=int)
if badsize then return
text=btext[b]; where=but[b]
if ben[b] then do -- enabled
col=bcol[b]; up=bup[b]
if over=b then col=col.brighter -- highlight if active
end
else do -- not enabled
col=Color.gray; up=1
end
draw.setColor(col)
draw.fillRect(where.x, where.y, where.width-1, where.height-1)
draw.setColor(Color.gray)
-- remember, draw3DRect in JDK 1.0 draws too large
draw.draw3DRect(where.x, where.y, where.width-1, where.height-1, up)
draw.draw3DRect(where.x+1, where.y+1, where.width-3, where.height-3, up)
draw.setColor(Color.gray.darker)
draw.drawLine(where.x, where.y, where.x+1, where.y+1) -- add definition
if text.length=0 then return
-- have some text to add
draw.setFont(Font("Helvetica", Font.PLAIN, 15)) -- choose font
fm=draw.getFontMetrics -- find out about font
w=fm.stringWidth(text) -- actual width
x=(where.width-w)%2 -- offset
if x<0 then return -- won't fit
y=(where.height-fm.getHeight)%2 -- raw offset
y=y+fm.getAscent+fm.getLeading -- adjust for whitespace
if y<0 then return -- won't fit
draw.setColor(Color.black)
draw.drawString(text, where.x+x, where.y+y)
/* Returns number of button we are over, or -1 if none */
method hit(m=MouseEvent) returns int
if badsize | m=null then return -1 -- no buttons or no event
x=m.getX; y=m.getY
loop b=0 to buttons-1
if x>=but[b].x
then if x<but[b].x+but[b].width
then if y>=but[b].y
then if y<but[b].y+but[b].height then return b
end b
return -1
/* The mouse is moving over our image */
method mouseMoved(m=MouseEvent)
new=hit(m)
if new=over then return -- no change
/* change of button (or move off button) */
if spinner<>null then do; spinner.halt; spinner=null; end
old=over; over=new
-- indicate button state change
if old >=0 then do; bup[old]=1; bdi[old]=1; end
if over>=0 then do; bup[over]=1; bdi[over]=1; end
this.repaint
/* Drag is treated just like Move (redraws button if leaves button) */
method mouseDragged(m=MouseEvent)
mouseMoved(m)
/* mouseReleased redraws the button, if we're over one */
method mouseReleased(m=MouseEvent)
if spinner<>null then do; spinner.halt; spinner=null; end
new=hit(m)
if new=-1 then return -- not over a button
over=new
bup[over]=1; bdi[over]=1
this.repaint
/* mousePressed takes an action then redraws the button, if we're over one */
method mousePressed(m=MouseEvent)
new=hit(m)
if new=-1 then return -- not over a button
over=new
bup[over]=0; bdi[over]=1
if spinner<>null then do; spinner.halt; spinner=null; end -- just in case
-- [from here, all paths should initiate a repaint eventually]
if \ben[over] then this.repaint -- not enabled, just repaint
else select label action -- enabled, so take action
when over=0 then spinner=PingerSpinner(this, 1, +1)
when over=1 then spinner=PingerSpinner(this, 0, +1)
when over=2 then spinner=PingerSpinner(this, 1, -1)
when over=3 then spinner=PingerSpinner(this, 0, -1)
when over=4 then do -- reset
if timer<>null then stoptimer -- stop the timer
do protect current
current.mm=0; current.ss=0 -- back to zero
-- [or could simply make a new PingerTime object]
end
newtime
end
when over=5 then do
if timer<>null then stoptimer -- Stop or Beep state
else timer=PingerTimer(this) -- Start state
settextStart
this.repaint
end
end action
/* mouseEntered and mouseExited call our mouseMoved to ensure known state */
method mouseEntered(m=MouseEvent); mouseMoved(m)
method mouseExited(m=MouseEvent); mouseMoved(m)
/* Increase or decrease minutes and seconds numbers
Arg1 is increment (+1 or -1)
returns 1 if result is 00:00
*/
method bumpmm(inc=int) returns boolean
do protect current
current.mm=current.mm+inc
if current.mm>maxmm then current.mm=maxmm
else if current.mm<minmm then current.mm=minmm
end
return newtime
method bumpss(inc=int) returns boolean
do protect current
current.ss=current.ss+inc
if current.ss>maxss then /* carry */ do
current.ss=minss
current.mm=current.mm+1
if current.mm>maxmm then do; current.mm=maxmm; current.ss=maxss; end
end
if current.ss<minss then /* borrow */ do
current.ss=maxss
current.mm=current.mm-1
if current.mm<minmm then do; current.mm=minmm; current.ss=minss; end
end
end
return newtime
/* we have a new time -- update buttons, etc.
returns 1 if the current time is 00:00 */
method newtime returns boolean
-- set buttons enablement and state
ben[0]=1; ben[1]=1
do protect current
if current.mm=maxmm then do
ben[0]=0
if current.ss=maxss then ben[1]=0
end
atzero=current.mm=0 & current.ss=0
end
ben[2]=\atzero; ben[3]=ben[2]
ben[4]=\atzero | timer<>null; ben[5]=ben[4]
settextStart
loop i=0 to 5; bdi[i]=1; end -- redraw all buttons
timedi=1 -- redraw the time
this.repaint -- update display
return atzero
/* set the text for button 5 */
method settextStart
do protect current
atzero=current.mm=0 & current.ss=0 -- take safe snapshot
end
if atzero then do
if timer=null then btext[5]="Time?"
else btext[5]="Beep!"
end
else /* nonzero */ do
if timer=null then btext[5]="Start"
else btext[5]="Stop"
end
bdi[5]=1 -- button needs redraw
/* prime -- called to initialize the sound system */
method prime(filename)
if filename='' then filename=Pinger.defaultclip
bleeper=PingerBleeper(filename) -- make the bleeper object
/* ping -- sound the bleeper once */
method ping
bleeper.bleep
/* stoptimer -- called when the timer is to be stopped */
method stoptimer
-- closing: stop the timer last, because we may have been called from it
savetimer=timer; timer=null
newtime -- update display
if savetimer<>null then do; savetimer.halt; savetimer=null; end
return
/* ------------------------------------------------------------------ */
/* PingerSpinner -- a class for the spinner Thread */
/* ------------------------------------------------------------------ */
class PingerSpinner extends Thread
count=0 -- ticks
owner=PingerComponent -- who we work for
mins=boolean -- 1 if a minutes spinner
inc=int -- up or down
spin=boolean 1 -- spin allowed
properties constant
slow=400 -- initial delay time [ms]
fast=30 -- fastest delay time [ms]
over=6 -- how many ticks to accelerate over
/* Construct with
Arg1 is parent object
Arg2 is 1=minute, 0=seconds
Arg3 is increment (+1 or -1)
*/
method PingerSpinner(newowner=PingerComponent, newmins=boolean, newinc=int)
owner=newowner; mins=newmins; inc=newinc
this.start
/* This runs so long as the button is held down (after which halt is
set), bumping either minutes or seconds, at intervals */
method run
loop until \spin -- always bump once
if mins then owner.bumpmm(inc)
else owner.bumpss(inc)
count=count+1
if count<=over then wait=fast+(over+1-count)*(slow-fast)%over
else wait=fast
sleep(wait)
catch InterruptedException
return
end
/* This method is called to request that the spin stop. We use this
pending request, as a direct call to stop could leave the call to
owner unfinished. */
method halt
spin=0
/* ------------------------------------------------------------------ */
/* PingerTimer -- a main pinger timer Thread */
/* ------------------------------------------------------------------ */
/* This thread must update the pinger in real time, so it keeps */
/* an eye on 'wall-clock' time so the timer cannot drift. Any delays */
/* will be corrected as soon as the thread gets control. */
/* */
/* When done, it notifies its owner with calls to the 'ping' method, */
/* for each bleep, and then to the 'stoptimer' method. */
class PingerTimer extends Thread
owner=PingerComponent -- who we work for
started=System.currentTimeMillis -- timestamp of when we were born
down=boolean 1 -- count down while 1
/* Construct with
Arg1 is parent object
*/
method PingerTimer(newowner=PingerComponent)
owner=newowner
this.setPriority(Thread.MAX_PRIORITY) -- time matters, here
this.start -- off we go
/* This runs until stopped, or we reach 0 */
method run
loop millisecs=started+1000 by 1000 -- when next tick
-- calculate how long to next second
wait=millisecs-System.currentTimeMillis
if wait<=0 then iterate -- badly behind
sleep(wait)
if \down then leave -- halt request
if owner.bumpss(-1) then leave -- decrement and quit if 0
catch InterruptedException
return
end
if down then loop for 3
-- reach here iff countdown not halted
owner.ping
sleep(600)
catch InterruptedException
nop
end
owner.stoptimer -- definitely done
/* This method is called to request that the count stop. We use this
pending request, as a direct call to stop could leave the bumpss
call to owner unfinished. */
method halt
down=0
/* ------------------------------------------------------------------ */
/* PingerBleeper -- the sound maker */
/* ------------------------------------------------------------------ */
class PingerBleeper
soundfile=File -- File of The Sound
okfile=boolean 0 -- 1 if the file is good
input=InputStream -- .. as a stream
audio=AudioStream -- .. as audio
/* Constructor.
Arg1 is beep file (.au) to play
*/
method PingerBleeper(filename)
-- first ensure that the file exists
soundfile=File(filename)
if \soundfile.exists | \soundfile.isFile then do
say 'The audio file "'filename'" could not be found'
return
end
okfile=1 -- file is plausible
-- now prime the audio subsystem by sending it a 0 byte. This loads
-- the audio software so the final bleeps will be prompt (starting
-- up the audio subsystem can take seconds on some platforms).
do
input=ByteArrayInputStream([byte 0])
-- next lines prime the audio subsystem, by trying to play an
-- invalid stream
audio=AudioStream(input)
AudioPlayer.player.start(audio)
catch IOException
-- we expect to get here
end
/* bleep once */
method bleep
if \okfile then return -- no good file to play
do
-- set the input stream each time ('rewind')
input=FileInputStream(soundfile)
audio=AudioStream(input)
catch IOException
say "Could not play file '"soundfile"'"
okfile=0 -- don't even try next time
end
AudioPlayer.player.start(audio)
/* ------------------------------------------------------------------ */
/* PingerDialog -- tell about us */
/* ------------------------------------------------------------------ */
class PingerDialog extends Dialog adapter implements WindowListener
owner=Pinger -- who we work for
form=int -- form of text
-- Drawing and layout properties
shadow=Image -- shadow image
d=Graphics -- where we can draw
width=0; height=0 -- current picture dimensions
properties constant
HELP=0 -- build Help text
ABOUT=1 -- build About text
/* Construct a general dialog for Pinger */
method PingerDialog(newowner=Pinger, newform=int)
super(newowner, title(newform), 1)
owner=newowner; form=newform
addWindowListener(this) -- we want window events
-- position centre-screen
sizex=400; sizey=300 -- TV shape
screen=Toolkit.getDefaultToolkit.getScreenSize
this.addNotify -- ensure peer exists
setBounds((screen.width-sizex)%2,(screen.height-sizey)%2, sizex, sizey)
setResizable(0) -- fixed size panel, please
-- display (show) will be triggered by creator
/* provide a title string, on demand */
method title(titleform=int) static returns String
if titleform=HELP then return "Help for Pinger"
return "About Pinger"
/* update -- overridden, because we set background */
method update(g=Graphics); paint(g)
/* paint -- called when the window needs to be redrawn */
method paint(g=Graphics)
-- we do not need to protect the image, here, as there should be
-- only one. However, a race condition is possible, so we check for
-- an uninitialized 'shadow'.
if width<>getSize.width | height<>getSize.height then newsize
else if shadow<>null then g.drawImage(shadow, 0, 0, this)
/* newsize -- here when a new size detected; should be called once */
method newsize
/* make a new image to draw in, if needed */
width=getSize.width; height=getSize.height
shadow=this.createImage(width, height) -- make image
d=shadow.getGraphics -- context to draw
d.setColor(Color.white) -- background
d.fillRect(0, 0, width, height) -- ..
d.setFont(Font("TimesRoman", Font.PLAIN, 20)) -- measure font
fm=d.getFontMetrics -- get metrics
h=fm.getHeight+2 -- +2 pels leading
y=(height-h*5)%3 -- offset
-- Add some text, etc. Any graphics allowed, here.
if form=ABOUT then do
secs=int Date().getTime//7000
d.setColor(Color.getHSBColor(secs/6999, 1, 0.5))
d.drawString("Simple 'Pinger' application", 20, y+h)
d.drawString("For the NetRexx source, see:", 20, y+h*2)
d.drawString(" http://www2.hursley.ibm.com/netrexx/", 20, y+h*3)
d.drawString("Mike Cowlishaw, 1996-1997 ", 20, y+h*4)
end
else /* Help */ do
d.setColor(Color.blue.darker)
d.drawString("This is a 'kitchen timer' application", 20, y+h)
d.drawString("Set the time using the unmarked buttons.", 20, y+h*2)
d.drawString("Press 'Start' to start countdown.", 20, y+h*3)
d.drawString("Press 'Reset' to zero timer.", 20, y+h*4)
end
d.setFont(Font("Helvetica", Font.PLAIN, 12))
d.setColor(Color.black)
d.drawString("Compiled with" version, 20, y+h*5)
this.repaint
/* windowClosing -- called when the window is closed.
We need to handle this to end the program. */
method windowClosing(e=WindowEvent)
owner.requestFocus
this.dispose()
return