unit Leaksub;
{ ===================================================================
  WinWCP - Digital leak current subtraction module
  (c) J.Dempster 1996, University of Strathclyde, All Rights Reserved
  11/6/97 ... LEAK record are subtracted and included in SUB file
              when in FILE MODE
  13/6/97 ... Save Leak Records check box added
  ===================================================================}
interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, ExtCtrls, StdCtrls, global, fileio, shared, maths;

type
  TLeakSubFrm = class(TForm)
    pbDisplay: TPaintBox;
    lbTMax: TLabel;
    lbTMin: TLabel;
    RecordGrp: TGroupBox;
    Label2: TLabel;
    edRecordNum: TEdit;
    cbRecordType: TComboBox;
    ckBadRecord: TCheckBox;
    sbRecordNum: TScrollBar;
    AnalysisGrp: TGroupBox;
    bDoSubtraction: TButton;
    bCancel: TButton;
    bAbort: TButton;
    Timer: TTimer;
    cbVoltageChannel: TComboBox;
    Label1: TLabel;
    Label6: TLabel;
    cbCurrentChannel: TComboBox;
    edRange: TEdit;
    Label5: TLabel;
    lbVHold: TLabel;
    lbVTest: TLabel;
    ModeGrp: TGroupBox;
    rbGroupMode: TRadioButton;
    rbFileMode: TRadioButton;
    GroupBox1: TGroupBox;
    rbAutoScaling: TRadioButton;
    rbFixedScaling: TRadioButton;
    edIScale: TEdit;
    EdProgress: TEdit;
    EdGroup: TEdit;
    Label3: TLabel;
    ckSaveLeaks: TCheckBox;
    procedure TimerTimer(Sender: TObject);
    procedure sbRecordNumChange(Sender: TObject);
    procedure pbDisplayMouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure pbDisplayMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure pbDisplayMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure bDoSubtractionClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormShow(Sender: TObject);
    procedure bCancelClick(Sender: TObject);
    procedure bAbortClick(Sender: TObject);
    procedure FormKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure ckBadRecordClick(Sender: TObject);
    procedure cbRecordTypeChange(Sender: TObject);
    procedure EdGroupKeyPress(Sender: TObject; var Key: Char);
    procedure FormCreate(Sender: TObject);
    procedure pbDisplayPaint(Sender: TObject);
    procedure cbVoltageChannelChange(Sender: TObject);
    procedure cbCurrentChannelChange(Sender: TObject);
    procedure rbFileModeClick(Sender: TObject);
    procedure rbGroupModeClick(Sender: TObject);
  private
    { Private declarations }
    procedure DisplayRecord ;
    procedure CreateLeakSubtractedFile ;
    procedure HeapBuffers( Operation : THeapBufferOp ) ;
  public
    { Public declarations }
  end;

var
  LeakSubFrm: TLeakSubFrm;

implementation

uses mdiform ;

{$R *.DFM}
type
    TState = ( DoRecord, DoSubtractions, EndOfSubtractions, Idle) ;
    TCursorState = ( NoCursor, ZeroCursor, PulseCursor ) ;
    TSubtractionJob = record
                Running : Boolean ;
                StartAt : LongInt ;
                EndAt : LongInt ;
                VHoldCursor : Integer ;
                VTestCursor : Integer ;
                RecordNum : LongInt ;
                GroupNumber : single ;
                nLeak : LongInt ;
                nTest : LongInt ;
                LastTestRecord : LongInt ;
                end ;
var
   TimerBusy : boolean ;
   State : TState ;
   CursorState : TCursorState ;
   CursorChannel : TChannel ;
   MoveCursor : Boolean ;
   SubtractionJob : TSubtractionJob ;
   ITest : ^TSingleArray ;
   VTest : ^TSingleArray ;
   ILeak : ^TSingleArray ;
   VLeak : ^TSingleArray ;
   ADC : ^TIntArray ;
   RecHeader : TRecHeader ;
   VoltsCh : LongInt ;
   CurCh : LongInt ;
   BuffersAllocated : boolean ;{ Indicates if memory buffers have been allocated }


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


