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