home *** CD-ROM | disk | FTP | other *** search
/ PC Online 1997 May / PCO_5_97.ISO / FilesBBS / OS2 / NETREXX.ARJ / NETREXX.ZIP / NetRexx / Pinger.nrx < prev    next >
Encoding:
Text File  |  1997-02-15  |  25.8 KB  |  659 lines

  1. /* Pinger: a count-down ('kitchen') timer.                            */
  2. /* ------------------------------------------------------------------ */
  3. /* This is a sample stand-alone Java application with frame, menus,   */
  4. /* and a custom component that shows how to use images and graphics.  */
  5. /*                                                                    */
  6. /* When the timer expires, an audio clip is played.  You'll only hear */
  7. /* this if:                                                           */
  8. /*   -- you have a sound card or equivalent installed and working.    */
  9. /*   -- you have speakers, headphone, etc. attached to the sound      */
  10. /*      card, in range of your hearing, and switched on if need be.   */
  11. /*   -- the file Pinger.au is available (this can be any sound clip   */
  12. /*      in .au format, 0.5 seconds or less; a default is supplied).   */
  13. /* ------------------------------------------------------------------ */
  14. /* Mike Cowlishaw -- April 1996, February 1997                        */
  15.  
  16. options binary                     -- optional, for speed
  17. import sun.audio.                  -- for sound clip player
  18.  
  19. /* ------------------------------------------------------------------ */
  20. /* Pinger -- a stand-alone application                                */
  21. /* ------------------------------------------------------------------ */
  22. class Pinger extends Frame         -- a Frame window object
  23.  
  24.  properties private
  25.  -- active dialogs --
  26.  aboutdialog=Dialog
  27.  helpdialog =Dialog
  28.  
  29.  -- menu selection objects --
  30.  menuhelp=MenuItem("Help")
  31.  menuabout=MenuItem("About")
  32.  
  33.  /* The 'main' method is called when this class is started as an application */
  34.  method main(s=String[]) static
  35.    Pinger("Pinger" Rexx(s))                  -- make a Pinger, with title
  36.  
  37.  /* The constructor/initializer for Pinger */
  38.  method Pinger(s=String)
  39.    super(s)                                  -- pass title to Frame
  40.  
  41.    -- setup the logical structure of the frame
  42.    createmenus                               -- set up menubar
  43.    setLayout(BorderLayout())                 -- we must have a layout
  44.    add("Center", PingerComponent())          -- add new pinger image
  45.    this.pack                                 -- recalculate the frame
  46.  
  47.    -- position centre-screen
  48.    sizex=200; sizey=175
  49.    screen=Toolkit.getDefaultToolkit.getScreenSize
  50.    reshape((screen.width-sizex)%2,(screen.height-sizey)%2, sizex, sizey)
  51.  
  52.    this.show                                 -- and make us visible
  53.  
  54.  /* Create menus and menu bar */
  55.  method createmenus
  56.    menubar=MenuBar()
  57.  
  58.    -- Help menu --
  59.    menu=Menu("Help")
  60.    menu.add(menuhelp)
  61.    -- menu.addSeparator                      -- [how to add a separator]
  62.    menu.add(menuabout)
  63.    menubar.add(menu)
  64.  
  65.    setMenuBar(menubar)
  66.  
  67.  /* Method for handling events */
  68.  method handleEvent(e=Event) returns boolean
  69.    if e.id=Event.ACTION_EVENT then           -- it's an action ..
  70.     if e.target<=MenuItem then do            -- .. from a MenuItem
  71.      select label menuitems
  72.        when e.target=menuhelp  then do
  73.          if helpdialog=null then
  74.            helpdialog=PingerDialog(this, PingerDialog.HELP)
  75.          helpdialog.show
  76.          end
  77.        when e.target=menuabout then do
  78.          -- aboutdialog varies, so we always construct a new one
  79.          aboutdialog=PingerDialog(this, PingerDialog.ABOUT)
  80.          aboutdialog.show
  81.          end
  82.        end menuitems
  83.      return 1                                -- we handled it
  84.      end
  85.  
  86.    if e.id=Event.WINDOW_DESTROY then exit    -- exit on close
  87.    return super.handleEvent(e)               -- otherwise take default action
  88.  
  89. /* ------------------------------------------------------------------ */
  90. /* PingerTime -- an object that holds a time, initially 0:00          */
  91. /* ------------------------------------------------------------------ */
  92. class PingerTime
  93.   properties public           -- we're just a data receptacle
  94.   mm=0                        -- minutes
  95.   ss=0                        -- seconds
  96.  
  97. /* ------------------------------------------------------------------ */
  98. /* PingerComponent -- a custom Pinger component                       */
  99. /* ------------------------------------------------------------------ */
  100. /* Notes:                                                             */
  101. /*  1. java.awt.Graphics draws some rectangles one pixel too large,   */
  102. /*     so you'll see "width-1" and "height-1" in unexpected places.   */
  103.  
  104. class PingerComponent extends Canvas
  105.  
  106.  -- Timer functional properties
  107.  current=PingerTime()              -- current time [minutes and seconds]
  108.  
  109.  properties private
  110.  spinner=Thread                    -- spin-button Thread, non-null if spinning
  111.  timer  =Thread                    -- main timer Thread, null unless started
  112.  
  113.  properties constant
  114.  minmm=0; maxmm=99                 -- bounds
  115.  minss=0; maxss=59                 -- ..
  116.  
  117.  -- Drawing and layout properties
  118.  properties private
  119.  shadow=Image                      -- shadow image
  120.  width=0; height=0                 -- current picture dimensions
  121.  draw=Graphics                     -- 'context' where we can draw
  122.  background=Color.yellow           -- guess what
  123.  
  124.  badsize =boolean 1                -- too small
  125.  resizing=boolean 0                -- we are preparing new shadow
  126.  spinbutw=0                        -- spin button width
  127.  spinbuth=10                       -- spin button height [fixed]
  128.  controlw=0                        -- control button width
  129.  controlh=24                       -- control button height [fixed]
  130.  gap=5                             -- margin [fixed]
  131.  timerect  =Rectangle              -- where time will go
  132.  timefont  ="TimesRoman"           -- font face for time
  133.  timepoints=0                      -- pointsize for time
  134.  timeweight=Font.BOLD              -- weight for time
  135.  timecol   =Color.black            -- color for time
  136.  timedi    =boolean                -- time needs redraw
  137.  buttons=6                         -- number of buttons
  138.  but=Rectangle[buttons]            -- button rectangles
  139.                                    -- 0/1 are Ups; 2/3 are Downs
  140.                                    -- 4/5 are reset/start
  141.  bcol=Color[buttons]               -- button colors
  142.  btext=String[buttons]             -- button labels
  143.  ben=boolean[buttons]              -- button enabled
  144.  bup=boolean[buttons]              -- button up
  145.  bdi=boolean[buttons]              -- button dirty (needs redraw)
  146.  over=-1                           -- button we are over [-1 is none]
  147.  
  148.  /* Construct the component */
  149.  method PingerComponent
  150.    nop                             -- nothing special to do here
  151.  
  152.  /* update  -- called in reponse to repaint() */
  153.  -- We update our off-screen image here, to avoid embarrassment to the
  154.  -- AWT caused by asynchronous calls to paint()
  155.  method update(g=Graphics)
  156.    -- redraw areas that have changed since last repaint.  We draw into
  157.    -- an off-screen image, later copied to the screen in a single call.
  158.    loop i=0 to 5                      -- redraw dirty buttons
  159.      if bdi[i] then do
  160.        drawbutton(i)
  161.        bdi[i]=0
  162.        end
  163.      end i
  164.    if timedi then do
  165.      drawtime(timerect)               -- redraw the time
  166.      timedi=0
  167.      end
  168.    paint(g)
  169.  
  170.  /* paint  -- called when the window needs to be resized or redrawn */
  171.  method paint(g=Graphics)
  172.    if resizing then return         -- ignore paints while recalculating
  173.    if shadow=null | width<>size.width | height<>size.height then do
  174.      resizing=1
  175.      newsize
  176.      return
  177.      end
  178.    g.drawImage(shadow, 0, 0, this) -- copy to screen
  179.  
  180.  /* newsize -- here when a new size detected */
  181.  method newsize
  182.    width=size.width; height=size.height
  183.  
  184.    -- The very first time that we get here, there won't be an existing
  185.    -- Image, so we have to create it.  It cannot be set up in advance,
  186.    -- as there's no physical context earlier.
  187.    shadow=createImage(width, height)         -- need new image
  188.    draw=shadow.getGraphics                   -- for graphics
  189.    if height<gap*6+spinbuth*2+controlh*3/2 | width<50
  190.     then do label toosmall
  191.      badsize=1
  192.      text="Window too small"
  193.      draw.setColor(Color.white)              -- warner
  194.      draw.fillRect(0, 0, width-1, height-1)
  195.      draw.setFont(Font("Helvetica", Font.PLAIN, 10))
  196.      fm=draw.getFontMetrics                  -- find out about font
  197.      w=fm.stringWidth(text)                  -- actual width
  198.      x=(width-w)%2                           -- offset
  199.      y=(height-fm.getHeight)%2               -- raw offset
  200.      y=y+fm.getAscent+fm.getLeading          -- adjust for whitespace
  201.      draw.setColor(Color.black)
  202.      draw.drawString(text, x, y)
  203.      end toosmall
  204.     else do label bigenough
  205.      badsize=0
  206.      draw.setColor(background)             -- ok
  207.      draw.fillRect(0, 0, width-1, height-1)
  208.      spinbutw=(width-gap-gap)*4%9          -- button sizes
  209.      controlw=(width-gap*3)%2
  210.  
  211.      -- Calculate button positions
  212.      but[0]=Rectangle(gap,                gap, spinbutw, spinbuth)
  213.      but[1]=Rectangle(width-gap-spinbutw, gap, spinbutw, spinbuth)
  214.      lowspins=height-gap*3-1-controlh-spinbuth
  215.      but[2]=Rectangle(gap,                lowspins, spinbutw, spinbuth)
  216.      but[3]=Rectangle(width-gap-spinbutw, lowspins, spinbutw, spinbuth)
  217.      lowconts=height-gap-controlh
  218.      but[4]=Rectangle(gap,                lowconts, controlw, controlh)
  219.      but[5]=Rectangle(width-gap-controlw, lowconts, controlw, controlh)
  220.  
  221.      -- Calculate time position and pointsize
  222.      timerect=Rectangle(gap, gap*2+spinbuth,-
  223.        width-gap*2, height-controlh-spinbuth*2-gap*6-1)
  224.      -- use 24 point for measuring, as an exact font almost certainly there
  225.      draw.setFont(Font(timefont, timeweight, 24)) -- measure font
  226.      fm=draw.getFontMetrics                       -- get metrics
  227.      x0=fm.stringWidth('0')
  228.      xc=fm.stringWidth(':')
  229.      y0=fm.getAscent
  230.      xpoints=timerect.width*24/(x0*4+xc*3)
  231.      ypoints=timerect.height*24/y0
  232.      if xpoints<ypoints then timepoints=int xpoints
  233.                         else timepoints=int ypoints
  234.  
  235.      -- Draw separator
  236.      draw.setColor(Color.lightGray)
  237.      sepy=lowspins+gap+spinbuth+1
  238.      draw.drawLine(0, sepy, width-1, sepy)
  239.      end bigenough
  240.  
  241.    -- Set default state for buttons --
  242.    loop i=0 to buttons-1
  243.      select
  244.        when i<4 then do
  245.          btext[i]="";      bcol[i]=Color.orange.darker
  246.          end
  247.        when i=4 then do
  248.          btext[i]="Reset"; bcol[i]=Color.cyan.darker
  249.          end
  250.        when i=5 then do
  251.          btext[i]="Start"; bcol[i]=Color.cyan.darker
  252.          end
  253.        end
  254.      bup[i]=1; ben[i]=1; bdi[i]=1
  255.      end
  256.  
  257.    -- Update the time and buttons
  258.    resizing=0                           -- OK to use shadow, now
  259.    newtime                              -- this will refresh the display
  260.  
  261.  /* Draw the time */
  262.  method drawtime(where=Rectangle)
  263.    if badsize then return
  264.    draw.setColor(background)
  265.    draw.fillRect(where.x, where.y, where.width-1, where.height-1)
  266.    draw.setFont(Font(timefont, timeweight, timepoints ))
  267.    fm=draw.getFontMetrics               -- find out about font
  268.    y=(where.height-fm.getHeight)%2      -- raw offset
  269.    y=y+fm.getAscent+fm.getLeading       -- adjust for whitespace
  270.  
  271.    do protect current                   -- snapshot
  272.      mmtext=String current.mm
  273.      sstext=String current.ss
  274.      end
  275.    if sstext.length=1 then sstext='0'sstext
  276.    ctext=':'
  277.    draw.setColor(timecol)
  278.    -- centre the colon
  279.    cw=fm.stringWidth(ctext)
  280.    cx=(where.width-cw)%2
  281.    draw.drawString(ctext,  where.x+cx, where.y+y)
  282.    -- right-align minutes
  283.    mw=fm.stringWidth(mmtext)
  284.    mx=spinbutw-mw   -- shouldn't really use spinbutw
  285.    draw.drawString(mmtext, where.x+mx, where.y+y)
  286.    -- left-align seconds
  287.    -- sw=fm.stringWidth(sstext)
  288.    sx=where.width-spinbutw
  289.    draw.drawString(sstext, where.x+sx, where.y+y)
  290.  
  291.  /* Draw a button, up or down, filling with colour */
  292.  method drawbutton(b=int)
  293.    if badsize then return
  294.    text=btext[b]; where=but[b]
  295.    if ben[b] then do                    -- enabled
  296.      col=bcol[b]; up=bup[b]
  297.      if over=b then col=col.brighter    -- highlight if active
  298.      end
  299.     else do                             -- not enabled
  300.      col=Color.gray; up=1
  301.      end
  302.    draw.setColor(col)
  303.    draw.fillRect(where.x, where.y, where.width-1, where.height-1)
  304.    draw.setColor(Color.gray)
  305.    -- remember, draw3DRect in JDK 1.0 draws too large
  306.    draw.draw3DRect(where.x, where.y, where.width-1, where.height-1, up)
  307.    draw.draw3DRect(where.x+1, where.y+1, where.width-3, where.height-3, up)
  308.    draw.setColor(Color.gray.darker)
  309.    draw.drawLine(where.x, where.y, where.x+1, where.y+1) -- add definition
  310.    if text.length=0 then return
  311.    -- have some text to add
  312.    draw.setFont(Font("Helvetica", Font.PLAIN, 15))   -- choose font
  313.    fm=draw.getFontMetrics               -- find out about font
  314.    w=fm.stringWidth(text)               -- actual width
  315.    x=(where.width-w)%2                  -- offset
  316.    if x<0 then return                   -- won't fit
  317.    y=(where.height-fm.getHeight)%2      -- raw offset
  318.    y=y+fm.getAscent+fm.getLeading       -- adjust for whitespace
  319.    if y<0 then return                   -- won't fit
  320.    draw.setColor(Color.black)
  321.    draw.drawString(text, where.x+x, where.y+y)
  322.  
  323.  /* Returns number of button we are over, or -1 if none */
  324.  method hit(e=Event, x=int, y=int) returns int
  325.    if badsize | e=null then return -1        -- no buttons or no event
  326.    loop b=0 to buttons-1
  327.      if x>=but[b].x
  328.       then if x<but[b].x+but[b].width
  329.       then if y>=but[b].y
  330.       then if y<but[b].y+but[b].height then return b
  331.      end b
  332.    return -1
  333.  
  334.  /* The mouse is moving over our image */
  335.  method mouseMove(e=Event, x=int, y=int) returns boolean
  336.    new=hit(e, x, y)
  337.    if new=over then return 0                 -- no change
  338.    /* change of button (or move off button) */
  339.    if spinner<>null then do; spinner.stop; spinner=null; end
  340.    old=over; over=new
  341.    -- indicate button state change
  342.    if old >=0 then do; bup[old]=1;  bdi[old]=1;  end
  343.    if over>=0 then do; bup[over]=1; bdi[over]=1; end
  344.    this.repaint
  345.    return 1
  346.  
  347.  /* Drag is treated just like Move (redraws button if leaves button) */
  348.  method mouseDrag(e=Event, x=int, y=int) returns boolean
  349.    return mouseMove(e, x, y)
  350.  
  351.  /* mouseUp redraws the button, if we're over one */
  352.  method mouseUp(e=Event, x=int, y=int) returns boolean
  353.    if spinner<>null then do; spinner.stop; spinner=null; end
  354.    new=hit(e, x, y)
  355.    if new=-1 then return 0              -- not over a button
  356.    over=new
  357.    bup[over]=1; bdi[over]=1
  358.    this.repaint
  359.    return 1
  360.  
  361.  /* mouseDown takes an action then redraws the button, if we're over one */
  362.  method mouseDown(e=Event, x=int, y=int) returns boolean
  363.    new=hit(e, x, y)
  364.    if new=-1 then return 0              -- not over a button
  365.    over=new
  366.    bup[over]=0; bdi[over]=1
  367.    if spinner<>null then do; spinner.stop; spinner=null; end  -- just in case
  368.  
  369.    -- [from here, all paths should initiate a repaint eventually]
  370.    if \ben[over] then this.repaint      -- not enabled, just repaint
  371.     else select label action            -- enabled, so take action
  372.      when over=0 then spinner=PingerSpinner(this, 1, +1)
  373.      when over=1 then spinner=PingerSpinner(this, 0, +1)
  374.      when over=2 then spinner=PingerSpinner(this, 1, -1)
  375.      when over=3 then spinner=PingerSpinner(this, 0, -1)
  376.      when over=4 then do  -- reset
  377.        if timer<>null then pinged(0)               -- stop the timer
  378.        do protect current
  379.          current.mm=0; current.ss=0                -- back to zero
  380.          -- [or could simply make a new PingerTime object]
  381.          end
  382.        newtime
  383.        end
  384.      when over=5 then do
  385.        if timer<>null then pinged(0)               -- Stop or Beep state
  386.                       else timer=PingerTimer(this) -- Start state
  387.        settextStart
  388.        this.repaint
  389.        end
  390.      end action
  391.    return 1
  392.  
  393.  /* Enter and exit call our mouseMove to ensure known state */
  394.  method mouseEnter(e=Event, x=int, y=int) returns boolean
  395.    return mouseMove(e, x, y)
  396.  method mouseExit(e=Event, x=int, y=int) returns boolean
  397.    return mouseMove(e, x, y)
  398.  
  399.  /* Increase or decrease minutes and seconds numbers
  400.     Arg1 is increment (+1 or -1)
  401.     returns 1 if result is 00:00
  402.   */
  403.  method bumpmm(inc=int) returns boolean
  404.    do protect current
  405.      current.mm=current.mm+inc
  406.      if current.mm>maxmm then current.mm=maxmm
  407.       else if current.mm<minmm then current.mm=minmm
  408.      end
  409.    return newtime
  410.  
  411.  method bumpss(inc=int) returns boolean
  412.    do protect current
  413.      current.ss=current.ss+inc
  414.      if current.ss>maxss then /* carry */ do
  415.        current.ss=minss
  416.        current.mm=current.mm+1
  417.        if current.mm>maxmm then do; current.mm=maxmm; current.ss=maxss; end
  418.        end
  419.      if current.ss<minss then /* borrow */ do
  420.        current.ss=maxss
  421.        current.mm=current.mm-1
  422.        if current.mm<minmm then do; current.mm=minmm; current.ss=minss; end
  423.        end
  424.      end
  425.    return newtime
  426.  
  427.  /* we have a new time -- update buttons, etc.
  428.     returns 1 if the current time is 00:00 */
  429.  method newtime returns boolean
  430.    -- set buttons enablement and state
  431.    ben[0]=1; ben[1]=1
  432.    do protect current
  433.      if current.mm=maxmm then do
  434.        ben[0]=0
  435.        if current.ss=maxss then ben[1]=0
  436.        end
  437.      atzero=current.mm=0 & current.ss=0
  438.      end
  439.    ben[2]=\atzero;               ben[3]=ben[2]
  440.    ben[4]=\atzero | timer<>null; ben[5]=ben[4]
  441.    settextStart
  442.    loop i=0 to 5; bdi[i]=1; end         -- redraw all buttons
  443.    timedi=1                             -- redraw the time
  444.    this.repaint                         -- update display
  445.    return atzero
  446.  
  447.  /* set the text for button 5 */
  448.  method settextStart
  449.   do protect current
  450.     atzero=current.mm=0 & current.ss=0  -- take safe snapshot
  451.     end
  452.   if atzero then do
  453.     if timer=null then btext[5]="Time?"
  454.                   else btext[5]="Beep!"
  455.     end
  456.    else /* nonzero */ do
  457.     if timer=null then btext[5]="Start"
  458.                   else btext[5]="Stop"
  459.     end
  460.   bdi[5]=1                              -- button needs redraw
  461.  
  462.  /* pinged -- called when the pinger has pinged, or timer is to be stopped
  463.     Arg1 is 1 for first call, 0 when pinger is ending
  464.     returns count of bleeps to make [if first]
  465.  */
  466.  method pinged(first=boolean) returns int
  467.    if first then return 3
  468.    -- closing: stop the timer last, because we may have been called from it
  469.    savetimer=timer; timer=null
  470.    newtime                              -- update display
  471.    if savetimer<>null then do; savetimer.stop; savetimer=null; end
  472.    return 0
  473.  
  474. /* ------------------------------------------------------------------ */
  475. /* PingerSpinner  -- a class for the spinner Thread                   */
  476. /* ------------------------------------------------------------------ */
  477. class PingerSpinner extends Thread
  478.   count=0                     -- ticks
  479.   owner=PingerComponent       -- who we work for
  480.   mins=boolean                -- 1 if a minutes spinner
  481.   inc=int                     -- up or down
  482.  
  483.   properties constant
  484.   slow=400                    -- initial delay time [ms]
  485.   fast=50                     -- fastest delay time [ms]
  486.   over=5                      -- how many ticks to accelerate over
  487.  
  488.   /* Construct with
  489.      Arg1 is parent object
  490.      Arg2 is 1=minute, 0=seconds
  491.      Arg3 is increment (+1 or -1)
  492.   */
  493.   method PingerSpinner(newowner=PingerComponent, newmins=boolean, newinc=int)
  494.     owner=newowner; mins=newmins; inc=newinc
  495.     this.start
  496.  
  497.   /* This runs so long as the button is held down, bumping either
  498.      minutes or seconds, at intervals */
  499.   method run
  500.     loop forever
  501.       if mins then owner.bumpmm(inc)
  502.               else owner.bumpss(inc)
  503.       count=count+1
  504.       if count<=over then wait=fast+(over+1-count)*(slow-fast)%over
  505.        else wait=fast
  506.       sleep(wait)
  507.     catch InterruptedException
  508.       return
  509.     end
  510.  
  511. /* ------------------------------------------------------------------ */
  512. /* PingerTimer  -- a main pinger timer Thread                         */
  513. /* ------------------------------------------------------------------ */
  514. /* This thread must update the pinger in real time, so it keeps       */
  515. /* an eye on 'wall-clock' time so the timer cannot drift.  Any delays */
  516. /* will be corrected as soon as the thread gets control.              */
  517. /*                                                                    */
  518. /* When done, it notifies its owner with calls to the 'pinged' method.*/
  519.  
  520. class PingerTimer extends Thread
  521.   owner=PingerComponent            -- who we work for
  522.   soundfile="Pinger.au"            -- The Sound
  523.   sfile=FileInputStream            -- .. as a file
  524.   audio=AudioStream                -- .. as audio
  525.   started=System.currentTimeMillis -- timestamp of when we were born
  526.  
  527.   /* Construct with
  528.      Arg1 is parent object
  529.      Arg2 is beep file (.au) to play
  530.   */
  531.   method PingerTimer(newowner=PingerComponent)
  532.     owner=newowner
  533.     this.setPriority(Thread.MAX_PRIORITY)         -- time matters, here
  534.     this.start                                    -- off we go
  535.     do
  536.       sfile=FileInputStream(soundfile)
  537.       audio=AudioStream(sfile)
  538.       -- next line primes the audio subsystem
  539.       AudioPlayer.player.start(audio)
  540.       AudioPlayer.player.stop(audio)
  541.     catch IOException
  542.       say "Could not play file '"soundfile"'"
  543.     end
  544.  
  545.   /* This runs until stopped, or we reach 0 */
  546.   method run
  547.     loop millisecs=started+1000 by 1000           -- when next tick
  548.       if owner.bumpss(-1) then leave              -- decrement and quit if 0
  549.       -- calculate how long to next second
  550.       wait=millisecs-System.currentTimeMillis
  551.       if wait<=0 then iterate                     -- badly behind
  552.       sleep(wait)
  553.     catch InterruptedException
  554.       return
  555.     end
  556.  
  557.     -- reach here iff zero attained
  558.     loop label bleeps for owner.pinged(1)
  559.       sfile=FileInputStream(soundfile)
  560.       audio=AudioStream(sfile)
  561.       AudioPlayer.player.start(audio)
  562.       sleep(600)
  563.     catch IOException
  564.       say "Could not play file '"soundfile"'"
  565.     catch InterruptedException
  566.       nop
  567.     end bleeps
  568.  
  569.     owner.pinged(0)                               -- definitely done
  570.  
  571. /* ------------------------------------------------------------------ */
  572. /* PingerDialog -- tell about us                                      */
  573. /* ------------------------------------------------------------------ */
  574. class PingerDialog extends Dialog
  575.  owner=Pinger                      -- who we work for
  576.  form=int                          -- form of text
  577.  
  578.  -- Drawing and layout properties
  579.  shadow=Image                      -- shadow image
  580.  d=Graphics                        -- where we can draw
  581.  width=0; height=0                 -- current picture dimensions
  582.  
  583.  properties constant
  584.  HELP=0                            -- build Help text
  585.  ABOUT=1                           -- build About text
  586.  
  587.  /* Construct a general dialog for Pinger */
  588.  method PingerDialog(newowner=Pinger, newform=int)
  589.    super(newowner, title(newform), 1)
  590.    owner=newowner; form=newform
  591.  
  592.    -- position centre-screen
  593.    sizex=400; sizey=300            -- TV shape
  594.    screen=Toolkit.getDefaultToolkit.getScreenSize
  595.    this.addNotify                  -- ensure peer exists
  596.    reshape((screen.width-sizex)%2,(screen.height-sizey)%2, sizex, sizey)
  597.    setResizable(0)                 -- fixed size panel, please
  598.    -- display (show) will be triggered by creator
  599.  
  600.  /* provide a title string, on demand */
  601.  method title(titleform=int) static returns String
  602.    if titleform=HELP  then return "Help for Pinger"
  603.    return "About Pinger"
  604.  
  605.  /* update  -- overridden, because we set background */
  606.  method update(g=Graphics); paint(g)
  607.  
  608.  /* paint  -- called when the window needs to be redrawn */
  609.  method paint(g=Graphics)
  610.    -- we do not need to protect the image, here, as there should be
  611.    -- only one.  However, a race condition is possible, so we check for
  612.    -- an uninitialized 'shadow'.
  613.    if width<>size.width | height<>size.height then newsize
  614.     else if shadow<>null then g.drawImage(shadow, 0, 0, this)
  615.  
  616.  /* newsize -- here when a new size detected; should be called once */
  617.  method newsize
  618.    /* make a new image to draw in, if needed */
  619.    width=size.width; height=size.height
  620.    shadow=this.createImage(width, height)         -- make image
  621.    d=shadow.getGraphics                           -- context to draw
  622.    d.setColor(Color.white)                        -- background
  623.    d.fillRect(0, 0, width, height)                -- ..
  624.  
  625.    d.setFont(Font("TimesRoman", Font.PLAIN, 20))  -- measure font
  626.    fm=d.getFontMetrics                            -- get metrics
  627.    h=fm.getHeight+2                               -- +2 pels leading
  628.    y=(height-h*5)%3                               -- offset
  629.  
  630.    -- Add some text, etc.  Any graphics allowed, here.
  631.    if form=ABOUT then do
  632.      secs=int Date().getTime//7000
  633.      d.setColor(Color.getHSBColor(secs/6999, 1, 0.5))
  634.      d.drawString("Simple 'Pinger' application",  20, y+h)
  635.      d.drawString("For the NetRexx source, see:", 20, y+h*2)
  636.      d.drawString("  http://www2.hursley.ibm.com/netrexx/", 20, y+h*3)
  637.      d.drawString("Mike Cowlishaw, April 1996",   20, y+h*4)
  638.      end
  639.     else /* Help */ do
  640.      d.setColor(Color.blue.darker)
  641.      d.drawString("This is a 'kitchen timer' application",  20, y+h)
  642.      d.drawString("Set the time using the unmarked buttons.", 20, y+h*2)
  643.      d.drawString("Press 'Start' to start countdown.", 20, y+h*3)
  644.      d.drawString("Press 'Reset' to zero timer.", 20, y+h*4)
  645.      end
  646.    d.setFont(Font("Helvetica", Font.PLAIN, 12))
  647.    d.setColor(Color.black)
  648.    d.drawString("Compiled with" version, 20, y+h*5)
  649.  
  650.    this.repaint
  651.  
  652.  method handleEvent(e=Event) returns boolean
  653.    if e.id=Event.WINDOW_DESTROY then do      -- we are going away
  654.      dispose()
  655.      owner.requestFocus
  656.      return 1; end                           -- we've handled it
  657.    return super.handleEvent(e)               -- take default action
  658.  
  659.