procedure TLeakSubFrm.FormCreate(Sender: TObject);
{ --------------------------------------
  Procedures when form is created
  -------------------------------}
begin
     { Disable "Leak Subtraction" item in "Analysis" menu }
     Main.LeakCurrentSubtraction.enabled := false ;
     { No dynamic memory buffers allocated yet }
     BuffersAllocated := False ;
     ckSaveLeaks.Checked := false ;
     ckSaveLeaks.Enabled := false ;
     end;


procedure TLeakSubFrm.TimerTimer(Sender: TObject);
{ ---------------------
  Timed event scheduler
  ---------------------}
begin
     { Execute any requested operations }
     if not TimerBusy then begin
        TimerBusy := True ;
        case State of
             DoRecord : begin
                 pbDisplay.canvas.FillRect( pbDisplay.canvas.ClipRect ) ;
                 DisplayRecord ;
                 State := Idle ;
                 end ;
             DoSubtractions : begin
                 Screen.Cursor := crHourGlass ;
                 CreateLeakSubtractedFile ;
                 end ;
             EndOfSubtractions : begin
                 SaveHeader( LeakfH ) ;
                 State := Idle ;
                 Screen.Cursor := crDefault ;
                 bAbort.enabled := False ;
                 bDoSubtraction.enabled := True ;
                 bCancel.enabled := True ;
                 bCancel.click ;
                 end ;
          end ;
        TimerBusy := False ;
        end ;
     end;

procedure TLeakSubFrm.sbRecordNumChange(Sender: TObject);
{ ----------------------------------------------------------
  Request a new record to be displayed when slider bar moved
  ----------------------------------------------------------}
begin
     State := DoRecord ;
     end;


procedure TLeakSubFrm.DisplayRecord ;
{ ===============================
  Display digitised signal record
  ===============================}
var
   i,j,MaxCh,ch,ChOffset,Rec : LongInt ;
   x,y,dx : single ;
   xPix,yPix,nOffScreen : Integer ;
begin
     if  RawfH.NumRecords > 0 then begin

          RawfH.RecordNum := SbRecordNum.position ;

          { Read record data from file }
          GetRecord( RawfH, RecHeader, RawfH.RecordNum, ADC^ ) ;

          { Initialise display scaling factors }
          InitializeDisplay( Channel, RawfH.NumChannels, RecHeader,
                             lbTMin,lbTMax, pbDisplay) ;
          lbTMin.Left := pbDisplay.Left ;
          lbTMin.Top := pbDisplay.Top + pbDisplay.Height + 1 ;
          lbTMax.Top := lbTMin.Top ;
          lbTMax.Left := pbDisplay.Left + pbDisplay.Width - lbTMax.Width ;

          { Special channel for vertical cursors which occupies
            the whole display area }
          CursorChannel := Channel[0] ;
          CursorChannel.Top := 0 ;
          CursorChannel.Bottom := pbDisplay.Height ;
          CursorChannel.color := clRed ;

          { Erase display }
          EraseDisplay(pbDisplay) ;

          for ch := 0 to RawfH.NumChannels-1 do begin
              if (ch = cbCurrentChannel.ItemIndex)
                 or (ch = cbVoltageChannel.ItemIndex) 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 RawfH.NumSamples-1 do begin

                     y := ADC^[(i*RawfH.NumChannels) + ChOffset ] ;
                     xPix := Trunc(x) ;
                     yPix := Trunc(Channel[ch].Bottom
                             - 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) ;
                           nOffScreen := nOffScreen + 1 ;
                        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 ;

                 DrawHorizontalCursor(pbDisplay,Channel[ch],Channel[ch].ADCZero) ;
                 { Display Channel Name }
                 pbDisplay.Canvas.TextOut( Channel[ch].Left,
                      (Channel[ch].Top + Channel[ch].Bottom) div 2,
                       ' ' + Channel[ch].ADCName ) ;

                 end ;
            end ;

          { Draw cursors }
          CursorChannel.color := clRed ;
          DrawCursor(pbDisplay,SubtractionJob.VHoldCursor,CursorChannel,lbVHold) ;
          DrawCursor(pbDisplay,SubtractionJob.VTestCursor,CursorChannel,lbVTest) ;

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

          { 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);

          edGroup.text := format('%d',[Trunc(RecHeader.Number)] ) ;

          ch := 0 ;

          end ;
     end ;


