unit Curvfit;
{ ==========================================================================
  WinWCP - Curve fitting module (c) J Dempster, 1996, All Rights Reserved
  V1.7f 16/7/97 ... Blocks of data in table and Summary grid can now be
                    selected for copying to clipboard
  V1.7c 11/9/97 ... X-Y plots now hold 6000 points
                    Histograms now copied to clipboard as Bin Mid,Y pairs
  1/12/97 Accepted/rejected toggling added (Ctrl-R)
          Ctrl-T etc sets record type
  24/2/98 ... +/- keys now step forward/back through records
  23/3/98 ... Curve fitting data buffer skip factor now correctly set,
              avoiding crashes when working on large records
              User is now also warned when record is too large for fitting
  12/4/99 ... V2.2 Residual standard deviation added to variable list

  ==========================================================================}
interface

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

type

  TDestination = ( ToScreen, ToPrinter, ToClipboardAsData, ToClipboardAsImage ) ;
  TFitFrm = class(TForm)
    TabbedNotebook: TTabbedNotebook;
    lbTMin: TLabel;
    lbTMax: TLabel;
    RecordGrp: TGroupBox;
    edRecordNum: TEdit;
    ckBadRecord: TCheckBox;
    sbRecordNum: TScrollBar;
    AnalysisGrp: TGroupBox;
    bDoFit: TButton;
    bCancel: TButton;
    ResultsGrp: TGroupBox;
    sgResults: TStringGrid;
    pbPlot: TPaintBox;
    XYPlotGrp: TGroupBox;
    bNewXYPlot: TButton;
    XGroup: TGroupBox;
    cbXVariable: TComboBox;
    YGroup: TGroupBox;
    cbYVariable: TComboBox;
    GroupBox1: TGroupBox;
    Label12: TLabel;
    EdRecRangeXYPlot: TEdit;
    edRecTypeXYPlot: TEdit;
    bCustomiseXYPlot: TButton;
    pbHist: TPaintBox;
    HistGrp: TGroupBox;
    bNewHistogram: TButton;
    GroupBox3: TGroupBox;
    Label11: TLabel;
    Label18: TLabel;
    Label19: TLabel;
    Label8: TLabel;
    cbHistVariable: TComboBox;
    edNumBins: TEdit;
    EdHistMin: TEdit;
    edHistMax: TEdit;
    Recs: TGroupBox;
    Label17: TLabel;
    edRecRangeHistogram: TEdit;
    EdRecTypeHistogram: TEdit;
    bCustomiseHistogram: TButton;
    VariablesGrp: TGroupBox;
    ckVariable0: TCheckBox;
    ckVariable1: TCheckBox;
    ckVariable2: TCheckBox;
    ckVariable3: TCheckBox;
    ckVariable4: TCheckBox;
    ckVariable5: TCheckBox;
    ckVariable6: TCheckBox;
    ckVariable7: TCheckBox;
    ckVariable8: TCheckBox;
    ckVariable9: TCheckBox;
    ckVariable10: TCheckBox;
    Summary: TStringGrid;
    GroupBox4: TGroupBox;
    Label9: TLabel;
    edRecRangeSummary: TEdit;
    edRecTypeSummary: TEdit;
    Table: TStringGrid;
    GroupBox7: TGroupBox;
    Label13: TLabel;
    EdRecRangetable: TEdit;
    EdRecTypeTable: TEdit;
    Timer: TTimer;
    cbEquation: TComboBox;
    Label10: TLabel;
    lbTZeroCursor: TLabel;
    GroupBox6: TGroupBox;
    edResidualSD: TEdit;
    Label16: TLabel;
    edNumIterations: TEdit;
    Label20: TLabel;
    pbResiduals: TPaintBox;
    bSetParameters: TButton;
    CursorsGrp: TGroupBox;
    cbDataLimitCursors: TComboBox;
    Label1: TLabel;
    edLimits: TEdit;
    rbManualCursors: TRadioButton;
    rbAutoCursors: TRadioButton;
    EdDegreesFreedom: TEdit;
    Label14: TLabel;
    EdInfo: TEdit;
    lbCursor0: TLabel;
    lbCursor1: TLabel;
    pbDisplay: TPaintBox;
    RangeGrp: TGroupBox;
    edRecRange: TEdit;
    rbThisRecord: TRadioButton;
    rbRecordRange: TRadioButton;
    cbTypeToBeAnalysed: TComboBox;
    Label7: TLabel;
    Label6: TLabel;
    cbFitChannel: TComboBox;
    rbAllRecords: TRadioButton;
    Label2: TLabel;
    cbRecordType: TComboBox;
    procedure FormCreate(Sender: TObject);
    procedure TimerTimer(Sender: TObject);
    procedure FormPaint(Sender: TObject);
    procedure sbRecordNumChange(Sender: TObject);
    procedure FormDestroy(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 bDoFitClick(Sender: TObject);
    procedure bCancelClick(Sender: TObject);
    procedure bNewXYPlotClick(Sender: TObject);
    procedure bNewHistogramClick(Sender: TObject);
    procedure bCustomiseXYPlotClick(Sender: TObject);
    procedure cbHistVariableChange(Sender: TObject);
    procedure bCustomiseHistogramClick(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 edRecordNumKeyPress(Sender: TObject; var Key: Char);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure bSetParametersClick(Sender: TObject);
    procedure cbFitChannelChange(Sender: TObject) ;
    procedure TabbedNotebookChange(Sender: TObject; NewTab: Integer;
      var AllowChange: Boolean);
    procedure pbDisplayPaint(Sender: TObject);
    procedure pbResidualsPaint(Sender: TObject);
    procedure pbPlotPaint(Sender: TObject);
    procedure pbHistPaint(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure EdRecRangeXYPlotChange(Sender: TObject);
    procedure edRecRangeHistogramChange(Sender: TObject);
    procedure cbEquationChange(Sender: TObject);
    procedure pbDisplayDblClick(Sender: TObject);
    procedure FormKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure FormShow(Sender: TObject);
    procedure cbRecordTypeChange(Sender: TObject);

  private
    { Private declarations }
    procedure DisplayRecord ;
    procedure DisplayXYPlot( Destination : TDestination )  ;
    procedure FitWaveform ;
    procedure DisplayHistogram( Destination : TDestination )  ;
    procedure CalculateSummary ;
    procedure FillSummaryTable( Destination : TDestination ) ;
    procedure FillTable( Destination : TDestination ) ;
    procedure ResetForNewFile ;
    procedure NewVariableNames ;
    function GetFitValues : Boolean ;
    procedure SetFittingDataCursors( var TZero,iStart,iEnd : Integer ) ;
    procedure SetSummaryCheckBox( var CheckBox : TCheckBox ; iVar : LongInt  ) ;
    function UseRecord ( const RecH : TRecHeader ; RecType : string ) : Boolean ;
    procedure SetPlotButtonStatus ;
    procedure HeapBuffers( Operation : THeapBufferOp ) ;
    procedure PlotRecord( Destination : TDestination ) ;
    procedure CopyRecordToClipBoard ;
  public
    { Public declarations }
    NewFile :Boolean ;
    CopyToClipBoardAsData : Boolean ;
    CopyToClipBoardAsImage : Boolean ;
    PrintHardCopy : boolean ;
    TimerBusy : boolean ;
  end;

var
  FitFrm: TFitFrm;



implementation

uses SSQUnit,Zoom, mdiform, setbmap,copyrec ;
{$R *.DFM}

type
    TState = ( DoRecord, DoXYPlot, DoFit, DoHistogram, DoSummary,
               NewHistVariable, Idle, EndOfFitting, DoTable, RefreshDisplay,
               DoHardCopy, CopyToClip ) ;

    TCursorState = ( Cursor0,
                     Cursor1,
                     TZeroCursor,
                     ZeroLevelCursor,
                     NoCursor ) ;

    TFitJob = record
                 Running : Boolean ;
                 StartAt : LongInt ;
                 EndAt : LongInt ;
                 RecordNum : LongInt ;
                 Eqn : TEquation ;
                 end ;

    TFit = record
          VarName : array[0..FitVarLimit] of string[12] ;
          Units : array[0..FitVarLimit] of string[8] ;
          Value : array[0..FitVarLimit] of single ;
          ValueSD : array[0..FitVarLimit] of single ;
          MaxValue : array[0..FitVarLimit] of single ;
          MinValue : array[0..FitVarLimit] of single ;
          Mean : array[0..FitVarLimit] of single ;
          StDev : array[0..FitVarLimit] of single ;
          StErr : array[0..FitVarLimit] of single ;
          InSummary : array[0..FitVarLimit] of Boolean ;
          Num : LongInt ;
          NumVariables : LongInt ;
          end ;


    TTableVars = record
           Num : LongInt ;
           Chan : LongInt ;
           InUse : Boolean ;
           end ;
const
     NumFitChannels = 3 ;
     LastFitChannel = 2 ;
     ChData = 0 ;
     ChFit = 1 ;
     ChRes = 2 ;
     AnalysisPage = 0 ;
     XYPlotPage = 1 ;
     HistogramPage = 2 ;
     SummaryPage = 3 ;
     TablePage = 4 ;
var
   State : TState ;
   CursorState : TCursorState ;
   CurCh : LongInt ;
   MoveCursor : Boolean ;
   CursorChannel : TChannel ;
   ADC : ^TIntArray ;
   FitJob : TFitJob ;
   OldPageIndex : LongInt ;
   XYPlotAvailable : Boolean ;
   HistogramAvailable : Boolean ;
   SummaryAvailable : Boolean ;
   VarNames : TStringList ;
   TableVariables : array[0..7] of TTableVars ;
   RH : ^TRecHeader ; { Record header }
   XYPlot : ^TPlot ;
   HistPlot : ^TPlot ;
   ToClipboard : TDestination ;
   FitChannel : Array[0..LastFitChannel] of TChannel ;
   Fit : TFit ;
   BuffersAllocated : boolean ;{ Indicates if memory buffers have been allocated }


procedure TFitFrm.HeapBuffers( Operation : THeapBufferOp ) ;
{ -----------------------------------------------
  Allocate/deallocation dynamic buffers from heap
  -----------------------------------------------}
begin
     case Operation of
          Allocate : begin
             if not BuffersAllocated then begin
                New(ADC) ;
                New(HistPlot) ;
                New(XYPlot) ;
                New(RH) ;
                VarNames := TStringList.Create ;
                BuffersAllocated := True ;
                end ;
             end ;
          Deallocate : begin
             if BuffersAllocated then begin
                Dispose(ADC) ;
                Dispose(HistPlot) ;
                Dispose(XYPlot) ;
                Dispose(RH) ;
                BuffersAllocated := False ;
                VarNames.Free ;
                end ;
             end ;
          end ;
     end ;

procedure TFitFrm.FormCreate(Sender: TObject);
{ ----------------------------------------------------------------------------
  Initialisations when form is created ... Only done once when program starts
  ----------------------------------------------------------------------------}
var
   i : LongInt ;
   EquationList : TStringList ;
begin
     { Disable curve fitting item in Analysis menu }
     Main.CurveFitting.enabled := false ;

     { Create memory buffers }
     BuffersAllocated := False ;
     HeapBuffers( Allocate ) ;

     { Set flag in main form indicating that the curve fitting form is open }
     Main.ShowFit.visible := True ;

     Width := TabbedNoteBook.width + 50 ;
     Height := TabbedNoteBook.Height + 50 ;

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

     { Get list of equations available for fitting }
     EquationList := TStringList.Create ;
     GetEquationList( EquationList ) ;
     cbEquation.items := EquationList ;
     if cbEquation.ItemIndex < 0 then cbEquation.ItemIndex := 0 ;
     EquationList.Free ;
     if cbDataLimitCursors.ItemIndex < 0 then cbDataLimitCursors.ItemIndex := 1 ;

     { 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 ;
     Screen.Cursor := crDefault ;
     end;


procedure TFitFrm.ResetForNewFile ;
{ -----------------------------------------------------
  Reset variables and channel when a new file is loaded
  -----------------------------------------------------}
var
   i,ch,Par : LongInt ;

begin

     caption := 'Fit ' + fH.FileName ;

     { Initialise Fit Channel combo box }
     cbFitChannel.items := ChannelNames ;
     if cbFitChannel.ItemIndex < 0 then cbFitChannel.ItemIndex := 0 ;

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

     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]) ;
        end
     else begin
          edRecRange.text := 'None' ;
        end ;

     { If analysis area cursors overlap set them to default (whole record) }
     FitJob.Eqn.Cursor0 := 10 ;
     FitJob.Eqn.Cursor1 := fH.NumSamples - 10 ;
     FitJob.Eqn.TZeroCursor := 0 ;
     FitJob.Eqn.ParametersSet := False ;

     { Create a set of variable names for currently selected equation }
     NewVariableNames ;

     { 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 := 4 ;
     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 TFitFrm.NewVariableNames ;
{ ---------------------------------
  Create new set of variable names
  --------------------------------}
var
   Par,i,ch,nPars : LongInt ;
   Name : String ;
   Equation : TEquation ;
begin

     i := 0 ;
     Fit.VarName[i] := 'Record' ;
     Fit.Units[i] := ' ' ;
     Inc(i) ;

     Fit.VarName[i] := 'Group' ;
     Fit.Units[i] := ' ' ;
     Inc(i) ;

     Fit.VarName[i] := 'Time' ;
     Fit.Units[i] := 's' ;
     Inc(i) ;

     { Equation parameters }
     Equation.EqnType := TEqnType(cbEquation.ItemIndex) ;
     nPars := GetNumEquationParameters( Equation ) ;
     for Par := 0 to nPars-1 do begin
         GetParameterInfo( Equation, Fit.VarName[i], Fit.Units[i],Settings.TUnits,
         Channel[cbFitChannel.ItemIndex].ADCUnits, Par ) ;
         Inc(i) ;
         end ;

     { Averages within limits cursor (for ALL channels except the fitting one) }

     for ch := 0 to FH.NumChannels-1 do begin
         if ch <> cbFitChannel.ItemIndex then begin
            Fit.VarName[i] := ' Avg. (' + Channel[ch].ADCName + ')' ;
            Fit.Units[i] := Channel[ch].ADCUnits ;
            Inc(i) ;
            end ;
         end ;

     { Residual standard deviation }
     Fit.VarName[i] := 'ResSD' ;
     Fit.Units[i] := Channel[ch].ADCUnits ;
     Inc(i) ;

     Fit.NumVariables := i ;

     { Fill string list object with variable names }
     VarNames.Clear ;
     for i := 0 to Fit.NumVariables-1 do VarNames.Add( Fit.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 := 3 ;
     cbHistVariable.items := VarNames  ;
     if cbHistVariable.ItemIndex < 0  then cbHistVariable.ItemIndex := 0 ;

     end ;


procedure TFitFrm.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 = 0 then begin
              { Analysis }
              GetIntRangeFromEditBox(edRecRange,FitJob.StartAt,FitJob.EndAt,
                                     1, fH.NumRecords ) ;
              State := DoRecord ;
              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
                 if (FH.NumRecords > 0) then begin
                    { If the record is too big ... warn user and close window }
                    if FH.NumSamples <= (MaxPoints div 3) then
                        DisplayRecord
                    else begin
                        MessageDlg( format('Record too large to fit (limit=%d samples)',
                                  [MaxPoints div 3] ),mtWarning, [mbOK], 0 ) ;
                        close ;
                        end ;
                    end ;
                MoveCursor := False ;
                if FitJob.Running then State := DoFit
                                  else State := Idle ;
                Main.SetCopyMenu( True, True ) ;
                Main.Print1.Enabled := True ;
                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 ;
             DoHardCopy : begin
                State := Idle ;
                if (TabbedNotebook.PageIndex = AnalysisPage) then
                   PlotRecord( ToPrinter ) ;
                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 = AnalysisPage) then begin
                   if ToClipboard = ToClipboardAsData then CopyRecordToClipboard
                                                      else PlotRecord( ToClipboard ) ;
                   end ;
                if (TabbedNotebook.PageIndex = XYPlotPage) then
                   DisplayXYPlot( ToClipboard ) ;
                if (TabbedNotebook.PageIndex = HistogramPage) then
                   DisplayHistogram( ToClipboard ) ;
                if (TabbedNotebook.PageIndex = SummaryPage) then
                   CopyStringGrid( Summary ) ;
                if (TabbedNotebook.PageIndex = TablePage) then
                   CopyStringGrid( Table ) ;
                end ;
             DoFit : begin
                FitWaveform ;
                end ;
             EndofFitting : begin
                { Tidy up after a curve fitting run has been completed }
                edRecRange.text := format(' %d-%d',
                                    [FitJob.StartAt,FitJob.RecordNum] ) ;
                bDoFit.enabled := True ;
                bCancel.enabled := False ;
                CalculateSummary ;
                FillSummaryTable( ToScreen ) ;
                { Enable X/Y and histogram plotting }
                FitJob.Running := False ;
                State := DoRecord ;
                end ;
             NewHistVariable : begin
                iVar := cbHistVariable.ItemIndex ;
                SetPlotButtonStatus ;
                if not SummaryAvailable then CalculateSummary ;
                if Fit.Num > 0 then begin
                   edHistMin.text := format( '%8.3g', [Fit.MinValue[iVar] ] ) ;
                   edHistMax.text := format( '%8.3g', [Fit.MaxValue[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 }
                 if not SummaryAvailable then CalculateSummary ;
                 FillSummaryTable( ToScreen ) ;
                 Main.SetCopyMenu( True, False ) ;
                 Main.Print1.Enabled := True ;
                 State := Idle
                 end ;
             DoTable : Begin
                  { Fill table of selected results }
                  FillTable( ToScreen ) ;
                  Main.SetCopyMenu( True, False ) ;
                  Main.Print1.Enabled := True ;
                  State := Idle ;
                  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 := DoHardCopy ;
                   PrintHardCopy := False ;
                   end ;

                if Screen.Cursor <> crDefault then Screen.Cursor := crDefault ;
                end ;
             end ;

        TimerBusy := False ;

        end ;
     end;


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


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


procedure TFitFrm.FormDestroy(Sender: TObject);
begin
     HeapBuffers( Deallocate ) ;
     end;


procedure TFitFrm.pbDisplayMouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
{ -----------------------------------------------------------------
  Select and move zero level and analysis area cursors over display
  -----------------------------------------------------------------}
Const
     Margin = 4 ;
var
   OldIndex : Integer ;
   OldLevel : LongInt ;
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,FitJob.Eqn.Cursor0,CursorChannel ) then begin
             CursorState := Cursor0  ;
             pbDisplay.Cursor := crSizeWE ;
             end ;
          { Is mouse over cursor 1 ? }
          if OverVerticalCursor(X,FitJob.Eqn.Cursor1,CursorChannel ) then begin
             CursorState := Cursor1  ;
             pbDisplay.Cursor := crSizeWE ;
             end ;

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

          { Is mouse over zero level ? }
          if OverHorizontalCursor( Y, FitChannel[ChData] ) 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
              CursorChannel.color := clRed ;
              VerticalCursorScale( X,FitJob.Eqn.Cursor0,OldIndex,CursorChannel);
              MoveVerticalCursor(pbDisplay,FitJob.Eqn.Cursor0,OldIndex,
                                 CursorChannel,lbCursor0) ;
              end ;

          { Move cursor 1 }
          Cursor1 : begin
              CursorChannel.color := clRed ;
              VerticalCursorScale( X,FitJob.Eqn.Cursor1,OldIndex,CursorChannel);
              MoveVerticalCursor(pbDisplay,FitJob.Eqn.Cursor1,OldIndex,
                                 CursorChannel,lbCursor1) ;
              end ;

          { Move time zero cursor  }
          TZeroCursor : begin
              CursorChannel.color := clPurple ;
              VerticalCursorScale( X,FitJob.Eqn.TZeroCursor,OldIndex,CursorChannel);
              MoveVerticalCursor(pbDisplay,FitJob.Eqn.TZeroCursor,OldIndex,
                                 CursorChannel,lbTZeroCursor) ;
              end ;

          ZeroLevelCursor : Begin
              HorizontalCursorScale( Y,FitChannel[ChData].ADCZero,OldLevel,
                                     FitChannel[ChData]);
              Channel[cbFitChannel.ItemIndex].ADCZero := FitChannel[ChData].ADCZero ;
              MoveHorizontalCursor(pbDisplay,FitChannel[ChData].ADCZero,OldLevel,
                                   FitChannel[ChData]) ;
              end ;

          else ;
          end ;
          end ;
      end ;


procedure TFitFrm.pbDisplayMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
{ -----------------------------------------------------------
  Activated zero level setup form if right button was pressed
  -----------------------------------------------------------}
var
   OldLevel : LongInt ;
begin
     MoveCursor := False ;
     if (Button = mbRight) and (CursorState = ZeroLevelCursor) then begin
        Channel[ZeroFrm.ChSel].ADCZero := FitChannel[ChData].ADCZero ;
        OldLevel := FitChannel[ChData].ADCZero ;
        ZeroFrm.ChSel := cbFitChannel.ItemIndex ;
        ZeroFrm.NewZeroAt := Trunc( (X - Channel[0].Left) / Channel[0].xScale
                                        + Channel[0].xMin ) ;
        ZeroFrm.ShowModal ;
        FitChannel[ChData].ADCZero := Channel[ZeroFrm.ChSel].ADCZero ;
        MoveHorizontalCursor(pbDisplay,FitChannel[ChData].ADCZero,OldLevel,
                             FitChannel[ChData]) ;
        State := DoRecord ;
        end ;
     end;


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


procedure TFitFrm.bDoFitClick(Sender: TObject);
{ -------------------
  Start curve fitting
  -------------------}
begin
     if rbAllRecords.checked then begin
        { Analyse all records in file }
        FitJob.StartAt := 1 ;
        FitJob.EndAt := FH.NumRecords ;
        edRecRange.text := format( ' %d-%d', [FitJob.StartAt,FitJob.EndAt] ) ;
        end
     else if rbThisRecord.checked then begin
        { Analyse the currently displayed record }
        FitJob.StartAt := FH.RecordNum ;
        FitJob.EndAt := FH.RecordNum ;
        end
     else begin
          { Analyse the user entered range of records }
          GetIntRangeFromEditBox( edRecRange, FitJob.StartAt,FitJob.EndAt,
                                  1, fH.NumRecords ) ;
          end ;
     FitJob.RecordNum := FitJob.StartAt ;
     FitJob.Eqn.Channel := cbFitChannel.ItemIndex ;
     FitJob.Running := True ;
     bDoFit.enabled := False ;
     bCancel.enabled := True ;
     Screen.cursor := crHourGlass ;
     State := DoFit ;
     end;


procedure TFitFrm.bCancelClick(Sender: TObject);
{ -------------------
  Abort curve fitting
  -------------------}
begin
     FitJob.Running := False ;
     bDoFit.enabled := True ;
     bCancel.enabled := False ;
     Screen.Cursor := crDefault ;
     State := EndOfFitting ;
     end;


procedure TFitFrm.bNewXYPlotClick(Sender: TObject);
{ --------------------
  Request an X/Y Plot
  -------------------}
var
   AutoRange : Boolean ;
   i : LongInt ;
begin
     State := doXYPlot ;
     XYPlot^.XAxis.AutoRange := True ;
     XYPlot^.YAxis.AutoRange := True ;
     XYPlot^.XAxis.log := False ;
     XYPlot^.YAxis.log := False ;
     XYPlotAvailable := True ;
     end;


procedure TFitFrm.DisplayRecord ;
{ ========================================================
  Display digitised signal record on Page 0 of notebook
  ========================================================}
var
   i,j,MaxCh,ch,ChOffset,Rec,iFrom,iTo,FittedCh,nPars,Par : LongInt ;
   x,y,dx,t,TScale : single ;
   xPix,yPix,nOffScreen : Integer ;
   row,col : LongInt ;
   Name : string ;
   ParName : String[10] ;
   ParUnits : String[10] ;

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

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

     { If bad cursor settings ... reset them }
     if (rH^.Equation.Cursor0 < 0) or (rH^.Equation.Cursor0 > FH.NumSamples-1) or
        (rH^.Equation.Cursor1 < 0) or (rH^.Equation.Cursor1 > FH.NumSamples-1) or
        (rH^.Equation.TZeroCursor < 0) or (rH^.Equation.TZeroCursor > FH.NumSamples-1) or
        (rH^.Equation.Cursor0 = rH^.Equation.Cursor1) then begin
        rH^.Equation.Available := False ;
        rH^.Equation.Cursor0 := 10 ;
        rH^.Equation.Cursor1 := FH.NumSamples-10 ;
        rH^.Equation.TZeroCursor := 0 ;
        end ;

     { If there is equation data available for this record
       and a fitting run is NOT in progress ... update the fitting equation }
     if (not FitJob.Running) and rH^.Equation.Available
        and (not FitJob.Eqn.ParametersSet) then begin
            FitJob.Eqn := rH^.Equation ;
            cbFitChannel.ItemIndex := FitJob.Eqn.Channel ;
            end ;

     { Copy into first channel position in ADC for fitting}
     ChOffset :=  Channel[cbFitChannel.ItemIndex].ChannelOffset ;
     for i := fH.NumSamples-1 downto 0 do begin
         iFrom := i*fH.NumChannels + ChOffset ;
         iTo := i*NumFitChannels + ChData ;
         ADC^[iTo] := ADC^[iFrom] ;
         end ;

     FitChannel[ChData] := Channel[cbFitChannel.ItemIndex] ;
     InitializeDisplay( FitChannel, 1, rH^, lbTMin,lbTMax, pbDisplay) ;

     for ch := 1 to High(FitChannel) do FitChannel[ch] := FitChannel[0] ;
     FitChannel[ChRes].yScale := (FitChannel[ChData].yScale *
                                  pbResiduals.Height) / pbDisplay.Height ;
     FitChannel[ChRes].yScale := FitChannel[ChRes].yScale*5. ;
     FitChannel[ChRes].yMin := - (0.5*pbResiduals.height) /
                                  FitChannel[ChRes].yScale ;
     FitChannel[ChRes].Top := 0 ;
     FitChannel[ChRes].Bottom := pbResiduals.Height ;
     FitChannel[ChRes].ADCZero := 0 ;

     CursorChannel := FitChannel[ChData] ;

     EraseDisplay(pbDisplay) ;
     EraseDisplay(pbResiduals) ;
     pbResiduals.Canvas.Textout( 1,1, 'Residuals (X5)' ) ;

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

     if rH^.Equation.Available then begin

        { Update equation selection list ... if none set yet }
        if TEqnType(cbEquation.ItemIndex) = None then begin
           cbEquation.ItemIndex := Integer(rH^.Equation.EqnType) ;
           NewVariableNames ;
           end ;

        nPars := GetNumEquationParameters( rH^.Equation ) ;

        FitChannel[ChFit].InUse := True ;
        FitChannel[ChFit].Color := clRed ;
        FitChannel[ChRes].InUse := True ;
        FitChannel[ChRes].Color := clRed ;

        for i := 0 to fH.NumSamples-1 do begin
            t := (i - rH^.Equation.TZeroCursor)*rH^.dt ;
            if t >= 0. then y := MathFunc( RH^.Equation, t )
                       else y := 0. ;
            j := i*NumFitChannels ;
            ADC^[j+ChFit] := Trunc( (y/FitChannel[ChData].ADCScale)
                                     + FitChannel[ChData].ADCZero ) ;
            ADC^[j+ChRes] := ADC^[j+ChData] - ADC^[j+ChFit] ;
            end ;
        end
     else begin
        FitChannel[ChFit].InUse := False ;
        FitChannel[ChRes].InUse := False ;
        end ;

     FitChannel[ChData].InUse := True ;

     { Plot A/D data and best fitting line (if available) }

     for ch := 0 to NumFitChannels-1 do begin
         nOffScreen := 0 ;
         if FitChannel[ch].InUse then begin
            pbDisplay.canvas.pen.color := FitChannel[ch].color ;
            pbResiduals.canvas.pen.color := FitChannel[ch].color ;
            dx := FitChannel[ch].xScale ;
            x := -FitChannel[ch].xScale*FitChannel[ch].xMin + FitChannel[ch].Left ;
            for i := 0 to fH.NumSamples-1 do begin
                y := ADC^[(i*NumFitChannels) + ch ] ;
                xPix := Trunc(x) ;
                yPix := Trunc( FitChannel[ch].Bottom
                            - FitChannel[ch].yScale*(y - FitChannel[ch].yMin));

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

                if (nOffScreen > 1) or (i=0) then begin
                   if ch <> ChRes then pbDisplay.canvas.moveto(xPix,yPix)
                                  else pbResiduals.canvas.moveto(xPix,yPix) ;
                   end
                else begin
                   if ch <> ChRes then pbDisplay.canvas.lineto(xPix,yPix)
                                  else pbResiduals.canvas.lineto(xPix,yPix) ;
                   end ;
                x := x + dx ;
                end ;
            end ;
         end ;

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

     { Draw cursors }
     FitJob.Eqn.Cursor0 := MinInt([MaxInt([FitJob.Eqn.Cursor0,0]),fh.NumSamples-1]) ;
     CursorChannel.color := clRed ;
     DrawCursor( pbDisplay, FitJob.Eqn.Cursor0,CursorChannel, lbCursor0) ;

     FitJob.Eqn.Cursor1 := MinInt([MaxInt([FitJob.Eqn.Cursor1,0]),fh.NumSamples-1]) ;
     CursorChannel.color := clRed ;
     DrawCursor( pbDisplay, FitJob.Eqn.Cursor1,CursorChannel, lbCursor1);

     FitJob.Eqn.TZeroCursor := MinInt([MaxInt([FitJob.Eqn.TZeroCursor,0]),
                                                 fh.NumSamples-1]) ;
     CursorChannel.color := clPurple ;
     DrawCursor(pbDisplay,FitJob.Eqn.TZeroCursor,CursorChannel,lbTZeroCursor);

     DrawHorizontalCursor( pbDisplay,FitChannel[ChData],FitChannel[ChData].ADCZero) ;

     { Draw zero line on residuals plot }
     DrawHorizontalCursor( pbResiduals,FitChannel[ChRes],FitChannel[ChRes].ADCZero) ;

     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 ;

     { Fill results table for the record Col 0 = Variable Name Col 1 = Value }
     sgResults.ColCount := 3 ;
     sgResults.RowCount := 1 ;
     sgResults.cells[0,0] := 'Par.' ;
     sgResults.cells[1,0] := 'Best Fit' ;
     sgResults.cells[2,0] := 'St. Error' ;
     edDegreesFreedom.text := '' ;
     edResidualSD.text := '' ;
     edNumIterations.text := '' ;
     edInfo.text := format( 'Rec. %d  at %f s (from Group %d) ',
                            [fH.RecordNum,rH^.Time,Trunc(rH^.Number)]);

     if rH^.Equation.Available then begin
        { Get information about fitting equation }
        nPars := GetNumEquationParameters( rH^.Equation ) ;
        sgResults.RowCount := nPars + fH.NumChannels ;
        Row := 1 ;
        for Par := 0 to nPars-1 do begin
            GetParameterInfo( RH^.Equation, ParName, ParUnits,Settings.TUnits,
                              Channel[cbFitChannel.ItemIndex].ADCUnits, Par ) ;
            sgResults.cells[0,row] := ParName ;
            if ParUnits = Settings.TUnits then TScale := Settings.TScale
                                          else TScale := 1. ;
            sgResults.cells[1,row] := format('%.3g',[TScale*rH^.Equation.Par[Par]] )
                                           + '  ' + ParUnits ;
            if rH^.Equation.ParSD[Par] = -1. then
               { St. Dev. = -1 ... parameter has been fixed }
               sgResults.cells[2,row] := '(Fixed)'
            else
               sgResults.cells[2,row] := format( '%.3g',
                                         [TScale*rH^.Equation.ParSD[Par]] )
                                           + '  '+ ParUnits ;
            Inc(Row) ;
            end ;
        { Averages from other channels }
        for ch := 0 to fH.NumChannels-1 do begin
            if ch <> cbFitChannel.ItemIndex then begin
               sgResults.cells[0,row] := 'Avg. (' + Channel[ch].ADCName + ')';
               sgResults.cells[1,row] := format('%.3g',[rH^.Equation.Average[ch]] )
                                           + '  ' + Channel[ch].ADCUnits ;
               sgResults.cells[2,row] := ' ' ;
               end ;
            end ;

        edDegreesFreedom.text := format('%d',[rH^.Equation.DegreesFreedom]) ;
        edResidualSD.text :=  format( '%8.3g', [rH^.Equation.ResidualSD] )
                              + ' ' + FitChannel[0].ADCUnits ;
        edNumIterations.text := format( '%d', [rH^.Equation.NumIterations] ) ;

        EdInfo.Text := edInfo.text
                       + ' Eqn: ' + cbEquation.Items[Integer(rH^.Equation.EqnType)];
        end
     else EdInfo.Text := edInfo.text + ' No fit' ;

     end ;


procedure TFitFrm.FitWaveform ;
{ -------------------------------------------------------
  Fit a mathematical function to part of digitised record
  -------------------------------------------------------}
var
   i,j,k,nPoints,ChOffset,nPars,ch,iStart,iEnd,iStep : LongInt ;
   Sum : Single ;
   Data : ^TXYData ;
begin

    FitJob.Eqn.EqnType := TEqnType(cbEquation.ItemIndex) ;

    if FitJob.Eqn.EqnType = None then begin
        { Clear any fitted equation }
        { Read record data from file }
        GetRecordHeaderOnly( fH, rH^, FitJob.RecordNum ) ;
        FitChannel[ChData] := Channel[FitJob.Eqn.Channel] ;
        rH^.Equation.Available := False ;
        { Save record header containing analysis results back to file  }
        PutRecordHeaderOnly( fH, rH^, FitJob.RecordNum ) ;
        end
     else begin
          { ** Fit an equation ** }
          { Allocate space for data to be fitted }
          New(Data) ;

          { Read record data from file }
          GetRecord( fH, rH^, FitJob.RecordNum, ADC^ ) ;
          FitChannel[ChData] := Channel[FitJob.Eqn.Channel] ;

          { Only use ACCEPTED records of appropriate type }
          rH^.Equation.Available := True ;
          if UseRecord(rH^,cbTypeToBeAnalysed.text) then begin

             if rbAutoCursors.checked then
                SetFittingDataCursors( FitJob.Eqn.TZeroCursor,
                                       FitJob.Eqn.Cursor0,
                                       FitJob.Eqn.Cursor1) ;

             { Get range of samples to be fitted from positions of cursors 0 and 1 }
             if FitJob.Eqn.Cursor1 > FitJob.Eqn.Cursor0 then begin
                iStart := FitJob.Eqn.Cursor0 ;
                iEnd := FitJob.Eqn.Cursor1 ;
                end
             else begin
                iStart := FitJob.Eqn.Cursor1 ;
                iEnd := FitJob.Eqn.Cursor0 ;
                end ;

             { Calculate average of data within cursors for each channel }
             for ch := 0 to fh.NumChannels-1 do begin
                 ChOffset :=  Channel[ch].ChannelOffset ;
                 Sum := 0. ;
                 for i := iStart to iEnd do begin
                     j := i*fH.NumChannels + ChOffset ;
                     Sum := Sum + ADC^[j] ;
                     end ;
                 FitJob.Eqn.Average[ch] := Channel[ch].ADCScale *
                                ( Sum /(iEnd-iStart+1) - Channel[ch].ADCZero ) ;
                 end ;

             { Copy data points into SSQMIN's fitting array
               (if necessary miss points to make it fit) }
             nPoints := 0 ;
             ChOffset := Channel[FitJob.Eqn.Channel].ChannelOffset ;
             iStep := MaxInt( [((iEnd - iStart) + High(Data^.x)+1)
                                 div (High(Data^.x)+1),1]) ;
             i := iStart ;
             repeat
                 Data^.x[nPoints] := (i - FitJob.Eqn.TZeroCursor) * rH^.dt ;
                 j := i*fH.NumChannels + ChOffset ;
                 Data^.y[nPoints] := ( ADC^[j]- FitChannel[ChData].ADCZero )
                                     * FitChannel[ChData].ADCScale ;
                 i := i + iStep ;
                 if nPoints < High(Data^.x) then Inc(nPoints) ;
                 until i > iEnd ;

             { Get type of equation to be fitted }
             nPars := GetNumEquationParameters( FitJob.Eqn ) ;

             { Fit curve if there is a valid equation and enough points }
             if (nPoints > nPars) and (FitJob.Eqn.EqnType <> None) then begin
                { Fit curve }
                FitCurve( Data^, nPoints, FitJob.Eqn ) ;
                rH^.Equation := FitJob.Eqn ;
                end
             else rH^.Equation.Available := false ;

             { Save record header containing analysis results back to file  }
             PutRecordHeaderOnly( fH, rH^, FitJob.RecordNum ) ;

             Dispose(Data) ;
             end ;
          end ;

     { Terminate the job if that was the last record }
     sbRecordNum.position := FitJob.RecordNum ;
     Inc(FitJob.RecordNum) ;
     if FitJob.RecordNum > FitJob.EndAt then begin
           FitJob.RecordNum := FitJob.EndAt ;
           FitJob.Running := False ;
           { Request end-of-analysis processes }
           State := EndofFitting ;
           Screen.Cursor := crDefault ;
           end
     else State := DoRecord ;

     end ;


procedure TFitFrm.DisplayXYPlot( Destination : TDestination ) ;
{ ==========================================================
  Draw the X/Y plot of one waveform variable against another
  ==========================================================}
const
     { Clipboard copy buffer limits }
     BufSize = 65100 ;
     BufLimit = 65000 ;
var
   Rec,RecStart,RecEnd,XCh,XVar,YCh,YVar,n,i : LongInt ;
   xy : TxyBuf ;
   xPix,yPix : LongInt ;
   OK,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 ;


     { Set up plotting area (on screen or printer page
       ===============================================}

     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 ;

     { Do plotting ...
       ===============}

     if PlotOK then begin

        Screen.Cursor := crHourglass ;
        { Create X/Y plotting buffer object}
        xy := TxyBuf.create ;

        XYPlot^.Title := 'The title' ;
        XYPlot^.NumGraphs := 1 ;

        { Determine records, channels & variables to be plotted }
        GetIntRangeFromEditBox( edRecRange, RecStart,RecEnd, 1, fH.NumRecords ) ;
        XVar := cbXVariable.ItemIndex ;
        YVar := cbYVariable.ItemIndex ;

        { Create X and Y axes labels }
        XYPlot^.xAxis.lab := Fit.VarName[xVar] + ' ' + Fit.Units[xVar] ;
        XYPlot^.yAxis.lab := Fit.VarName[yVar] + ' ' + Fit.Units[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) then begin
               OK := GetFitValues ;
               if OK and ( n < High(xy.x) ) then begin
                  xy.x[n] := Fit.Value[ XVar  ] ;
                  xy.y[n] := Fit.Value[ YVar ] ;
                  Inc(n) ;
                  end ;
               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 ;
                   xPix := Printer.PageWidth div 10 ;
                   yPix := Printer.PageHeight div 40 ;
                   { 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 ) ;
                   { 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 TFitFrm.DisplayHistogram( Destination : TDestination ) ;
{ ========================================================
  Plot the histogram of a selected waveform variable
  ========================================================}
const
     { Clipboard copy buffer limits }
     BufSize = 65100 ;
     BufLimit = 65000 ;
var
   Rec,RecStart,RecEnd,HistCh,HistVar,n,i : LongInt ;
   x,HScale,HLowerLimit : Single ;
   xPix,yPix : LongInt ;
   Hist : THistBuf ;
   OldBrush : TBrush ;
   OK,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 display area }
        pbHist.width := TabbedNotebook.width + TabbedNotebook.left - pbPlot.left - 30 ;
        pbHist.canvas.FillRect( pbHist.canvas.ClipRect ) ;
        pbHist.Canvas.Brush := OldBrush ;
        end ;

     { Set up plotting area (on screen or printer page
       ===============================================}

     if HistogramAvailable and (Fit.Num > 0) 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 ;

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

        { Determine records, channels & variables to be plotted }
        GetIntRangeFromEditBox( edRecRange, RecStart,RecEnd, 1, fH.NumRecords ) ;
        HistCh := FitJob.Eqn.Channel ;
        HistVar := cbHistVariable.ItemIndex ;

        { Create X and Y axes labels }
        HistPlot^.xAxis.lab := Fit.VarName[HistVar] + ' ' + Fit.Units[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
               OK := GetFitValues ;
               if OK then begin
                  x := Fit.Value[ 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 ;
                  Inc(n) ;
                  end ;
               end ;
            end ;

        { Plot histogram }
        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 ;

                 printer.canvas.textout( xPix, yPix, 'File :' + FH.FileName ) ;
                 { 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 TFitFrm.bNewHistogramClick(Sender: TObject);
{ -------------------------------------
  Request a new histogram to be plotted
  -------------------------------------}
begin
     State := doHistogram ;
     HistPlot^.XAxis.AutoRange := True ;
     HistPlot^.YAxis.AutoRange := True ;
     HistogramAvailable := True ;
     end;


procedure TFitFrm.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.ShowModal ;
     if SetAxesFrm.ModalResult = mrOK then begin
        XYPlot^ := SetAxesFrm.SetPlot ;
        XYPlot^.XAxis.AutoRange := False ;
        XYPlot^.YAxis.AutoRange := False ;
        State := DoXYPlot ;
        end ;
     end;


procedure TFitFrm.cbHistVariableChange(Sender: TObject);
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 TFitFrm.CalculateSummary ;
{ ========================================================
  Calculate summary of curve fitting data held in file
  ========================================================}
var
   Rec,RecStart,RecEnd,Ch,iVar,n : LongInt ;
   y, LoValue, HiValue : Single ;
   OK : Boolean ;
begin

     screen.cursor := crHourGlass ;

     GetRangeFromEditBox( edRecRange, LoValue, HiValue, 1., fH.NumRecords,
                                '%.0f-%.0f','') ;
     RecStart := Trunc(LoValue) ;
     RecEnd := Trunc(HiValue) ;

     { 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 Fit.NumVariables-1 do begin
                Fit.MaxValue[iVar] := -MaxSingle ;
                Fit.MinValue[iVar] := MaxSingle ;
                Fit.Mean[iVar] := 0. ;
                Fit.StDev[iVar] := 0. ;
                Fit.StErr[iVar] := 0. ;
                Fit.Num := 0 ;
                end ;
            end ;

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

         end ;

     { Calculate mean value for each variable}

     n := MaxInt([Fit.Num,1]) ;
     for iVar := 0 to Fit.NumVariables-1 do
         Fit.Mean[iVar]:= Fit.Mean[iVar] / n ;

     { Re-read the analysis blocks to get residual deviations from mean }
     for Rec := FitJob.StartAt to FitJob.EndAt do begin
         GetRecordHeaderOnly( fH, rH^, Rec ) ;
         if UseRecord(rH^,cbTypeToBeAnalysed.text) then begin
                OK := GetFitValues ;
                if OK then begin
                   for iVar := 0 to Fit.NumVariables-1 do begin
                       y := Fit.Value[iVar] - Fit.Mean[iVar] ;
                       Fit.StDev[iVar] := Fit.StDev[iVar] + y*y ;
                       end ;
                   end ;
                end ;
         end ;

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

     screen.cursor := crDefault ;
     end ;


function TFitFrm.GetFitValues : Boolean ;
{ ---------------------------------------------------------------
  Extract best fit parameters and other values from record header
  and put into Fit.Values array
  ---------------------------------------------------------------}
var
   i,j,nPars,ch : LongInt ;
begin
     if rH^.Equation.Available and
        (rh^.Equation.EqnType = TEqnType(cbEquation.ItemIndex)) then begin
        { Get information about fitting equation }
        nPars := GetNumEquationParameters( rH^.Equation ) ;

        j := 0 ;
        Fit.Value[j] := fH.RecordNum ;
        Inc(j) ;
        Fit.Value[j] := rh^.Number ;
        Inc(j) ;
        Fit.Value[j] := rh^.Time ;
        Inc(j) ;

        { Parameter values }
        for i := 0 to nPars-1 do begin
            Fit.Value[j] := rh^.Equation.Par[i] ;
            { Convert s --> ms if necessary}
            if Fit.Units[j] = 'ms' then  Fit.Value[j] := Fit.Value[j]*1000. ;
            Inc(j) ;
            end ;

        { Averages from other channels }
        for ch := 0 to fH.NumChannels-1 do begin
            if ch <> cbFitChannel.ItemIndex then begin
               Fit.Value[j] := rH^.Equation.Average[ch] ;
               Inc(j) ;
               end ;
            end ;

        Fit.Value[j] := rH^.Equation.ResidualSD ;
        Inc(j) ;

        GetFitValues := True ;
        end
     else GetFitValues := False ;

     end ;


procedure TFitFrm.bCustomiseHistogramClick(Sender: TObject);
{ -----------------------------
  Customise histogram plot axes
  -----------------------------}
begin
     CustHistFrm.SetPlot := HistPlot^ ;
     CustHistFrm.ShowModal ;

     if CustHistFrm.ModalResult = mrOK then begin
        HistPlot^ := CustHistFrm.SetPlot ;
        State := DoHistogram ;
        end ;
     end;


procedure TFitFrm.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,ColWidth,WidthLimit,TotalWidth : LongInt ;
begin
     { Set up check boxes which selecy which variables are to be
       included in the summary }
     SetSummaryCheckBox( ckVariable0, 0 ) ;
     SetSummaryCheckBox( ckVariable1, 1 ) ;
     SetSummaryCheckBox( ckVariable2, 2 ) ;
     SetSummaryCheckBox( ckVariable3, 3 ) ;
     SetSummaryCheckBox( ckVariable4, 4 ) ;
     SetSummaryCheckBox( ckVariable5, 5 ) ;
     SetSummaryCheckBox( ckVariable6, 6 ) ;
     SetSummaryCheckBox( ckVariable7, 7 ) ;
     SetSummaryCheckBox( ckVariable8, 8 ) ;
     SetSummaryCheckBox( ckVariable9, 9 ) ;
     SetSummaryCheckBox( ckVariable10, 10 ) ;

     { 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 Fit.NumVariables-1 do begin
          if Fit.InSummary[iVar] then begin
             Row := Row + 1 ;
             Summary.RowCount := Row + 1 ;
             { Enter row of data }
             Summary.cells[cName,Row]  := Fit.VarName[iVar] ;
             if Fit.Units[iVar] <> ' ' then
                Summary.cells[cName,Row]  := Summary.cells[cName,Row] + ' (' +
                                             Fit.Units[iVar] + ')';
             Summary.cells[cMean,Row]  := format( '%8.4g',[Fit.Mean[iVar]] ) ;
             Summary.cells[cStDev,Row] := format( '%8.4g',[Fit.StDev[iVar]] ) ;
             Summary.cells[cStErr,Row] := format( '%8.4g',[Fit.StErr[iVar]] ) ;
             Summary.cells[cMin,Row]   := format( '%8.4g',[Fit.MinValue[iVar]] ) ;
             Summary.cells[cMax,Row]   := format( '%8.4g',[Fit.MaxValue[iVar]] ) ;
             Summary.cells[cNum,Row]   := format( '%d',[Fit.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 TFitFrm.SetSummaryCheckBox( var CheckBox : TCheckBox ; iVar : LongInt) ;
begin
     CheckBox.caption := Fit.VarName[iVar] ;
     Fit.InSummary[iVar] := CheckBox.checked ;
     if iVar < Fit.NumVariables then CheckBox.Visible := True
                                else CheckBox.Visible := False ;
     end ;


procedure TFitFrm.FillTable( Destination : TDestination ) ;
{ ===================================================
  Fill table with selected waveform measurement lists
  ===================================================}
var
   Row,Col,Rec,n,nVar : LongInt ;
   OK : Boolean ;
begin
     { Create column title }
     for Col := 0 to Table.ColCount-1 do begin
         if (TableVariables[Col].InUse) then begin
            nVar := MinInt( [TableVariables[Col].Num,VarNames.count-1] );
            Table.cells[Col,0] := VarNames[nVar] ;
            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 }
     Table.RowCount := FitJob.EndAt - FitJob.StartAt + 3 ;
     n := 0 ;
     Row := 1 ;
     for Rec := FitJob.StartAt to FitJob.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
            OK := GetFitValues ;
            { Fill row with selected variables }
            if OK then begin
               Row := Row + 1 ;
               for Col := 0 to Table.ColCount-1 do begin
                   if TableVariables[Col].InUse then begin
                       nVar := MinInt( [TableVariables[Col].Num,VarNames.count-1] );
                       Table.cells[Col,Row] := format('%8.4g',[Fit.Value[nVar]]) ;
                       end
                   else Table.cells[Col,Row] := '' ;
                   Inc(n) ;
                   end ;
               end ;
            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 TFitFrm.TableDblClick(Sender: TObject);
{ =======================================================
  Pop-up dialog box to set variable & channel for column
  =======================================================}
begin
     SetVarFrm.cbChannel.clear ;
     setVarFrm.cbChannel.Items.add( Channel[cbFitChannel.ItemIndex].ADCName ) ;
     SetVarFrm.cbChannel.ItemIndex := 0 ;

     { Variable name selection combo box }
     setVarFrm.cbVariable.items := VarNames ;
     { Add a blank on to the end so user can deselect }
     setVarFrm.cbVariable.Items.add('');

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

     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].Num := SetVarFrm.cbVariable.ItemIndex ;
             end ;
        State := DoTable ;
        end ;
     end;


procedure TFitFrm.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 TFitFrm.FormActivate(Sender: TObject);
begin
     { Set tick in Windows menu }
     Main.ClearTicksInWindowsMenu ;
     Main.ShowFit.checked := True ;
     TimerBusy := False ;
     Timer.enabled := TRue ;
     end;

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


procedure TFitFrm.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 TFitFrm.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 TFitFrm.FormClose(Sender: TObject; var Action: TCloseAction);
{ ---------------------------------------------
  Destroy the form to remove it from the screen
  ---------------------------------------------}
begin
     Main.ShowFit.visible := False ;
     Main.CurveFitting.enabled := true ;
     Action := caFree ;
     end;


procedure TFitFrm.bSetParametersClick(Sender: TObject);
var
   i,iStart,iEnd,j,k,nPoints,ChOffset,iStep : LongInt ;
   Data : ^TXYData ;
begin

     { Find initial guesses ... if not already done }
     if not FitJob.Eqn.ParametersSet then begin
        New(Data) ;
        { Read record data from file }
        GetRecord( fH, rH^, sbRecordNum.Position, ADC^ ) ;
        FitChannel[ChData] := Channel[FitJob.Eqn.Channel] ;
        FitJob.Eqn.EqnType := TEqnType(cbEquation.ItemIndex) ;

        { Get range of samples to be fitted from positions of cursors 0 and 1 }
        if FitJob.Eqn.Cursor1 > FitJob.Eqn.Cursor0 then begin
           iStart := FitJob.Eqn.Cursor0 ;
           iEnd :=   FitJob.Eqn.Cursor1 ;
           end
        else begin
             iStart := FitJob.Eqn.Cursor1 ;
             iEnd :=   FitJob.Eqn.Cursor0 ;
        end ;

        { Copy data points into SSQMIN's fitting array }
        nPoints := 0 ;
        ChOffset := Channel[FitJob.Eqn.Channel].ChannelOffset ;
        iStep := MaxInt( [((iEnd - iStart) + High(Data^.x)+1)
                                 div (High(Data^.x)+1),1]) ;
        i := iStart ;
        repeat
            Data^.x[nPoints] := (i - FitJob.Eqn.TZeroCursor) * rH^.dt ;
            j := i*fH.NumChannels + ChOffset ;
            Data^.y[nPoints] := ( ADC^[j]- FitChannel[ChData].ADCZero )
                                * FitChannel[ChData].ADCScale ;
            i := i + iStep ;
            if nPoints < High(Data^.x) then Inc(nPoints) ;
            until i >= iEnd ;

        InitialiseParameters( Data^, nPoints, FitJob.Eqn ) ;
        Dispose(Data) ;
        end ;

     FitSetupFrm.Equation :=FitJob.Eqn ;
     FitSetupFrm.xUnits := Settings.TUnits ;
     FitSetupFrm.yUnits := FitChannel[ChData].ADCUnits ;
     FitSetupFrm.ShowModal ;
     if FitSetupFrm.ModalResult = mrOK then begin
        FitJob.Eqn := FitSetupFrm.Equation ;
        FitJob.Eqn.ParametersSet := True ;
        end ;

     end;

procedure TFitFrm.SetFittingDataCursors( var TZero,iStart,iEnd : Integer ) ;
{ -------------------------------------------------------
  Automatically define the range of sample points to be used in the curve fit
  by finding the location of a transient within the record
  and placing cursors on the rising or decay phase or the whole transient
  Returns
  TZero : the t=0 time for the fitting curve
  iStart : the first sample used in the fit
  iEnd : the last sample used in the fit
  -----------------------------------------------------------------------}
var
   y,yPeak,i,j,iPeak,MakePositive,yHi,yLo,yZero,ChOffset : LongInt ;
   RiseStart,RiseEnd,DecayStart,DecayEnd,nValues : LongInt ;
   Values : Array[0..1] of Single ;
begin

     yZero := Channel[FitJob.Eqn.Channel].ADCZero ;
     ChOffset := Channel[FitJob.Eqn.Channel].ChannelOffset ;

     { Find the location of the peak of the transient }
     yPeak := 0 ;
     iPeak := 0 ;
     for i := 0 to fH.NumSamples-1 do begin
         j := i*fH.NumChannels + ChOffset ;
         y := ADC^[j] - yZero ;
         if Abs(y) > Abs(yPeak) then begin
            yPeak := y ;
            iPeak := i ;
            end ;
         end ;

     { If the waveform is negative-going ... invert it for processing }
     if yPeak > 0 then MakePositive := 1
                  else MakePositive := -1 ;

     { Get the amplitude limits of the data to be used in the fit
       Default is 5%-95% of peak amplitude }

     nValues := ExtractListofFloats( edLimits.text, Values, True ) ;
     if nValues = 1 then
        Values[1] := Values[0]
     else if nValues <= 0 then begin
        Values[0] := 5. ;
        Values[1] := 95. ;
        end ;
     yLo := Trunc( yPeak*MakePositive*Abs(MinFlt(Values))*0.01 );
     yHi := Trunc( yPeak*MakePositive*Abs(MaxFlt(Values))*0.01 ) ;
     edLimits.text := format( '%.1f - %.1f',[Values[0],Values[1]] ) ;

     { Find decay phase of waveform }
     DecayEnd := iPeak ;
     DecayStart := iPeak ;
     repeat
           j := DecayEnd*fH.NumChannels + ChOffset ;
           y := (ADC^[j] - yZero)*MakePositive ;
           if y >= yHi then DecayStart := DecayEnd ;
           Inc(DecayEnd) ;
           until (y < yLo) or (DecayEnd >= (fH.NumSamples-1)) ;

     { Find rising phase of waveform }
     RiseEnd := iPeak ;
     RiseStart := iPeak ;
     repeat
           j := RiseStart*fH.NumChannels + ChOffset ;
           y := (ADC^[j] - yZero)*MakePositive ;
           if y >= yHi then RiseEnd := RiseStart ;
           Dec(RiseStart) ;
           until (y < yLo) or (RiseStart <= 0) ;

    if cbDataLimitCursors.text = 'On Rise' then begin
       { Place cursors on rising phase of waveform }
       iStart := RiseStart ;
       iEnd := RiseEnd ;
       TZero := iStart ;
       end
    else if cbDataLimitCursors.text = 'On Decay' then begin
       { Place cursors on decay phase of waveform }
       iStart := DecayStart ;
       iEnd := DecayEnd ;
       TZero := iPeak ;
       end
    else begin
         { Place cursors on whole waveform }
         iStart := RiseStart ;
         iEnd := DecayEnd ;
         TZero := RiseStart ;
         end ;
    end ;


procedure TFitFrm.cbFitChannelChange(Sender: TObject);
begin
     State := DoRecord ;
     end;



procedure TFitFrm.TabbedNotebookChange(Sender: TObject; NewTab: Integer;
  var AllowChange: Boolean);
begin
     OldPageIndex := -1 ;
end;

procedure TFitFrm.pbDisplayPaint(Sender: TObject);
begin
     { Ensure that display is repainted if paintbox area is erased
       by TabbedNotebook ... This happens when the page is viewed for
       the first time }
     OldPageIndex := -1 ;
     end;

procedure TFitFrm.pbResidualsPaint(Sender: TObject);
begin
     {Ensure that display is repainted if paintbox area is erased
       by TabbedNotebook ... This happens when the page is viewed for
       the first time }
     OldPageIndex := -1 ;
end;

procedure TFitFrm.pbPlotPaint(Sender: TObject);
begin
     { Ensure that display is repainted if paintbox area is erased
       by TabbedNotebook ... This happens when the page is viewed for
       the first time }
     OldPageIndex := -1 ;
end;

procedure TFitFrm.pbHistPaint(Sender: TObject);
begin
     {Ensure that display is repainted if paintbox area is erased
       by TabbedNotebook ... This happens when the page is viewed for
       the first time }
     OldPageIndex := -1 ;
end;

procedure TFitFrm.FormResize(Sender: TObject);
var
   MinWidth,MinHeight,Bottom : LongInt ;
begin
     { Set size of Notebook }
     TabbedNoteBook.Width := ClientWidth - 2*TabbedNoteBook.Left - 5;
     MinWidth := ResultsGrp.Left + ResultsGrp.Width + 30 ;
     if TabbedNoteBook.Width < MinWidth then TabbedNoteBook.Width := MinWidth ;
     {TabbedNoteBook.Height := ClientHeight - TabbedNoteBook.Top - 5 ;}
     MinHeight := AnalysisGrp.Height + AnalysisGrp.top + 10 ;
{                  + 10 - TabbedNoteBook.top;}
     if TabbedNoteBook.Height < MinHeight then TabbedNoteBook.Height := MinHeight ;
     ClientHeight := TabbedNoteBook.Top + TabbedNoteBook.Height + 2 ;
     Bottom := TabbedNoteBook.Height  - 35 ;
     AnalysisGrp.Height := Bottom -  AnalysisGrp.top ;
     ResultsGrp.Top := Bottom - ResultsGrp.Height ;
     XYPlotGrp.Height := Bottom - XYPlotGrp.Top ;
     HistGrp.Height := Bottom - HistGrp.Top ;
     Table.Width := TabbedNoteBook.Width - 2*Table.Left - 5 ;
     {Set size of plotting areas within notebook }

     pbResiduals.Width := TabbedNoteBook.Width - pbDisplay.Left - 50 ;
     pbResiduals.Top := ResultsGrp.Top - pbResiduals.Height - lbTMin.Height - 2 ;

     pbDisplay.Width := pbResiduals.Width ;
     pbDisplay.Height := pbResiduals.Top - lbCursor0.Height - 2 - pbDisplay.Top ;

     pbPlot.Width := TabbedNoteBook.Width - pbPlot.Left - 50 ;
     pbPlot.Height := Bottom - pbPlot.Top ;
     pbHist.Width := TabbedNoteBook.Width - pbHist.Left - 50 ;
     pbHist.Height := Bottom - pbHist.Top ;
     end;

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

procedure TFitFrm.EdRecRangeXYPlotChange(Sender: TObject);
begin
     edRecRange.text := edRecRangeXYPlot.text ;
     end;

procedure TFitFrm.edRecRangeHistogramChange(Sender: TObject);
begin
     edRecRange.text := edRecRangeHistogram.text ;
     end;

procedure TFitFrm.cbEquationChange(Sender: TObject);
begin
     NewVariableNames ;
     end;

procedure TFitFrm.SetPlotButtonStatus ;
begin
     if not SummaryAvailable then CalculateSummary ;
     { Enable X/Y and histogram plotting }
     if Fit.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 TFitFrm.pbDisplayDblClick(Sender: TObject);
{-----------------------------
 Open display zoom dialog box
 ----------------------------}
var
   i : Integer ;
begin
     ZoomFrm.ChOnDisplay := cbFitChannel.ItemIndex ;
     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 TFitFrm.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
{ ------------------------
  Function key processing
  1/12/97 Accepted/rejected toggling added (Ctrl-R)
          Ctrl-T etc sets record type
  -----------------------}
type
    TAction = (MoveCursor,ChangeRecord,None,NewRecordType,ChangeRejectedStatus) ;
var
   Action : TAction ;
   Step,OldPosition : Integer ;
   NewRecord : LongInt ;
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
                 Case CursorState of
                      Cursor0 : begin
                           OldPosition := FitJob.Eqn.Cursor0  ;
                           FitJob.Eqn.Cursor0 := OldPosition + Step ;
                           CursorChannel.color := clRed ;
                           MoveVerticalCursor( pbDisplay,FitJob.Eqn.Cursor0,
                                               OldPosition,CursorChannel,lbCursor0) ;
                           end ;
                      Cursor1 : begin
                           OldPosition := FitJob.Eqn.Cursor1  ;
                           FitJob.Eqn.Cursor1 := OldPosition + Step ;
                           CursorChannel.color := clRed ;
                           MoveVerticalCursor( pbDisplay,FitJob.Eqn.Cursor1,
                                               OldPosition,CursorChannel,lbCursor1 ) ;
                           end ;
                      TZeroCursor : begin
                           OldPosition := FitJob.Eqn.TZeroCursor  ;
                           FitJob.Eqn.TZeroCursor := OldPosition + Step ;
                           CursorChannel.color := clPurple ;
                           MoveVerticalCursor( pbDisplay,FitJob.Eqn.TZeroCursor,
                                               OldPosition,CursorChannel,lbTZeroCursor ) ;
                           end ;
                      end ;
                 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 ;

             { Update record type }
             NewRecordType : begin
                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 accepted/rejected status }
             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 TFitFrm.FormShow(Sender: TObject);
begin
     { Set tick in Windows menu }
     Main.ClearTicksInWindowsMenu ;
     Main.ShowFit.checked := True ;
     end;


procedure TFitFrm.PlotRecord( Destination : TDestination ) ;
{ ---------------------------------------------------------------------
  Plot currently displayed record on printer or as bitmap in clipboard
  ---------------------------------------------------------------------}

var
    PrChan : array[0..LastFitChannel] of TChannel ;
    Page : TPlot ;
    TimeBarValue : single  ;
    PlotOK : boolean ;
    ch, ChLast, i, Num, xPix, yPix,LineHeight,CharWidth,ColSpacing : LongInt ;
    ChanTop,ChanHeight,ChOffset,Row,Col,TableTop,LeftMargin  : LongInt;
    x,y,dx : single ;
    OldPenWidth,Tick,nOffScreen : Integer ;
    BMap : TBitmap ;

begin

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

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


     if PlotOK then begin

          Screen.Cursor := crHourglass ;

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

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

                    Printer.BeginDoc ;
                    { Print file name }
                    LeftMargin := Printer.PageWidth div 10 ;
                    xPix := LeftMargin ;
                    yPix := Printer.PageHeight div 60 ;
                    Printer.Canvas.TextOut(xPix,yPix, 'File ... ' + fH.FileName ) ;
                    { Print ident line }
                    yPix := yPix + LineHeight ;
                    Printer.Canvas.TextOut( xPix, yPix, fH.IdentLine ) ;
                    { Print record and equation fitted }
                    yPix := yPix + LineHeight ;
                    Printer.Canvas.TextOut( xPix, yPix, EdInfo.text ) ;
                    { Print best fit parameters }
                    { Find maximum width of table entry }
                    ColSpacing := 0 ;
                    for Row := 0 to sgResults.RowCount do
                        for Col := 0 to sgResults.ColCount do
                            ColSpacing := MaxInt([ColSpacing,
                                        Printer.Canvas.TextWidth(sgResults.Cells[Col,Row])]) ;
                    { Print table }
                    TableTop := yPix + LineHeight ;
                    for Row := 0 to sgResults.RowCount do begin
                        yPix := yPix + LineHeight ;
                        xPix := LeftMargin ;
                        for Col := 0 to sgResults.ColCount do begin
                            Printer.Canvas.TextOut( xPix, yPix, sgResults.Cells[Col,Row] ) ;
                            xPix := xPix + ColSpacing ;
                            end ;
                        end ;
                    { Residual S.D. }
                    yPix := TableTop ;
                    xPix := LeftMargin + (7*ColSpacing) div 2 ;
                    Printer.Canvas.TextOut(xPix,yPix,'Residual standard deviation = '
                                                     + edResidualSD.text ) ;
                    { Degrees of freedom }
                    yPix := yPix + LineHeight ;
                    Printer.Canvas.TextOut(xPix,yPix,'Degrees of freedom = '
                                                     + edDegreesFreedom.text ) ;
                    { Number of interations }
                    yPix := yPix + LineHeight ;
                    Printer.Canvas.TextOut(xPix,yPix,'No. of Iterations = '
                                                     + edNumIterations.text ) ;

                    end ;

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

          ChanHeight := (Page.Bottom - Page.Top) div 2 ;
          for Ch := 0 to LastFitChannel do begin
              { Make a copy of channel scaling information }
              PrChan[ch] := FitChannel[Ch] ;
              { if colours not to be used, change to black }
              if not Settings.Plot.UseColor then PrChan[ch].Color := clBlack ;
              { Set top of trace plotting }
              if ch = ChRes then ChanTop := Page.Top + ChanHeight
                            else ChanTop := Page.Top ;
              { Set up channel display areas on printed page }
              PrChan[ch].Left := Page.Left ;
              PrChan[ch].Right := Page.Right ;
              PrChan[ch].Top := ChanTop ;
              PrChan[ch].Bottom := ChanTop + ChanHeight ;
              PrChan[ch].xScale := (PrChan[ch].Right - PrChan[ch].Left) /
                                   (PrChan[ch].xMax  - PrChan[ch].xMin ) ;
              PrChan[ch].yScale := (PrChan[ch].Bottom - PrChan[ch].Top) /
                                   (PrChan[ch].yMax   - PrChan[ch].yMin ) ;
              end ;

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

          { Plot fitted signal record currently on display}
          for Ch := 0 to LastFitChannel do if PrChan[Ch].InUse then begin

              { Set trace color }
              if Destination = ToPrinter then
                 Printer.Canvas.pen.Color := PrChan[Ch].Color
              else
                 BMap.Canvas.pen.Color := PrChan[Ch].Color ;

              ChLast := Ch ;
              x := 0. ;
              dx := 1. ;
              nOffScreen := 0 ;
              ChOffset := Ch ;
              for i := 0 to fH.NumSamples-1 do begin

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

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

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

              { Print zero level cursor }
              If Settings.ShowZeroLevels then begin
                 if Destination = ToPrinter then
                    PrintHorizontalCursor(Printer.Canvas,PrChan[ch],PrChan[ch].ADCZero)
                 else
                    PrintHorizontalCursor(BMap.Canvas,PrChan[ch],PrChan[ch].ADCZero)
                 end ;
              end ;


          case Destination of
               ToPrinter : begin
                    Printer.Canvas.pen.Color := clBlack ;
                    { Horizontal (time) calibration bar }
                    HorizontalBar(Printer.canvas,PrChan[ChLast],Settings.ShowLabels,
                                  Settings.TimeBarValue,rh^.dt,Settings.TScale,
                                  Settings.TUnits) ;
                    { Vertical calibration bars }
                    VerticalBar(Printer.canvas,PrChan[ChData],Settings.ShowLabels,
                                Settings.BarValue[ChData],PrChan[ChData].ADCUnits);
                    if PrChan[ChRes].InUse then
                        VerticalBar(Printer.canvas,PrChan[ChRes],Settings.ShowLabels,
                                Settings.BarValue[ChData],PrChan[ChData].ADCUnits);
                    Printer.EndDoc ;
                    end ;
               ToClipboardAsImage : begin
                    BMap.Canvas.pen.Color := clBlack ;
                    { Horizontal (time) calibration bar }
                    HorizontalBar(BMap.canvas,PrChan[ChData],Settings.ShowLabels,
                                  Settings.TimeBarValue,rh^.dt,Settings.TScale,
                                  Settings.TUnits) ;
                    { Vertical calibration bars }
                    VerticalBar(BMap.canvas,PrChan[ChData],Settings.ShowLabels,
                                Settings.BarValue[ChData],PrChan[ChData].ADCUnits);
                    if PrChan[ChRes].InUse then
                        VerticalBar(BMap.canvas,PrChan[ChRes],Settings.ShowLabels,
                                Settings.BarValue[ChData],PrChan[ChData].ADCUnits);
                    Clipboard.Assign(BMap) ;
                    BMap.Free ;
                    end ;
               end ;

          Screen.Cursor := crDefault ;
          end ;
     end;


procedure TFitFrm.CopyRecordToClipBoard ;
{ --------------------------------------------------
  Copy the currently displayed record to the clipboard
  --------------------------------------------------}
const
     BufSize = 65000 ;
     NumBytesPerNumber = 12 ;
var
   i,j,iSkip,x,ch,n,nc,NumBytesNeeded : LongInt ;
   Line : String ;
   CopyBuf0,Line0 : PChar ;
   OK : Boolean ;
   ClipRec : ^TWCPClipboardRecord ;

begin
     try
        { Get control of clipboard }
        OK := OpenClipboard( Handle ) ;
        OK := EmptyClipBoard ;

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

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

        i := Trunc(FitChannel[0].xMin) ;
        screen.cursor := crHourglass ;
        while  i <= FitChannel[0].xMax do begin
             { Create a line of <tab> separated ASCII data values terminated by <cr> <lf>
               Time <tab> Ch.0.Value <tab> Ch.1.Value .... <cr> <lf> }
             Line := format( '%5.4g', [i*rh^.dt*Settings.TScale] ) ;
             for ch := 0 to LastFitChannel do
                 if FitChannel[ch].InUse then begin
                    j := i*NumFitChannels + Ch ;
                    Line := Line + chr(9) + Format('%.4g',
                                            [(ADC^[j] - FitChannel[ch].ADCZero)
                                               * FitChannel[ch].ADCScale] ) ;
                    end ;
             Line := Line + chr(13) + chr(10) ;
             StrPCopy( Line0, Line ) ;
             CopyBuf0 := StrCat( CopyBuf0, Line0 ) ;
             i := i + iSkip ;
             end ;

        { Copy text accumulated in copy buffer to clipboard }
        ClipBoard.SetTextBuf( CopyBuf0 ) ;

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


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

end.
