unit Measure;
{ ==========================================================================
  WinWCP - Waveform measurement module
  (c) John Dempster, University of Strathclyde, 1997.  All Rights Reserved.
  V1.6f 16/7/97 ... Blocks of data in table and Summary grid can now be
                    selected for copying to clipboard
  V1.7a 31/8/97 ... Availability status for records not selected for
                    measurement now left unchanged
  V1.7c 11/9/97 ... X-Y plots now hold 6000 points
                    Histograms now copied to clipboard as Bin Mid,Y pairs
  V1.7d 1/12/97 ... Ctrl-R changes Accepted/Rejected, Record types can be set by key
  V1.8 17/12/97 ... Rise time 10%-90% limits now defined as
                    10-90% of Peak-Baseline (instead of PeakPos-PeakNeg)
  24/2/98 ... +/- keys now step forward/back through records
  2/4/98 ... 2.0 Interval for calculating rate of rise can now be
                 set by user.
                 Rate of rise limits now set by data cursors
                 (used to be Cursor 0 to Peak)
  ==========================================================================}
interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls, Grids, ExtCtrls, TabNotBk, Global, Shared, FileIo,
  Zero, PlotLib, SetAxes, CustHist, SetVar, PrintGra, Printers, ClipBrd, maths ;

type
  TDestination = ( ToScreen, ToPrinter, ToClipboardAsData, ToClipboardAsImage ) ;
  TMeasureFrm = class(TForm)
    TabbedNotebook: TTabbedNotebook;
    RecordGrp: TGroupBox;
    Label2: TLabel;
    edRecordNum: TEdit;
    cbRecordType: TComboBox;
    ckBadRecord: TCheckBox;
    sbRecordNum: TScrollBar;
    AnalysisGrp: TGroupBox;
    bDoAnalysis: TButton;
    pbDisplay: TPaintBox;
    lbTMin: TLabel;
    lbTMax: TLabel;
    ResultsGrp: TGroupBox;
    sgResults: TStringGrid;
    Timer: TTimer;
    cbPeakMode: TComboBox;
    Label6: TLabel;
    cbTypeToBeAnalysed: TComboBox;
    Label7: TLabel;
    edProgress: TEdit;
    bCancel: TButton;
    XYPlotGrp: TGroupBox;
    bNewXYPlot: TButton;
    pbPlot: TPaintBox;
    XGroup: TGroupBox;
    Label15: TLabel;
    cbXVariable: TComboBox;
    cbXChannel: TComboBox;
    YGroup: TGroupBox;
    Label16: TLabel;
    cbYVariable: TComboBox;
    cbYChannel: TComboBox;
    GroupBox1: TGroupBox;
    Label12: TLabel;
    EdRecRangeXYPlot: TEdit;
    edRecTypeXYPlot: TEdit;
    bCustomiseXYPlot: TButton;
    GroupBox2: TGroupBox;
    bNewHistogram: TButton;
    GroupBox3: TGroupBox;
    Label10: TLabel;
    cbHistVariable: TComboBox;
    cbHistChannel: TComboBox;
    GroupBox5: TGroupBox;
    Label17: TLabel;
    edRecRangeHistogram: TEdit;
    EdRecTypeHistogram: TEdit;
    bCustomiseHistogram: TButton;
    Label11: TLabel;
    Label18: TLabel;
    Label19: TLabel;
    pbHist: TPaintBox;
    edNumBins: TEdit;
    EdHistMin: TEdit;
    edHistMax: TEdit;
    Label8: TLabel;
    VariablesGrp: TGroupBox;
    ckVariable0: TCheckBox;
    ckVariable1: TCheckBox;
    ckVariable2: TCheckBox;
    ckVariable3: TCheckBox;
    ckVariable4: TCheckBox;
    ckVariable5: TCheckBox;
    ckVariable6: TCheckBox;
    ckVariable7: TCheckBox;
    ckVariable8: TCheckBox;
    ckVariable9: TCheckBox;
    ckVariable10: TCheckBox;
    ckVariable11: TCheckBox;
    ckVariable12: TCheckBox;
    ckVariable13: TCheckBox;
    Summary: TStringGrid;
    GroupBox4: TGroupBox;
    Label9: TLabel;
    edRecRangeSummary: TEdit;
    EdRecTypeSummary: TEdit;
    GroupBox6: TGroupBox;
    cbSummaryChannel: TComboBox;
    Table: TStringGrid;
    GroupBox7: TGroupBox;
    Label13: TLabel;
    EdRecRangeTable: TEdit;
    EdRecTypeTable: TEdit;
    GroupBox8: TGroupBox;
    edRecRange: TEdit;
    rbAllRecords: TRadioButton;
    rbThisRecord: TRadioButton;
    rbRange: TRadioButton;
    lbTZeroCursor: TLabel;
    lbCursor0: TLabel;
    lbCursor1: TLabel;
    edRateofRiseInterval: TEdit;
    Label1: TLabel;
    procedure TimerTimer(Sender: TObject);
    procedure sbRecordNumChange(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormPaint(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 bDoAnalysisClick(Sender: TObject);
    procedure bCancelClick(Sender: TObject);
    procedure bNewXYPlotClick(Sender: TObject);
    procedure pbPlotPaint(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure bCustomiseXYPlotClick(Sender: TObject);
    procedure bNewHistogramClick(Sender: TObject);
    procedure cbHistVariableChange(Sender: TObject);
    procedure bCustomiseHistogramClick(Sender: TObject);
    procedure ckVariable0Click(Sender: TObject);
    procedure cbSummaryChannelChange(Sender: TObject);
    procedure TableDblClick(Sender: TObject);
    procedure TableMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormActivate(Sender: TObject);
    procedure FormDeactivate(Sender: TObject);
    procedure ckBadRecordClick(Sender: TObject);
    procedure cbRecordTypeChange(Sender: TObject);
    procedure edRecordNumKeyPress(Sender: TObject; var Key: Char);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure pbDisplayPaint(Sender: TObject);
    procedure pbHistPaint(Sender: TObject);
    procedure EdRecRangeTableChange(Sender: TObject);
    procedure edRecRangeHistogramChange(Sender: TObject);
    procedure EdRecRangeXYPlotChange(Sender: TObject);
    procedure EdRecRangeTableKeyPress(Sender: TObject; var Key: Char);
    procedure edRecRangeSummaryKeyPress(Sender: TObject; var Key: Char);
    procedure edRecRangeSummaryChange(Sender: TObject);
    procedure pbDisplayDblClick(Sender: TObject);
    procedure FormKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure FormShow(Sender: TObject);
    procedure edRateofRiseIntervalKeyPress(Sender: TObject; var Key: Char);

  private

    { Private declarations }
    procedure DisplayRecord ;
    procedure DisplayXYPlot( Destination : TDestination ) ;
    procedure AnalyseWaveforms ;
    procedure DisplayHistogram( Destination : TDestination ) ;
    procedure CalculateSummary ;
    procedure FillSummaryTable( Destination : TDestination ) ;
    procedure FillTable( Destination : TDestination ) ;
    procedure ResetForNewFile ;
    function UseRecord ( const RecH : TRecHeader ; RecType : string ) : Boolean ;
    procedure SetPlotButtonStatus ;
    procedure CreateVariable( const iNum : Integer ;
                         var Analysis : TAnalysis ;
                         const VName : string ;
                         const VUnits : Array of TString8 ;
                         const VScale :single ) ;
    procedure HeapBuffers( Operation : THeapBufferOp ) ;
  public
    { Public declarations }
    PlotRecord : Boolean ;
    CopyToClipBoardAsData : Boolean ;
    CopyToClipBoardAsImage : Boolean ;
    PrintHardCopy : boolean ;
    NewFile : Boolean ;
    TimerBusy : boolean ;

  end;


var
  MeasureFrm: TMeasureFrm;
  XYPlot : TPlot ;
  HistPlot : TPlot ;
  ToClipboard : TDestination ;

implementation

uses          MDIForm,Zoom,setbmap ;

{$R *.DFM}
type
    TMeasureState = ( DoRecord, DoXYPlot, DoAnalysis, DoHistogram, DoSummary,
                    NewHistVariable, Idle, EndOfAnalysis, DoTable, RefreshDisplay,
                    HardCopy, CopyToClip ) ;

    TCursorState = ( Cursor0,
                     Cursor1,
                     ZeroLevelCursor,
                     TZeroCursor,
                     NoCursor ) ;
    TPeakMode = ( PositivePeaks,
                  NegativePeaks,
                  AbsPeaks ) ;

    TAnalysisJob = record
                 Running : Boolean ;
                 StartAt : LongInt ;
                 EndAt : LongInt ;
                 PeakMode : TPeakMode ;
                 RecordNum : LongInt ;
                 PreviousTime : single ;
                 RateOfRiseInterval : LongInt ;
                 end ;
TTableVars = record
           Num : LongInt ;
           Chan : LongInt ;
           InUse : Boolean ;
           end ;
const
     AnalysisPage = 0 ;
     XYPlotPage = 1 ;
     HistogramPage = 2 ;
     SummaryPage = 3 ;
     TablePage = 4 ;

var
   State : TMeasureState ;
   CursorState : TCursorState ;
   CurCh : LongInt ;
   CursorChannel : TChannel ;
   MoveCursor : Boolean ;
   ADC : ^TIntArray ;
   AnalysisJob : TAnalysisJob ;
   OldPageIndex : LongInt ;
   XYPlotAvailable : Boolean ;
   HistogramAvailable : Boolean ;
   SummaryAvailable : Boolean ;
   MouseOverChannel : Integer ;
   VarNames : TStringList ;
   TableVariables : array[0..7] of TTableVars ;
   RH : TRecHeader ; { Record header }
   BuffersAllocated : boolean ;{ Indicates if memory buffers have been allocated }


procedure TMeasureFrm.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 TMeasureFrm.FormCreate(Sender: TObject);
{ --------------------------------------
  Initialisations when form is created
  ... Only done once when program starts
  --------------------------------------}
begin
     { Disable waveform measurements menu so no more measurement
       windows can be created }
     Main.WaveformMeasurements.enabled := False ;
     Main.ShowMeasure.visible := True ;

     BuffersAllocated := False ;
     { Create string list object to hold variable names }
     VarNames := TStringList.Create ;

     edRateofRiseInterval.text := format( ' %.3g ms', [RawFH.dt*SecsToMs] ) ;

     Width := TabbedNoteBook.width + 50 ;
     Height := TabbedNoteBook.Height + 50 ;
     { Initialise histogram plot }
     HistPlot.BinFillColor := clRed ;
     HistPlot.BinFillStyle := bsClear ;
     HistPlot.BinBorders := True ;
     HistPlot.XAxis.log := False ;
     HistPlot.YAxis.log := False ;
     { Initialise X/Y plot }
     XYPlot.XAxis.log := False ;
     XYPlot.YAxis.log := False ;
     SummaryAvailable := False ;
     NewFile := True ;
     end;


procedure TMeasureFrm.FormActivate(Sender: TObject);
begin
     { Allocate dynamic buffers }
     HeapBuffers( Allocate ) ;

     { Set tick in Windows menu }
     Main.ClearTicksInWindowsMenu ;
     Main.ShowMeasure.checked := True ;
     Timer.enabled := TRue ;
     TimerBusy := False ;
     end;


procedure TMeasureFrm.ResetForNewFile ;
{ -----------------------------------------------------
  Reset variables and channel when a new file is loaded
  -----------------------------------------------------}
var
   i,ch : Integer ;
   VarUnits : Array[0..ChannelLimit] of TString8 ;
begin

     caption := 'Measure ' + fH.FileName ;

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

     cbTypeToBeAnalysed.items := RecordTypes ;
     if cbTypeToBeAnalysed.itemIndex < 0 then cbTypeToBeAnalysed.itemIndex := 0 ;

     { Create waveform analysis record }
     for ch := 0 to ChannelLimit do VarUnits[ch] := ' ' ;
     CreateVariable(  vRecord, rH.Analysis, 'Record', VarUnits, 1. ) ;

     for ch := 0 to ChannelLimit do VarUnits[ch] := ' ' ;
     CreateVariable( vGroup, rH.Analysis, 'Group', VarUnits, 1. ) ;

     for ch := 0 to ChannelLimit do VarUnits[ch] := 's' ;
     CreateVariable( vTime, rH.Analysis, 'Time', VarUnits, 1. ) ;

     for ch := 0 to ChannelLimit do VarUnits[ch] := Channel[ch].ADCUnits ;
     CreateVariable(vAverage,rH.Analysis,'Average',VarUnits,1. ) ;

     for ch := 0 to ChannelLimit do VarUnits[ch] := Channel[ch].ADCUnits + '.'
                                                    + Settings.TUnits ;
     CreateVariable(vArea,rH.Analysis,'Area',VarUnits,Settings.TScale ) ;

     for ch := 0 to ChannelLimit do VarUnits[ch] := Channel[ch].ADCUnits ;
     CreateVariable( vPeak,rH.Analysis,'Peak(a)',VarUnits,1. ) ;

     for ch := 0 to ChannelLimit do VarUnits[ch] := Channel[ch].ADCUnits + '^2' ;
     CreateVariable(vVariance,rH.Analysis,'Variance',VarUnits, 1. ) ;

     for ch := 0 to ChannelLimit do VarUnits[ch] := Settings.TUnits ;
     CreateVariable(vRiseTime,rH.Analysis,'Rise Time',VarUnits, Settings.TScale ) ;

     for ch := 0 to ChannelLimit do VarUnits[ch] := Channel[ch].ADCUnits +
                                                     '/' + Settings.TUnits ;
     CreateVariable( vRateofRise,rH.Analysis,'Rate of Rise',VarUnits,
                                                       1./Settings.TScale ) ;

     for ch := 0 to ChannelLimit do VarUnits[ch] := Settings.TUnits ;
     CreateVariable(vLatency,rH.Analysis,'Latency',VarUnits, Settings.TScale ) ;

     for ch := 0 to ChannelLimit do VarUnits[ch] := Settings.TUnits ;
     CreateVariable(vT50,rH.Analysis,'T.50%',VarUnits, Settings.TScale ) ;

     for ch := 0 to ChannelLimit do VarUnits[ch] := Settings.TUnits ;
     CreateVariable(vT90,rH.Analysis,'T.90%',VarUnits, Settings.TScale ) ;

     for ch := 0 to ChannelLimit do VarUnits[ch] := 's' ;
     CreateVariable(vInterval,rH.Analysis,'Interval',VarUnits, 1. ) ;

     for ch := 0 to ChannelLimit do VarUnits[ch] := Channel[ch].ADCUnits ;
     CreateVariable(vBaseline,rH.Analysis,'Baseline',VarUnits, 1. ) ;

     rH.Analysis.NumVariables := vBaseline + 1 ;

     VarNames.Clear ;
     for i := 0 to rH.Analysis.NumVariables-1 do
          VarNames.Add( rH.Analysis.VarName[i] ) ;

     { Create variable name list for X/Y plot }
     cbXVariable.items := VarNames  ;
     if cbXVariable.ItemIndex < 0  then cbXVariable.ItemIndex := 0 ;
     cbYVariable.items := VarNames  ;
     if cbYVariable.ItemIndex < 0  then cbYVariable.ItemIndex := MaxInt(
                                                        [1,FH.NumChannels-1] );
     cbHistVariable.items := VarNames  ;
     if cbHistVariable.ItemIndex < 0  then cbHistVariable.ItemIndex := 0 ;

     PlotRecord := True ;
     if fH.Numrecords > 0 then begin
        sbRecordNum.Max := fH.NumRecords ;
        sbRecordNum.Min := 1 ;
        sbRecordNum.Enabled := True ;
        sbRecordNum.Position := 1 ;
        edRecRange.text := format(' 1-%d',[fH.NumRecords] ) ;
        edRecRangeXYPlot.text := edRecRange.text ;
        edRecRangeHistogram.text := edRecRange.text ;
        edRecRangeSummary.text := edRecRange.text ;
        edRecRangeTable.text := edRecRange.text ;
        end
     else begin
          edRecRange.text := 'None' ;
        end ;

     { If analysis area cursors overlap set them to default (whole record) }
     for ch := 0 to fH.NumChannels - 1 do begin
         if Channel[ch].Cursor0 = Channel[ch].Cursor0 then begin
            Channel[ch].Cursor0 := fH.NumSamples div 100 ;
            Channel[ch].Cursor1 := fH.NumSamples - Channel[ch].Cursor0 ;
            end ;
         end ;
     CursorChannel.TZeroCursor := 0 ;

     { Update channel selector boxes }
     cbXChannel.items := ChannelNames ;
     if cbXChannel.ItemIndex < 0 then cbXChannel.ItemIndex := 0 ;
     cbYChannel.items := ChannelNames ;
     if cbYChannel.ItemIndex < 0 then cbYChannel.ItemIndex := 0 ;
     cbHistChannel.items := ChannelNames ;
     if cbHistChannel.ItemIndex < 0 then cbHistChannel.ItemIndex := 0 ;
     { Set up Summary channel selector box }
     cbSummaryChannel.items := ChannelNames ;
     if cbSummaryChannel.ItemIndex < 0 then cbSummaryChannel.ItemIndex := 0 ;

     { Initialise peak mode combo box }
     if cbPeakMode.ItemIndex < 0 then cbPeakMode.ItemIndex := 0 ;

     { Initialise Table }
     Table.ColCount := high(TableVariables) + 1 ;
     for i := 0 to high(TableVariables) do begin
         TableVariables[i].Num := 0 ;
         TableVariables[i].Chan := 0 ;
         TableVariables[i].InUse := False
         end ;
     TableVariables[0].InUse := True ;
     TableVariables[1].Num := vPeak ;
     TableVariables[1].InUse := True ;

     OldPageIndex := -1 ;
     TabbedNotebook.PageIndex := 0 ;

     { Disable X/Y and histogram plotting }
     bCustomiseXYPlot.Enabled := False ;
     bCustomiseHistogram.Enabled := False ;
     XYPlotAvailable := False ;
     HistogramAvailable := False ;
     SummaryAvailable := False ;
     end;


procedure TMeasureFrm.TimerTimer(Sender: TObject);
{ ------------------------------
  Timer-based process scheduler
  -----------------------------}
var
   iCh,iVar : LongInt ;
begin

     if not TimerBusy then begin

        TimerBusy := True ;

        { If a new file has been opened ... reset measurements }
        if NewFile then begin
           ResetForNewFile ;
           NewFile := False ;
           State := DoRecord ;
           end ;

        { Determine whether a notebook page has been changed
          and take appropriate action }
        if TabbedNotebook.PageIndex <> OldPageIndex then begin
           if TabbedNotebook.PageIndex = AnalysisPage then begin
              { Analysis }
              State := DoRecord ;
              edRecordNum.Setfocus ;
              end
           else if TabbedNotebook.PageIndex = XYPlotPage then begin
              { X/Y Plot }
              SetPlotButtonStatus ;
              edRecRangeXYPlot.text:= edRecRange.text ;
              edRecTypeXYPlot.text := cbTypeToBeAnalysed.text ;
              State := DOXYPlot ;
              end
           else if TabbedNotebook.PageIndex = HistogramPage then begin
              { Histogram }
              SetPlotButtonStatus ;
              edRecRangeHistogram.text:= edRecRange.text ;
              edRecTypeHistogram.text := cbTypeToBeAnalysed.text ;
              if HistogramAvailable then State := DoHistogram
                                    else State := NewHistVariable ;
              end
           else if TabbedNotebook.PageIndex = SummaryPage then begin
              SetPlotButtonStatus ;
              edRecRangeSummary.text:= edRecRange.text ;
              edRecTypeSummary.text := cbTypeToBeAnalysed.text ;
              State := DoSummary ;
              end
           else if TabbedNotebook.PageIndex = TablePage then begin
              SetPlotButtonStatus ;
              edRecRangeTable.text:= edRecRange.text ;
              edRecTypeTable.text := cbTypeToBeAnalysed.text ;
              State := DoTable ;
              end ;
           end ;
        OldPageIndex := TabbedNotebook.PageIndex ;

        { Execute any requested operations }
        case State of
          DoRecord : begin
                State := Idle ;
                DisplayRecord ;
                MoveCursor := False ;
                Main.SetCopyMenu( False, False ) ;
                end ;
          DoXYPlot : begin
                State := Idle ;
                DisplayXYPlot( ToScreen ) ;
                Main.SetCopyMenu( XYPlotAvailable, XYPlotAvailable ) ;
                Main.Print1.Enabled := XYPlotAvailable ;
                end ;
          DoHistogram : begin
                State := Idle ;
                DisplayHistogram( ToScreen ) ;
                Main.SetCopyMenu( HistogramAvailable, HistogramAvailable ) ;
                Main.Print1.Enabled := HistogramAvailable ;
                end ;
          HardCopy : begin
                State := Idle ;
                if (TabbedNotebook.PageIndex = XYPlotPage) then
                   DisplayXYPlot( ToPrinter ) ;
                if (TabbedNotebook.PageIndex = HistogramPage) then
                   DisplayHistogram( ToPrinter ) ;
                if (TabbedNotebook.PageIndex = SummaryPage) then
                   FillSummaryTable( ToPrinter ) ;
                if (TabbedNotebook.PageIndex = TablePage) then
                   FillTable( ToPrinter ) ;
                end ;
          CopyToClip : begin
                State := Idle ;
                if (TabbedNotebook.PageIndex = XYPlotPage) then
                   DisplayXYPlot( ToClipboard ) ;
                if (TabbedNotebook.PageIndex = HistogramPage) then
                   DisplayHistogram( ToClipboard ) ;
                if (TabbedNotebook.PageIndex = SummaryPage)
                   and (ToClipboard = ToClipboardAsData) then
                   CopyStringGrid( Summary ) ;
                if (TabbedNotebook.PageIndex = TablePage)
                   and (ToClipboard = ToClipboardAsData) then
                   CopyStringGrid( Table ) ;
                end ;

          DoAnalysis : begin
                AnalyseWaveforms ;
                end ;
          EndofAnalysis : begin
                { Tidy up after a waveform analysis run has been completed }
                edRecRange.text := format('%d - %d',[AnalysisJob.StartAt,
                                                      AnalysisJob.RecordNum] ) ;
                bDoAnalysis.enabled := True ;
                bCancel.enabled := False ;
                edProgress.text := ' ' ;
                CalculateSummary ;
                FillSummaryTable( ToScreen ) ;
                { Enable X/Y and histogram plotting }
                State := DoRecord ;
                end ;
          NewHistVariable : begin
                 iCh := cbHistChannel.ItemIndex ;
                 iVar := cbHistVariable.ItemIndex ;
                 if not SummaryAvailable then CalculateSummary ;
                if rH.Analysis.Num > 0 then begin
                   edHistMin.text := format( '%8.3g', [rH.Analysis.MinValue[iCh,iVar]
                                                      *rH.Analysis.UnitsScale[iCh,iVar]]) ;
                   edHistMax.text := format( '%8.3g', [rH.Analysis.MaxValue[iCh,iVar]
                                                      *rH.Analysis.UnitsScale[iCh,iVar]]) ;
                   end
                else begin
                   edHistMin.text := 'No data' ;
                   edHistMax.text := 'No data' ;
                   end ;
                HistogramAvailable := False ;
                 State := DoHistogram ;
                 end ;
          DoSummary : Begin
                 { Fill summary table with mean,st.dev data from rH.Analysis }
                 State := Idle ;
                 if not SummaryAvailable then CalculateSummary ;
                 FillSummaryTable( ToScreen ) ;
                 Main.SetCopyMenu( True, False ) ;
                 Main.Print1.Enabled := True ;
                 end ;
          DoTable : Begin
                  { Fill table of selected results }
                   State := Idle ;
                  FillTable( ToScreen ) ;
                  Main.SetCopyMenu( True, False ) ;
                  Main.Print1.Enabled := True ;
                  end ;
          RefreshDisplay : Begin
                  OldPageIndex := -1 ;
                  end ;
          Idle : begin
                if CopyToClipboardAsData then begin
                   State := CopyToClip ;
                   CopyToClipboardAsData := False ;
                   ToClipboard := ToClipboardAsData ;
                   end
                else if CopyToClipboardAsImage then begin
                   State := CopyToClip ;
                   CopyToClipboardAsImage := False ;
                   ToClipboard := ToClipboardAsImage ;
                   end
                else if PrintHardCopy then begin
                   State := HardCopy ;
                   PrintHardCopy := False ;
                   end ;
                if Screen.Cursor <> crDefault then Screen.Cursor := crDefault ;
                end ;
          end ;
          TimerBusy := false ;
        end ;
     end;


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



procedure TMeasureFrm.FormDestroy(Sender: TObject);
begin
     VarNames.Free ;
     
     end;


procedure TMeasureFrm.FormPaint(Sender: TObject);
begin
     { Request record to be re-plotted on next timer tick }
     OldPageIndex := -1 ;
     end;


procedure TMeasureFrm.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 ;
   OldColor : TColor ;
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 }

          { Find which channel the mouse is pointing to }
          for ch := 0 to fH.NumChannels-1 do
              if Channel[ch].InUse and
              (Channel[ch].Top <= Y ) and ( Y <= Channel[ch].Bottom ) then
                 CurCh := ch ;

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

          { Is mouse over cursor 0 ? }
          if OverVerticalCursor(X,Channel[CurCh].Cursor0,Channel[CurCh] ) then begin
             CursorState := Cursor0  ;
             pbDisplay.Cursor := crSizeWE ;
             end ;

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

          { Is mouse over zero time cursor  ? }
          if OverVerticalCursor(X,CursorChannel.TZeroCursor,CursorChannel ) then begin
             CursorState := TZeroCursor  ;
             pbDisplay.Cursor := crSizeWE ;
             end ;

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

          end
      else begin

          { If in Move Cursor mode, move selected cursor to new position }
          OldColor := Channel[CurCh].color ;

          case CursorState of
          { Move cursor 0 }
          Cursor0 : begin
                  Channel[CurCh].color := clRed ;
                  VerticalCursorScale( X,Channel[CurCh].Cursor0,OldIndex,Channel[CurCh]);
                  MoveVerticalCursor(pbDisplay,Channel[CurCh].Cursor0,OldIndex,
                                     Channel[CurCh],lbCursor0) ;
                  end ;
          { Move cursor 1 }
          Cursor1 : begin
                  Channel[CurCh].color := clRed ;
                  VerticalCursorScale( X,Channel[CurCh].Cursor1,OldIndex,Channel[CurCh]);
                  MoveVerticalCursor(pbDisplay,Channel[CurCh].Cursor1,OldIndex,
                                            Channel[CurCh],lbCursor1) ;
                  end ;
          { Move time zero cursor }
          TZeroCursor : begin
                  CursorChannel.color := clPurple ;
                  VerticalCursorScale( X,CursorChannel.TZeroCursor,OldIndex,CursorChannel);
                  Channel[0].TZeroCursor := CursorChannel.TZeroCursor ;
                  MoveVerticalCursor(pbDisplay,CursorChannel.TZeroCursor,OldIndex,
                                            CursorChannel,lbTZeroCursor) ;
                  end ;
          { Signal zero reference level }
          ZeroLevelCursor : Begin
              HorizontalCursorScale( Y,Channel[CurCh].ADCZero,OldLevel,Channel[CurCh]);
              MoveHorizontalCursor(pbDisplay,Channel[CurCh].ADCZero,OldLevel,
                                   Channel[CurCh]) ;
              end ;

          else ;
          end ;
          Channel[CurCh].Color := OldColor ;
          end ;
      end ;


procedure TMeasureFrm.pbDisplayMouseUp(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
{ -----------------------------------------------------------
  Activated zero level setup form if right button was pressed
  -----------------------------------------------------------}
var
   OldZeroLevel : LongInt ;
begin
     MoveCursor := False ;
     if (Button = mbRight) and (CursorState = ZeroLevelCursor) then begin
        { Save current zero level for the channel under the mouse }
        OldZeroLevel := Channel[CurCh].ADCZero ;
        { Pop-up zero level setting dialog }
        ZeroFrm.ChSel := CurCh ;
        ZeroFrm.NewZeroAt := Trunc( (X - Channel[0].Left) / Channel[0].xScale
                                        + Channel[0].xMin ) ;
        ZeroFrm.ShowModal ;
        { Update zero level cursor }
        MoveHorizontalCursor( pbDisplay, Channel[CurCh].ADCZero, OldZeroLevel,
                              Channel[CurCh] ) ;
        State := DoRecord ;
        end ;
     end;


procedure TMeasureFrm.pbDisplayMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
   ch : Integer ;
begin
     MoveCursor := True ;
     for ch := 0 to FH.NumChannels-1 do
         if (Channel[ch].Bottom >= Y) and (Y >= Channel[ch].top) then
            MouseOverChannel := ch ;


     end;


procedure TMeasureFrm.bDoAnalysisClick(Sender: TObject);
{ -----------------------
  Start waveform analysis
  -----------------------}
var
   Temp : LongInt ;
   x : single ;
begin
     if rbAllRecords.checked then begin
        { Analyse all records in file }
        AnalysisJob.StartAt := 1 ;
        AnalysisJob.EndAt := FH.NumRecords ;
        edRecRange.text := format( ' %d-%d', [AnalysisJob.StartAt,
                                              AnalysisJob.EndAt] ) ;
        end
     else if rbThisRecord.checked then begin
        { Analyse the currently displayed record }
        AnalysisJob.StartAt := FH.RecordNum ;
        AnalysisJob.EndAt := FH.RecordNum ;
        end
     else begin
          { Analyse the user entered range of records }
          GetIntRangeFromEditBox(edRecRange,AnalysisJob.StartAt,AnalysisJob.EndAt,
                                 1, fH.NumRecords ) ;
          end ;
     AnalysisJob.RecordNum := AnalysisJob.StartAt ;
     if cbPeakMode.ItemIndex = 2 then AnalysisJob.PeakMode := NegativePeaks
     else if cbPeakMode.ItemIndex = 1 then AnalysisJob.PeakMode := PositivePeaks
     else AnalysisJob.PeakMode := AbsPeaks ;

     { Rate of rise measurement interval }
     x := GetFromEditBox( edRateofRiseInterval, RawFH.dt*SecsToMs,
                          RawFH.dt*SecsToMs, RawFH.dt*RawFH.NumSamples*SecsToMs,
                          ' %.3g',' ms' )*MsToSecs ;
     if RawFH.dt <= 0.0 then RawFH.dt := 1.0 ;
     AnalysisJob.RateOfRiseInterval := MaxInt( [Trunc(x/RawFH.dt),1] ) ;

     { Set previous time variable negative to indicate that it is invalid }
     AnalysisJob.PreviousTime := -1. ;
     AnalysisJob.Running := True ;
     bDoAnalysis.enabled := False ;
     bCancel.enabled := True ;
     Screen.cursor := crHourGlass ;
     State := DoAnalysis ;
     end;

procedure TMeasureFrm.bCancelClick(Sender: TObject);
{ -----------------------
  Abort waveform analysis
  -----------------------}
begin
     AnalysisJob.Running := False ;
     bDoAnalysis.enabled := True ;
     bCancel.enabled := False ;
     State := EndOfAnalysis ;
     edProgress.text := 'Aborted' ;
     end;


procedure TMeasureFrm.bNewXYPlotClick(Sender: TObject);
{ --------------------
  Request an X/Y Plot
  -------------------}
begin
     State := doXYPlot ;
     XYPlot.XAxis.AutoRange := True ;
     XYPlot.YAxis.AutoRange := True ;
     XYPlot.XAxis.log := False ;
     XYPlot.YAxis.log := False ;
     XYPlotAvailable := True ;
     bCustomiseXYPlot.Enabled := True ;
     end;


procedure TMeasureFrm.DisplayRecord ;
{ ========================================================
  Display digitised signal record on Page 0 of notebook
  ========================================================}
var
   i,j,ch,ChOffset,Rec : LongInt ;
   x,y,dx : single ;
   xPix,yPix,nOffScreen : Integer ;
   row,col : LongInt ;
   OldColor : TColor ;
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 ;
          if EdRecRange.text = '' then
             edRecRange.text := format(' 1-%d',[fH.NumRecords]);

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

          InitializeDisplay( Channel, fH.NumChannels, rH,
                             lbTMin,lbTMax, pbDisplay) ;
          CursorChannel := Channel[0] ;
          CursorChannel.Top := 0 ;
          CursorChannel.Bottom := pbDisplay.Height ;

          { Set width of display area }
          pbDisplay.width := TabbedNotebook.width + TabbedNotebook.left
                             - pbDisplay.left - 30 ;
           { 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) ;

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

                     y := ADC^[(i*fH.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 := MinInt( [ MaxInt( [xPix,Channel[ch].Left]),
                                       Channel[ch].Right] ) ;
                           yPix := MinInt( [ MaxInt( [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 ;

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

                 { Draw cursors }
                 OldColor := Channel[Ch].Color ;
                 Channel[Ch].Color := clRed ;
                 DrawCursor( pbDisplay, Channel[ch].Cursor0,Channel[ch], lbCursor0) ;
                 DrawCursor( pbDisplay, Channel[ch].Cursor1,Channel[ch], lbCursor1);
                 Channel[Ch].Color := OldColor ;

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

          { Draw time zero cursor }
          CursorChannel.color := clPurple ;
          DrawCursor( pbDisplay, CursorChannel.TZeroCursor,CursorChannel,lbTZeroCursor) ;

          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);
          ch := 0 ;

          sgResults.ColCount := fH.NumChannels + 1 ;

          for ch := 0 to fH.NumChannels do begin
              col := ch + 1 ;
              if Channel[ch].InUse then
                 sgResults.cells[col,0] := Channel[ch].ADCName
              else sgResults.cells[col,0] := ' ' ;
              end ;

          sgResults.RowCount := rH.Analysis.NumVariables + 1 ;
          for i := 0 to rH.Analysis.NumVariables-1 do begin
              row := i + 1 ;
              sgResults.cells[0,row] := rH.Analysis.VarName[i] ;
              for ch := 0 to fH.NumChannels-1 do begin
                  col := ch + 1 ;
                  if Channel[ch].InUse and rH.Analysis.Available then begin
                     sgResults.cells[col,row] := format( '%8.3g %s',
                                                 [rH.Analysis.Value[ch,i]*
                                                  rH.Analysis.UnitsScale[ch,i],
                                                  rH.Analysis.Units[ch,i]] ) ;
                     end
                  else sgResults.cells[col,row] := ' ' ;
                  end ;
              end ;
          end ;
     end ;


procedure TMeasureFrm.DisplayXYPlot( Destination : TDestination ) ;
{ ==========================================================
  Draw the X/Y plot of one waveform variable against another
  Destination : Screen or Printer
  ==========================================================}
const
     { Clipboard copy buffer limits }
     BufSize = 65100 ;
     BufLimit = 65000 ;

var
   Rec,RecStart,RecEnd,XCh,XVar,YCh,YVar,n,i : LongInt ;
   xPix,yPix : LongInt ;
   xy : TxyBuf ;
   PlotOK : Boolean ;
   Line : String ;
   CopyBuf0,Line0 : PChar ;
   BMap : TBitMap ;
begin

     if Destination = ToScreen then begin
        { If screen ... Erase plotting area to background colour }
        pbPlot.Canvas.Brush.color := clWhite ;
        { Set width of display area }
        pbPlot.width := TabbedNotebook.width + TabbedNotebook.left - pbPlot.left - 30 ;
        pbPlot.canvas.FillRect( pbPlot.canvas.ClipRect ) ;
        end ;

     PlotOK := True ;
     if XYPlotAvailable then begin

        if Destination = ToPrinter then begin
             { Let user set page margins for plot on printer }
             PrintGraphFrm.ShowModal ;
             if PrintGraphFrm.ModalResult <> mrOK then PlotOK := False ;
             end
        else if Destination = ToClipboardAsImage then begin
             { Let user set size of bit map image to go to clipboard }
             SetBitmapFrm.ShowModal ;
             if SetBitmapFrm.ModalResult <> mrOK then PlotOK := False
             end ;
        end
     else PlotOK := False ;

     { If everything OK plot graph }

     if PlotOK then begin
           Screen.Cursor := crHourglass ;
           XYPlot.Title := 'The title' ;
           XYPlot.NumGraphs := 1 ;
           xy := TXYBuf.Create ;

           { Determine record range/channels to be plotted }
           GetIntRangeFromEditBox(edRecRange,RecStart,RecEnd,1,fH.NumRecords) ;
           Xch := cbXChannel.itemIndex ;
           XVar := cbXVariable.ItemIndex ;
           Ych := cbYChannel.itemIndex ;
           YVar := cbYVariable.ItemIndex ;

           { Create X and Y axes labels }
           XYPlot.xAxis.lab := '' ;
           if xVar > vTime then XYPlot.xAxis.Lab := Channel[xCh].ADCName + ' ';
           XYPlot.xAxis.Lab := XYPlot.xAxis.Lab + + rH.Analysis.VarName[xVar] ;
           if rH.Analysis.Units[xCh,xVar] <> ' ' then XYPlot.xAxis.Lab :=
              XYPlot.xAxis.Lab + ' (' + rH.Analysis.Units[xCh,xVar] + ')' ;

           XYPlot.yAxis.lab := '' ;
           if yVar > vTime then XYPlot.yAxis.Lab := Channel[yCh].ADCName + ' ';
           XYPlot.yAxis.Lab := XYPlot.yAxis.Lab + + rH.Analysis.VarName[yVar] ;
           if rH.Analysis.Units[yCh,yVar] <> ' ' then XYPlot.yAxis.Lab :=
              XYPlot.yAxis.Lab + ' (' + rH.Analysis.Units[yCh,yVar] + ')' ;

           { Create graph }
           n := 0 ;
           for Rec := RecStart to RecEnd do begin
                { Read record analysis block from file }
                GetRecordHeaderOnly( fH, rH, Rec ) ;
                if UseRecord( rH, cbTypeToBeAnalysed.text ) and (n < High(xy.x)) then  begin
                   xy.x[n] := rH.Analysis.Value[ XCh, XVar  ]
                              * rH.Analysis.UnitsScale[XCh,XVar] ;
                   xy.y[n] := rH.Analysis.Value[ YCh, YVar ]
                              * rH.Analysis.UnitsScale[YCh,YVar] ;
                   Inc(n) ;
                   end ;
                end ;

           if n > 0 then begin

              xy.NumPoints := n ;
              xy.MarkerShape := SquareMarker ;
              xy.MarkerSolid := True ;

              { Plot graph }
              case Destination of
                   ToScreen : begin
                       { Set plot size/fonts for screen }
                       XYPlot.Left :=   pbPlot.Width div 6 ;
                       XYPlot.Right :=  pbPlot.Width - (pbPlot.Width div 10) ;
                       XYPlot.Top :=    pbPlot.Height div 8 ;
                       XYPlot.Bottom := pbPlot.Height - XYPlot.Top*2 ;
                       XYPlot.MarkerSize := Settings.Plot.MarkerSize ;
                       xy.MarkerSize := XYPlot.MarkerSize ;
                       xy.Color := clRed ;
                       XYPlot.LineThickness := Settings.Plot.LineThickness ;
                       pbPlot.canvas.font.name := Settings.Plot.FontName ;
                       pbPlot.canvas.font.size := Settings.Plot.FontSize ;
                       { ** Plot graph on screen ** }
                       pbPlot.Canvas.Brush.color := clWhite ;
                       pbPlot.Canvas.Pen.Width := XYPlot.LineThickness ;
                       pbPlot.canvas.FillRect( pbPlot.canvas.ClipRect ) ;
                       DrawAxes( pbPlot.canvas, XYPlot, xy ) ;
                       if Settings.Plot.ShowLines then
                          DrawLine( pbPlot.canvas, XYPlot, xy ) ;
                       if Settings.Plot.ShowMarkers then
                          DrawMarkers( pbPlot.canvas, XYPlot, xy ) ;
                       end ;

                   ToPrinter : begin
                       { Set plot size/fonts }
                       Printer.canvas.font.name := Settings.Plot.FontName  ;
                       Printer.canvas.font.height := PrinterPointsToPixels(
                                                     Settings.Plot.FontSize) ;
                       XYPlot.Left := PrinterCmToPixels('H',Settings.Plot.LeftMargin) ;
                       XYPlot.Right := Printer.pagewidth -
                                PrinterCmToPixels('H',Settings.Plot.RightMargin) ;
                       XYPlot.Top := PrinterCmToPixels('V',Settings.Plot.TopMargin) ;
                       XYPlot.Bottom := Printer.pageheight -
                                 PrinterCmToPixels('V',Settings.Plot.BottomMargin) ;
                       XYPlot.MarkerSize := PrinterPointsToPixels(Settings.Plot.MarkerSize);
                       xy.MarkerSize := XYPlot.MarkerSize ;
                       Printer.Canvas.Pen.Width := PrinterPointsToPixels(Settings.Plot.LineThickness);
                       { ** Print hard copy of graph **}
                       Printer.BeginDoc ;
                       xy.color := clBlack ;
                       { Print file name and ident info }
                       xPix := Printer.PageWidth div 10 ;
                       yPix := Printer.PageHeight div 40 ;
                       printer.canvas.textout( xPix, yPix, 'File :' + FH.FileName ) ;
                       yPix := yPix + (12*Printer.Canvas.TextHeight('X')) div 10 ;
                       printer.canvas.textout( xPix, yPix, FH.IdentLine ) ;

                       { Plot X and Y axes }
                       DrawAxes( Printer.canvas, XYPlot, xy ) ;

                       if Settings.Plot.ShowLines then
                          DrawLine( Printer.canvas, XYPlot, xy ) ;
                       if Settings.Plot.ShowMarkers then
                          DrawMarkers( Printer.canvas, XYPlot, xy ) ;
                       Printer.EndDoc ;
                       end ;

                   ToClipboardAsData : begin
                      { ** Copy graph data to clipboard ** }
                       CopyBuf0 := StrAlloc( BufSize ) ;
                       StrPCopy( CopyBuf0, '' ) ;
                       Line0 := StrAlloc(  256 ) ;
                       n := 0 ;
                       { Copy as list of ASCII numbers }
                       for i := 0 to xy.NumPoints-1 do begin
                           Line := format( '%10.4g', [xy.x[i]] ) + chr(9) +
                                   format( '%10.4g', [xy.y[i]] )
                                   + chr(13) + chr(10) ;
                           n := n + length(Line) ;
                           StrPCopy( Line0, Line ) ;
                           if n < BufLimit then CopyBuf0 := StrCat(CopyBuf0,Line0) ;
                           end ;
                       ClipBoard.SetTextBuf( CopyBuf0 ) ;
                       StrDispose( Line0 ) ;
                       StrDispose( CopyBuf0 ) ;
                       end ;

                   ToClipboardAsImage : begin
                       { ** Copy bitmap containing plot to clipboard **}
                       try
                           BMap := TBitmap.Create ;
                           BMap.Height := Settings.BitmapHeight ;
                           BMap.Width := Settings.BitmapWidth ;
                           XYPlot.Left :=   BMap.Width div 6 ;
                           XYPlot.Right :=  BMap.Width - (BMap.Width div 10) ;
                           XYPlot.Top :=    BMap.Height div 8 ;
                           XYPlot.Bottom := BMap.Height - XYPlot.Top*2 ;
                           XYPlot.MarkerSize := Settings.Plot.MarkerSize ;
                           xy.MarkerSize := XYPlot.MarkerSize ;
                           xy.Color := clRed ;
                           BMap.Canvas.Pen.Width := XYPlot.LineThickness ;
                           BMap.canvas.font.name := Settings.Plot.FontName ;
                           BMap.canvas.font.size := Settings.Plot.FontSize ;
                            { ** Plot graph on screen ** }
                           BMap.Canvas.Brush.color := clWhite ;
                           {BMap.Canvas.FillRect( BMap.Canvas.ClipRect ) ;}
                           DrawAxes( BMap.Canvas, XYPlot, xy ) ;
                           if Settings.Plot.ShowLines then
                              DrawLine( BMap.Canvas, XYPlot, xy ) ;
                           if Settings.Plot.ShowMarkers then
                              DrawMarkers( BMap.Canvas, XYPlot, xy ) ;
                           Clipboard.Assign(BMap) ;
                       finally
                           BMap.Free ;
                           end ;
                       end ;
                   end ;
              end ;

           xy.Free ;
           Screen.Cursor := crDefault ;
           end ;
     end ;


procedure TMeasureFrm.DisplayHistogram( Destination : TDestination ) ;
{ ========================================================
  Plot the histogram of a selected waveform variable
  ========================================================}
const
     BufSize = 65100 ;
     BufLimit = 65000 ;
var
   Rec,RecStart,RecEnd,HistCh,HistVar,n,i : LongInt ;
   x,HScale,HLowerLimit : Single ;
   xPix,yPix : LongInt ;
   Hist : THistBuf ;
   OldBrush : TBrush ;
   PlotOK : Boolean ;
   Line : String ;
   CopyBuf0,Line0 : PChar ;
   BMap : TBitMap ;
begin

     if Destination = ToScreen then begin
        { If screen ... Erase plotting area to background colour }
        OldBrush := pbHist.Canvas.Brush ;
        pbHist.Canvas.Brush.color := clWhite ;
        { Set width of histogram display area }
        pbHist.width := TabbedNotebook.width + TabbedNotebook.left - pbHist.left - 30 ;
        pbHist.canvas.FillRect( pbHist.canvas.ClipRect ) ;
        pbHist.Canvas.Brush := OldBrush ;
        end ;

     if HistogramAvailable then begin
        PlotOK := True ;
        case Destination of
             ToPrinter : begin
                { Set plot size/fonts for printer }
                PrintGraphFrm.ShowModal ;
                if PrintGraphFrm.ModalResult <> mrOK then PlotOK := False ;
                end ;
             ToClipboardAsImage : begin
                { Let user set size of bit map image to go to clipboard }
                SetBitmapFrm.ShowModal ;
                if SetBitmapFrm.ModalResult <> mrOK then PlotOK := False
                end ;
             end ;
        end
     else PlotOK := False ;

     { Plot the histogram if everything OK }

     if PlotOK then begin
        Screen.Cursor := crHourglass ;
        HistPlot.Title := 'The title' ;
        Hist := THistBuf.Create ;

        { Determine records, channels & variables to be plotted }
        GetIntRangeFromEditBox(edRecRange,RecStart,RecEnd,1,fH.NumRecords) ;
        HistCh := cbHistChannel.itemIndex ;
        HistVar := cbHistVariable.ItemIndex ;
        if not SummaryAvailable then CalculateSummary ;

        { Create X and Y axes labels }
        HistPlot.xAxis.lab := '' ;
        if HistVar > vTime then HistPlot.xAxis.Lab := Channel[HistCh].ADCName + ' ';
        HistPlot.xAxis.Lab := HistPlot.xAxis.Lab + + rH.Analysis.VarName[HistVar] ;
        if rH.Analysis.Units[HistCh,HistVar] <> ' ' then HistPlot.xAxis.Lab :=
              HistPlot.xAxis.Lab + ' (' + rH.Analysis.Units[HistCh,HistVar] + ')' ;

        HistPlot.yAxis.lab := '' ;

        { Clear Histogram }
        x := ExtractFloat( edHistMin.text, 0. ) ;
        Hist.NumBins := ExtractInt( EdNumBins.text ) ;
        Hist.BinWidth := ( ExtractFloat( edHistMax.text, 0. ) - x ) / (Hist.NumBins) ;
        for i := 0 to Hist.NumBins-1 do begin
            Hist.Bin[i].lo := x ;
            Hist.Bin[i].Mid := x + Hist.BinWidth / 2. ;
            Hist.Bin[i].Hi := x + Hist.BinWidth ;
            Hist.Bin[i].y := 0. ;
            x := x + Hist.BinWidth ;
            end ;

        { Fill histogram }
        n := 0 ;
        HScale := (Hist.NumBins) / ( Hist.Bin[Hist.NumBins-1].Mid
                                         - Hist.Bin[0].Mid ) ;
        HLowerLimit := Hist.Bin[0].Lo ;
        for Rec := RecStart to RecEnd do begin
            { Read record analysis block from file }
            GetRecordHeaderOnly( fH, rH, Rec ) ;
            if UseRecord( rH, cbTypeToBeAnalysed.text ) then begin
               x := rH.Analysis.Value[ HistCh, HistVar  ]
                    * rH.Analysis.UnitsScale[HistCh,HistVar] ;;
               i := Trunc( (x - HLowerLimit) * HScale ) ;
               i := MaxInt( [MinInt( [i,Hist.NumBins-1] ), 0] ) ;
               Hist.Bin[i].y := Hist.Bin[i].y + 1. ;
               if Hist.Bin[i].y > Hist.yHi then Hist.yHi := Hist.Bin[i].y ;
               n := n + 1 ;
               end ;
            end ;

        { Plot graph }
        case Destination of
             ToScreen : begin
                 { Set plot size/fonts for screen }
                 HistPlot.Left :=   pbHist.Width div 6 ;
                 HistPlot.Right :=  pbHist.Width - (pbHist.Width div 10) ;
                 HistPlot.Top :=    pbHist.Height div 8 ;
                 HistPlot.Bottom := pbHist.Height - HistPlot.Top*2 ;
                 pbHist.canvas.font.name := Settings.Plot.FontName ;
                 pbHist.canvas.font.size := Settings.Plot.FontSize ;
                 pbHist.canvas.pen.width := Settings.Plot.LineThickness ;
                 { Draw histogram }
                 DrawAxes( pbHist.canvas, HistPlot, Hist ) ;
                 DrawHistogram( pbHist.canvas, HistPlot, Hist ) ;
                 end ;

             ToPrinter : begin
                 { Set plot size/fonts for screen }
                 Printer.canvas.font.name := Settings.Plot.FontName  ;
                 Printer.canvas.font.height := PrinterPointsToPixels(
                                              Settings.Plot.FontSize) ;
                 HistPlot.Left := PrinterCmToPixels('H',Settings.Plot.LeftMargin) ;
                 HistPlot.Right := Printer.pagewidth -
                                PrinterCmToPixels('H',Settings.Plot.RightMargin) ;
                 HistPlot.Top := PrinterCmToPixels('V',Settings.Plot.TopMargin) ;
                 HistPlot.Bottom := Printer.pageheight -
                                 PrinterCmToPixels('V',Settings.Plot.BottomMargin) ;
                 HistPlot.MarkerSize := PrinterPointsToPixels(Settings.Plot.MarkerSize);
                 Printer.Canvas.Pen.Width := PrinterPointsToPixels(Settings.Plot.LineThickness);

                 xPix := Printer.PageWidth div 10 ;
                 yPix := Printer.PageHeight div 40 ;

                 Printer.BeginDoc ;

                 { Print file name and ident info }
                 printer.canvas.textout( xPix, yPix, 'File :' + FH.FileName ) ;
                 yPix := yPix + (12*Printer.Canvas.TextHeight('X')) div 10 ;
                 printer.canvas.textout( xPix, yPix, FH.IdentLine ) ;

                 { Draw histogram }
                 DrawAxes( Printer.canvas, HistPlot, Hist ) ;
                 DrawHistogram( Printer.canvas, HistPlot, Hist ) ;
                 Printer.EndDoc ;
                 end ;

             ToClipboardAsData : begin
                 { ** Copy graph data to clipboard ** }
                 CopyBuf0 := StrAlloc( BufSize ) ;
                 StrPCopy( CopyBuf0, '' ) ;
                 Line0 := StrAlloc(  256 ) ;
                 n := 0 ;
                 { Copy as list of ASCII numbers }
                 for i := 0 to Hist.NumBins-1 do begin
                     Line := format( '%10.4g', [Hist.Bin[i].Mid] ) + chr(9) +
                             format( '%10.4g', [Hist.Bin[i].y] ) +
                             + chr(13) + chr(10) ;
                     n := n + length(Line) ;
                     StrPCopy( Line0, Line ) ;
                     if n < BufLimit then CopyBuf0 := StrCat(CopyBuf0,Line0) ;
                     end ;
                 ClipBoard.SetTextBuf( CopyBuf0 ) ;
                 StrDispose( Line0 ) ;
                 StrDispose( CopyBuf0 ) ;
                 end ;

             ToClipboardAsImage : begin
                { ** Copy bitmap containing histogram to clipboard **}
                try
                   BMap := TBitmap.Create ;
                   BMap.Height := Settings.BitmapHeight ;
                   BMap.Width := Settings.BitmapWidth ;
                   HistPlot.Left :=   BMap.Width div 6 ;
                   HistPlot.Right :=  BMap.Width - (BMap.Width div 10) ;
                   HistPlot.Top :=    BMap.Height div 8 ;
                   HistPlot.Bottom := BMap.Height - HistPlot.Top*2 ;
                   BMap.Canvas.Pen.Width := HistPlot.LineThickness ;
                   BMap.canvas.font.name := Settings.Plot.FontName ;
                   BMap.canvas.font.size := Settings.Plot.FontSize ;
                   BMap.Canvas.Pen.Width := Settings.Plot.LineThickness ;
                   { ** Plot histogram ** }
                   BMap.Canvas.Brush.color := clWhite ;
                   DrawAxes( BMap.Canvas, HistPlot, Hist ) ;
                   DrawHistogram( BMap.canvas, HistPlot, Hist ) ;
                   Clipboard.Assign(BMap) ;
                finally
                   BMap.Free ;
                   end ;
                end ;

             end ;

        Hist.Free ;
        Screen.Cursor := crDefault ;
        end ;
     end ;



procedure TMeasureFrm.AnalyseWaveforms ;
{ ===================================================
  Analyse parameters of waveforms stored in data file
  ===================================================}
var
   i,j,MaxCh,ch,ChOffset,Rec : LongInt ;
   x,y,dx : single ;
   Peak,PeakAt,Peak10,Peak50,Peak90,Invert : LongInt ;
   PeakPositive,PeakPositiveAt : LongInt ;
   PeakNegative,PeakNegativeAt : LongInt ;
   Baseline : LongInt ;
   Diff,MaxDiff : LongInt ;
   iStart,iEnd,Temp : LongInt ;
   row,col : LongInt ;
   iY,i0,i1,i50,i90,j0,j1 : LongInt ;
   Sum,Residual,Avg,NumAvg : Single ;
begin

     { Read record data from file }
     GetRecord( fH, rH, AnalysisJob.RecordNum, ADC^ ) ;

     { Analyse only records selected according to UseRecord's criteria }

     rH.Analysis.Available := True ;
     if UseRecord( rH, cbTypeToBeAnalysed.text ) then begin

        if AnalysisJob.PreviousTime < 0. then AnalysisJob.PreviousTime := rH.Time ;

        for ch := 0 to fH.NumChannels-1 do begin

            rH.Analysis.Value[ch,vRecord] := AnalysisJob.RecordNum ;
            rH.Analysis.Value[ch,vGroup] := AnalysisJob.RecordNum ;
            rH.Analysis.Value[ch,vTime] := rH.Time ;
            rH.Analysis.Value[ch,vInterval] := rH.Time  - AnalysisJob.PreviousTime ;

            ChOffset := Channel[ch].ChannelOffset ;

            { Subtract zero level }
            for i := 0 to fH.NumSamples-1 do begin
                j := i*fH.NumChannels + ChOffset ;
                ADC^[j] := ADC^[j] - Channel[ch].ADCZero ;
                end ;

            { Get range of samples to be analysed from positions of cursors 0 and 1 }
            iStart := Channel[ch].Cursor0 ;
            iEnd := Channel[ch].Cursor1 ;
            if iStart > iEnd then begin
                  Temp := iStart ;
                  iStart := iEnd ;
                  iEnd := Temp ;
                  end ;

            { Calculate average and area within cursors 0-1 }
            Sum := 0. ;
            for i := iStart to iEnd do begin
                j := i*fH.NumChannels + ChOffset ;
                Sum := Sum + ADC^[j] ;
                end ;
            NumAvg := (iEnd - iStart) + 1 ;
            Avg := Sum / NumAvg ;
            rH.Analysis.Value[ch,vAverage] := Avg * Channel[ch].ADCScale ;
            rH.Analysis.Value[ch,vArea] := Sum * rH.dt * Channel[ch].ADCScale ;

            { Calculate variance within cursors 0-1 }
            Sum := 0. ;
            for i := iStart to iEnd do begin
                j := i*fH.NumChannels + ChOffset ;
                Residual := ADC^[j] - Avg ;
                Sum := Sum + Residual*Residual ;
                end ;
            Avg := Sum / NumAvg ;
            rH.Analysis.Value[ch,vVariance] := Avg * Channel[ch].ADCScale
                                                   * Channel[ch].ADCScale ;

            { Find peaks within cursor 0-1 region}
            PeakPositive := MinADCValue*2 ;
            PeakNegative := MaxADCValue*2 ;
            for i := iStart to iEnd do begin
                j := i*fH.NumChannels + ChOffset ;
                iY := ADC^[j] ;
                if iY > PeakPositive then begin
                   PeakPositive := iY ;
                   PeakPositiveAt := i ;
                   end ;
                if iY < PeakNegative then begin
                   PeakNegative := iY ;
                   PeakNegativeAt := i ;
                   end ;
                end ;

            case AnalysisJob.PeakMode of
                 PositivePeaks : begin
                     Peak := PeakPositive ;
                     PeakAt := PeakPositiveAt ;
                     Invert := 1 ;
                     end ;
                 NegativePeaks : begin
                     Peak := PeakNegative ;
                     PeakAt := PeakNegativeAt ;
                     Invert := -1 ;
                     end ;
                 AbsPeaks : begin
                     if Abs(PeakPositive) < Abs(PeakNegative) then begin
                        Peak := PeakNegative ;
                        PeakAt := PeakNegativeAt ;
                        Invert := -1 ;
                        end
                     else begin
                        Peak := PeakPositive ;
                        PeakAt := PeakPositiveAt ;
                        Invert := 1 ;
                        end ;
                     end ;
                 end ;
            rH.Analysis.Value[ch,vPeak] := Peak * Channel[ch].ADCScale ;

            { Calculate 10% - 90% rise time }
            Peak10 := Abs(Peak) div 10 ;
            Peak90 := Abs(Peak) - Peak10 ;
            i0 := PeakAt + 1;
            repeat
                  i0 := i0 - 1 ;
                  j := i0*fH.NumChannels + ChOffset ;
                  iY := ADC^[j]*Invert ;
                  if iY >= Peak90 then i1 := i0 ;
                  until (iY <= Peak10) or (i0 < iStart) ;
            rH.Analysis.Value[ch,vRiseTime] := (i1-i0)*rH.dt ;

            { Calculate latency as time from zero time cursor to 10% of peak }
            rH.Analysis.Value[ch,vLatency] := (i0 - CursorChannel.TZeroCursor )*rH.dt ;


            { Calculate max. rate of rise (over range from iStart ... peak ) }
            MaxDiff := 0 ;
            i0 := iStart ;
            i1 := iStart + AnalysisJob.RateOfRiseInterval ;
            repeat
                j0 := i0*fH.NumChannels + ChOffset ;
                j1 := i1*fH.NumChannels + ChOffset ;
                Diff := Invert*( ADC^[j1] - ADC^[j0] ) ;
                if Diff > MaxDiff then MaxDiff := Diff ;
                Inc(i0) ;
                Inc(i1) ;
                until i1 > iEnd ;
            rH.Analysis.Value[ch,vRateOfRise] := (MaxDiff * Invert *
                                                  Channel[ch].ADCSCale) /
                                                  (rH.dt*AnalysisJob.RateOfRiseInterval) ;

            { Calculate time to 50% and 90% decay }
            Peak50 := Abs(Peak) div 2 ;
            i90 := PeakAt - 1;
            repeat
                  Inc(i90) ;
                  j := i90*fH.NumChannels + ChOffset ;
                  iY := ADC^[j]*Invert ;
                  if iY >= Peak50 then i50 := i90 ;
                  until (iY <= Peak10) or (i > iEnd) ;
            rH.Analysis.Value[ch,vT50] := (i50-PeakAt)*rH.dt ;
            rH.Analysis.Value[ch,vT90] := (i90-PeakAt)*rH.dt ;


            { Baseline level determined from average of samples at TZero cursor
              (Note. TZero cursor always taken from channel 0) }
            i0 := MinInt([MaxInt([CursorChannel.TZeroCursor,0]),FH.NumSamples-1]) ;
            i1 := MinInt([i0 + FH.NumZeroAvg - 1,FH.NumSamples-1]) ;
            Sum := 0. ;
            for i := i0 to i1 do begin
                j := i*fH.NumChannels + ChOffset ;
                Sum := Sum + ADC^[j] ;
                end ;
            NumAvg := (i1 - i0) + 1 ;
            Avg := Sum / NumAvg ;
            rH.Analysis.Value[ch,vBaseline] := Avg*Channel[ch].ADCScale ;

            end ;

        { Update time of previous record }
        AnalysisJob.PreviousTime := rH.Time ;

        end ;

     { Save record header containing analysis results back to file  }
     PutRecordHeaderOnly( fH, rH, AnalysisJob.RecordNum ) ;
     edProgress.text := format(' %d/%d',[AnalysisJob.RecordNum,AnalysisJob.EndAt]) ;

     { Terminate the job if that was the last record }
     AnalysisJob.RecordNum := AnalysisJob.RecordNum + 1;
     if AnalysisJob.RecordNum > AnalysisJob.EndAt then begin
        AnalysisJob.RecordNum := AnalysisJob.EndAt ;
        AnalysisJob.Running := False ;
        { Request end-of-analysis processes }
        State := EndofAnalysis ;
        PlotRecord := True ;
        Screen.Cursor := crDefault ;
        end ;
     end ;


procedure TMeasureFrm.pbPlotPaint(Sender: TObject);
begin
     { Request record to be re-plotted on next timer tick }
     OldPageIndex := -1 ;
     end;

procedure TMeasureFrm.FormResize(Sender: TObject);
begin
     TabbedNoteBook.Width := Width - TabbedNoteBook.Left - 15 ;
     State := RefreshDisplay ;
     {OldPageIndex := -1 ;}
     end;

procedure TMeasureFrm.bCustomiseXYPlotClick(Sender: TObject);
{ Display Set Axes/Labels form to let user customise plot
  then request that the plot be re-displayed }
begin
     SetAxesFrm.SetPlot := XYPlot ;
     SetAxesFrm.SetPlot.FontName := pbHist.canvas.font.name ;
     SetAxesFrm.SetPlot.FontSize := pbHist.canvas.font.size ;

     SetAxesFrm.ShowModal ;

     if SetAxesFrm.ModalResult = mrOK then begin
        XYPlot := SetAxesFrm.SetPlot ;
        pbPlot.canvas.font.name := XYPlot.FontName ;
        pbPlot.canvas.font.size := XYPlot.FontSize ;
        XYPlot.XAxis.AutoRange := False ;
        XYPlot.YAxis.AutoRange := False ;
        State := DoXYPlot ;
        end ;
     end;


procedure TMeasureFrm.bNewHistogramClick(Sender: TObject);
{ -------------------------------------
  Request a new histogram to be plotted
  -------------------------------------}
begin
     State := doHistogram ;
     HistPlot.XAxis.AutoRange := True ;
     HistPlot.YAxis.AutoRange := True ;
     bCustomiseHistogram.Enabled := True ;
     HistogramAvailable := True ;
     end;

procedure TMeasureFrm.cbHistVariableChange(Sender: TObject);
var
   iCh,iVar : LongInt ;
begin
     { When the selected histogram variable is changed ...
       force a call to the histogram generation routine to update
       the histogram upper/lower limit boxes and erase the currently
       displayed histogram }
     State := NewHistVariable ;
     HistogramAvailable := False ;
     end;


procedure TMeasureFrm.CalculateSummary ;
{ ========================================================
  Calculate summary of waveform analysis data held in file
  ========================================================}
var
   Rec,RecStart,RecEnd,Ch,iVar,n : LongInt ;
   y : Single ;
begin

     screen.cursor := crHourGlass ;

     { Determine record range/channels to be plotted }
     GetIntRangeFromEditBox(edRecRange,RecStart,RecEnd,1,fH.NumRecords) ;

     { Update Min/Max range of values in file }
     for Rec :=RecStart to RecEnd do begin

         { Get record header containing analysis results }
         GetRecordHeaderOnly( fH, rH, Rec ) ;

         if Rec = RecStart then begin
            { Initialise Min/Max if this is first record }
            for iVar := 0 to rH.Analysis.NumVariables-1 do begin
                for ch := 0 to fH.NumChannels -1 do begin
                    rH.Analysis.MaxValue[ch,iVar] := -MaxSingle ;
                    rH.Analysis.MinValue[ch,iVar] := MaxSingle ;
                    rH.Analysis.Mean[ch,iVar] := 0. ;
                    rH.Analysis.StDev[ch,iVar] := 0. ;
                    rH.Analysis.StErr[ch,iVar] := 0. ;
                    rH.Analysis.Num := 0 ;
                    end ;
                end ;
            end ;

         if UseRecord( rH, cbTypeToBeAnalysed.text ) then begin
                for iVar := 0 to rH.Analysis.NumVariables-1 do begin
                    for ch := 0 to fH.NumChannels-1 do begin
                        { Min./Max. values }
                        if rH.Analysis.MaxValue[ch,iVar] < rH.Analysis.Value[ch,iVar] then
                           rH.Analysis.MaxValue[ch,iVar] := rH.Analysis.Value[ch,iVar] ;
                        if rH.Analysis.MinValue[ch,iVar] > rH.Analysis.Value[ch,iVar] then
                           rH.Analysis.MinValue[ch,iVar] := rH.Analysis.Value[ch,iVar] ;
                        { Mean }
                        rH.Analysis.Mean[ch,iVar] := rH.Analysis.Mean[ch,iVar]
                                                   + rH.Analysis.Value[ch,iVar] ;
                        end ;
                    end ;
                Inc(rH.Analysis.Num) ;
                end ;

         end ;

     { Calculate mean value for each variable}
     n := MaxInt( [rH.Analysis.Num, 1 ] ) ;
     for ch := 0 to fH.NumChannels-1 do
         for iVar := 0 to rH.Analysis.NumVariables-1 do
                rH.Analysis.Mean[ch,iVar]:= rH.Analysis.Mean[ch,iVar] / n ;

     { Re-read the analysis blocks to get residual deviations from mean }
     for Rec := RecStart to RecEnd do begin
         GetRecordHeaderOnly( fH, rH, Rec ) ;
         if UseRecord( rH, cbTypeToBeAnalysed.text ) then begin
            for iVar := 0 to rH.Analysis.NumVariables-1 do begin
                for ch := 0 to fH.NumChannels-1 do begin
                    y := rH.Analysis.Value[ch,iVar] - rH.Analysis.Mean[ch,iVar] ;
                    rH.Analysis.StDev[ch,iVar] := rH.Analysis.StDev[ch,iVar]
                                                  + y*y ;
                    end ;
                end ;
            end ;
         end ;

     { Calculate standard deviation and error for each variable}
     for ch := 0 to fH.NumChannels-1 do begin
         for iVar := 0 to rH.Analysis.NumVariables-1 do begin
             rH.Analysis.StDev[ch,iVar]:= sqrt( rH.Analysis.StDev[ch,iVar] /
                                          MaxInt([n - 1,1]) ) ;
             rH.Analysis.StErr[ch,iVar]:= rH.Analysis.StDev[ch,iVar]/sqrt( n ) ;
             end ;
         end ;

     screen.cursor := crDefault ;
     SummaryAvailable := True ;
     end ;


procedure TMeasureFrm.bCustomiseHistogramClick(Sender: TObject);
{ -----------------------------
  Customise histogram plot axes
  -----------------------------}
begin
     CustHistFrm.SetPlot := HistPlot ;
     CustHistFrm.ShowModal ;
     if CustHistFrm.ModalResult = mrOK then begin
        HistPlot := CustHistFrm.SetPlot ;
        HistPlot.XAxis.AutoRange := False ;
        HistPlot.YAxis.AutoRange := False ;
        State := DoHistogram ;
        end ;
     end;


procedure TMeasureFrm.FillSummaryTable( Destination : TDestination ) ;
{ =======================================================================
  Fill summary table with mean/stdevs etc of waveform measurement results
  =======================================================================}
const
     cName = 0 ;
     cMean = 1 ;
     cStDev = 2;
     cStErr = 3 ;
     cMin = 4 ;
     cMax = 5 ;
     cNum = 6 ;
var
   SumCh,Row,Col,iVar,MaxWidth,WidthLimit,TotalWidth : LongInt ;
   ColWidth,ColHeight,CharWidth,CharHeight,w,BoxLeft,BoxTop,TableTop : LongInt ;
begin
     if cbSummaryChannel.itemindex < 0 then cbSummaryChannel.itemindex := 0 ;
     SumCh := cbSummaryChannel.itemindex ;

     { Set up variables to be summarised }
     ckVariable0.caption := rH.Analysis.VarName[0] ;
     rH.Analysis.InSummary[0] := ckVariable0.checked ;
     ckVariable1.caption := rH.Analysis.VarName[1] ;
     rH.Analysis.InSummary[1] := ckVariable1.checked ;
     ckVariable2.caption := rH.Analysis.VarName[2] ;
     rH.Analysis.InSummary[2] := ckVariable2.checked ;
     ckVariable3.caption := rH.Analysis.VarName[3] ;
     rH.Analysis.InSummary[3] := ckVariable3.checked ;
     ckVariable4.caption := rH.Analysis.VarName[4] ;
     rH.Analysis.InSummary[4] := ckVariable4.checked ;
     ckVariable5.caption := rH.Analysis.VarName[5] ;
     rH.Analysis.InSummary[5] := ckVariable5.checked ;
     ckVariable6.caption := rH.Analysis.VarName[6] ;
     rH.Analysis.InSummary[6] := ckVariable6.checked ;
     ckVariable7.caption := rH.Analysis.VarName[7] ;
     rH.Analysis.InSummary[7] := ckVariable7.checked ;
     ckVariable8.caption := rH.Analysis.VarName[8] ;
     rH.Analysis.InSummary[8] := ckVariable8.checked ;
     ckVariable9.caption := rH.Analysis.VarName[9] ;
     rH.Analysis.InSummary[9] := ckVariable9.checked ;
     ckVariable10.caption := rH.Analysis.VarName[10] ;
     rH.Analysis.InSummary[10] := ckVariable10.checked ;
     ckVariable11.caption := rH.Analysis.VarName[11] ;
     rH.Analysis.InSummary[11] := ckVariable11.checked ;
     ckVariable12.caption := rH.Analysis.VarName[12] ;
     rH.Analysis.InSummary[12] := ckVariable12.checked ;
     ckVariable13.caption := rH.Analysis.VarName[13] ;
     rH.Analysis.InSummary[13] := ckVariable13.checked ;

     { Set column titles }
     Summary.ColCount := cNum + 1 ;
     Summary.RowCount := 2 ;
     Summary.cells[0,0] := 'Variable' ;
     Summary.cells[cMean,0] := 'Mean' ;
     Summary.cells[cStDev,0] := 'St. Dev.' ;
     Summary.cells[cStErr,0] := 'St. Error' ;
     Summary.cells[cMin,0] := 'Min.' ;
     Summary.cells[cMax,0] := 'Max.' ;
     Summary.cells[cNum,0] := '(n)' ;

     { Fill rows of table }
     Row := 0 ;
     for iVar := 0 to rH.Analysis.NumVariables-1 do begin
          if rH.Analysis.InSummary[iVar] then begin
             Row := Row + 1 ;
             Summary.RowCount := Row + 1 ;
             { Enter row of data }
             Summary.cells[cName,Row]  := rH.Analysis.VarName[iVar] ;
             if rH.Analysis.Units[SumCh,iVar] <> ' ' then
                Summary.cells[cName,Row]  := Summary.cells[cName,Row] + ' (' +
                                             rH.Analysis.Units[SumCh,iVar] + ')';
             Summary.cells[cMean,Row]  := format( '%8.4g',
                                          [rH.Analysis.Mean[SumCh,iVar] *
                                           rH.Analysis.UnitsScale[SumCh,iVar]] ) ;
             Summary.cells[cStDev,Row] := format( '%8.4g',
                                          [rH.Analysis.StDev[SumCh,iVar] *
                                          rH.Analysis.UnitsScale[SumCh,iVar]] ) ;
             Summary.cells[cStErr,Row] := format( '%8.4g',
                                          [rH.Analysis.StErr[SumCh,iVar] *
                                          rH.Analysis.UnitsScale[SumCh,iVar]] ) ;
             Summary.cells[cMin,Row]   := format( '%8.4g',
                                          [rH.Analysis.MinValue[SumCh,iVar] *
                                          rH.Analysis.UnitsScale[SumCh,iVar]] ) ;
             Summary.cells[cMax,Row]   := format( '%8.4g',
                                          [rH.Analysis.MaxValue[SumCh,iVar] *
                                          rH.Analysis.UnitsScale[SumCh,iVar]] ) ;
             Summary.cells[cNum,Row]   := format( '%d',[rH.Analysis.Num] ) ;
             end ;
          end ;

     { Set widths of columns to maximum of data }
     TotalWidth := 20 ;
     for Col := cName to cNum do begin
         MaxWidth := 0 ;
         for Row := 0 to Summary.RowCount do begin
             Summary.cells[Col,Row] := ' ' + TidyNumber(Summary.cells[Col,Row]) + ' ';
             ColWidth := Summary.canvas.TextWidth(Summary.cells[Col,Row]) ;
             if  ColWidth > MaxWidth then MaxWidth := ColWidth ;
             end ;
        Summary.ColWidths[Col] := MaxWidth ;
        TotalWidth := TotalWidth + MaxWidth ;
        end ;
     { Set width of summary table }
     WidthLimit := TabbedNoteBook.Width + TabbedNoteBook.Left - Summary.Left - 30 ;
     if TotalWidth < WidthLimit then Summary.Width := TotalWidth
                                else Summary.Width := WidthLimit ;

     case Destination of
          ToPrinter : PrintStringGrid( Summary ) ;
          ToClipboardAsData : CopyStringGrid( Summary ) ;
          end ;

     end ;


procedure TMeasureFrm.ckVariable0Click(Sender: TObject);
{ Request that the summary table be updated }
begin
     State := DoSummary ;
     end;

procedure TMeasureFrm.cbSummaryChannelChange(Sender: TObject);
begin
     { Request that the summary table be updated }
     State := DoSummary ;
     end;


procedure TMeasureFrm.FillTable( Destination : TDestination ) ;
{ ===================================================
  Fill table with selected waveform measurement lists
  ===================================================}
var
   StartAt,EndAt,Row,Col,Rec,n : LongInt ;
   PrinterText : TextFile ;
   Line : string ;
begin
     { Create column title }
     for Col := 0 to Table.ColCount-1 do begin
         if TableVariables[Col].InUse then begin
            Table.cells[Col,0] := VarNames[TableVariables[Col].Num] ;
            Table.cells[Col,1] := ChannelNames[TableVariables[Col].Chan] ;
            end
         else begin
            Table.cells[Col,0] := '' ;
            Table.cells[Col,1] := '' ;
            end ;
         end ;

     { Update table with data }

     GetIntRangeFromEditBox( edRecRange, StartAt,EndAt, 1, fH.NumRecords ) ;
     Table.RowCount := EndAt - StartAt + 3 ;
     n := 0 ;
     Row := 1 ;
     for Rec := StartAt to EndAt do begin
         { Read analysis data from record }
         GetRecordHeaderOnly( fH, rH, Rec ) ;
         { Only use ACCEPTED records of appropriate type }
         if UseRecord( rH, cbTypeToBeAnalysed.text ) then begin
            { Fill row with selected variables }
            Inc(Row) ;
            for Col := 0 to Table.ColCount-1 do begin
                if TableVariables[Col].InUse then
                   Table.cells[Col,Row] := format('%8.4g',
                   [rH.Analysis.Value[TableVariables[Col].Chan,
                                      TableVariables[Col].Num ] *
                    rH.Analysis.UnitsScale[TableVariables[Col].Chan,
                                      TableVariables[Col].Num]] )
                else Table.cells[Col,Row] := '' ;
                end ;
            Inc(n) ;
            end ;
         end ;
     Table.RowCount := n + 3 ;

     { Set width table }
     Table.Width := TabbedNoteBook.Width + TabbedNoteBook.Left - Table.Left - 30 ;

     case Destination of
          ToPrinter : PrintStringGrid( Table ) ;
          ToClipboardAsData : CopyStringGrid( Table ) ;
          end ;

     end ;


procedure TMeasureFrm.TableDblClick(Sender: TObject);
{ =======================================================
  Pop-up dialog box to set variable & channel for column
  =======================================================}
begin
     SetVarFrm.cbChannel.Items := ChannelNames ;
     SetVarFrm.cbChannel.ItemIndex := TableVariables[Table.Col].Chan ;
     setVarFrm.cbVariable.items := VarNames ;
     setVarFrm.cbVariable.Items.add('');

     { Set the combo boxes to the current column variable and channel }
     if TableVariables[Table.Col].InUse then begin
        SetVarFrm.cbVariable.ItemIndex := TableVariables[Table.Col].Num ;
        SetVarFrm.cbChannel.ItemIndex := TableVariables[Table.Col].Chan ;
        end
     else begin
        SetVarFrm.cbVariable.ItemIndex := -1 ;
        SetVarFrm.cbChannel.ItemIndex := 0 ;
        end ;

     SetVarFrm.Left := (Table.Col div Table.DefaultColWidth)
                       + Left + Table.Left + 10 ;
     SetVarFrm.Top := Top + Table.Top + 10 ;
     SetVarFrm.ShowModal ;

     if SetVarFrm.modalResult = mrOK then begin
        { If variable is blank disable this column }
        if SetVarFrm.cbVariable.text = '' then
           TableVariables[Table.Col].InUse := False
        else begin
             TableVariables[Table.Col].InUse := True ;
             TableVariables[Table.Col].Chan := SetVarFrm.cbChannel.ItemIndex ;
             TableVariables[Table.Col].Num := SetVarFrm.cbVariable.ItemIndex ;
             end ;
        State := DoTable ;
        end ;
     end;


procedure TMeasureFrm.TableMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
   sRect : TGridRect ;
   AtRow,AtCol : Integer ;
begin
     AtRow := (Y - Table.Top) div Table.DefaultRowHeight ;
     AtCol := (X - Table.Left) div Table.DefaultColWidth ;
     sRect.Left := AtCol ;
     sRect.Right := AtCol ;
     if  AtRow < 2 then begin
         sRect.Top := 0 ;
         sRect.Bottom := Table.RowCount-1 ;
         end
     else begin
         sRect.Top := AtRow ;
         sRect.Bottom := AtRow ;
         end;
     Table.Selection := sRect ;
     end ;


procedure TMeasureFrm.FormDeactivate(Sender: TObject);
begin
     Timer.enabled := False ;
     TimerBusy := False ;

     { Deallocate dynamic buffers }
     HeapBuffers( Deallocate ) ;

     Main.SetCopyMenu( False, False ) ; { Disable copy menu options}
     Main.Print1.Enabled := False ; { Disable printing }
     end;


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


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


procedure TMeasureFrm.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 := DoRecord ;
        end ;
     end;


procedure TMeasureFrm.FormClose(Sender: TObject; var Action: TCloseAction);
{ ---------------------------------------------
  Destroy the form to remove it from the screen
  ---------------------------------------------}
begin
     { Dellocate dynamic buffers }
     HeapBuffers( Deallocate ) ;

     Main.WaveformMeasurements.enabled := True ;
     Main.ShowMeasure.visible := False ;
     Action := CaFree ;
     end;

procedure TMeasureFrm.pbDisplayPaint(Sender: TObject);
begin
     { Request record to be re-plotted on next timer tick }
     OldPageIndex := -1 ;
     end;

procedure TMeasureFrm.pbHistPaint(Sender: TObject);
begin
{ Request record to be re-plotted on next timer tick }
     OldPageIndex := -1 ;
     end;

function TMeasureFrm.UseRecord ( const RecH : TRecHeader ;
                                 RecType : string ) : Boolean ;
{ -----------------------------------------------------
  Select record for inclusion on graph, histogram, etc.
  -----------------------------------------------------}
begin
     if (RecH.Status = 'ACCEPTED') and RecH.Analysis.Available
        and ( (RecH.RecType = RecType) or ( RecType = 'ALL') ) then
        UseRecord := True
     else UseRecord := False ;
     end ;

procedure TMeasureFrm.EdRecRangeTableChange(Sender: TObject);
begin
     edRecRange.text := edRecRangeTable.text ;
     SummaryAvailable := False ;
     end;

procedure TMeasureFrm.edRecRangeHistogramChange(Sender: TObject);
begin
     edRecRange.text := edRecRangeHistogram.text ;
     SummaryAvailable := False ;
     end;

procedure TMeasureFrm.EdRecRangeXYPlotChange(Sender: TObject);
begin
     edRecRange.text := edRecRangeXYPlot.text ;
     SummaryAvailable := False ;
     end;

procedure TMeasureFrm.edRecRangeSummaryChange(Sender: TObject);
begin
     edRecRange.text := edRecRangeSummary.text ;
     SummaryAvailable := False ;
     end;


procedure TMeasureFrm.SetPlotButtonStatus ;
begin
     if not SummaryAvailable then CalculateSummary ;
     { Enable X/Y and histogram plotting }
     if rH.Analysis.Num > 0 then begin
        bNewXYPlot.Enabled := True ;
        bCustomiseXYPlot.Enabled := True ;
        bNewHistogram.Enabled := True ;
        bCustomiseHistogram.Enabled := True ;
        end
     else begin
          bNewXYPlot.Enabled := False ;
          bCustomiseXYPlot.Enabled := False ;
          bNewHistogram.Enabled := False ;
          bCustomiseHistogram.Enabled := False ;
          end ;
     end ;


procedure TMeasureFrm.CreateVariable( const iNum : Integer ;
                         var Analysis : TAnalysis ;
                         const VName : string ;
                         const VUnits : Array of TString8 ;
                         const VScale : single )  ;
var
   ch : Integer ;
begin
     Analysis.VarName[iNum] := VName ;
     for ch := 0 to ChannelLimit do begin
         if ch > High(VUnits) then ch := High(VUnits) ;
         Analysis.Units[ch,iNum] := VUnits[ch] ;
         Analysis.UnitsScale[ch,iNum] := VScale ;
         end ;
    end ;


procedure TMeasureFrm.EdRecRangeTableKeyPress(Sender: TObject;
  var Key: Char);
begin
    if key = chr(13) then begin
        SummaryAvailable := False ;
        State :=DoTable ;
        end ;
     end;

procedure TMeasureFrm.edRecRangeSummaryKeyPress(Sender: TObject;
  var Key: Char);
begin
     if key = chr(13) then begin
        SummaryAvailable := False ;
        State :=DoSummary ;
        end ;
     end;


procedure TMeasureFrm.pbDisplayDblClick(Sender: TObject);
var
   i : Integer ;
begin
     ZoomFrm.ChOnDisplay := MouseOverChannel ;
     ZoomFrm.ShowModal ;
     { Refresh child windows that exist }
     with Main do
          for i := 0 to MDIChildCount-1 do MDICHildren[i].Refresh ;
     State := DoRecord ;
     MoveCursor := False ;
     CursorState := NoCursor ;
     end;

procedure TMeasureFrm.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
{ ------------------------
  Function key processing
  1/12/97 ... Ctrl-R changes Accepted/Rejected, Record types can be set by key
  -----------------------}
type
    TAction = (MoveCursor,ChangeRecord,None,NewRecordType,ChangeRejectedStatus) ;
var
   Action : TAction ;
   Step,OldPosition : Integer ;
   NewRecord : LongInt ;
   OldColor : TColor ;
begin
     { function keys only active when a signal record is displayed }
     if (TabbedNotebook.PageIndex = AnalysisPage) then begin
        case key of
          VK_LEFT : begin
             Action := MoveCursor ;
             Step := -1 ;
             end ;
          VK_RIGHT : begin
             Action := MoveCursor ;
             Step := 1 ;
             end ;
          VK_SUBTRACT : begin
             Action := ChangeRecord ;
             Step := -1 ;
             end ;
          VK_ADD : begin
             Action := ChangeRecord ;
             Step := 1 ;
             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
                 OldColor := Channel[CurCh].color ;
                 Case CursorState of
                      Cursor0 : begin
                           OldPosition := Channel[CurCh].Cursor0 ;
                           Channel[CurCh].Cursor0 := OldPosition + Step ;
                           Channel[CurCh].Color := clRed ;
                           MoveVerticalCursor( pbDisplay,Channel[CurCh].Cursor0,
                                               OldPosition,Channel[CurCh],lbCursor0) ;
                           end ;
                      Cursor1 : begin
                           OldPosition := Channel[CurCh].Cursor1  ;
                           Channel[CurCh].Cursor1 := OldPosition + Step ;
                           Channel[CurCh].Color := clRed ;
                           MoveVerticalCursor( pbDisplay,Channel[CurCh].Cursor1,
                                               OldPosition,Channel[CurCh],lbCursor1 ) ;
                           end ;
                      TZeroCursor : begin
                           OldPosition := CursorChannel.TZeroCursor  ;
                           CursorChannel.TZeroCursor := OldPosition + Step ;
                           Channel[0].TZeroCursor := CursorChannel.TZeroCursor ;
                           CursorChannel.color := clPurple ;
                           MoveVerticalCursor( pbDisplay,CursorChannel.TZeroCursor,
                                               OldPosition,CursorChannel,lbTZeroCursor ) ;
                           end ;
                      end ;
                 Channel[CurCh].Color := OldColor ;
                 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 ;
                RH.RecType := cbRecordType.text ;
                PutRecordHeaderOnly( fH, RH, fH.RecordNum ) ;
                end ;

             { Change record currently displayed }
             ChangeRecord : begin
                 sbRecordNum.SetFocus ;
                 If ckBadRecord.Checked then RH.Status := 'REJECTED'
                                        else RH.Status := 'ACCEPTED' ;
                 RH.RecType := cbRecordType.text ;
                 fH.RecordNum := sbRecordNum.Position ;
                 PutRecordHeaderOnly( fH, RH, fH.RecordNum ) ;
                 NewRecord := MinInt([MaxInt([FH.RecordNum + Step,1]),FH.NumRecords]) ;
                 sbRecordNum.Position := NewRecord ;
                 State := DoRecord ;
                 end ;

            ChangeRejectedStatus : begin
                 ckBadRecord.Checked := not ckBadRecord.Checked ;
                 If ckBadRecord.Checked then RH.Status := 'REJECTED'
                                        else RH.Status := 'ACCEPTED' ;
                 PutRecordHeaderOnly( fH, RH, fH.RecordNum ) ;
                 end ;
            end;
        end ;
     end;


procedure TMeasureFrm.FormShow(Sender: TObject);
begin

     { Allocate dynamic buffers }
     HeapBuffers( Allocate ) ;

     { Set tick in Windows menu }
     Main.ClearTicksInWindowsMenu ;
     Main.ShowMeasure.checked := True ;
     end;

procedure TMeasureFrm.edRateofRiseIntervalKeyPress(Sender: TObject;
  var Key: Char);
var
   x : single ;
begin
     if key = chr(13) then begin
        x := GetFromEditBox( edRateofRiseInterval, RawFH.dt*SecsToMs,
                          RawFH.dt*SecsToMs, RawFH.dt*RawFH.NumSamples*SecsToMs,
                          ' %.3g',' ms' )*MsToSecs ;
        x := MaxInt([Trunc(x/RawFH.dt),1])*RawFH.dt ;
        edRateofRiseInterval.text := format(' %.3g ms',[x*SecsToMs]) ;
        end ;
     end;

end.