procedure TLeakSubFrm.bDoSubtractionClick(Sender: TObject);
{ ----------------------
  Start leak subtraction
  ----------------------}
var
   LoValue,HiValue : single ;
   OldHandle : Integer ;
begin
     bDoSubtraction.Enabled := False ;
     bCancel.Enabled := False ;
     bAbort.Enabled := True ;

     GetRangeFromEditBox(edRange,LoValue,HiValue,1., RawfH.NumRecords,'%.0f-%.0f','') ;
     SubtractionJob.StartAt := Trunc( LoValue ) ;
     SubtractionJob.EndAt := Trunc( HiValue ) ;
     SubtractionJob.RecordNum := SubtractionJob.StartAt ;
     SubtractionJob.nLeak := 0 ;
     SubtractionJob.nTest := 0 ;

     { Create leak subtraction file }

     { Copy details from original file header (except file handle!!!!) }
     OldHandle := LeakFH.FileHandle ;
     LeakfH := RawfH ;
     LeakFH.FileHandle := OldHandle ;

     { The averages data file name has the same name as the original file
       but with the extension .sub }
     LeakfH.FileName := ReplaceFileEnding( RawfH.FileName, '.sub' ) ;
     { Create file to hold averages }
     if LeakFH.FileHandle >= 0 then FileClose(  LeakFH.FileHandle ) ;
     LeakfH.FileHandle := FileCreate( LeakfH.FileName ) ;
     { Save header block and request subtractions to start}
     if LeakfH.FileHandle >= 0 then begin
           LeakfH.NumRecords := 0 ;
           SaveHeader( LeakfH ) ;
           State := DoSubtractions ;
           end
     else MessageDlg( 'FileCreate Error ='+ IntToStr(LeakfH.FileHandle),
             mtWarning, [mbOK], 0 ) ;

     end;


procedure TLeakSubFrm.CreateLeakSubtractedFile ;
{ ======================================================
  Create a file containing leak subtracted signal records
  ======================================================}
const
     VLimit = 0.0001 ;
     nAvg = 20 ;
var
   i,j,ch,ChOffset,Rec : LongInt ;
   IOffset,VOffset,IZero,VZero : Integer ;
   IScale,VScale : single ;
   iStart,iEnd : LongInt ;
   iY,i0,i1 : LongInt ;
   VHoldLeak,VPulseLeak,VHoldTest,VPulseTest,VLeakStep,VPulseStep : single ;
   LeakScale : single ; {Leak current scaling up factor }
   OK, SaveRecord : boolean ;
