                                    Timer
                                    =====
                                                          March 22nd 2004

Timer is pjwitte 2oo4.
WL_DRAW_LED is copyright (c) W. Lenerz 2003

Timer is a job attached to a SB keyword that counts the time in 1 sec steps
and, optionally, displays the result in a SB channel. The timer is not
designed for accuracy! It synchronises on a clock tick and then maintains
synchonisation until timeout. Accuracy is within 1 second how ever long the
timing period.

WL_DRAW_LED Draws numbers as seven segmented LEDs.


Status:

The authors are not responsible for any damage caused by this software:

                                Use at own risk!

You may use this software for your own, non-commercial, purposes as you please.

The full source code is part of the distribution. The key files (includes) may
be had from the SMSQ/E Registrar.

Please feed back any improvements and bugfixes!


History:

Timer V0.08 released March 22nd 2004
      V0.09 relased March 28th 2005
            added TIMER_DISP


Manual:

        TIMER_INIT      F - Initialise an asynchronous timer job
        TIMER_SET       P - Set or reset timer times
        TIMER_START     P - Start timer
        TIMER_STOP      P - Stop timer
        TIMER_PAUSE     P - Toggle timer pause
        TIMER_KILL      P - Remove timer job
        TIMER_LED       P - Draw LED number
        TIMER_TIME%     F - Get current timer value
        TIMER_STATE%    F - Get current timer state
        TIMER_DISP      P - Switch running timer display on or off
        WL_DRAW_LED     P - Draw a 7-segment LED number

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

        id  = TIMER_INIT([#ch% , | ! number_of_digits%])

        Set up a timer job

 where:
        id
                is the job ID of this timer (more than one can be running
                at the same time). It may also return an error code, eg
                "insufficient memory"

                Parameter errors stop SB program execution with an error
                message in the normal way.

        ch
                is an optional SB channel number to display the counter.
                If none is given, then there is no visual output, otherwise
                the channel's paper and ink colours are used for the display.
                Note: The timer uses the whole channel window!

        number_of_digits%
                is the number of digits to display. Valid values are 1..6
                If no channel is given this parameter must also not be given

        separator:
                , => counter is un-padded (also ; and \)
                ! => counter padded with leading zeros (also TO)


# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

        TIMER_SET id, start_time%, end_time% [,warning%]

        Set the timer times

 where:
        id
                is the timer job ID as above

        start_time%
                is the start time in seconds

        end_time%
                is the time (number of seconds) on which to terminate.

                The maximum value for either of these parameters is
                approx +/- 2^15. However, if the number of digits specified
                is too small to display a value in the range, only the last
                digits are displayed.

                If start_time% < than end_time%, the timer works as a
                count-down timer.

        warning%
                an optional warning time can be given. The value should be
                between start_time% and end_time%. When the timer reaches this
                value it sends an event #2 to the owner job and sets the
                corresponding status bit (see beleow).

        Note: The timer will not start before a time has been set.
              Changing the time while the timer is running will have no effect
              until the timer is re-started


# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

        TIMER_START id

        Start, reset and restart, or resume a paused timer (toggle override)

 where:
        id      is the ID as above

        Resets counter values to current settings.


# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

        TIMER_STOP id

        stop timing

 where:
        id      is the ID as above

        The timer can only be restarted with TIMER_START, not TIMER_PAUSE


# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

        TIMER_PAUSE id

        Pause the timer or resume a paused timer (toggle)

 where:
        id      is the ID as above

        This will not pause a stopped or timed-out timer!


# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

        TIMER_KILL id

        Kill a timer job

 where:
        id      is the ID as above

        Kills the timer job.

        The timer is automatically removed if the job owning it is removed or
        its output channel (if any) is closed.


# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

        TIMER_LED [#ch%;] no_digits%, number$

        Draw LED number using current ink colour

 where:
        ch      is the timer's channel ID, as above

        no_digits%
                is the number of digits to display

        number$ is the value to draw

        See WL_DRAW_LED below for more details


# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

        time% = TIMER_TIME%(id)

        Get current timer time

 where:
        time%
                is the current value of the timer

        id      is the ID as above


# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

        state% = TIMER_STATE%(id)

        Get timer state.

 where:
        state%
                is the current status of the timer (see technical notes below
                for details)

        id      is the ID as above

        If there is no timer job present it returns 0 (ie it doesnt error)

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

        TIMER_DISP id, switch%

        Switch a running timer display on or off

 where:
        switch% = 0 => off and switch% <> 0 => on

        Warning: Must not be used to turn on the display of a timer without
        a channel!

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

===========
WL_DRAW_LED
===========

Draws numbers as seven segmented LEDs. The number is fitted into the
entire window, so as to fill it completely (as much as possible).

syntax:
        * command

        WL_DRAW_LED     [#channel%], colour, how_many_figures, number$

        where:

        -> #channel% is an optional SBasic channel number. This is the channel
        in which the number will be drawn.
        The default is #1. If you do give a channel number, then the
        '#' MUST ALSO be given, else the command will fail to recognize
        the parameter as a valid channel number!

        -> colour is the colour to be used for drawing the LED. The colour
        should correspond to the mode you are in: COLOUR_QL, COLOUR_PAL
        or COLOUR_24.

        ****************
        * PLEASE NOTE: *
        ****************

        With the high colour modes, block drawing is handled
        via different traps, depending on which colour mode (QL, 8 bit Palette
        24 bit high colour etc) you are in. The software tries to work out
        which trap to use from within each Basic. For the software to be able
        to do that correctly, you HAVE to use one of the

        COLOUR_QL
        COLOUR_24
        COLOUR_PAL
        COLOUR_NATIVE

        keywords (once) before using WL_DRAW_LED. You pass to the software the
        colour corresponding to the colour mode you're currently in:
        0-255 if QL mode, 0-255 if 8 bit Palette mode, a long word if "true"
        colour mode. Please note that COLOUR_QL is the default state.

        -> how_many_figures tells the software how many LED figures SHOULD FIT
        in the window. Thus, if you want to draw '1000', there are 4 figures,
        for 100000 there are 6  etc. Please note that a leading minus sign
        MUST be counted as a figure - in '-1000' there are 5 figures!

        number$ is the number to be drawn (passed as a string).
        number$ may only contain figures (0...9) a minus sign and ONE decimal
        point.

        If the number passed in number$ has more figures than those given in
        how_many_figures, then only the LAST figures of number are displayed:
        if how_many_figures is 4 and number$ = "01234567879", you will only
        see "6789". No errror is generated for this



        error returns

        err.ipar        bad parameter, eg the string contained illegal chars
        err.orng        the char(s) couldn't fit into the window - the minimum
                        space needed to display a figure is 9 pixels wide
                        and 9 pixels high per figure - you then must add 1 to
                        the xsize of the window so obtained
                        The decimal point needs an additional 6 pixels


# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

Using the timer:
----------------

The timer is event-driven. The events controlling the timer's operation
are dealt with by the keywords supplied; the user doesnt need to send any
events to the timer.

The program using the timer should, however, listen out for the events sent by
the timer. This can be achieved either by using the system keyword (SMSQ/E)

        WAIT_EVENT (event_mask, [timeout])

or via the Pointer Interface by using one of the many implementations of
Read Pointer (RDPT, RPTR)

or via the Window Manager (MCALLT, RD_PTRT, etc)

The timer can send out two events:

        Event #1 => the timer timed out, or
              #2 => the warning point has been reached

When either of these events are signalled, the event-aware keywords above will
return, indicating which event triggered the return, and the program can act
accordingly. Example, using EasyPtr:

events% = (1 + 2) * 256: rem Shift events to msb
tio% = -1: rem Wman call timeout
:
tid = TIMER_INIT #ct! 4: rem Connect timer to channel ch ! four digits, padded
TIMER_SET tid, 999, 0, 10: rem Countdown from 999, warn on 10 secs left
TIMER_START tid: rem Start timer now
:
eve% = events%
:
rem     Main program loop
rep main
 k = mcallt(#cd, eve%, tio%, k, c0%)
 sel on k
 = -1: MoveWindow #cd
 = -2: ResizeWindow #cd
 = -3: GotoSleep

 ....

 = -1280: rem Event
   ev% = eve% div 256: eve% = events%
   sel on ev%
   = 1: TimedOut
   = 2: rem Warning
     Beep 2,2: ink#ct; red: rem Set red ink in timer channel
     ...

endrep main


# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

Technical notes on Timer:
-------------------------

The timer ID used in the S*Basic commands above are actually the job ID. This
means you could perform other job functions on it (eg RJOB). However the
TIMER_ commands only allow access to the timer from the job that owns it!


States: Check with PRINT BIN$(TIMER_STATE%(tid), 8)

        bit set indicates state. If all bits clear => no timer job

        7: channel - timer display channel exists
        6: zero    - leading zeros in effect
        5: notset  - initial state of unset timer job
        4: warning - the warning point has passed
        3: running - timer is currently running
        2: timeout - the timer has timed out        }
        1: stopped - the timer was stopped by user   } stop bits
        0: paused  - the timer was paused by user   }

Events:

  Timer  ->  Owner:

        event        -       action
        ----------------------------------------------
        #1 timeout           clear running bit and all stop bits
                             set timeout bit
                             emit timeout signal
                             timer stop

        #2 warning           set warning bit
                             emit warning signal (only once)

  Owner -> Timer:

        event        -       action
        ----------------------------------------------
        #1 resume            (while paused)
                             clear paused bit and resume

        #1 pause             (while running)
                             set paused bit and pause

        #2 stop              (while running)
                             clear running bit
                             set stopped bit and stop

                             (while paused or stopped)
                             clear all other stop bits
                             set stopped bit and stop

        #4 start             (while stopped)
                             clear all stop bits
                             set running bit
                             reset timer to set values and resume

                             (while paused)
                             clear all stop bits and resume

                             (while running)
                             reset timer to set values and resume
