unit Recedit;
{ ==========================================================
  WinWCP - Record editing module (c) J. Dempster 1997
  V2.1a 12/4/99 A/D voltage range can now be amended by user
  ========================================================== }
interface

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

type
  TEditFrm = class(TForm)
    RecordGrp: TGroupBox;
    Label2: TLabel;
    Label1: TLabel;
    Group: TLabel;
    edRecordNum: TEdit;
    cbRecordType: TComboBox;
    ckBadRecord: TCheckBox;
    sbRecordNum: TScrollBar;
    edTime: TEdit;
    edGroup: TEdit;
    pbDisplay: TPaintBox;
    lbTMin: TLabel;
    lbTMax: TLabel;
    lbCursor0: TLabel;
    Timer: TTimer;
    GroupBox1: TGroupBox;
    cbChannel: TComboBox;
    bClose: TButton;
    bUndo: TButton;
    lbCursor1: TLabel;
    HorGrp: TGroupBox;
    EdXShift: TEdit;
    bLeft: TButton;
    Button1: TButton;
    VertGrp: TGroupBox;
    EdYShift: TEdit;
    bUp: TButton;
    bDown: TButton;
    YScaleGrp: TGroupBox;
    EdYScale: TEdit;
    bScale: TButton;
    TableGrp: TGroupBox;
    Table: TStringGrid;
    CursorGrp: TGroupBox;
    mmCursor: TMemo;
    edADCVoltageRange: TEdit;
    Label3: TLabel;
    procedure FormShow(Sender: TObject);
    procedure TimerTimer(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure sbRecordNumChange(Sender: TObject);
    procedure bCloseClick(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 bLeftClick(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure EdXShiftKeyPress(Sender: TObject; var Key: Char);
    procedure EdYShiftKeyPress(Sender: TObject; var Key: Char);
    procedure EdYScaleKeyPress(Sender: TObject; var Key: Char);
    procedure bScaleClick(Sender: TObject);
    procedure bUpClick(Sender: TObject);
    procedure bDownClick(Sender: TObject);
    procedure TableKeyPress(Sender: TObject; var Key: Char);
    procedure bUndoClick(Sender: TObject);
    procedure FormKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure FormCreate(Sender: TObject);
    procedure pbDisplayPaint(Sender: TObject);
    procedure edADCVoltageRangeKeyPress(Sender: TObject; var Key: Char);

  private
    { Private declarations }
    procedure DisplayRecord ;
    procedure NewChannelSelected ;
    procedure ShiftHorizontal( ShiftBy : Integer ) ;
    procedure ShiftVertical( ShiftBy : Integer ) ;
    procedure ScaleVertical( ScaleBy : single ) ;
    procedure UpdateXShift ;
    procedure UpdateYShift ;
    procedure UpdateYScale ;
    procedure HeapBuffers( Operation : THeapBufferOp ) ;
  public
    { Public declarations }
  end;

var
  EditFrm: TEditFrm;

implementation
uses          MDIForm,Zoom ;

type
    TCursorState = ( Cursor0,Cursor1,ZeroLevelCursor,NoCursor ) ;
    TEditState = (DoRecord,CursorReadout,Idle) ;

var
   TimerBusy : boolean ;
   EditChannel : TChannel ;
   ChSelected : Integer ;
   MoveCursor : Boolean ;
   CursorState : TCursorState ;
   State : TEditState ;
   SaveRecord : Boolean ;
   rH : ^TRecHeader ;      { Signal record header block }
   ADC : ^TIntArray ;
   Buf : ^TIntArray ;
   MouseOverChannel : Integer ;
   XShift : Integer ;
   YShift : Integer ;
   YScale : single ;
   BackUpFH : TFileHeader ;
   BuffersAllocated : boolean ;{ Indicates if memory buffers have been allocated }
{$R *.DFM}


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


procedure TEditFrm.DisplayRecord ;
{ ===============================
  Display digitised signal record
  ===============================}
var
   i,j,ch,ChOffset,Rec : LongInt ;
   x,y,dx : single ;
   xPix,yPix,nOffScreen : Integer ;
begin
     if  fH.NumRecords > 0 then begin
          sbRecordNum.Max := fH.NumRecords ;
          sbRecordNum.Min := 1 ;
          sbRecordNum.Enabled := True ;
          fH.RecordNum := SbRecordNum.position ;
          fH.CurrentRecord := SbRecordNum.position ;

          { Read record data from file and extract channel to be edited }
          GetRecord( fH, rH^, fH.RecordNum, ADC^ ) ;
          j := Channel[ChSelected].ChannelOffset ;
          for i := 0 to fH.NumSamples-1 do begin
              Buf^[i] := ADC^[j] ;
              j := j + fH.NumChannels ;
              end ;

          EditChannel.ADCZero := Channel[ChSelected].ADCZero ;
          InitializeDisplay( EditChannel, 1, rH^,
                             lbTMin,lbTMax, pbDisplay) ;

           { Set position of time Min/Max. labels }
          lbTMin.Left := pbDisplay.Left ;
          lbTMin.Top := pbDisplay.Top + pbDisplay.Height + 2 ;
          lbTMax.Left := pbDisplay.Left + pbDisplay.Width
                         - canvas.TextWidth(lbTMax.caption) ;
          lbTMax.Top := lbTMin.Top ;

          { Erase display }
          EraseDisplay(pbDisplay) ;
          { Plot record }
          x := 0. ;
          dx := 1. ;
          nOffScreen := 0 ;
          for i := 0 to fH.NumSamples-1 do begin

              y := Buf^[i] ;
              xPix := Trunc(EditChannel.xScale*(x - EditChannel.xMin)
                             + EditChannel.Left) ;
              yPix := Trunc(EditChannel.Bottom
                             - EditChannel.yScale*(y - EditChannel.yMin));

              if (xPix < EditChannel.Left)
                 or (EditChannel.Right < xPix )
                 or (yPix < EditChannel.Top)
                 or (EditChannel.Bottom < yPix ) then begin
                 xPix := IntLimitTo( xPix, EditChannel.Left, EditChannel.Right ) ;
                 yPix := IntLimitTo( yPix, EditChannel.Top, EditChannel.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 ;

          { Display Channel Name }
          pbDisplay.Canvas.TextOut( EditChannel.Left,
                      (EditChannel.Top + EditChannel.Bottom) div 2,
                       ' ' + EditChannel.ADCName ) ;

          { Draw cursors }

          DrawHorizontalCursor(pbDisplay,EditChannel,EditChannel.ADCZero) ;
          EditChannel.color := clBlue ;
          DrawCursor( pbDisplay, EditChannel.Cursor0,EditChannel, lbCursor0 ) ;
          DrawCursor( pbDisplay, EditChannel.Cursor1,EditChannel, lbCursor1 ) ;
          edRecordNum.text := format( 'Rec. %d/%d ',[sbRecordNum.position,
                                                     fH.NumRecords] ) ;

          { Show whether record has been rejected by operator }
          if rH^.Status = 'ACCEPTED' then ckBadRecord.checked := False
                                    else ckBadRecord.checked := True ;
          { Show type of record }
          if cbRecordType.items.indexOf(rH^.RecType) >= 0 then
             cbRecordType.ItemIndex := cbRecordType.items.indexOf(rH^.RecType);
          edTime.text := format( '%f s', [rH^.Time] ) ;
          edGroup.text := format( '%d', [ Trunc(rH^.Number) ] ) ;
          edADCVoltageRange.text := format( '%.3g V',[rh^.ADCVoltageRange[ChSelected]]) ;

          { Set signal shift/scaling steps }
          edXShift.text := format('%6.3g ms',[XShift*rH^.dt*SecsToMs]);
          edYShift.text := format('%6.3g %s',[YShift*EditChannel.ADCScale,
                                             EditChannel.ADCUnits] );
          edYScale.text := format('%.2f',[YScale] );

          { Set data editing table options }
          Table.options := [goFixedVertLine,goFixedHorzLine,
                            goVertLine,goHorzLine,goEditing] ;
          Table.RowCount := FH.NumSamples+1 ;
          Table.ColCount := 2 ;
          Table.DefaultRowHeight := Table.Canvas.TextHeight('x') ;
          Table.DefaultColWidth := Table.Canvas.TextWidth(' 99999.999 ') ;
          Table.Cells[0,0] := 'Time ms' ;
          Table.Cells[1,0] := EditChannel.ADCName + ' ' + EditChannel.ADCUnits ;
          x := 0 ;
          for i := 0 to FH.NumSamples-1 do begin
              Table.Cells[0,i+1] := format(' %.3f', [x] ) ;
              Table.Cells[1,i+1] := format(' %.2f',[Buf^[i]*EditChannel.ADCScale]);
              x := x + rH^.dt*SecsToMs ;
              end ;
          end ;
     end ;



procedure TEditFrm.FormShow(Sender: TObject);
{ ---------------------------------------
  Initialisations done when form is shown
  ---------------------------------------}
var
   i : LongInt ;
begin
     { Create working buffers }
     HeapBuffers( Allocate ) ;


     cbRecordType.items := RecordTypes ;
     cbRecordType.items.delete(0) ; {Remove 'ALL' item}

     cbChannel.items := ChannelNames ;
     cbChannel.ItemIndex := 0 ;
     NewChannelSelected ;

     XShift := 1 ;
     YShift := 1 ;
     YScale := 1. ;
     EditChannel.Cursor0 := 0 ;
     EditChannel.Cursor1 := FH.NumSamples div 2 ;

     BackUpFH := FH ;
     BackUpFH.FileName := Settings.ProgDirectory + 'editrec.bak' ;
     BackUpFH.FileHandle := FileCreate( BackUpFH.FileName ) ;
     SaveHeader( BackUpFH ) ;
     for i := 1 to FH.NumRecords do begin
         GetRecord( fH, rH^, i, Buf^ ) ;
         PutRecord( BackUpfH, rH^, i, Buf^ ) ;
         end ;


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

     { Request a record to be displayed }
     State := DoRecord ;

     end;



procedure TEditFrm.TimerTimer(Sender: TObject);
{ ------------------------------
  Timer-based process scheduler
  -----------------------------}

begin

     if not TimerBusy then begin

        TimerBusy := True ;


        { Execute any requested operations }
        case State of
          DoRecord : begin

                if SaveRecord then begin
                   rh^.ADCVoltageRange[ChSelected] := ExtractFLoat(
                                                      edADCVoltageRange.Text,
                                                      rh^.ADCVoltageRange[ChSelected] ) ;
                   PutRecord( fH, rH^, fH.RecordNum, Buf^ ) ;
                   SaveRecord := False ;
                   end ;

                State := CursorReadout ;
                DisplayRecord ;
                MoveCursor := False ;
                end ;
          CursorReadout : begin
                mmCursor.lines[0] := Format( 'T= %.2f ms',[(EditChannel.Cursor1
                                    - EditChannel.Cursor0)*rH^.dt*SecsToMs] ) ;
                mmCursor.lines[1]:= Format( 'Y= %.2f, %s',
                                    [(Buf^[EditChannel.Cursor1]-EditChannel.ADCZero)
                                      *EditChannel.ADCScale,EditChannel.ADCUnits ] ) ;
                State := Idle ;
                end ;
          Idle : begin
               if ChSelected <> cbChannel.ItemIndex then NewChannelSelected ;
               end ;
          end ;
       TimerBusy := False ;
       end ;
     end ;


procedure TEditFrm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
     HeapBuffers( Deallocate ) ;
     { Ensable "Edit Records" item of "Analysis" menu }
     Main.EditRecord.Enabled := true ;
     FileClose( BackUpFH.FileHandle ) ;
     Action := caFree ;
     end;


procedure TEditFrm.NewChannelSelected ;
var
   Temp1,Temp2 : Integer ;
begin
     ChSelected := cbChannel.ItemIndex ;
     Temp1 := EditChannel.Cursor0 ;
     Temp2 := EditChannel.Cursor1 ;
     EditChannel := Channel[ChSelected] ;
     EditChannel.InUse := True ;
     EditChannel.Cursor0 := Temp1 ;
     EditChannel.Cursor1 := Temp2 ;
     State := DoRecord ;
     end ;

procedure TEditFrm.sbRecordNumChange(Sender: TObject);
begin
     State := DoRecord ;
     end;

procedure TEditFrm.bCloseClick(Sender: TObject);
begin
     Close ;
     end;

procedure TEditFrm.pbDisplayMouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
{ -----------------------------------------------------------------
  Select and move zero level and analysis area cursors over display
  -----------------------------------------------------------------}
var
   X_Zero,ch,OldLevel : LongInt ;
   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 cursor 0 ? }
          if OverVerticalCursor(X,EditChannel.Cursor0,EditChannel ) then begin
             CursorState := Cursor0  ;
             pbDisplay.Cursor := crSizeWE ;
             end ;

          { Is mouse over cursor 1 ? }
          if OverVerticalCursor(X,EditChannel.Cursor1,EditChannel ) then begin
             CursorState := Cursor1  ;
             pbDisplay.Cursor := crSizeWE ;
             end ;

          { Is mouse over zero level ? }
          if OverHorizontalCursor( Y, EditChannel ) then begin
                CursorState := ZeroLevelCursor ;
                pbDisplay.Cursor := crSizeNS ;
                end ;

          end
      else begin

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

          case CursorState of
             { Move cursor 0 }
             Cursor0 : begin
               VerticalCursorScale( X,EditChannel.Cursor0,OldIndex,EditChannel);
               MoveVerticalCursor(pbDisplay,EditChannel.Cursor0,OldIndex,
                                  EditChannel,lbCursor0) ;
               end ;
             { Move cursor 1 }
             Cursor1 : begin
               VerticalCursorScale( X,EditChannel.Cursor1,OldIndex,EditChannel);
               MoveVerticalCursor(pbDisplay,EditChannel.Cursor1,OldIndex,
                                  EditChannel,lbCursor1) ;
               end ;
             { Signal zero reference level }
             ZeroLevelCursor : Begin
               HorizontalCursorScale( Y,EditChannel.ADCZero,OldLevel,EditChannel);
               MoveHorizontalCursor(pbDisplay,EditChannel.ADCZero,OldLevel,EditChannel) ;
               end ;
             else ;
             end ;
          State := CursorReadout ;
          end ;
      end ;


procedure TEditFrm.pbDisplayMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
{ -----------------------------------------------------------
  Activated zero level setup form if right button was pressed
  -----------------------------------------------------------}
begin
     MoveCursor := False ;
     if (Button = mbRight) and (CursorState = ZeroLevelCursor) then begin
        ZeroFrm.ChSel := ChSelected ;
        ZeroFrm.NewZeroAt := Trunc( (X - EditChannel.Left) / EditChannel.xScale
                                        + EditChannel.xMin ) ;
        ZeroFrm.ShowModal ;
        end ;
     end;

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

procedure TEditFrm.ShiftHorizontal( ShiftBy : Integer ) ;
{ ---------------------------------------------
  Shift digitised signal data by SHIFTBY samples
  ----------------------------------------------}
var
   ch,i,j,k,ChOffset,EndOfData : Integer ;
begin
     { Read record data from file }
     GetRecord( fH, rH^, fH.RecordNum, ADC^ ) ;

     { Shift all channels in signal record }
     EndOfData := (FH.NumChannels*FH.NumSamples) - 1 ;
     for ch := 0 to FH.NumChannels-1 do begin
         ChOffset := Channel[ch].ChannelOffset ;
         for i := 0 to FH.NumSamples-1 do begin
             j := i*FH.NumChannels + ChOffset ;
             k := (i + ShiftBy)*FH.NumChannels + ChOffset ;
             { Repeat points at ends of buffer }
             k := IntLimitTo( j + ShiftBy, 0, EndOfData ) ;
             Buf^[j] := ADC^[k] ;
             end ;
         end ;
     { Save record back to file }
     PutRecord( fH, rH^, fH.RecordNum, Buf^ ) ;
     end ;

procedure TEditFrm.ShiftVertical( ShiftBy : Integer ) ;
{ ---------------------------------------------
  Shift digitised signal data by SHIFTBY samples
  ----------------------------------------------}
var
   i,j,ChOffset : Integer ;
begin
     { Read record data from file }
     GetRecord( fH, rH^, fH.RecordNum, ADC^ ) ;

     { Shift the channel selected for editing up/down }
     j := Channel[ChSelected].ChannelOffset ;
     for i := 0 to FH.NumSamples-1 do begin
         ADC^[j] := ADC^[j] + ShiftBy ;
         j := j + FH.NumChannels ;
         end ;
     { Save record back to file }
     PutRecord( fH, rH^, fH.RecordNum, ADC^ ) ;
     end ;

procedure TEditFrm.ScaleVertical( ScaleBy : single ) ;
{ ---------------------------------------------
  Scale digitised signal data by ScaleBy factor
  ---------------------------------------------}
var
   i,j,ChOffset : Integer ;
begin
     { Read record data from file }
     GetRecord( fH, rH^, fH.RecordNum, ADC^ ) ;

     { Shift the channel selected for editing up/down }
     j := Channel[ChSelected].ChannelOffset ;
     for i := 0 to FH.NumSamples-1 do begin
         ADC^[j] := Trunc( (ADC^[j] - EditChannel.ADCZero)*ScaleBy )
                    + EditChannel.ADCZero  ;
         j := j + FH.NumChannels ;
         end ;
     { Save record back to file }
     PutRecord( fH, rH^, fH.RecordNum, ADC^ ) ;
     end ;


{ *** Horizontal shift functions *** }

procedure TEditFrm.bLeftClick(Sender: TObject);
{ ------------------------
  Shift record to the left
  ------------------------}
begin
     UpdateXShift ;
     ShiftHorizontal( -XShift ) ;
     State := DoRecord ;
     end;


procedure TEditFrm.Button1Click(Sender: TObject);
{ -------------------------
  Shift record to the right
  -------------------------}
begin
     UpdateXShift ;
     ShiftHorizontal( XShift ) ;
     State := DoRecord ;
     end;


procedure TEditFrm.EdXShiftKeyPress(Sender: TObject; var Key: Char);
{ -------------------------------------------------
  Update XShift value if user has entered new value
  -------------------------------------------------}
begin
     if key = chr(13) then UpdateXShift ;
     end ;


procedure TEditFrm.UpdateXShift ;
begin
     XShift := Trunc( (MsToSecs*ExtractFloat(edXShift.text,XShift*rH^.dt*SecsToMs))
                         /rH^.dt );
     XShift := Abs(XShift) ;
     edXShift.text := format('%6.3g ms',[XShift*rH^.dt*SecsToMs]);
     end ;

{ *** Vertical shift function *** }

procedure TEditFrm.EdYShiftKeyPress(Sender: TObject; var Key: Char);
{ -------------------------------------------------
  Update YShift value if user has entered new value
  -------------------------------------------------}
begin
     if key = char(13) then UpdateYShift ;
     end;


procedure TEditFrm.UpdateYShift ;
begin
     YShift := Trunc( (ExtractFloat(edYShift.text,YShift*EditChannel.ADCScale))
                         /EditChannel.ADCScale );
     YShift := Abs(YShift) ;
     edYShift.text := format('%6.3g %s',[YShift*EditChannel.ADCScale,
                                                EditChannel.ADCUnits]);
     end ;


procedure TEditFrm.bUpClick(Sender: TObject);
{ ---------------------------
  Shift channel up vertically
  ---------------------------}
begin
     UpdateYShift ;
     ShiftVertical( YShift) ;
     State := DoRecord ;
     end;


procedure TEditFrm.bDownClick(Sender: TObject);
{ ------------------------
  Shift down up vertically
  ------------------------}
begin
     UpdateYShift ;
     ShiftVertical( -YShift) ;
     State := DoRecord ;
     end;

{ *** Vertical scaling function *** }

procedure TEditFrm.EdYScaleKeyPress(Sender: TObject; var Key: Char);
{ -------------------------------------------------
  Update YScale value if user has entered new value
  -------------------------------------------------}
begin
     If key = chr(13) then UpdateYScale ;
     end;


procedure TEditFrm.UpdateYScale ;
begin
     YScale := ExtractFloat(edYScale.text,YScale) ;
     edYScale.text := format('%6.3g',[YScale]);
     end ;


procedure TEditFrm.bScaleClick(Sender: TObject);
begin
     UpdateYScale ;
     ScaleVertical( YScale ) ;
     State := DoRecord ;
end;


procedure TEditFrm.TableKeyPress(Sender: TObject; var Key: Char);
{ -------------------------------------- }
var
   ChOffset,j,i : integer ;
   y : single ;
begin
     if key = chr(13) then begin
        { Read record data from file }
        GetRecord( fH, rH^, fH.RecordNum, ADC^ ) ;
        Choffset := Channel[ChSelected].ChannelOffset ;
        j := (Table.Row-1)*FH.NumChannels + ChOffset ;
        Y := (ADC^[j] - EditChannel.ADCZero)*EditChannel.ADCScale ;
        ADC^[j] := Trunc( ExtractFloat(Table.Cells[1,Table.Row],Y) /
                          EditChannel.ADCScale ) + EditChannel.ADCZero ;
       { Save record back to file }
       PutRecord( fH, rH^, fH.RecordNum, ADC^ ) ;
       State := DoRecord ;
       end ;
     end ;

procedure TEditFrm.bUndoClick(Sender: TObject);
begin
     GetRecord(BackUpfH, rH^, FH.RecordNum, Buf^ ) ;
     PutRecord( fH, rH^, FH.RecordNum, Buf^ ) ;
     State := DoRecord ;
     end;

procedure TEditFrm.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
                      Cursor0 : begin
                           OldPosition := EditChannel.Cursor0 ;
                           EditChannel.Cursor0 := OldPosition + Step ;
                           MoveVerticalCursor( pbDisplay,EditChannel.Cursor0,
                                               OldPosition,EditChannel,lbCursor0) ;
                           end ;
                      Cursor1 : begin
                           OldPosition := EditChannel.Cursor1  ;
                           EditChannel.Cursor1 := OldPosition + Step ;
                           MoveVerticalCursor( pbDisplay,EditChannel.Cursor1,
                                               OldPosition,EditChannel,lbCursor1 ) ;
                           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 TEditFrm.FormCreate(Sender: TObject);
begin
     { Disable "Edit Records" item of "Analysis" menu }
     Main.EditRecord.Enabled := false ;
     end;

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

procedure TEditFrm.edADCVoltageRangeKeyPress(Sender: TObject;
  var Key: Char);
{ ----------------------------------
  Save new A/D voltage range setting
  ----------------------------------}
begin
     if key = chr(13) then begin
        rh^.ADCVoltageRange[ChSelected] := ExtractFLoat( edADCVoltageRange.Text,
                                           rh^.ADCVoltageRange[ChSelected] ) ;
        edADCVoltageRange.text := format( '%.4g V',[rh^.ADCVoltageRange[ChSelected]]) ;
        end ;
     SaveRecord := True ;
     end;

end.