begin

     { Current and voltage channel offsets into ADC buffer }
     IOffset := Channel[cbCurrentChannel.ItemIndex].ChannelOffset;
     VOffset := Channel[cbVoltageChannel.ItemIndex].ChannelOffset;

     { If in FILE MODE ... compute an averaged leak record
       from all the LEAK type records in the file }

     if rbFileMode.checked then begin

        SubtractionJob.nLeak := 0 ;
        for Rec := SubtractionJob.StartAt to SubtractionJob.EndAt do begin

            { Read record data from file }
            GetRecord( RawfH, RecHeader, Rec, ADC^ ) ;

            { If record is LEAK type and ACCEPTED ... add it to average }
            if (RecHeader.Status = 'ACCEPTED') and
               (RecHeader.RecType = 'LEAK' ) then begin

               { Current and voltage scaling factors }
               IScale := Channel[cbCurrentChannel.ItemIndex].ADCScale ;
               VScale := Channel[cbVoltageChannel.ItemIndex].ADCScale ;
               { Use levels under VHold cursor as zero references }
               IZero := ADC^[SubtractionJob.VHoldCursor*RawFH.NumChannels + IOffset] ;
               VZero := ADC^[SubtractionJob.VHoldCursor*RawFH.NumChannels + VOffset] ;
               { Add current and voltage channels to summation buffers }
                j := 0 ;
                for i := 0 to RawFH.NumSamples-1 do begin
                    if SubtractionJob.nLeak = 0 then begin
                       ILeak^[i] := 0. ;
                       VLeak^[i] := 0. ;
                       end ;
                    ILeak^[i] := ILeak^[i] + IScale*(ADC^[j+IOffset] - IZero) ;
                    VLeak^[i] := VLeak^[i] + VScale*(ADC^[j+VOffset] - VZero) ;
                    j := j + RawFH.NumChannels ;
                    end ;
                Inc( SubtractionJob.nLeak ) ;
                end ;
            end ;

        { Calculate averaged leak current and voltage }
        if SubtractionJob.nLeak > 0 then begin
              for i := 0 to RawFH.NumSamples-1 do begin
                  VLeak^[i] := VLeak^[i] / SubtractionJob.nLeak ;
                  ILeak^[i] := ILeak^[i] / SubtractionJob.nLeak ;
                  end ;
              end ;
        end ;

     { Read next record from file }
     GetRecord( RawfH, RecHeader, SubtractionJob.RecordNum, ADC^ ) ;
     SubtractionJob.GroupNumber := RecHeader.Number ;

     if RecHeader.Status = 'ACCEPTED' then begin

        if ckSaveLeaks.checked or (RecHeader.RecType <> 'LEAK') then begin
           { *** TEST records *** }
           SubtractionJob.LastTestRecord := SubtractionJob.RecordNum ;
           { Current and voltage scaling factors }
           IScale := Channel[cbCurrentChannel.ItemIndex].ADCScale ;
           VScale := Channel[cbVoltageChannel.ItemIndex].ADCScale ;
           { Use levels under VHold cursor as zero references }
           IZero := ADC^[SubtractionJob.VHoldCursor*RawFH.NumChannels + IOffset] ;
           VZero := ADC^[SubtractionJob.VHoldCursor*RawFH.NumChannels + VOffset] ;
           { Add current and voltage channels to summation buffers }
           j := 0 ;
           for i := 0 to RawFH.NumSamples-1 do begin
               if SubtractionJob.nTest = 0 then begin
                  ITest^[i] := 0. ;
                  VTest^[i] := 0. ;
                  end ;
               ITest^[i] := ITest^[i] + IScale*(ADC^[j+IOffset] - IZero) ;
               VTest^[i] := VTest^[i] + VScale*(ADC^[j+VOffset] - VZero) ;
               j := j + RawFH.NumChannels ;
               end ;

           Inc( SubtractionJob.nTest ) ;
           end
        else if rbGroupMode.checked then begin
           { *** LEAK records *** }
           { Current and voltage scaling factors }
           IScale := Channel[cbCurrentChannel.ItemIndex].ADCScale ;
           VScale := Channel[cbVoltageChannel.ItemIndex].ADCScale ;
           { Use levels under VHold cursor as zero references }
           IZero := ADC^[SubtractionJob.VHoldCursor*RawFH.NumChannels + IOffset] ;
           VZero := ADC^[SubtractionJob.VHoldCursor*RawFH.NumChannels + VOffset] ;
           j := 0 ;
           for i := 0 to RawFH.NumSamples-1 do begin
               if SubtractionJob.nLeak = 0 then begin
                  ILeak^[i] := 0. ;
                  VLeak^[i] := 0 ;
                  end ;
               ILeak^[i] := ILeak^[i] + IScale*(ADC^[j+IOffset] - IZero) ;
               VLeak^[i] := VLeak^[i] + VScale*(ADC^[j+VOffset] - VZero) ;
               j := j + RawFH.NumChannels ;
               end ;
           Inc( SubtractionJob.nLeak ) ;
           end ;
        end ;

     { Is this record the last in its group or the last in the file }
     if SubtractionJob.RecordNum >= SubtractionJob.EndAt then SaveRecord := True
     else begin
          GetRecordHeaderOnly( RawfH, RecHeader, SubtractionJob.RecordNum+1 ) ;
          if RecHeader.Number <> SubtractionJob.GroupNumber then SaveRecord := True
                                                            else SaveRecord := False ;
          end ;

     if SaveRecord then begin

        { If in Group-mode leak subtraction ... average leak record }

        if (SubtractionJob.nTest > 0) and (SubtractionJob.nLeak > 0) then OK := True
                                                                     Else OK := False ;
        if rbGroupMode.checked then begin
           if SubtractionJob.nLeak > 0 then begin
                for i := 0 to RawFH.NumSamples-1 do begin
                    VLeak^[i] := VLeak^[i] / SubtractionJob.nLeak ;
                    ILeak^[i] := ILeak^[i] / SubtractionJob.nLeak ;
                    end ;
                SubtractionJob.nLeak := 0 ;
                end ;
           end ;

        { Average test records }
        if SubtractionJob.nTest > 0 then begin
             for i := 0 to RawFH.NumSamples-1 do begin
                 VTest^[i] := VTest^[i] / SubtractionJob.nTest ;
                 ITest^[i] := ITest^[i] / SubtractionJob.nTest ;
                 end ;
             SubtractionJob.nTest := 0 ;
             end ;

        { Calculate factor by which leak current is to be scaled up by
          before being subtracted from test current }
        if OK and rbAutoScaling.checked then begin
             { Calculate holding voltage level for leak and test records }
             i0 := SubtractionJob.VHoldCursor ;
             i1 := MinInt([SubtractionJob.VHoldCursor + nAvg - 1,
                           RawFH.NumSamples-1])  ;
             VHoldLeak := 0. ;
             VHoldTest := 0. ;
             for i := i0 to i1 do begin
                 VHoldLeak := VHoldLeak + VLeak^[i] ;
                 VHoldTest := VHoldTest + VTest^[i] ;
                 end ;
             VHoldLeak := VHoldLeak / (i1 - i0 + 1 ) ;
             VHoldTest := VHoldTest / (i1 - i0 + 1 ) ;

             { Calculate pulse voltage level for leak and test records }
             i0 := SubtractionJob.VTestCursor ;
             i1 := MinInt([SubtractionJob.VTestCursor + nAvg - 1,
                           RawFH.NumSamples-1])  ;
             VPulseLeak := 0. ;
             VPulseTest := 0. ;
             for i := i0 to i1 do begin
                 VPulseLeak := VPulseLeak + VLeak^[i] ;
                 VPulseTest := VPulseTest + VTest^[i] ;
                 end ;
             VPulseLeak := VPulseLeak / (i1 - i0 + 1 ) ;
             VPulseTest := VPulseTest / (i1 - i0 + 1 ) ;

             { Voltage steps }
             VPulseStep := VPulseTest - VHoldTest ;
             VLeakStep := VPulseLeak - VHoldLeak ;

             { If either current or voltage steps are too small ... give up }
             if (Abs(VPulseStep) > VLimit) and (Abs(VLeakStep) > VLimit) then begin
                OK := True ;
                LeakScale := VPulseStep / VLeakStep ;
                end
             else OK := False ;
             end ;

        if OK then begin

             { If fixed current scaling ... get scaling factor from user }
             if rbFixedScaling.checked then
                LeakScale := ExtractFloat( edIScale.text, 1. ) ;

             { Subtract scaled leak current from test current }
             for i := 0 to RawFH.NumSamples-1 do
                 ITest^[i] := ITest^[i] - LeakScale*ILeak^[i] ;

             {Get data from last TEST record }
             GetRecord( RawfH, RecHeader, SubtractionJob.LastTestRecord, ADC^ ) ;

             { Replace current channel }
             IScale := Channel[cbCurrentChannel.ItemIndex].ADCScale ;
             { Use current level under VHold cursor as zero reference }
             IZero := ADC^[SubtractionJob.VHoldCursor*RawFH.NumChannels + IOffset] ;
             j := Channel[cbCurrentChannel.ItemIndex].ChannelOffset ;
             for i := 0 to RawFH.NumSamples-1 do begin
                 ADC^[j] := Trunc( ITest^[i]/IScale ) + IZero ;
                 j := j + RawFH.NumChannels ;
                 end ;

             { Save record to leak subtracted record file  }
             Inc(LeakfH.NumRecords) ;
             PutRecord( LeakfH, RecHeader, LeakfH.NumRecords, ADC^ ) ;
             end ;
        end ;

     edProgress.text := format( '%d/%d (Group=%d)', [SubtractionJob.RecordNum,
                                                     SubtractionJob.EndAt,
                                                     Trunc(RecHeader.Number)]) ;

     Inc(SubtractionJob.RecordNum ) ;
     { Terminate the job if that was the last record }

     if SubtractionJob.RecordNum > SubtractionJob.EndAt then begin
        SubtractionJob.RecordNum := SubtractionJob.EndAt ;
        { Request end-of-subtractions processes }
        if LeakFH.NumRecords <= 0 then MessageDlg(
           ' No Leak records created. ',mtWarning, [mbOK], 0 ) ;
        State := EndofSubtractions ;
        end ;
     end ;


