unit Replay;
{ ================================================
  WinWCP View Records module (c) J. Dempster 1996-97
  Lets users view sets of signals records on screen
  2/5/97 ... Channel names on display and print-out
             now only displayed when channel enabled
  12/6/97 ... COPY DATA Buffer overflow fixed
  16/6/97 ... Cursor can now be written to log file
              C1-C0 relative cursor mode added
  25/8/97 ... Size of mmCursor box adjusted,
              number format reduced to 3 sign. figs.
  31/8/97 ... Short cut keys for setting record type
  10/9/97 ... Active record now only displayed once (V1.7c)
  24/2/98 ... +/- keys now step forward/back records
              Accepted/Rejected and Record Type no longer accidentally
              changed by short cut keys.
  2/4/98 .... Cursor readout now displays both absolute and C1-C0 readings
  ================================================}
interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls, ExtCtrls, Global, Shared, FileIo, Zero, ClipBrd,
  PrintRec,Printers, PlotLib, menus, maths, Grids ;

type
  TDestination = (ToPrinter,ToClipboard) ;
  TReplayFrm = class(TForm)
    CursorGrp: TGroupBox;
    RecordGrp: TGroupBox;
    pbDisplay: TPaintBox;
    edRecordNum: TEdit;
    Label2: TLabel;
    cbRecordType: TComboBox;
    ckBadRecord: TCheckBox;
    sbRecordNum: TScrollBar;
    lbTMin: TLabel;
    lbTMax: TLabel;
    Timer: TTimer;
    lbCursor0: TLabel;
    edTime: TEdit;
    Label1: TLabel;
    edGroup: TEdit;
    Group: TLabel;
    FilterGrp: TGroupBox;
    cbLowPassFilter: TComboBox;
    edIdent: TEdit;
    Label3: TLabel;
    lbCursor1: TLabel;
    EdRecordIdent: TEdit;
    bSaveCursor: TButton;
    sgCursor: TStringGrid;
    procedure pbDisplayPaint(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure sbRecordNumChange(Sender: TObject);
    procedure TimerTimer(Sender: TObject);
    procedure pbDisplayMouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure pbDisplayMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure pbDisplayMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormShow(Sender: TObject);
    procedure FormDeactivate(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    procedure edRecordNumKeyPress(Sender: TObject; var Key: Char);
    procedure ckBadRecordClick(Sender: TObject);
    procedure cbRecordTypeChange(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure cbLowPassFilterChange(Sender: TObject);
    procedure pbDisplayDblClick(Sender: TObject);
    procedure FormKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure edIdentChange(Sender: TObject);
    procedure edIdentKeyPress(Sender: TObject; var Key: Char);
    procedure EdRecordIdentChange(Sender: TObject);
    procedure edGroupKeyPress(Sender: TObject; var Key: Char);
    procedure FormHide(Sender: TObject);
    procedure bSaveCursorClick(Sender: TObject);

  private
    { Private declarations }
    procedure DrawHorizontalCursor(var pb : TPaintBox ; Const Gr : TChannel ;
                                    Level : Integer ) ;
    procedure PlotRecords( Destination : TDestination ) ;
    procedure UpdateLowPassFilterList(dt : single) ;
    procedure CopyDataToClipBoard ;
    procedure HeapBuffers( Operation : THeapBufferOp ) ;
  public
    { Public declarations }
   TimerBusy : boolean ;
   HardCopy : boolean ;
   NewFile : Boolean ;
   CopyToClipboardAsImage : boolean ;
   CopyToClipboardAsData : boolean ;


  end;


var
  ReplayFrm: TReplayFrm;

implementation

uses          MDIForm,Zoom,copyrec ;

type
    TCursorState = ( Cursor0,Cursor1,ZeroLevelCursor,NoCursor ) ;
    TReplayState = (PlotRecord,UpdateCursorReadout,Idle,PrintHardCopy,
                   DoCopyToClipboardAsImage,DoCopyToClipboardAsData) ;

var
   CurCh : LongInt ;
   MoveCursor : Boolean ;
   CursorState : TCursorState ;
   State : TReplayState ;
   RecHeader : TRecHeader ;      { Signal record header block }
   ADC : ^TIntArray ;
   Display : TDisplayList ;
   Olddt : single ;
   MouseOverChannel : Integer ;
   Destination : TDestination ;
   CursorChannel : TChannel ;
   BuffersAllocated : boolean ;{ Indicates if memory buffers have been allocated }

{$R *.DFM}

procedure TReplayFrm.HeapBuffers( Operation : THeapBufferOp ) ;
{ -----------------------------------------------
  Allocate/deallocation dynamic buffers from heap
  -----------------------------------------------}
begin
     case Operation of
          Allocate : begin
             if not BuffersAllocated then begin
                New(ADC) ;
                BuffersAllocated := True ;
                end ;
             end ;
          Deallocate : begin
             if BuffersAllocated then begin
                Dispose(ADC) ;
                BuffersAllocated := False ;
                end ;
             end ;
          end ;
     end ;


procedure TReplayFrm.FormShow(Sender: TObject);
{ ---------------------------------------
  Initialisations done when form is shown
  ---------------------------------------}
var
   i : Integer ;
begin

     HeapBuffers( Allocate ) ;

     Main.ClearTicksInWindowsMenu ;
     Main.ShowView.checked := True ;

     Height := CursorGrp.Top + CursorGrp.Height + 50 ;
     Width := edRecordIdent.Left + EdRecordIdent.Width + 30 ;
     cbRecordType.items := RecordTypes ;
     cbRecordType.items.delete(0) ; {Remove 'ALL' item}

     { Start scheduling timer }
     Timer.enabled := True ;
     TimerBusy := False ;

     { Clear record display list }
     Display.ActiveRecord := High(Display.List) ;
     Display.EndOfList := Display.ActiveRecord - 1 ;
     for i := 0 to Display.EndOfList do begin
         Display.List[i] := 0 ;
         Display.OnDisplay[i] := False ;
         end ;
     Display.Latest := 0 ;

     Channel[0].Cursor0 := 0 ;
     Channel[0].Cursor1 := FH.NumSamples div 2 ;

     { Request a record to be displayed }
     State := PlotRecord ;
     HardCopy := False ;
     Olddt := 0. ;
     Display.Refresh := True ;

     end;


procedure TReplayFrm.pbDisplayPaint(Sender: TObject);
begin
     State := PlotRecord ;
     Display.Refresh := True ;
     end ;


procedure TReplayFrm.FormResize(Sender: TObject);
{ ---------------------------
  Re-size components on form
  --------------------------}
begin
     pbDisplay.left := RecordGrp.Left + RecordGrp.width + 10 ;
     pbDisplay.width := Width - pbDisplay.left - 20 ;
     pbDisplay.top := EdIdent.Top + EdIdent.Height + 2 ;
     pbDisplay.Height := Height  - pbDisplay.Top -
                         (3*lbTMin.Height) div 2 - 40 ;

     lbTMin.Left := pbDisplay.left ;
     lbTMin.Top := pbDisplay.top +  pbDisplay.Height + 2 ;
     lbTMax.Left := pbDisplay.left + pbDisplay.width - lbTMax.Width ;
     lbTMax.Top := lbTMin.Top ;

     State := PlotRecord ;
     Display.Refresh := True ;
     end;

procedure TReplayFrm.sbRecordNumChange(Sender: TObject);
begin
     { If the record number has changed ask for the display to be updated }
     State := PlotRecord ;
     end;


procedure TReplayFrm.TimerTimer(Sender: TObject);
{ -----------------------------------------------------------
  Process scheduler ... plots records, updates cursor readout
  -----------------------------------------------------------}
var
   ch,i,i0,xPix,yPix,MaxCh,nOffScreen,rec,ChOffset : LongInt ;
   cw : Integer ;
   InList : Boolean ;
   x,dx,y : Single ;

begin

     { If a new file has been opened reset display module }

     if not TimerBusy then  begin

        TimerBusy := True ;

        if NewFile then begin
           caption := 'View ' + FH.FileName ;
           if ExtractFileExt( FH.FileName ) = '.avg' then
              caption := caption + ' (Averages)'
           else if ExtractFileExt( FH.FileName ) = '.lea' then
              caption := caption + ' (Leak Subtracted)' ;

           EdIdent.text := FH.IdentLine ;

           { Clear record display list }
           for i := 0 to High(Display.List) do begin
               Display.List[i] := 0 ;
               Display.OnDisplay[i] := False ;
               end ;
           Display.Latest := 0 ;

           { Set up readout cursor table }
           cw := sgCursor.Canvas.TextWidth('X') ;
           sgCursor.RowCount := FH.NumChannels + 2 ;
           sgCursor.Cells[0,1] := 'Time ' ;
           sgCursor.ColWidths[0] := 5*cw ;
           sgCursor.Cells[1,0] := 'C.1' ;
           sgCursor.ColWidths[1] := 7*cw ;
           sgCursor.Cells[2,0] := 'C.1-C.0' ;
           sgCursor.ColWidths[2] := 7*cw ;
           sgCursor.Cells[3,1] := Settings.TUnits ;
           sgCursor.ColWidths[3] := 3*cw ;
           for ch := 0  to FH.NumChannels-1 do begin
               sgCursor.Cells[0,ch+2] := Channel[ch].ADCName ;
               sgCursor.Cells[3,ch+2] := Channel[ch].ADCUnits ;
               end ;

           if FH.NumRecords > 0 then Main.Print1.enabled := True ;
           FH.RecordNum := 1 ;
           sbRecordNum.Position := 1 ;
           NewFile := False ;
           State := PlotRecord ;
           Display.Refresh := True ;
           end ;

        case State of

            { Plot record(s) on display }
            PlotRecord : begin
                if fH.NumRecords > 0 then begin

                   Main.SetCopyMenu( True, True ) ;
                   Main.Print1.Enabled := True ;

                   sbRecordNum.Max := fH.NumRecords ;
                   sbRecordNum.Min := 1 ;
                   sbRecordNum.Enabled := True ;
                   fH.RecordNum := sbRecordNum.position ;

                   InitializeDisplay( Channel, fH.NumChannels, RecHeader,
                                      lbTMin,lbTMax, pbDisplay) ;

                   if (Channel[0].Cursor1 < Trunc(Channel[0].xMin)) or
                      (Channel[0].Cursor1 > Trunc(Channel[0].xMax)) then
                      Channel[0].Cursor1 := Trunc( (Channel[0].xMin +
                                                    Channel[0].xMax)/2. ) ;

                   { Remove cursors before updating display }
                   CursorChannel := Channel[0] ;
                   CursorChannel.Top := 0 ;
                   CursorChannel.Bottom := pbDisplay.Height ;
                   CursorChannel.color := clRed ;
                   DrawCursor( pbDisplay, Channel[0].Cursor1, CursorChannel,lbCursor1 ) ;
                   CursorChannel.color := clPurple ;
                   DrawCursor( pbDisplay, Channel[0].Cursor0, CursorChannel,lbCursor0 ) ;
                   for ch := 0 to fH.NumChannels-1 do if Channel[ch].InUse then
                       DrawHorizontalCursor(pbDisplay,Channel[ch],Channel[ch].ADCZero) ;

                   { If in auto-erase mode ... clear display list of records }
                   if Settings.AutoErase then begin
                      for i := 0 to Display.EndOfList do Display.List[i] := 0 ;
                      Display.Refresh := True ;
                      Display.Latest := 0 ;
                      end ;

                   { Is current record in display list }
                   InList := False ;
                   for i := 0 to Display.EndOfList do
                     if Display.List[i] = fH.RecordNum then begin
                        Display.ActiveRecordIndex := i ;
                        InList := True ;
                        end ;

                   { Add current record to display list if it is not already in it }
                   if not InList then begin
                      Display.List[Display.Latest] := fH.RecordNum ;
                      Display.ActiveRecordIndex := Display.Latest ;
                      Inc(Display.Latest) ;
                      if Display.Latest > Display.EndOfList then Display.Latest := 0 ;
                      end ;

                   InitializeDisplay( Channel, fH.NumChannels, RecHeader,
                                      lbTMin,lbTMax, pbDisplay) ;

                   { If display has to be completely re-drawn ... plot background }
                   if Display.Refresh then begin
                      for i := 0 to Display.EndOfList do Display.OnDisplay[i] := False ;
                      Display.Refresh := False ;
                      EraseDisplay(pbDisplay) ;
                      end ;

                   { Display all records not on screen }
                   Display.List[Display.ActiveRecord] := fh.RecordNum ;
                   Display.OnDisplay[Display.ActiveRecord] := False ;

                   for rec := 0 to Display.ActiveRecord do begin
                       if (Display.List[rec] > 0 )
                          and (not Display.OnDisplay[rec])
                          and ((rec = Display.ActiveRecord)
                                    or (Display.List[rec] <> fh.recordnum)) then begin

                          Display.OnDisplay[rec] := True ;

                          { Set trace colour }
                          if rec = Display.ActiveRecord then begin
                             pbDisplay.canvas.pen.color := clBlue ;
                             Display.OnDisplay[Display.ActiveRecordIndex] := False ;
                             end
                          else pbDisplay.canvas.pen.color := clAqua ;

                          { Read A/D data from file }
                          GetRecord( fH, RecHeader, Display.List[rec], ADC^ ) ;

                          { Plot each channel}

                          for ch := 0 to fH.NumChannels-1 do begin
                              if Channel[ch].InUse then begin
                                 ChOffset := Channel[Ch].ChannelOffset ;
                                 dx := Channel[ch].xScale ;
                                 x := -Channel[ch].xScale*Channel[ch].xMin
                                      + Channel[ch].Left ;
                                 nOffScreen := 0 ;
                                 for i := 0 to fH.NumSamples-1 do begin

                                     y := ADC^[(i*fH.NumChannels) + ChOffset] ;
                                     xPix := Trunc(x) ;
                                     yPix := Channel[ch].Bottom -
                                             Trunc( Channel[ch].yScale *
                                                  (y - Channel[ch].yMin)) ;

                                     if   (xPix < Channel[ch].Left)
                                          or (Channel[ch].Right < xPix )
                                          or (yPix < Channel[ch].Top)
                                          or (Channel[ch].Bottom < yPix )
                                          then begin
                                             xPix := IntLimitTo( xPix,
                                                     Channel[ch].Left,
                                                     Channel[ch].Right ) ;
                                             yPix := IntLimitTo( yPix,
                                                     Channel[ch].Top,
                                                     Channel[ch].Bottom ) ;
                                             Inc(nOffScreen) ;
                                             end
                                          else nOffScreen := 0 ;

                                     if (nOffScreen > 1) or (i=0) then
                                        pbDisplay.canvas.moveto(xPix,yPix)
                                     else pbDisplay.canvas.lineto(xPix,yPix);

                                     x := x + dx ;

                                     end ;
                                 end ;
                              end ;
                          end ;
                       end ;

                   { Display Channel Name(s) }
                   for ch := 0 to FH.NumChannels-1 do if Channel[ch].InUse then
                       pbDisplay.Canvas.TextOut( Channel[ch].Left,
                                  (Channel[ch].Top + Channel[ch].Bottom) div 2,
                                   ' ' + Channel[ch].ADCName ) ;

                   edRecordNum.text := format('Rec. %d/%d',[sbRecordNum.position,
                                                            fH.NumRecords]) ;
                   FH.CurrentRecord := sbRecordNum.position ;

                   { Show whether record has been rejected by operator }
                   if RecHeader.Status = 'ACCEPTED' then ckBadRecord.checked := False
                                                    else ckBadRecord.checked := True ;
                   { Show type of record }
                   if cbRecordType.items.indexOf(RecHeader.RecType) >= 0 then
                      cbRecordType.ItemIndex := cbRecordType.items.indexOf(RecHeader.RecType);

                   edTime.text := format( '%f s', [RecHeader.Time] ) ;
                   edGroup.text := format( '%d', [ Trunc(RecHeader.Number) ] ) ;
                   edRecordIdent.text := RecHeader.Ident ;

                   if RecHeader.dt <> Olddt then begin
                      UpdateLowPassFilterList( RecHeader.dt ) ;
                      Olddt := RecHeader.dt ;
                      end ;
                   MoveCursor := False ;
                   State := UpdateCursorReadout ;
                   end
                else begin
                   { No records available }
                   ch := 0 ;
                   EraseDisplay(pbDisplay) ;
                   InitializeDisplay( Channel, fH.NumChannels, RecHeader,
                             lbTMin,lbTMax, pbDisplay) ;
                   edRecordNum.text := 'No Records ' ;
                   sbRecordNum.Enabled := False ;
                   State := Idle ;
                   end ;

                { Restore cursors }
                CursorChannel.color := clRed ;
                DrawCursor( pbDisplay, Channel[0].Cursor1, CursorChannel, lbCursor1 ) ;
                CursorChannel.color := clPurple ;
                DrawCursor( pbDisplay, Channel[0].Cursor0, CursorChannel, lbCursor0 ) ;
                for ch := 0 to fH.NumChannels-1 do if Channel[ch].InUse then
                    DrawHorizontalCursor(pbDisplay,Channel[ch],Channel[ch].ADCZero) ;

                end ;


            UpdateCursorReadout : begin

               { ** Display time and sample values under cursor **}

               { Cursor time }
               sgCursor.Cells[1,1] := format( ' %5.4g',
                                              [Channel[0].Cursor1*RecHeader.dt*
                                               Settings.TScale]) ;
               sgCursor.Cells[2,1] := format( ' %5.4g',
                                              [(Channel[0].Cursor1 -
                                                Channel[0].Cursor0)*RecHeader.dt*
                                                Settings.TScale]) ;
               { Channel levels }
               for ch := 0 to fH.NumChannels-1 do if Channel[ch].InUse then begin
                   Channel[ch].CursorTime := Channel[0].CursorTime ;
                   ChOffset := Channel[ch].ChannelOffset ;
                   { Absolute level (relative to baseline) }
                   i := Channel[0].Cursor1*fH.NumChannels + ChOffset ;
                   Channel[ch].CursorValue := (ADC^[i] - Channel[ch].ADCZero)
                                               * Channel[ch].ADCScale ;
                   sgCursor.Cells[1,ch+2] := format( '%.3g',
                                             [Channel[ch].CursorValue] ) ;
                   { Difference between levels at C1 and C0 }
                   i0 := Channel[0].Cursor0*fH.NumChannels + ChOffset ;
                   sgCursor.Cells[2,ch+2] := format( '%.3g',
                                             [ (ADC^[i] - ADC^[i0])
                                               * Channel[ch].ADCScale ] ) ;
                   end ;
               State := Idle ;
               end;

            PrintHardCopy : begin
                     State := Idle ;
                     PlotRecords( ToPrinter ) ;
                     end ;

            DoCopyToClipboardAsImage : begin
                     State := Idle ;
                     PlotRecords( ToClipboard ) ;
                     end ;

            DoCopyToClipboardAsData : begin
                     State := Idle ;
                     CopyDataToClipboard ;
                     end ;

            Idle : begin
                     if HardCopy then begin
                        State := PrintHardCopy ;
                        HardCopy := False ;
                        end
                     else if CopyToClipboardAsImage then begin
                        State := DoCopyToClipboardAsImage ;
                        CopyToClipboardAsImage := False ;
                        end 
                     else if CopyToClipboardAsData then begin
                        State := DoCopyToClipboardAsData ;
                        CopyToClipboardAsData := False ;
                        end ;
                     end ;
            end ;
        TimerBusy := False ;
        end ;
     end ;



procedure TReplayFrm.DrawHorizontalCursor( var pb : TPaintBox ;
                                Const Gr : TChannel ;
                                Level : Integer ) ;
{ ----------------------------------------------------
  Draw dotted horizontal cursor at ADC level 'Level'
  in display area defined by  record data channel 'Gr'
  ----------------------------------------------------}
var
   yPix,xPix,TicSize : Integer ;
   OldColor : TColor ;
   OldStyle : TPenStyle ;
   OldMode : TPenMode ;
begin
     with pb.canvas do begin
          OldColor := pen.color ;
          OldStyle := pen.Style ;
          OldMode := pen.mode ;
          pen.mode := pmXor ;
          pen.color := clRed ;
          end ;

     yPix := Trunc( Gr.Bottom - (Level - Gr.yMin)*Gr.yScale ) ;
     pb.canvas.polyline([Point(Gr.Left,yPix),Point(Gr.Right,yPix)]);

     { if zero level has been calculated from record show the point used }

     if (Gr.xMin <= Gr.ADCZeroAt) and (Gr.ADCZeroAt <= Gr.xMax ) then begin
          xPix := Trunc((Gr.ADCZeroAt - Gr.xMin)*Gr.xScale - Gr.Left) ;
          TicSize := pb.Canvas.textheight('X') ;
          pb.canvas.polyline( [Point( xPix, yPix+TicSize ),
                               Point( xPix, yPix-TicSize )] );
          end ;

     with pb.canvas do begin
          pen.style := OldStyle ;
          pen.color := OldColor ;
          pen.mode := OldMode ;
          end ;
     { Request that the cursor readout memo box be updated }
     State := UpdateCursorReadout ;
    end ;

procedure TReplayFrm.pbDisplayMouseMove(Sender: TObject;
  Shift: TShiftState; X, Y: Integer);
{ -------------------------------------------
  Respond to mouse movement over display area
  -------------------------------------------}
Const
     Margin = 4 ;
var
   X_Cursor,Y_Cursor,X_Zero,ch : LongInt ;
   OldIndex : Integer ;
begin
     { Only process mouse position over display if form is active}
     if Active then begin
        { Make sure the the record number box has the focus
        to avoid unintended effect if arrow keys are used to move the cursors }
        edRecordNum.Setfocus ;

        { Find locations of measurement and zero time cursors }

        X_Cursor := Trunc( ((Channel[0].Cursor1 - Channel[0].xMin) *
                          Channel[0].xScale ) + Channel[0].Left ) ;
        X_Zero := Trunc( ((Channel[0].Cursor0 - Channel[0].xMin)*
                          Channel[0].xScale) + Channel[0].Left ) ;

        if not MoveCursor then begin

          { If not in the cursor move mode, check to see if the mouse
            is over any of the cursors and change its icon accordingly }

          CursorState := NoCursor ;
          pbDisplay.Cursor := crDefault ;

          if Abs(X - X_Cursor) < Margin then begin
                CursorState := Cursor1 ;
                pbDisplay.Cursor := crSizeWE ;
                end
          else if ( Abs(X - X_Zero) < Margin ) then begin
                CursorState := Cursor0 ;
                pbDisplay.Cursor := crSizeWE ;
                end
          else begin
                for ch := 0 to fH.NumChannels-1 do begin
                    Y_Cursor := Trunc( Channel[ch].Bottom -
                                ((Channel[ch].ADCZero - Channel[ch].yMin) *
                                Channel[ch].yScale) ) ;
                    if Abs(Y-Y_Cursor) < Margin then begin
                       CurCh := ch ;
                       CursorState := ZeroLevelCursor ;
                       pbDisplay.Cursor := crSizeNS ;
                       end ;
                    end ;
                end ;
          end
        else begin

          { If in Move Cursor mode, move selected cursor to new position }

          case CursorState of
          Cursor1 : begin
              CursorChannel.color := clRed ;
              VerticalCursorScale( X,Channel[0].Cursor1,OldIndex,CursorChannel);
              MoveVerticalCursor( pbDisplay, Channel[0].Cursor1, OldIndex,
                                  CursorChannel, lbCursor1 ) ;
              end ;
          Cursor0 : begin
              CursorChannel.color := clPurple ;
              VerticalCursorScale( X,Channel[0].Cursor0,OldIndex,CursorChannel);
              MoveVerticalCursor( pbDisplay, Channel[0].Cursor0, OldIndex,
                                  CursorChannel, lbCursor0 ) ;
              end ;

          ZeroLevelCursor : Begin

              { Remove existing cursor }

              DrawHorizontalCursor( pbDisplay, Channel[CurCh], Channel[Curch].ADCZero ) ;

              Channel[CurCh].ADCZero := Trunc( ((Channel[CurCh].Bottom - Y) /
                                               Channel[CurCh].yScale)
                                               + Channel[CurCh].yMin ) ;

              { Keep cursor within display limits }

              if Channel[CurCh].ADCZero < Channel[CurCh].yMin then
                 Channel[CurCh].ADCZero := Trunc(Channel[CurCh].yMin) ;
              if Channel[CurCh].ADCZero > Channel[CurCh].yMax then
                 Channel[CurCh].ADCZero := Trunc(Channel[CurCh].yMax) ;

              { Draw new cursor }

              DrawHorizontalCursor( pbDisplay, Channel[CurCh], Channel[CurCh].ADCZero ) ;
              end ;

              else ;
              end ; {case}
          State := UpdateCursorReadOut ;
          end ; {endif}
        end ;
     end ;



procedure TReplayFrm.pbDisplayMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
   ch : Integer ;
begin
     if State = Idle then begin
        MoveCursor := True ;
        { Find the channel that the mouse is over }
        for ch := 0 to FH.NumChannels-1 do
            if (Channel[ch].Bottom >= Y) and (Y >= Channel[ch].top) then
               MouseOverChannel := ch ;
        end ;

     end;


procedure TReplayFrm.pbDisplayMouseUp(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
     if State = Idle then begin
        if (Button = mbRight) and (CursorState = ZeroLevelCursor) then begin
           ZeroFrm.ChSel := CurCh ;
           ZeroFrm.NewZeroAt := Trunc( (X - Channel[0].Left) / Channel[0].xScale
                                        + Channel[0].xMin ) ;
           ZeroFrm.ShowModal ;
           State := PlotRecord ;
           end ;
        end ;
     MoveCursor := False ;
     end;


procedure TReplayFrm.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
{ ------------------------
  Function key processing
  31/8/97 ... Short cut keys for setting record type
  -----------------------}
type
    TAction = (MoveCursor,ChangeRecord,SaveCursor,NewRecordType,
               ChangeRejectedStatus,None) ;
var
   Action : TAction ;
   Step,OldPosition : Integer ;
   NewRecord : LongInt ;
begin
     case key of
          VK_LEFT : begin
             Action := MoveCursor ;
             Step := -1 ;
             end ;
          VK_RIGHT : begin
             Action := MoveCursor ;
             Step := 1 ;
             end ;
          VK_SUBTRACT : begin { - key }
                Action := ChangeRecord ;
                Step := -1 ;
             end ;
          VK_ADD : begin { + key }
                Action := ChangeRecord ;
                Step := 1 ;
             end ;
          VK_F1 : begin
             Action := SaveCursor ;
             end ;
          $54,$4c,$45,$4d,$46 : begin
               if (Shift = [ssCtrl]) then Action := NewRecordType ;
               end ;
          $52 : begin
               if (Shift = [ssCtrl]) then Action := ChangeRejectedStatus ;
               end ;
          else Action := None ;
          end ;

     case Action of
        { Move vertical display cursor }
        MoveCursor : begin
            Case CursorState of
             Cursor0 : begin
                 OldPosition := Channel[0].Cursor0 ;
                 Channel[0].Cursor0 := OldPosition + Step ;
                 CursorChannel.color := clPurple ;
                 MoveVerticalCursor( pbDisplay,Channel[0].Cursor0,OldPosition,
                                     CursorChannel, lbCursor0 ) ;
                 end ;
             Cursor1 : begin
                 OldPosition := Channel[0].Cursor1  ;
                 Channel[0].Cursor1 := OldPosition + Step ;
                 CursorChannel.color := clRed ;
                 MoveVerticalCursor( pbDisplay,Channel[0].Cursor1,OldPosition,
                                     CursorChannel, lbCursor1 ) ;
                 end ;
             end ;
            State := UpdateCursorReadout ;
            end ;
        { Change record currently displayed }
        ChangeRecord : begin
            sbRecordNum.SetFocus ;
            If ckBadRecord.Checked then RecHeader.Status := 'REJECTED'
                                   else RecHeader.Status := 'ACCEPTED' ;
            RecHeader.RecType := cbRecordType.text ;
            fH.RecordNum := sbRecordNum.Position ;
            PutRecordHeaderOnly( fH, RecHeader, fH.RecordNum ) ;
            NewRecord := LongIntLimitTo( FH.RecordNum + Step, 1,FH.NumRecords ) ;
            sbRecordNum.Position := NewRecord ;
            State := PlotRecord ;
            end ;
        SaveCursor : begin
            bSaveCursor.Click ;
            end ;
        NewRecordType : begin
            { Update record type }
            case Key of
                 $54 : cbRecordType.ItemIndex := cbRecordType.Items.IndexOf('TEST') ;
                 $4c : cbRecordType.ItemIndex := cbRecordType.Items.IndexOf('LEAK') ;
                 $45 : cbRecordType.ItemIndex := cbRecordType.Items.IndexOf('EVOK') ;
                 $4d : cbRecordType.ItemIndex := cbRecordType.Items.IndexOf('MINI') ;
                 $46 : cbRecordType.ItemIndex := cbRecordType.Items.IndexOf('FAIL') ;
                 end ;
            RecHeader.RecType := cbRecordType.text ;
            PutRecordHeaderOnly( fH, RecHeader, fH.RecordNum ) ;
            end ;
        ChangeRejectedStatus : begin
            ckBadRecord.Checked := not ckBadRecord.Checked ;
            If ckBadRecord.Checked then RecHeader.Status := 'REJECTED'
                                   else RecHeader.Status := 'ACCEPTED' ;
            PutRecordHeaderOnly( fH, RecHeader, fH.RecordNum ) ;
            end;

        end ;

     end;





procedure TReplayFrm.FormDeactivate(Sender: TObject);
begin
     Timer.Enabled := False ;
     TimerBusy := False ;
     HeapBuffers( Deallocate ) ;
     Main.SetCopyMenu( False, False ) ; { Disable copy menu options}
     Main.Print1.Enabled := False ;
     end;


procedure TReplayFrm.FormActivate(Sender: TObject);
begin
     Main.ClearTicksInWindowsMenu ;
     Main.ShowView.checked := True ;
     HeapBuffers( Allocate ) ;{ Allocate memory buffers }
     TimerBusy := False ;
     Timer.Enabled := True ;
     end;


procedure TReplayFrm.edRecordNumKeyPress(Sender: TObject; var Key: Char);
{ ------------------------------------
  Go to record number entered by user
  -----------------------------------}
var
   i : LongInt ;
begin
     if key = chr(13) then begin
        i := ExtractInt( edRecordNum.text ) ;
        if ( i > 0 ) and ( i < fH.NumRecords ) then sbRecordNum.position := i ;
        State := PlotRecord ;
        end ;
     end;

procedure TReplayFrm.ckBadRecordClick(Sender: TObject);
{ ------------------------------------------------
  Save new record ACCEPTED/REJECTED status to file
  ------------------------------------------------}
begin
     if ckBadRecord.checked then RecHeader.Status := 'REJECTED'
                            else RecHeader.Status := 'ACCEPTED' ;
     PutRecordHeaderOnly( fH, RecHeader, fH.RecordNum ) ;
     end;


procedure TReplayFrm.cbRecordTypeChange(Sender: TObject);
{ -----------------------------
  Save new record type to file
  ----------------------------}
begin
     RecHeader.RecType := cbRecordType.text ;
     PutRecordHeaderOnly( fH, RecHeader, fH.RecordNum ) ;
     end;

procedure TReplayFrm.CopyDataToClipBoard ;
{ --------------------------------------------------
  Copy the currently displayed record to the clipboard
  --------------------------------------------------
  14/12/97 (V1.8) Buffer size reduced to 30000 to avoid GPF }
const
     BufSize = 31000 ;
     BufLimit = BufSize - 80 ;
     NumBytesPerNumber = 12 ;
var
   i,j,iSkip,x,ch,NumChars : Word ;
   NumBytesNeeded : LongInt ;
   Line : String ;
   CopyBuf0,Line0 : PChar ;
   OK : Boolean ;
   ClipRec : ^TWCPClipboardRecord ;

begin
     try

        { Get sole access and clear clipboard }
        OK := OpenClipboard( Handle ) ;
        OK := EmptyClipBoard ;

        { Allocate buffer to hold signal }
        CopyBuf0 := StrAlloc( BufSize ) ;
        StrPCopy( CopyBuf0, '' ) ;
        Line0 := StrAlloc(  256 ) ;

        { Determine sample skip factor to ensure that the compete record
          fits into the buffer }
        NumBytesNeeded := 0 ;
        for ch := 0 to fH.NumChannels-1 do if Channel[ch].InUse then
            NumBytesNeeded := NumBytesNeeded + NumBytesPerNumber ;
        NumBytesNeeded := NumBytesNeeded*(Trunc(Channel[0].xMax - Channel[0].xMin)+1) ;
        iSkip := (NumBytesNeeded div BufSize) + 1 ;

        i := Trunc(Channel[0].xMin) ;
        screen.cursor := crHourglass ;
        NumChars := 0 ;
        while  (i <= Channel[0].xMax) and (NumChars < BufLimit) do begin
             { Create a line of <tab> separated ASCII data values terminated by <cr> <lf>
               Time <tab> Ch.0.Value <tab> Ch.1.Value .... <cr> <lf> }
             Line := format( '%.4g', [i*RecHeader.dt*Settings.TScale] ) ;
             for ch := 0 to fH.NumChannels-1 do if Channel[ch].InUse then begin
                 j := i*fH.NumChannels + Channel[ch].ChannelOffset ;
                 Line := Line + chr(9) + Format('%.4g',
                                         [(ADC^[j] - Channel[ch].ADCZero)
                                               * Channel[ch].ADCScale] ) ;
                 end ;
             Line := Line + chr(13) + chr(10) ;
             NumChars := NumChars + length(Line) ;
             StrPCopy( Line0, Line ) ;
             CopyBuf0 := StrCat( CopyBuf0, Line0 ) ;
             i := i + iSkip ;
             end ;
         NumChars := StrLen( CopyBuf0 ) ;
        { Copy text accumulated in copy buffer to clipboard }
         ClipBoard.SetTextBuf( CopyBuf0 ) ;

     finally
         screen.cursor := crDefault ;
         { Dispose of buffers }
         StrDispose( Line0 ) ;
         StrDispose( CopyBuf0 ) ;
         OK := CloseClipboard ;
         end ;
     end ;


procedure TReplayFrm.FormCreate(Sender: TObject);

begin
     { Set flag in main form indicating that the replay form is open }
     Main.ReplayChildExists := True ;
     Main.ShowView.visible := True ;
     BuffersAllocated := False ;
     end;


procedure TReplayFrm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
     HeapBuffers( Deallocate ) ;
     Main.ReplayChildExists := False ;
     Main.ShowView.visible := False ;
     Main.SetCopyMenu( False, False ) ; { Disable copy menu options}
     Main.Print1.Enabled := False ;
     Action := caFree ;
     end;


procedure TReplayFrm.PlotRecords( Destination : TDestination ) ;
{ ---------------------------------------------------------------------
  Plot currently displayed records on printer or as bitmap in clipboard
  ---------------------------------------------------------------------}
type
    TFirstRecord = record
             Done : boolean ;
             ADCVoltageRange : Array[0..ChannelLimit] of single ;
             ADCZero : Array[0..ChannelLimit] of LongInt ;
             dt : single
             end ;
var
    PrChan : array[0..ChannelLimit] of TChannel ;
    FirstRec : TFirstRecord ;
    YMult : single ;
    YShift : LongInt ;
    Page : TPlot ;
    TimeBarValue : single  ;
    PlotOK : boolean ;
    ch, i, Num, xPix, yPix,LineHeight,CharWidth : LongInt ;
    ChanHeight,ChanTop,ChOffset,ChLast : LongInt;
    Rec,PreviousRec,ListIndex : LongInt ;
    x,y,dx : single ;
    OldPenWidth,Tick,nOffScreen : Integer ;
    ShowRecords : string ;
    Separator : string[6] ;
    BMap : TBitmap ;

begin

     { If no previous settings ... define an usable set }
     if Settings.TimeBarValue = -1. then
        Settings.TimeBarValue := (Channel[0].Xmax - Channel[0].xMin) *
                                 RecHeader.dt*0.1  ;
     for ch := 0 to fh.NumChannels-1 do if Settings.BarValue[ch] = -1. then
         Settings.BarValue[ch] := (Channel[ch].yMax - Channel[ch].yMin)
                                           * Channel[ch].ADCScale * 0.1 ;

     { Get calibration bar sizes and other options from user }
     PlotOK := False ;
     case Destination of
        ToPrinter : begin
           PrintRecFrm.ShowModal ;
           if PrintRecFrm.ModalResult = mrOK then PlotOK := True ;
           end ;
        ToClipboard : begin
           CopyRecDlg.ShowModal ;
           if CopyRecDlg.ModalResult = mrOK then PlotOK := True ;
           end ;
        end ;


     if PlotOK then begin

          Screen.Cursor := crHourglass ;

          { Display a list of record numbers to be printed }

          ShowRecords := 'Records:' ;
          Separator := ' ' ;
          PreviousRec := -2 ;
          for Num := 0 to Display.EndOfList do begin
              if (Display.List[Num] > 0) and (Length(ShowRecords) < 80) then begin
                 Rec := Display.List[Num] ;
                 if Rec = PreviousRec + 1 then Separator := '-'
                 else begin
                     if Separator = '-' then ShowRecords := ShowRecords +
                                             Separator + IntToStr(PreviousRec) ;
                     if Separator <> ' ' then Separator := ', ' ;
                     ShowRecords := ShowRecords + Separator + IntToStr(Rec) ;
                     end ;
                 PreviousRec := Rec ;
                 end ;
              end ;
          { If this was the last record in the list - add its name }
          if (Separator = '-') then
             ShowRecords := ShowRecords + Separator + IntToStr(Rec) ;

          CharWidth := Printer.Canvas.TextWidth('X') ;
          LineHeight := (Printer.Canvas.TextHeight('X')*12) div 10 ;
          Tick := printer.canvas.textheight('X') div 2 ;

          case Destination of
               ToPrinter : begin
                    { Set page size etc. }
                    Printer.canvas.font.name := Settings.Plot.FontName ;
                    Printer.canvas.font.size := Settings.Plot.FontSize ;
                    Printer.Canvas.Pen.Width := PrinterPointsToPixels(
                                                Settings.Plot.LineThickness) ;
                    Page.Left := PrinterCmToPixels('H',Settings.Plot.LeftMargin) ;
                    Page.Right := printer.pagewidth -
                            PrinterCmToPixels('H',Settings.Plot.RightMargin) ;
                    Page.Top := PrinterCmToPixels('V',Settings.Plot.TopMargin) ;
                    Page.Bottom :=printer.pageheight -
                    PrinterCmToPixels('V',Settings.Plot.BottomMargin) ; ;

                    { Print file name, i.d. etc... }
                    Printer.BeginDoc ;
                    Printer.Canvas.Pen.Color := clBlack ;
                    { Print file name }
                    xPix := Printer.PageWidth div 10 ;
                    yPix := Printer.PageHeight div 60 ;
                    Printer.Canvas.TextOut(xPix,yPix, 'File ... ' + fH.FileName ) ;
                    { Print ident line }
                    yPix := yPix + LineHeight ;
                    Printer.Canvas.TextOut( xPix, yPix, fH.IdentLine ) ;
                    { Print list of records }
                    yPix := yPix + LineHeight ;
                    Printer.Canvas.TextOut( xPix, yPix, ShowRecords ) ;
                    end ;

                ToClipboard : begin
                    { Create and size bitmap object }
                    BMap := TBitmap.Create ;
                    BMap.Height := Settings.BitmapHeight ;
                    BMap.Width := Settings.BitmapWidth ;
                    BMap.canvas.font.name := Settings.Plot.FontName ;
                    BMap.canvas.font.size := Settings.Plot.FontSize ;
                    BMap.Canvas.Pen.Width := Settings.Plot.LineThickness ;
                    Page.Left := BMap.canvas.TextWidth('XXXXXXXXXX') ;
                    Page.Right := BMap.Width - Page.Left ;
                    Page.Top := BMap.canvas.TextHeight('X') ;
                    Page.Bottom :=BMap.Height - 5*Page.Top ;
                    end ;
                end ;

          { Make a copy of channel scaling information and find out
          how many channels are on display }
          Num := 0 ;
          for ch := 0 to fH.NumChannels-1 do begin
              { Copy Printer channel from Display channel record }
              PrChan[ch] := Channel[ch] ;
              { Use black if colour printing is not required }
              if not Settings.Plot.UseColor then PrChan[ch].Color := clBlack ;
              if PrChan[ch].InUse then Inc(Num) ;
              end ;
          ChanHeight := (Page.Bottom - Page.Top) div MaxInt( [Num,1] ) ;

          { Set up channel display areas on printed page }
          ChanTop := Page.Top ;
          for ch := 0 to fH.NumChannels-1 do begin
              PrChan[ch].Left := Page.Left ;
              PrChan[ch].Right := Page.Right ;
              PrChan[ch].Top := ChanTop ;
              PrChan[ch].Bottom := PrChan[ch].Top + ChanHeight ;
              PrChan[ch].xScale := (PrChan[ch].Right - PrChan[ch].Left) /
                                   (PrChan[ch].xMax  - PrChan[ch].xMin ) ;
              PrChan[ch].yScale := (PrChan[ch].Bottom - PrChan[ch].Top) /
                                   (PrChan[ch].yMax   - PrChan[ch].yMin ) ;
              if PrChan[ch].InUse then ChanTop := ChanTop + ChanHeight ;
              end ;

          { Display Channel Name(s) }
          if Settings.ShowLabels then begin
             for ch := 0 to fH.NumChannels-1 do if PrChan[ch].InUse then
                 if Destination = ToPrinter then
                       Printer.Canvas.TextOut( PrChan[ch].Left -
                                  printer.canvas.textwidth(prChan[ch].ADCName+' '),
                                  (PrChan[ch].Top + PrChan[ch].Bottom) div 2,
                                  PrChan[ch].ADCName )
                 else BMap.Canvas.TextOut( PrChan[ch].Left -
                                  printer.canvas.textwidth(prChan[ch].ADCName+' '),
                                  (PrChan[ch].Top + PrChan[ch].Bottom) div 2,
                                  PrChan[ch].ADCName ) ;
             end ;

          { Plot records in display list }
          FirstRec.Done := False ;
          for Num := 0 to Display.EndOfList do begin

              if Display.List[Num] > 0 then begin

                 { Read Record from file }
                 Rec := Display.List[Num] ;
                 GetRecord( fH, RecHeader, Rec, ADC^ ) ;

                 if not FirstRec.Done then begin
                    for ch := 0 to FH.NumChannels-1 do begin
                        FirstRec.ADCZero[ch] := Channel[ch].ADCZero ;
                        FirstRec.ADCVoltageRange[ch] := RecHeader.ADCVoltageRange[ch] ;
                        FirstRec.dt := RecHeader.dt ;
                        end ;
                    FirstRec.Done := True ;
                    end ;

                 { Plot each record channel in turn }
                 for ch := 0 to fH.NumChannels-1 do if PrChan[ch].InUse then begin

                     ChLast := ch ;
                     { Re-scale all records to the same X & Y scaling
                       and zero level position as the first record }
                     dx := RecHeader.dt / FirstRec.dt ;
                     YMult := RecHeader.ADCVoltageRange[ch] /
                              FirstRec.ADCVoltageRange[ch] ;
                     YShift := FirstRec.ADCZero[ch] - Channel[ch].ADCZero ;

                     x := 0. ;
                     nOffScreen := 0 ;
                     ChOffset := Channel[ch].ChannelOffset ;
                     for i := 0 to fH.NumSamples-1 do begin

                         xPix := Trunc(PrChan[ch].xScale*(x - PrChan[ch].xMin)
                                 + PrChan[ch].Left) ;
                         y := YMult*(ADC^[(i*fH.NumChannels)+ChOffset] + YShift );
                         yPix := Trunc(PrChan[ch].Bottom -
                                 PrChan[ch].yScale*(y - PrChan[ch].yMin));

                         if (xPix < PrChan[ch].Left) or
                            (PrChan[ch].Right < xPix ) or
                            (yPix < PrChan[ch].Top) or
                            (PrChan[ch].Bottom < yPix ) then begin
                            xPix := IntLimitTo( xPix,PrChan[ch].Left,
                                                PrChan[ch].Right ) ;
                            yPix := IntLimitTo( yPix,PrChan[ch].Top,
                                                PrChan[ch].Bottom ) ;
                            Inc(nOffScreen) ;
                            end
                         else nOffScreen := 0 ;

                         if (i = 0) or (nOffScreen >1) then begin
                            if Destination = ToPrinter then begin
                               Printer.canvas.pen.color := PrChan[ch].color ;
                               Printer.canvas.moveto(xPix,yPix) ;
                               end
                            else begin
                               BMap.canvas.pen.color := PrChan[ch].color ;
                               BMap.canvas.moveto(xPix,yPix) ;
                               end ;
                            end
                         else begin
                            if Destination = ToPrinter then Printer.canvas.lineto(xPix,yPix)
                                                       else BMap.canvas.lineto(xPix,yPix) ;
                            end ;
                         x := x + dx ;

                         end ;
                     end ;
                 end ;
              end ;

          { Print zero level cursor(s) }
          If Settings.ShowZeroLevels then begin
             for ch := 0 to fH.NumChannels-1 do if PrChan[ch].InUse then begin
                 if Destination = ToPrinter then begin
                    Printer.canvas.pen.color := PrChan[ch].color ;
                    PrintHorizontalCursor(Printer.Canvas,PrChan[ch],FirstRec.ADCZero[ch]) ;
                    end
                 else begin
                    BMap.canvas.pen.color := PrChan[ch].color ;
                    PrintHorizontalCursor(BMap.Canvas,PrChan[ch],FirstRec.ADCZero[ch]) ;
                    end ;
                 end ;
             end ;

          { Horizontal (time) calibration bar }
          if Destination = ToPrinter then  begin
                Printer.canvas.pen.color := clBlack ;
                HorizontalBar(Printer.canvas,PrChan[ChLast],Settings.ShowLabels,
                               Settings.TimeBarValue,RecHeader.dt,Settings.TScale,
                               Settings.TUnits) ;
                end
          else begin
                BMap.canvas.pen.color := clBlack ;
                HorizontalBar(BMap.canvas,PrChan[ChLast],Settings.ShowLabels,
                               Settings.TimeBarValue,RecHeader.dt,Settings.TScale,
                               Settings.TUnits) ;
                end ;

          { Vertical calibration bars }
          for ch := 0 to fH.NumChannels-1 do if PrChan[ch].InUse then begin
                if Destination = ToPrinter then
                   VerticalBar(Printer.canvas,PrChan[ch],Settings.ShowLabels,
                                  Settings.BarValue[ch],PrChan[ch].ADCUnits)
                else
                   VerticalBar(BMap.canvas,PrChan[ch],Settings.ShowLabels,
                                  Settings.BarValue[ch],PrChan[ch].ADCUnits) ;

                end ;

          { Close down printer or bitmap object }
          case Destination of
               ToPrinter : begin
                         Printer.EndDoc ;
                         end ;
               ToClipboard : begin
                         Clipboard.Assign(BMap) ;
                         BMap.Free ;
                         end ;
               end ;

          Screen.Cursor := crDefault ;
          end ;
     end;


procedure TReplayFrm.UpdateLowPassFilterList(dt : single) ;
{ -----------------------------------------------------------------------
  Update combo box containing list of low-pass filter cut-off frequencies
  -----------------------------------------------------------------------}
var
   cutoff,Diff,MinDiff : single ;
   i : Integer ;
begin
     { Add list of frequencies to box }
     { Note .. division by sampling rate to convert from samples^-1 units to Hz }
     cbLowPassFilter.Clear ;
     cbLowPassFilter.Items.Add( 'No Filter ') ;
     cbLowPassFilter.Items.Add( format( '%7.1f Hz', [0.1/dt] ) );
     cbLowPassFilter.Items.Add( format( '%7.1f Hz', [0.08/dt] ) );
     cbLowPassFilter.Items.Add( format( '%7.1f Hz', [0.05/dt] ) );
     cbLowPassFilter.Items.Add( format( '%7.1f Hz', [0.02/dt] ) );
     cbLowPassFilter.Items.Add( format( '%7.1f Hz', [0.01/dt] ) );
     cbLowPassFilter.Items.Add( format( '%7.1f Hz', [0.008/dt] ) );
     cbLowPassFilter.Items.Add( format( '%7.1f Hz', [0.005/dt] ) );

     { Find the entry which is closest to the current setting }
     if Settings.CutOffFrequency <= 0. then cbLowPassFilter.ItemIndex := 0
     else begin
          MinDiff := 1E30 ;
          for i := 1 to cbLowPassFilter.Items.Count-1 do begin
              Diff := Abs( ExtractFloat( cbLowPassFilter.text, 1. ) -
                           (Settings.CutOffFrequency/dt) );
              if Diff < MinDiff then begin
                 MinDiff := Diff ;
                 cbLowPassFilter.ItemIndex := i ;
                 end ;
              end ;
          end ;
     end ;


procedure TReplayFrm.cbLowPassFilterChange(Sender: TObject);
{ ----------------------------------------------------
  Request a new plot if the low pass filter is changed
  ----------------------------------------------------}
begin
     Display.Refresh := True ;
     State := PlotRecord ;
     { Update LP filter setting }
     if cbLowPassFilter.ItemIndex <= 0 then Settings.CutOffFrequency := 0.
     else Settings.CutOffFrequency := ExtractFloat( cbLowPassFilter.text,
                                      Settings.CutOffFrequency )*RecHeader.dt ;
     end;

procedure TReplayFrm.pbDisplayDblClick(Sender: TObject);
{ -------------------------------
  Activate Zoom In/Out dialog box
  -------------------------------}
var
   i : Integer ;
begin
     ZoomFrm.ChOnDisplay := MouseOverChannel ;
     Timer.Enabled := False ;
     ZoomFrm.ShowModal ;
     Timer.Enabled := True ;
     Display.Refresh := True ;
     State := PlotRecord ;
     MoveCursor := False ;
     {pbDisplay.Refresh ;}
     end;


procedure TReplayFrm.edIdentChange(Sender: TObject);
begin
     { Update ident line if it is changed }
     FH.IdentLine := edIdent.text ;
     RawFH.IdentLine := EdIdent.text ;
     SaveHeader(FH) ;
     SaveHeader(RawFH) ;
     end;

procedure TReplayFrm.edIdentKeyPress(Sender: TObject; var Key: Char);
begin
     if key = chr(13) then WriteToLogFile( edIdent.text ) ;
     end;

procedure TReplayFrm.EdRecordIdentChange(Sender: TObject);
begin
     RecHeader.Ident := EdRecordIdent.text ;
     PutRecordHeaderOnly( fH, RecHeader, fH.RecordNum ) ;
     end;

procedure TReplayFrm.edGroupKeyPress(Sender: TObject; var Key: Char);
{ ------------------------------------
  Save new record group number to file
  ------------------------------------}
begin
     if Key = chr(13) then begin
        RecHeader.Number := ExtractInt( Edgroup.text ) ;
        PutRecordHeaderOnly( fH, RecHeader, fH.RecordNum ) ;
        end ;
     end;

procedure TReplayFrm.FormHide(Sender: TObject);
begin
     HeapBuffers( Deallocate ) ;
     Timer.Enabled := False ;
     end;

procedure TReplayFrm.bSaveCursorClick(Sender: TObject);
{ ------------------------------------------------
  Save the current cursor reading to the log file
  -----------------------------------------------}
var
   s : string ;
   ch : Integer ;
begin
     s := format('%d',[FH.RecordNum] ) ;
     s := s + chr(9) + sgCursor.Cells[1,1] + chr(9) + sgCursor.Cells[2,1] ;
     for ch := 0 to FH.NumChannels do if Channel[ch].InUse then
         s := s + chr(9) + sgCursor.Cells[1,ch+2] + chr(9) + sgCursor.Cells[2,ch+2];
     WriteToLogFileNoDate( s ) ;
     end;


end.