procedure TLeakSubFrm.FormClose(Sender: TObject; var Action: TCloseAction);
{ -------------------
  Close form and exit
  -------------------}
begin
     { Free buffers }
     HeapBuffers( Deallocate ) ;

     if (LeakFH.NumRecords > 0) and (LeakFH.FileName <> '') then begin
        Main.ShowLeakSubtracted.visible := True ;
        Main.ShowLeakSubtracted.enabled := True ;
        Main.ShowLeakSubtracted.Click ;
        end ;

     { Update current/voltage channel settings }
     Settings.SealTest.CurrentChannel := cbCurrentChannel.ItemIndex ;
     Settings.SealTest.VoltageChannel := cbVoltageChannel.ItemIndex ;

     { Enable "Leak Subtraction" item in "Analysis" menu }
     Main.LeakCurrentSubtraction.enabled := true ;

     Action := caFree ;
     end;


procedure TLeakSubFrm.FormShow(Sender: TObject);
begin
{ --------------------------------------------
  Initialisation procedures when form is shown
  --------------------------------------------}
begin

     { Create buffers }
     HeapBuffers( Allocate ) ;

     { Set up current and voltage channels }
     cbCurrentChannel.items := ChannelNames ;
     cbCurrentChannel.ItemIndex := Settings.SealTest.CurrentChannel ;
     cbVoltageChannel.items := ChannelNames ;
     cbVoltageChannel.ItemIndex := Settings.SealTest.VoltageChannel ;

     sbRecordNum.Max := RawfH.NumRecords ;
     sbRecordNum.Min := 1 ;
     sbRecordNum.Enabled := True ;
     sbRecordNum.Position := 1 ;
     edRange.text := format( ' %d-%d ', [1,RawFH.NumRecords] ) ;
     cbRecordType.Items := RecordTypes ;

     SubtractionJob.VHoldCursor := 1 ;
     SubtractionJob.VTestCursor := RawFH.NumSamples div 2 ;

     { If there is only one channel in the file ... disable
       automatic voltage scaling }
     if RawFH.NumChannels < 2 then begin
        rbFixedScaling.checked := True ;
        rbAutoScaling.enabled := False ;
        end
     else begin
        rbAutoScaling.checked := True ;
        rbAutoScaling.enabled := True ;
        end ;

     edProgress.text := '' ;

     { Set button states }
     bAbort.enabled := False ;
     bDoSubtraction.enabled := True ;
     bCancel.enabled := True ;

     { Turn on event scheduling timer }
     TimerBusy := False ;
     Timer.enabled := True ;
     State := DoRecord ;
     end;
end;


procedure TLeakSubFrm.bCancelClick(Sender: TObject);
begin
     Close ;
     end;

procedure TLeakSubFrm.bAbortClick(Sender: TObject);
begin
     bDoSubtraction.Enabled := True ;
     bCancel.Enabled := True ;
     bAbort.Enabled := False ;
     end;


{ *** CURSOR CONTROL PROCEDURES *** }

procedure TLeakSubFrm.pbDisplayMouseMove(Sender: TObject;
  Shift: TShiftState; X, Y: Integer);
{ -------------------------------------
  Display measurement cursors handling
  ------------------------------------}
var
   OldIndex : Integer ;
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 ;

     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 ;

          { Is mouse over holding voltage cursor ? }
          if OverVerticalCursor(X,SubtractionJob.VHoldCursor,CursorChannel ) then begin
             CursorState := ZeroCursor  ;
             pbDisplay.Cursor := crSizeWE ;
             end ;

          { Is mouse over test voltage cursor ? }
          if OverVerticalCursor(X,SubtractionJob.VTestCursor,CursorChannel ) then begin
             CursorState := PulseCursor ;
             pbDisplay.Cursor := crSizeWE ;
             end ;

          end
      else begin

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

          case CursorState of
            { Move cursor 0 }
            ZeroCursor : begin
                VerticalCursorScale( X,SubtractionJob.VHoldCursor,OldIndex,CursorChannel);
                MoveVerticalCursor(pbDisplay,SubtractionJob.VHoldCursor,OldIndex,
                                   CursorChannel,lbVHold) ;
                end ;
            { Move cursor 1 }
            PulseCursor : begin
                VerticalCursorScale( X,SubtractionJob.VTestCursor,OldIndex,CursorChannel);
                MoveVerticalCursor(pbDisplay,SubtractionJob.VTestCursor,OldIndex,
                                   CursorChannel,lbVTest) ;
                end ;
            end ;
          end ;
      end ;


procedure TLeakSubFrm.pbDisplayMouseUp(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
     MoveCursor := False ;
     end;


procedure TLeakSubFrm.pbDisplayMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
     MoveCursor := True ;
     end;


procedure TLeakSubFrm.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
{ ------------------------
  Function key processing
  -----------------------}
type
    TAction = (MoveCursor,ChangeRecord,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_PRIOR : begin
             Action := ChangeRecord ;
             Step := -1 ;
             end ;
          VK_NEXT : begin
             Action := ChangeRecord ;
             Step := 1 ;
             end ;
          else Action := None ;
          end ;

        case Action of
             { Move vertical display cursor }
             MoveCursor : begin
                 Case CursorState of
                      ZeroCursor : begin
                           OldPosition := SubtractionJob.VHoldCursor ;
                           SubtractionJob.VHoldCursor := OldPosition + Step ;
                           MoveVerticalCursor( pbDisplay,SubtractionJob.VHoldCursor,
                                               OldPosition,CursorChannel,lbVHold ) ;
                           end ;
                      PulseCursor : begin
                           OldPosition := SubtractionJob.VTestCursor  ;
                           SubtractionJob.VTestCursor := OldPosition + Step ;
                           MoveVerticalCursor( pbDisplay,SubtractionJob.VTestCursor,
                                               OldPosition,CursorChannel,lbVTest ) ;
                           end ;
                      end ;
                 end ;
             { Change record currently displayed }
             ChangeRecord : begin
                 NewRecord := LongIntLimitTo(FH.RecordNum + Step,1,FH.NumRecords) ;
                 sbRecordNum.Position := NewRecord ;
                 State := DoRecord ;
                 end ;
             end ;
     end ;

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

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

procedure TLeakSubFrm.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( RawfH, RecHeader, RawfH.RecordNum ) ;
        end ;
     end;



procedure TLeakSubFrm.pbDisplayPaint(Sender: TObject);
begin
     if State = Idle then State := DoRecord ;
     end;

procedure TLeakSubFrm.cbVoltageChannelChange(Sender: TObject);
begin
     if State = Idle then State := DoRecord ;
     end;

procedure TLeakSubFrm.cbCurrentChannelChange(Sender: TObject);
begin
     if State = Idle then State := DoRecord ;
     end;

procedure TLeakSubFrm.rbFileModeClick(Sender: TObject);
begin
     ckSaveLeaks.Enabled := True ;
     end;

procedure TLeakSubFrm.rbGroupModeClick(Sender: TObject);
begin
     ckSaveLeaks.Enabled := false ;
     ckSaveLeaks.Checked := false ;
     end;

end.
