unit Spectrum;{ Nonvisible objects for control/file storage of acquired data }
{
  (C) 2000 - 2001 Jindrich Jindrich, Pavel Pisa, PiKRON Ltd.

  Originators of the CHROMuLAN project:

  Jindrich Jindrich - http://www.jindrich.com
                      http://orgchem.natur.cuni.cz/Chromulan/
                      software developer, project coordinator
  Pavel Pisa        - http://cmp.felk.cvut.cz/~pisa
                      embeded software developer
  PiKRON Ltd.       - http://www.pikron.com
                      project initiator, sponsor, instrument developer

  The CHROMuLAN project is distributed under the GNU General Public Licence.
  See file COPYING for details.

  Originators reserve the right to use and publish sources
  under different conditions too. If third party contributors
  do not accept this condition, they can delete this statement
  and only GNU license will apply.
}

{$I DEFINE.PAS}
interface
uses
  SysUtils, Classes,
  {$IFNDEF CONSOLE}
  Dialogs, Controls, Forms, Graphics,
  {$ENDIF}
  {v0.45}
  UtlType, WinUtl,
  {/v0.45
  TVType, MyType, MyLib,} Fileu,
  Msgu,
  {$IFNDEF CONSOLE}
  ShowMsg,
  {$ENDIF}
  UlanType, UlanGlob,
  ULRecTyp, ULObju, ULFObju,
  ULAType,  ULAObju,
  ULADType, ULADObju,
  ULAFType, ULAFObju,
  ULBType,  ULBObju,
  ULBRType, ULBRObju,
  ULEType,  ULEObju,
  ULPType,  ULPObju,
  ULPRType, ULPRObju,
  ULMType,  ULMObju,
  ULMFType, ULMFObju,
  ULVType,  ULVObju,
  ULVLType, ULVLObju,
  ULVOType, ULVOObju,
  {v0.50}
  UDLSType, UDLSObju,
  UDLRType, UDLRObju, 
  DataLineu,
  {/v0.50}
  ExpImpu
  {$IFNDEF CONSOLE}
  ,DebugFrm
  {$ENDIF}
  {v0.11}, MathCopr {/v0.11}
  {v0.13}
  ,ULIType, ULIObju
  {/v0.13}
  {v0.14}
  ,Channelsu
  {/v0.14}
  {v0.19}
  {$IFNDEF CONSOLE}
  ,infofrm
  {$ENDIF}
  {/v0.19}
  {v0.28}, Language {/v0.28}{v0.30}
  {$IFNDEF CONSOLE}
  ,FileMenuHdl
  {$ENDIF}
  {/v0.30}
  {v0.31}
  ,ULDRObju, Messages
  {/v0.31}
  {v0.43}, ExeLogu{/v0.43}
  {v0.50}
  , USPType, USPObju, SeqPrgu
  {/v0.50}
  {v0.51pi}
  ,ULDPType, ULDPObju
  {/v0.51pi}
  {v0.57}
  ,UDVRType, UDVRObju,
  UDVSType, UDVSObju
  {v0.61}, Math {/v0.61}
  ;

type
  {v0.11}
  ESpectrum = class(Exception);
  {/v0.11}
  {v0.13}
  EUserAborted = class(Exception);
  {/v0.13}
  {v0.14}
  EAcqData = class(Exception);
  {/v0.14}

type

  TProcPeak = record
    { Structure with data used for processing of one peak }
    PeakStart:TIndexPoint;
    Point_1,
    Point_2:TIndexPoint;
      { CurPoint-1, CurPoint-2 }
    LocMin_1, LocMax_1,
    LocMin_2, LocMax_2: TIndexPoint;
      { Last local (noise) extreme values. }
    CurMin, CurMax,
    Min_1, Max_1,
    Min_2, Max_2:TIndexPoint;
      { Last real extreme values. }
    IsInPeak:boolean;
  end;


  TSpectrum = class;

  {v0.24}
  TAcqData = class;
  {v0.57}
  TULADs = class;
  {/v0.57}

  TULADRc = class(TObject)
  {v0.57}
  private
    FULADs: TULADs;
    FULAD: TULADObj;
    FUDVR: TUDVRObj;
    procedure SetULAD(AULAD: TULADObj);
  public
  {/v0.57
    ULAD: TULADObj;}
    CurDataSize: integer;
    CurDataPos: integer;
    {v0.31}
    Addr:integer;
      {uLan net address of the device (detector) that gives the data}
    PointTimeInterval: single;
      { in what interval (in ms) come data points
        (default value = UlanGlob .UlanPointTimeInterval) }
    CurTime: single;
    YCoef: single;
      { used to multiply the Y value got from the device with (the result
        will be stored to the file) }
    {ulanglob}
    {/v0.31}
  protected
    function GetData: TStream;
  public
    constructor Create{v0.57}(AULADs: TULADs){/v0.57}; reintroduce;
    property Data: TStream read GetData;
    {v0.57}
    property ULAD: TULADObj read FULAD write SetULAD;
    property UDVR: TUDVRObj read FUDVR;
    {/v0.57}
  end;

  TULADs = class(TList)
  private
    FAcqData: TAcqData;

  protected
    function GetULADRc(Index: integer): TULADRc;
    procedure AddULAD(AULAD: TULADObj);
    procedure DelULAD(AULAD: TULADObj);
    function FindULAD(AULAD: TULADObj; var Index: integer): boolean;
  public
    procedure Clear;override;
    constructor Create(AAcqData: TAcqDAta);
    destructor Destroy;override;
    procedure Update;
    procedure Reset;
    procedure SetZeroSizes;
    procedure CurDataSizeUpdate;
    {v0.31}
    procedure Start;
      { set curTime to zero for all ULADs }
    {/v0.31}
    property ULAD[Index: integer]: TULADRc read GetULADRc;default;
  end;

  {TAcqData}
  TAcqData = class(TObject) { was apexpoints }
    { Analysis data - points }
    ULF: TULFObj;
      { TULFObj root; owner of ULA; created by reading FileName }
    ULA: TULAObj;
      { Pointer to Analysis object, owner of ULAD, ULAF, ULM }
    {v0.25}
    UsrDataIndex: integer;
    {/v0.25}
    {v0.24}
    ULADIndex: integer;
    ULAD: TULADs;
    {/v0.24
    ULAD: TULADObj;}
      { Pointer to Analysis raw Data object (using ULAD.Data: TStream);
        owned by ULA. }
    ULAF: TULAFObj;
      { Pointer to Analysis Filtered data object (using ULAF.Data: TStream);
        owned by ULA. }
    ULM: TULMObj;
      { Pointer to Method objects, owned by ULA, owner of ULMF }
    ULMF: TULMFObj;
      { Pointer to fiLter parameters (was FFilterPars: TFilterPars;) for
        filtering ULAD.Data to ULAF.Data; owned by ULA }
    ULM_ULP: TULPObj;
      { Method peaks (templates for autodetection of data peaks) }
    ULM_ULB: TULBObj;
      {  Method baseline records (in those baseline sections
         no peaks will be autodetected) }
    ULB: TULBObj;
      { Acquired Baseline sections }
    ULP: TULPObj;
      { Acquired Peaks }
    ULV: TULVObj;
      { Owner of view option records }
    ULVL: TULVLObj;
      { View limits option record }
    ULVO: TULVOObj;
      { View objects options }
    {v0.50}
    ULI_UDLS: TUDLSObj;
    {/v0.50}
    {v0.57}
    UDVS: TUDVSObj;
      { owner of data lines views options }
    {/v0.57}
    {v0.11}
    Cal_ULP: TULPObj;
      { Peaks from calibration file (specified in ULM.CalibrationFileName)
        used for calculation of responses, temporary object (doesn't get
        saved), can be nil, inicialized only when needed by reading from
        the file. }
    {v0.13}
    {v0.31 change to prop.}{/v0.31
    ULI: TULIObj;}
    {/v0.13}
    { FileName : string; tfilestream }
    { file for storing the data }
    SpectrumOpt: TSpectrumOpt;
    {ScreenDisp: TScreenDisp;
      { Size of graphic window where the drawing of points should be done }
    {v0.14}
    Spectrum: TSpectrum;
      { To which spectrum object are the data assigned right now
        (if any) }
    {/v0.14}
  private
    FMode: TOpenMode;
      { omCreate - creating = adding points; omRead, omReadOnly - reading points }
    CurIndex:Longint;
      { Current index used in sequential access using methods:
        Reset (or SeekIndex) and ReadIndexPoint }
    {v0.05}
    {BufPoints: array[0..MaxAvgPointCount-1] of TExpPoint;}
      { Buffer used by ReadPoint method to read ahead and eventually
        do the filtering. }
    {v0.20}{/v0.20 BufPointCount,}
      { Number of points in buffer. }
    {v0.20}{/v0.20 BufPointPos: integer;}
      { Index of point to be read next from the buffer. }
    { HeadSizeInFile:longint;
      { Set > 0 if .ULD file used; if .DAT file used, set to 0. }
    {Head:TUldRec;}
      { Used if HeadSizeInFile > 0. }
    UseFiltered:boolean;
      { If set, then public stream READING methods access filtered data
        in ULAF.Data. }
    {RawFileName: TFileName;
      { used in omCreate mode for storing acquired data; in omRead are
        the acquired data loaded into memory stream }
    {v0.11}
    FApplyMath: boolean;
      { should the mathematic operation be made on retrieved points? }
    FMultiplyX: TXValue;
    FMultiplyY: TYValue;
    FAddX: TXValue;
    FAddY: TYValue;
    {/v0.11}
    {v0.21}
    FCurDiluteCount: longint;
      { counter used in ReadPoint }
    FDiluteCount: longint;
      { default = 1, if > 1, then only every FDiluteCount'th data point will
        be considered }
    {/v0.21}
    {v0.20}
    {FCurDataPos: longint;}
      { Used in Reset, ReadPoint methods if ULAD.Data is TMemoryStream
        to access the memory directly (not through stream methods) }
    {FCurDataSize: longint;}
    {/v0.20}
    {v0.24}
    {$IFNDEF CONSOLE}
    FOwnerForm: TForm;
      { if non nil, then this form will destroy the data upon its destroying }
    {$ENDIF}
    {/v0.24}
    {v0.31}
    FChannel: TChannel;
      { assigned when aquiring data, to decide how many data lines to be acquired }
      {v0.41 also looked up if Channel property requested }{/v0.41}
    FULI: TULIObj;
    {/v0.31}
    {v0.34}
    FTemplateULADCount: integer;
    {/v0.34}
    {v0.45}
    FCreatedOK: boolean;
      { set at the end of constructor true, prevents saving incorrectly read
        file during destroy }
    {/v0.45}
    {v0.50}
    FSettingULI: boolean;
    FULI_USP: TUSPObj;{ sequence program }
    FPrg: TSeqPrg;
    {/v0.50}
    procedure Seek(APos:longint);
    function GetSize:longint;

    { Called by SetFileName, if some data present, finds extreme values
        of spectrum, calls Reset and then CheckPoint for each data point }
    procedure ScanData;
    {v0.61}
    { Called from ScanData, makes sure that also all peaks fit in the view }
    procedure ScanPeaks;
    {/v0.61}
    procedure FixPoint(var APoint:TExpPoint);
      { Eventually fix APoint coordinates to fit in allowed Limits
        (if any is out) }
    {v0.31}
    {$IFNDEF CONSOLE}
    procedure CheckChannel;
    {$ENDIF}
      { used if open omCreate, pointer to channel which devices
        used to acquire the data, eventually increase number of ULADs
        accorging to number detector devices in the channel }
      procedure RecreateDataLines;
        procedure RecreateDataLinesByDefinition;
        procedure RecreateDataLinesByDevices;
    {/v0.50}

    procedure WMAppMessage(var Message: TMessage); message WM_APPMESSAGE;
    procedure SetULI(AULI: TULIObj);
    function GetDetAddrList: string;
    {/v0.31}
    {v0.41}
    {$IFNDEF CONSOLE}
    function GetChannel: TChannel;
    {$ENDIF}
    {/v0.41}
    {v0.50}
    function GetULI_USP: TUSPObj;{ sequence program }
    {/v0.50}
  protected
    {v0.16}
    function GetIsTemplate: boolean;
    {/v0.16}
    {v0.24}
    function GetRawFileName(Index: integer): string;
    function GetActiveData: TStream;
    function GetDataCount: integer;
    function GetDataIndex: integer;
    procedure SetDataIndex(Index: integer);
    function GetActiveULADRc: TULADRc;
    {/v0.24}
    {v0.57}
    function GetUsrActiveULADRc: TULADRc;
    procedure SetUsrActiveULADRc(AULADRc: TULADRc);
    {/v0.57}
    {v0.25}
    function GetActiveDataName: string;
    procedure SetActiveDataName(const ADataName: string);
      { set ULADIndex accordingly to ADataName }

    function GetUsrActiveDataName: string;
    procedure SetUsrActiveDataName(const ADataName: string);
      { as SetActiveDataName, but also sets UsrDataIndex to the selected
        DataIndex }
    {/v0.25}
    {v0.50}
    function GetPrg: TSeqPrg;
    {/v0.50}
    {v0.61}
    function GetIsCalFile: boolean;
    {/v0.61}
  public
    destructor Destroy; override;
    {v0.20}{/v0.20 procedure BufPointsClear;}
    {procedure SetFilterPars(AFilterPars:TULMFObj);
    procedure GetFilterPars(AFilterPars:TULMFObj);}
    {procedure HeadInit(var AHead:TUldRec);}
    {/v0.05}
    constructor Create(const AFileName: string; AMode: TOpenMode);
      { Name of the Analysis }
    {v0.09}
    constructor CreateFromPoints(APoints: PExpPoints; APointCount: integer);
    procedure CheckULRecords;
    {/v0.09}
    {v0.11}
    procedure CheckULMRecords;
    {/v0.11}
    procedure Reset;
      { Moves to the beggining of data (both ULAD, ULAF). }
      { Direct stream access, using current file position: }
    function WritePoint(var APoint:TExpPoint):boolean;
    function ReadPoint(var APoint:TExpPoint):boolean;
    {v0.05}
    function GetPointCount:longint;
    {/v0.05}
    procedure SeekPoint(Index:Longint);
      { = Seek(Index*sizeof(TExpPoint);
        if Index < 0, then makes Seek(ActivData.Position + sizeof(TExpPoint)*Index) }
    function ReadIndexPoint(var APoint:TIndexPoint):boolean;
    {/direct stream..}
    procedure Clear;
      { Deletes all points. }{v0.20}{Clears also baseline/peaks}{/v0.20}
    procedure ClearScanInfo;
      { Clears found extreme values, gets ready for ScanData or for
        AddPoint calls. }
    procedure CheckPoint(const APoint:TExpPoint);
      {.. called from ScanData or from AddPoint; updates Extremes }
    {procedure SetLimits(MinX: TXValue; MinY: TYValue; MaxX: TXValue; MaxY: TYValue);
      { What is the maximal allowed ViewLimit, exp. values outside
        these bounds are set to these values }
    procedure SetFileName(const AFileName: string; AMode: TOpenMode);
    {procedure SetUserLimit(AUserLimit: TUserViewLimit);}
      { xorigin, xsize, yorigin, ysize : real)}
    {v0.24}
    procedure ChangeFileName(const AFileName: string);
      { just change ULF.FileName without any immediate saving (used after
        starting from template, when name of the file to be acquired
        is already known (AAA). Extension will be automatically be changed
        to .ULF }
    {/v0.24}
    procedure AddPoint(var APoint: TExpPoint);
      { Called e.g. during acquisition to append new experimental point }
    procedure PutPoint(Index: TPointCount; APoint: TExpPoint);
      { Replace point at given index or if Index = GetPointCount append APoint }
    function GetPoint(Index: TPointCount; var APoint: TExpPoint): boolean;
    function GetIndexPoint(Index: TPointCount; var APoint: TIndexPoint): boolean;
      { Returns point with its coordinates plus its ordinal number in Data^,
       (false if Index out of range) }
    procedure SaveTo(const AFileName: string);
     { Saves current data to AFileName and set it as the current file. }
    function Current: Longint;
      { Returns position of last inserted item in mempage buffer }
    {v0.45 obsolete}
    {/v0.45
    procedure Error(Code,Info:integer);}
    procedure GrowShowLimits(dX: TScreenX; dY: TScreenY);
    procedure MoveShowLimits(dX: TScreenX; dY: TScreenY);
    {procedure SetScreenDisp(const AScreenDisp: TScreenDisp);
      { Size of graphic window used for conversion to graphic coordinates. }
    function ExpToScreen(const AExpPoint: TExpPoint; const AScreenDisp: TScreenDisp;
      var AScreenPoint: TScreenPoint): integer;
      { Converts datapoint to screenpoint (coordinates) using SpectrumOpt
        and ScreenDisp parameters. Fills AScreenPoint, returns 0 if expoint is
        on the screen, sets opLeft on Result if on the left from screen,
        opRight if on the right from screen.
        If X is in screen range, but Y out of it, the Y is adjusted to the
        Y limit value (and 0 is returned). }
   function ExpSizeToScreenSize(const AExpPoint:TExpPoint;
     const AScreenDisp: TScreenDisp; var AScreenPoint:TScreenPoint): integer;
     { converts distance in exp units to distance in screen points }
    function ScreenToExp(const ScreenPoint: TScreenPoint; const AScreenDisp: TScreenDisp;
      var AExpPoint:TExpPoint): boolean;
      { Converts ScreenPoint to corresponding point in experimental coordinates,
        using SpectrumOpt and ScreenDisp parameters. }
    function FindDataPoint(const AExpPoint:TExpPoint;
      var ADataPoint:TExpPoint; var Index:TPointCount): boolean;
      { Finds closest point from collected data curve to the point
        AExpPoint (using x coordinate). }
    procedure SetViewLimit(const AViewLimit: TExpViewLimit);
    {v0.61}
    procedure GetViewLimit(var AViewLimit: TExpViewLimit);
    {/v0.61}
    procedure SetLimit(const ALimit: TExpViewLimit);
    function UserToExp(const AUserPoint: TUserPoint; var AExpPoint: TExpPoint): boolean;
    function ExpToUser(const AExpPoint: TExpPoint; var AUserPoint: TUserPoint): boolean;
    function UserToScreen(const AUserPoint:TUserPoint;
      const AScreenDisp: TScreenDisp; var AScreenPoint:TScreenPoint): boolean;
    function UserSizeToScreenSize(const AUserPoint:TUserPoint;
      const AScreenDisp: TScreenDisp; var AScreenPoint:TScreenPoint): boolean;
    procedure FilterUpdate;
    {v0.11}
    procedure MethodLoadFromFile(const AFileName: string);
    procedure MethodSaveToFile(const AFileName: string);
    procedure CalibrationFileSelect(const AFileName: string);
    {v0.30}
    procedure SpectrumGet(var sp: TSpectrum);
      { checks if some Spectrum already assigned to Self, if yes returns
        pointer to it,if not creates one, returns pointer to it;
        SpectrumRelease MUST be called when sp not needed anymore }
    procedure SpectrumRelease(sp: TSpectrum);
      { MUST be called after SpectrumGet if sp not needed anymore }
    {/v0.30}

    procedure AcquiredDataDiscard;
    procedure DoMath(mo:TDataMathOperation; r: single);
      { set value of mo corresponding variable and activate FApplyMath field
        (or desactivate if mo = moNone) }
    procedure MakeMathPermanent;
      { save the values obtained by getpoint or readpoint method
        (eventyally modified if FApplyMath active) back to the file,
        then disable FApplyMath }
    {/v0.11}
    {v0.14}
    procedure InstrumentLoadFromFile(const AFileName: string);
    procedure InstrumentSaveToFile(const AFileName: string);
    function GetNonameFileName: string;
    {/v0.14}
    {v0.15}
    procedure GetReadyForTemplate;
      { Strip the file off (data, peaks, baseline sections) }
    {/v0.15}
    {v0.24}
    function DataSize: integer;
    {/v0.24}
    {v0.25}
    procedure UsrDataIndexReselect;
    {/v0.25}
    {v0.31}
    function GetAddrULADIndex(AAddr: integer): integer;
      { returns ULADIndex of ULADRc, that has given AAddr; if
        not such found - returns 0 (default index) }
    {/v0.31}

    {properties}
    property Mode: TOpenMode read FMode;
    {v0.11}
    property MultiplyX: TXValue read FMultiplyX;
    property MultiplyY: TYValue read FMultiplyY;
    property AddX: TXValue read FAddX;
    property AddY: TXValue read FAddY;
    procedure CreateTemplate(const AFileName: string);
      { as SetFileName but does not initialize ULAD,
        Free should be called as the last action. }
    {/v0.11}
    {v0.15}
    procedure Truncate(AfterX: TXValue);
      { remove all acqisition data (together with peaks, baselines)
        after specified X value }
    {/v0.15}

    {v0.30}
    function CanClose: boolean;{ulobjusru}
    {/v0.30}
    {v0.50}
    function DataNamesEqual(ADataName1: string; ADataName2: string): boolean;
    procedure FormRegister(AForm: TObject);
    procedure FormUnregister(AForm: TObject);
    {/v0.50}
    {v0.55}
    function CheckCalibrationFilePresent: boolean;
      { returns true if ulm.calibrationfilename exists; should be called
        only if the name <> '' }
    {/v0.55}
    {v0.61}
    { Makes sure that all peaks are visible }
    procedure FitPeaksInView;
    {/v0.61}
    {/public methods}

    {v0.16}
    property IsTemplate: boolean read GetIsTemplate;
    {/v0.16}
    {v0.24}
    {$IFNDEF CONSOLE}
    property OwnerForm: TForm read FOwnerForm write FOwnerForm;
    {$ENDIF}
    property RawFileName[Index: integer]: string read GetRawFileName;
    property ActiveData: TStream read GetActiveData;
    property DataIndex: integer read GetDataIndex write SetDataIndex;
    property DataCount: integer read GetDataCount;
    property ActiveULADRc: TULADRc read GetActiveULADRc;
    {/v0.24}
    {v0.25}
    property ActiveDataName: string read GetActiveDataName write SetActiveDataName;
    property UsrActiveDataName: string read GetUsrActiveDataName write SetUsrActiveDataName;
    {/v0.25}
    {v0.57}
    property UsrActiveULADRc: TULADRc read GetUsrActiveULADRc write SetUsrActiveULADRc;
    {/v0.57}
    {v0.31}
    property ULI: TULIObj read FULI write SetULI;
    property ULDetAddrList: string read GetDetAddrList;
    {/v0.31}
    {v0.41}
    {$IFNDEF CONSOLE}
    property Channel: TChannel read GetChannel;
    {$ENDIF}
    {/v0.41}
    {v0.50}
    property ULI_USP: TUSPObj read GetULI_USP;
    property Prg: TSeqPrg read GetPrg;
    {/v0.50}
    {v0.61}
    { ReadOnly property, is true if the file has .ULC extension, i.e. holds only
      peaks for calibration (no acquisition data). }
    property IsCalFile: boolean read GetIsCalFile;
    {/v0.61}
  end;


  TSpectrum = class(TObject)
    {SpectrumOpt:TSpectrumOpt;{set by init, parameters for calculation
      of peaks (used in FinishPeak method, eventually in AutoDetect)}
    AcqData: TAcqData;
      { Set by Create; data points to investigate. }
    Peaks: TULPObj;
      { Peaks, user defined or autodetected. TULPObj owned by AcqData.ULA. }
    BaseLine: TULBObj;
    {ReportData:TReportData;{report created during CalculatePeaksRatio}
    AutoDetectPeaks: boolean;
      { False by default; used in DoPeak method to decide if some kind
        of autodetection of peak end and peak start should be tried }
    ProcPeak: TProcPeak;
      { Parameters for processing current peak, i.e. the one started
        by StartPeak method and finished by FinshPeak method; inbetween
        some calls for DoPeekPoint should be called for all peaks between
        those two. }
    BLPeak: TProcPeak;
    {v0.11}
    LastResult: TSpectrumResult;
    {/v0.11}
    constructor Create(AData: TAcqData);
      { if non nil, overrides AData.SpectrumOpt}
    procedure SetData(AData: TAcqData);
    procedure OrderPoints(var P1, P2: TIndexPoint);
      { Make sure P1.X < P2.X }
    procedure DoPoint(const APoint: TIndexPoint);
      { Used in autodetect to find start and end of peak, calls
        StartPeak, DoPeakPoint and FinishPeak, when it think it's
        appropriate }
    procedure StartPeak(const APoint: TIndexPoint);
      procedure ClearProcPeak;
      procedure ClearBLPeak;
    procedure FinishPeak(const APoint: TIndexPoint);
      function CountHeight(var P1, P2: TIndexPoint; var X:TXValue): TYValue;
      function CountArea(var P1, P2: TIndexPoint): TAreaSize;
      function CountWidth(var P1, P2: TIndexPoint; H: TYValue): TXValue;

    procedure PeakCreate(const P1, P2: TIndexPoint);
      { Calls StartPeak(P1), then FinishPeak(P2); used usually when
        defining peaks  manually}

    procedure BaseLineCreate(const P1, P2: TIndexPoint);
      procedure StartBaseLine(const APoint: TIndexPoint);
      procedure FinishBaseLine(const APoint: TIndexPoint);
    procedure BaseLineMerge;
      { remove all inner points from neighbouring selected baseline sections -
        make just one section } 
    procedure BaseLineGroup;
      { makes selected neighbouring baseline sections grouped - move edges
        together }
    procedure BaseLineUngroup;
      { makes grouped baseline sections independent }
    procedure DoBaseLineLimitsChanged(b: TULBRObj);
      {should be called if some X value of b changed manually,
        calls UpdateBaseLineRecY and recalculate peaks that are above
        this baseline rec }
      procedure UpdateBaseLineRecY(b: TULBRObj);
      { finds corresponding Y values to b.X1,X2 values in data points }

    function AutoDetectOld: boolean;
    function AutoDetect: boolean;
      { Can be called if automatic detection if peaks is desired (ignores
        AutoDetectPeaks value). }
      function AutoDetectForBase: boolean;
      procedure BaseLineUpdateFromMethod;
        { Called after AutodetectFromBase to remove or split those
          autodetected baseline records, that would overlap with those
          defined in method ( - where peaks should not be autodetected) }
      {v0.25}
      procedure BaseLineRemoveDuplicates;
        { check for overlapped baseline records (added by autodetecting
          for additional wavelengths), and delete them if found }
      {/v0.25}
      function AutoDetectForPeaks: boolean;
    {procedure PeaksClear;}
      { Disposes all peaks. }
    procedure RecalculatePeaks;
      { Recalculates percentual content of peak areas; call after
        all peaks created or peak limits change }
      procedure RecalculatePeak(Peak: TULPRObj);
      procedure PeaksCalculateRatios;
    function IsScreenXInPeak(X: TScreenX; const AScreenDisp:TScreenDisp;
      var PeakIndex: integer): boolean;
    function IsScreenPointInPeakEdge(X: TScreenX; Y: TScreenY;
      const AScreenDisp: TScreenDisp; var PeakIndex: integer): boolean;

    function IsScreenPeakStart(X: TScreenX; const AScreenDisp: TScreenDisp;
       var PeakIndex: integer): boolean;
    function IsScreenPeakStop(X: TScreenX; const AScreenDisp: TScreenDisp;
      var PeakIndex: integer): boolean;
    function MoveScreenPeak(OldStartX, NewStartX: TScreenX; const AScreenDisp: TScreenDisp;
      var PeakIndex: integer): boolean;
    function SizeScreenPeak(OldStopX, NewStopX: TScreenX; const AScreenDisp: TScreenDisp;
      var PeakIndex: integer; Right:boolean): boolean;
    procedure ToggleSelectScreenPeak(X: TScreenX; const AScreenDisp: TScreenDisp; Shift: TShiftState);
    procedure GetScreenPeaks(X: TScreenX; const AScreenDisp: TScreenDisp; var PrevPeak: TScreenPeak;
      var CurPeak: TScreenPeak; var NextPeak: TScreenPeak);
      { Get info about peaks around given screen X coordinate; if xPeak.X1 < 0 then
        no such peak found }

    procedure GetScreenBaseLineRec(X: TScreenX; const AScreenDisp: TScreenDisp;
      var CurIndex: integer; var PrevBL: TScreenPeak;
      var CurBL: TScreenPeak; var NextBL: TScreenPeak);
      { Get info about baseline records around given screen X coordinate; if xPeak.X1 < 0 then
        no such peak found }
    {v0.09}
    procedure GetScreenBaseLineNeighbours(Index:integer; const AScreenDisp: TScreenDisp;
      var PrevBL: TScreenPeak; var CurBL: TScreenPeak; var NextBL: TScreenPeak);
      { Returns inc CurBL screen coordinates of Index baseline section, plus
        in PrevBL and NextBL coordinates of sections on the left and on the right }
    {/v0.09}
    function IsScreenXInBaseLine(X: TScreenX; const AScreenDisp:TScreenDisp;
      var PeakIndex: integer): boolean;
    function IsScreenPointInBaseLineEdge(var X: TScreenX; Y: TScreenY;
      const AScreenDisp: TScreenDisp; var PeakIndex: integer; var LeftEdge:boolean): boolean;
    function MoveScreenBaseLine(OldStartX, NewStartX: TScreenX; const AScreenDisp: TScreenDisp;
      var Index: integer): boolean;
    function SizeScreenBaseLine(OldStopX, NewStopX: TScreenX; const AScreenDisp: TScreenDisp;
      var PIndex: integer; Right:boolean; SizingTwo:boolean): boolean;
    procedure ToggleSelectScreenBaseLine(X: TScreenX; Y: TScreenY;
      const AScreenDisp: TScreenDisp; Shift: TShiftState);
    procedure BaseLineInsertPointFromScreen(const AScreenDisp: TScreenDisp; X: TScreenX);


    procedure PeaksFindYAndIndexValues;
    procedure PeakFindYAndIndexValues(p: TULPRObj{v0.26}; ReselectUsrData: boolean{/v0.26});
      function FindBaseY(const ADataName: string; X: TXValue; var Y: TYValue):boolean;

    procedure DoPeakLimitsChanged(p: TULPRObj);

    procedure CopySelectedPeaksToMethod;
    procedure CopySelectedBaseLineToMethod;
    procedure PeaksAssignParamsFromMethod;
    procedure PeakAssignParamsFromMethod(p: TULPRObj);
    {v0.25}
    function PeakFindPeakInMethodByX(x: TXValue; var mp: TULPRObj):boolean;
    function PeakFindInPeaksByX(pl: TULPObj; x: TXValue; var fp: TULPRObj): boolean;
    function PeakFindInPeaksInXRange(pl: TULPObj; const ADataName: string; x, xmin, xmax: TXValue; var fp: TULPRObj): boolean;

    {/v0.25}
    function PeakFindPeakInMethod(p: TULPRObj; var mp: TULPRObj):boolean;
    {v0.11}
    function PeakFindInPeaks(pl: TULPObj; p: TULPRObj; var fp: TULPRObj): boolean;
      { find in pl peaks the peak fp, that has p.X in fp's window interval }
    {/v0.11}
    {v0.12}
    function FindPeakByName(pl: TULPObj; const AName: TPeakName; var fp: TULPRObj): boolean;
     { find in pl peaks the peak fp, that has p.Name = AName }
    {/v0.12}


    destructor Destroy; override;

    {v0.09}
    function ULBRToScreenPeak(BL:TULBRObj; const AScreenDisp: TScreenDisp;
      var SP:TScreenPeak): integer;
    {/0.09}
    {v0.13}
    function PeaksAutoCalculate: integer;
      { calls either PeaksResponsesCalculate or PeaksAmountsCalculate,
        depending on CalibrationStandard switch value }
    {/v0.13}
    {v0.11}
    function PeaksResponsesCalculate: integer;
      { Calculates Response value for all peaks, returns <> 0 if invalid
        values encountered (zero Amount value) }
    function PeaksAmountsCalculate: integer;
      { called from menu, calls the following tho functions according to
        AcqData method settings }
      {v0.12}
      function PeaksAmountsCalculateCalibrationFile: integer;
        { [recalculate responses in calibration file,] assign responses }
      function PeaksAmountsCalculateInternalStandard: integer;
      function PeaksAmountsCalculateValues: integer;
      {/v0.12
      function PeaksAmountsCalculateFromCalibrationFile: integer;
      function PeaksAmountsCalculateFromInternalStandard: integer;}
    procedure PeakAssignPeakCharacterization(Dest, Src: TULPRObj);
      { assign to Dest peak all Src peak characterization values like name,
        response, groupname }

    procedure ClearResult;
    procedure SetResult(sr: TSpectrumResult; const msg:string);
    {/v0.11}

  end;


const
  DefSpectrumOpt:TSpectrumOpt =
    { Values usable for y = 0 - 255, and x = time }
  ( MaxPointCount: 0; {no limits for time}
    MaxPeakCount: 100; {we don't know, do we?}
    Trashold: 0;
    BaseLine: 0;
    PeakHeight:(Min:0; Max:0);
    PeakArea:(Min:0; Max:0);
    PeakWidth:(Min:0; Max:0);

    PeakStartLevel : 0;
    PeakEndLevel: 0;
    ViewLimit: (Min: (x: MinAcqXValue; y: MinAcqYValue);
                Max: (x: MaxAcqXValue; y: MaxAcqYValue));
    Limit: (Min: (x: MinAcqXValue; y: MinAcqYValue);
            Max: (x: MaxAcqXValue; y: MaxAcqYValue));
    {UserLimit: (Min: (x: 0; y: MinAcqYValue);
                Max: (x: 20; y: MaxAcqYValue));}
    PointCount:0;
    Extreme:(Min:(x:0;y:0); Max:(x:0;y:0))
  );

procedure CalculatePeaksRatios(Peaks:TULPObj);
function GetChannelFileName({v0.29}const ADir: string;{/v0.29}AChannelName: string): string;
{
procedure InitPacketRec(const ApexPacket:TApexPacket; var ApexRec:TApexPacketRec);
 }
const
  SpectrumBufferSize:longint = 1024;

{v0.36}
{$IFNDEF CONSOLE}
function FindOpenedData(const AFileName: string; var AAcqData: TAcqData{v0.36}; var AForm: TForm{/v0.36}): boolean;
{$ENDIF}
{/v0.36}

{v0.50}
{$IFNDEF CONSOLE}
type
  TObjHandler = class(TObject)
    procedure ValuesSourceNeeded(Sender: TObject);
    procedure ChildCreated(Sender: TObject);
    procedure CheckIn(AObj: TULObj);
  end;

function ObjHandler: TObjHandler;
{$ENDIF}
{/v0.50}

{v0.61}
{ Initialize data object for non-acquisition purposes }
function CreateOrOpenDataFile(AFileName: string; var AAcqData: TAcqData): boolean;
{/v0.61}

implementation
{$IFNDEF CONSOLE}
uses
  Main, SpecForm {v0.13}, AnalSetupFrm{/v0.13}
  {v0.14}, Modulu{/v0.14}{v0.45}, ChkULFu{/v0.45};
{$ENDIF}

{v0.33}
{$IFNDEF CONSOLE}
function FindOpenedData(const AFileName: string; var AAcqData: TAcqData{v0.36}; var AForm: TForm{/v0.36}): boolean;
var
  f: TForm;
  i: integer;
begin
  Result := false;
  for i := 0 to Screen.FormCount - 1 do begin
    f := Screen.Forms[i];
    if f is TSpectrumForm then with f as TSpectrumForm do begin
      if FindAcqData(AFileName, AAcqData) then begin
        {v0.36}
        AForm := f;
        {/v0.36}
        Result := true;
        exit;
      end;
    end;
  end;
end;
{$ENDIF}
{/v0.33}


{v0.28}
function CombineFactors(Factor, MFactor, DFactor: TFactor): TFactor;
{var
  f: TFactor;}
begin
  if Factor = 0 then
    Factor := 1;
  if MFactor = 0 then
    MFactor := 1;
  if DFactor = 0 then
    DFactor := 1;
  Result := Factor * MFactor / DFactor;
end;
{/v0.28}

function CountYOnLine(X: TXValue; X1: TXValue; Y1: TYValue;
  X2: TXValue; Y2: TYValue): TYValue;
begin
  if (X2 = X1) then
    Result := 0
  else
    Result := Y1 + (Y2 - Y1)/(X2 - X1) * (X - X1);
end;

function GetChannelFileName({v0.29}const ADir: string;{/v0.29}AChannelName: string): string;
begin
  GetChannelFileName := {v0.29}ADir + {/v0.29} '$$$' + AChannelName + '.$$$';
end;

{TPeaks}
procedure CalculatePeaksRatios(Peaks:TULPObj);
var
  a: TAreaSize;
  cnt, i: integer;
  p: TULPRObj;
  c: TUsrPeakCoef;
  {ulmtype}
begin
  a := 0;
  cnt := Peaks.ChildCount;
  for i := 0 to cnt - 1 do begin
    {v0.41}
    p := TULPRObj(Peaks.Childs[i]);
    if p.RecID = ULPRID then begin
      c := p.UsrPeakCoef;
      if c = 0 then
        c := 1;
      a := a + p.AreaSize * c;
    end;
    {/v0.41
    a := a + TULPRObj(Peaks.Childs[i]).AreaSize; }
  end;
  if a = 0 then
    exit;
  Peaks.DoChangeLock;
  try
    for i := 0 to cnt - 1 do begin
      p := TULPRObj(Peaks.Childs[i]);
      if p.RecID = ULPRID then begin
        {v0.41}
        c := p.UsrPeakCoef;
        if c = 0 then
          c := 1;
        p.Ratio := p.AreaSize * c/ a;
        {/v0.41
        p.Ratio := p.AreaSize / a;}
      end;
    end;
  finally
    Peaks.DoChangeUnlock;
  end;
end;

{************************* TSpectrum ****************************}
constructor TSpectrum.Create(AData:TAcqData);
begin
  inherited Create;
  SetData(AData);
end;

procedure TSpectrum.SetData(AData: TAcqData);
begin
  if AData = nil then
    raise Exception.Create('TSpectrum.SetData AData = nil');
  {v0.14}
  if AcqData <> nil then
    AcqData.Spectrum := nil;
  AData.Spectrum := Self;
  {/v0.14}
  AcqData := AData;
  Peaks := TULPObj(AcqData.ULA.FindOrAdd(ULPID, ''));
    Peaks.SetFlag(rfCantDelete, true);
  BaseLine := TULBObj(AcqData.ULA.FindOrAdd(ULBID, ''));
    BaseLine.SetFlag(rfCantDelete, true);
end;

procedure TSpectrum.OrderPoints(var P1, P2:TIndexPoint);
  { Make sure P1.X < P2.X }
var P:TIndexPoint;
begin
  if P1.Point.X > P2.Point.X then begin
    P := P1;
    P1 := P2;
    P2 := P;
  end;
end;

procedure TSpectrum.StartPeak(const APoint: TIndexPoint);
begin
  ClearProcPeak;
  with ProcPeak do begin
    PeakStart := APoint;
    IsInPeak := true;
    CurMin := APoint;
    CurMax := APoint;
  end;
end;

function TSpectrum.CountHeight(var P1, P2: TIndexPoint; var X:TXValue): TYValue;
var
  h: TYValue;
  P: TIndexPoint;
  cnt: TPointCount;
begin
  {OrderPoints(P1, P2);}
  h := 0;
  cnt := AcqData.GetPointCount;
  AcqData.SeekPoint(P1.Index);
  repeat
    if not AcqData.ReadIndexPoint(P) then
      break;
    if P.Point.Y > h then begin
      h := P.Point.Y;
      X := P.Point.X;
    end;
    if P.Index >= P2.Index then
      break;
    dec(cnt);
  until cnt = 0;
  CountHeight := h - AcqData.SpectrumOpt.BaseLine;
end;

function TSpectrum.CountArea(var P1, P2: TIndexPoint): TAreaSize;
var
  sum: TAreaSize;
  LP, P: TIndexPoint;
  cnt: TPointCount;
begin
  cnt := AcqData.GetPointCount;
  {OrderPoints(P1, P2);}
  LP := P1;
  sum := 0;
  if P1.Index <> P2.Index then begin
    AcqData.SeekPoint(P1.Index);
    repeat
      if not AcqData.ReadIndexPoint(P) then
        break;
      if P.Point.X < LP.Point.X then
        break;
      sum := sum + (P.Point.X - LP.Point.X) * (LP.Point.Y -
        AcqData.SpectrumOpt.BaseLine);
      LP := P;
      if P.Index = P2.Index then
        break;
      dec(cnt);
    until cnt = 0;
  end;
  CountArea := sum;
end;

function TSpectrum.CountWidth(var P1, P2: TIndexPoint; H: TYValue): TXValue;
  { Give H (peak height) if you know, just to save same time,
   otherwise set H=0 }
var
  LP, P: TIndexPoint;
  cnt: TPointCount;
  w, x: TXValue;
  w1, w2: TIndexPoint;
  hx:TXValue;{x at h}
begin
  CountWidth := 0;
  {OrderPoints(P1, P2);}
  if h <= 0 then
    h := CountHeight(P1, P2, hx);
  h := h / 2;
  if h < 0 then
    exit;
  x := (P2.Point.X - P1.Point.X) / 2;
  if x <= 0 then
    exit;
  cnt := AcqData.GetPointCount;{NumPoints;}
  w1.Index := NoIndexValue;
  w2.Index := NoIndexValue;
  P1.Point.Y := P1.Point.Y - AcqData.SpectrumOpt.BaseLine;
  LP := P1;
  AcqData.SeekPoint(P1.Index);
  repeat
    if not AcqData.ReadIndexPoint(P) then
      break;
    P.Point.Y := P.Point.Y - AcqData.SpectrumOpt.BaseLine;
    if P.Point.Y > LP.Point.Y then begin
      if (LP.Point.Y <= h) and (P.Point.Y >= h) then
        if (P.Point.X <= x) and (W1.Index = NoIndexValue) then
          W1 := P
    end else if P.Point.Y < LP.Point.Y then begin
      if (LP.Point.Y >= h) and (P.Point.Y <= h) then
        if (P.Point.X >= x) then
          W2 := P;
    end;
    LP := P;
    if P.Index >= P2.Index then
      break;
    dec(cnt);
  until cnt = 0;
  w := 0;
  if W1.Index <> NoIndexValue then begin
    if W2.Index <> NoIndexValue then begin
      w:= W2.Point.x - W1.Point.x;
    end else
      w := 2 * (x - W1.Point.x);
  end else begin
    if W2.Index <> NoIndexValue then
      w := 2 * (W2.Point.x - x);
  end;
  CountWidth := w;
end;

procedure TSpectrum.FinishPeak(const APoint:TIndexPoint);
var
  Peak: TULPRObj;
  hw, hx: TXValue;
  h, a: TYValue;
  pe: TIndexPoint;

label
  ex;
begin
  pe := APoint;
  with ProcPeak do begin
    h := CountHeight(PeakStart, pe, hx);
    if (h < AcqData.SpectrumOpt.PeakHeight.Min) and
      (AcqData.SpectrumOpt.PeakHeight.Min <> 0) then
      goto ex;
    if (h > AcqData.SpectrumOpt.PeakHeight.Max) and
      (AcqData.SpectrumOpt.PeakHeight.Max <> 0) then
      goto ex;

    a := CountArea(PeakStart, pe);
    if (a < AcqData.SpectrumOpt.PeakArea.Min) and
       (AcqData.SpectrumOpt.PeakArea.Min <> 0) then
      goto ex;
    if (a > AcqData.SpectrumOpt.PeakArea.Max) and
       (AcqData.SpectrumOpt.PeakArea.Max <> 0) then
      goto ex;

    hw := CountWidth(PeakStart, pe, h);
    if (hw < AcqData.SpectrumOpt.PeakWidth.Min) and
       (AcqData.SpectrumOpt.PeakWidth.Min <> 0) then
      goto ex;
    if (hw > AcqData.SpectrumOpt.PeakWidth.Max) and
      (AcqData.SpectrumOpt.PeakWidth.Max <> 0) then
      goto ex;


    Peak := TULPRObj(Peaks.Add(ULPRID));
    Peak.DoChangeLock;
    try
      Peak.X1 := PeakStart.Point.X;
      Peak.Y1 := PeakStart.Point.Y;{Peak.P1 := PeakStart;}
      Peak.Index1 := PeakStart.Index;
      Peak.X2 := pe.Point.X;
      Peak.Y2 := pe.Point.Y;
      Peak.Index2 := pe.Index;
      {Peak^.P2 := pe;}
      Peak.X := hx;{CurMax.Point.X;}
      Peak.Height := h;
      Peak.Width := hw;
      Peak.AreaSize := a;

      DoPeakLimitsChanged(Peak);
      {v0.12}
      PeakAssignParamsFromMethod(Peak);
      {/v0.12}
    finally
      Peak.DoChangeUnlock;
    end;
    Peaks.Sort;
  ex:
    CurMin := pe;
    CurMax := pe;
    IsInPeak := false;
  end;
end;

procedure TSpectrum.RecalculatePeak(Peak: TULPRObj);
var
  p:TIndexPoint;
  maxy:TYValue;
  maxx:TXValue;
  a,a1:TAreaSize;
  x1,x2: TXValue;
  y1,y2: TYValue;
begin
  maxy := -$FFFFF;
  maxx := 0;
  a := 0;
  x1 := Peak.X1;
  y1 := Peak.BaseY1;
  x2 := Peak.X2;
  y2 := Peak.BaseY2;
  {v0.26}
  AcqData.ActiveDataName := Peak.DataName;
  {/v0.26}
  AcqData.SeekPoint(Peak.Index1);
  repeat
    if not AcqData.ReadIndexPoint(P) then
      break;
    if P.Index > Peak.Index2 then
      break;
    if P.Point.Y > maxy then begin
      maxy := P.Point.Y;
      maxx := P.Point.X;
    end;
    a1 := P.Point.Y - CountYOnLine(P.Point.X, x1, y1, x2, y2);
    if a1 > 0 then
      a := a + a1;
    {(P.Point.X - LP.Point.X) * (LP.Point.Y - SpectrumOpt.BaseLine);}
  until false;

  Peak.X := maxx;
  if x1 <> x2 then
    Peak.Height := maxy - CountYOnLine(maxx, x1, y1, x2, y2);
    {AcqData.SpectrumOpt.BaseLine;}
  Peak.AreaSize := a;
  Peak.Width := Peak.X2 - Peak.X1;{not really; just for now}

  {wy := Data.SpectrumOpt.BaseLine + (Peak.Height / 2);}

  (*
  P1.Point.Y := P1.Point.Y - SpectrumOpt.BaseLine;
  LP := P1;
  Data.SeekPoint(P1.Index);
  repeat
    if not Data.ReadIndexPoint(P) then
      break;
    P.Point.Y := P.Point.Y - SpectrumOpt.BaseLine;
    if P.Point.Y > LP.Point.Y then begin
      if (LP.Point.Y <= h) and (P.Point.Y >= h) then
        if (P.Point.X <= x) and (W1.Index = NoIndexValue) then
          W1 := P
    end
    else
    if P.Point.Y < LP.Point.Y then begin
      if (LP.Point.Y >= h) and (P.Point.Y <= h) then
        if (P.Point.X >= x) then
          W2 := P;
    end;
    LP := P;
    if P.Index >= P2.Index then
      break;
    dec(cnt);
  until cnt = 0;
  w := 0;
  if W1.Index <> NoIndexValue then begin
    if W2.Index <> NoIndexValue then begin
      w:= W2.Point.x - W1.Point.x;
    end else
      w := 2 * (x - W1.Point.x);
  end else begin
    if W2.Index <> NoIndexValue then
      w := 2 * (W2.Point.x - x);
  end;
 *)
end;

procedure TSpectrum.RecalculatePeaks;
var
  cnt, i: integer;
begin
  cnt := Peaks.ChildCount;
  if cnt = 0 then
    exit;
  Peaks.DoChangeLock;
  try
    for i := 0 to cnt - 1 do begin
      RecalculatePeak(TULPRObj(Peaks.Childs[i]));
    end;
    PeaksCalculateRatios;
    Peaks.Sort;
    {v0.11}
    if AcqData.ULM.CalcAmounts then begin
      {v0.13}
      PeaksAutoCalculate;
      {/v0.13
      PeaksAmountsCalculate;}
    end;
    {/v0.11}
  finally
    Peaks.DoChangeUnlock;
    {v0.26}
    AcqData.UsrDataIndexReselect;
    {/v0.26}
  end;
end;

{procedure TSpectrum.DoPeakPoint(const APoint:TIndexPoint);
begin
end;}

procedure TSpectrum.BaseLineCreate(const P1, P2: TIndexPoint);
begin
  if P1.Index <= P2.Index then begin
    StartBaseLine(P1);
    FinishBaseLine(P2);
  end else begin
    StartBaseLine(P2);
    FinishBaseLine(P1);
  end;
end;

procedure TSpectrum.StartBaseLine(const APoint: TIndexPoint);
begin
  ClearBLPeak;
  with BLPeak do begin
    PeakStart := APoint;
    IsInPeak := true;
    CurMin := APoint;
    CurMax := APoint;
  end;
end;

procedure TSpectrum.FinishBaseLine(const APoint:TIndexPoint);
var
  BR: TULBRObj;
{  hw, hx: TXValue;
  h, a: TYValue;}
  pe: TIndexPoint;
label ex;
begin
  pe := APoint;
  with BLPeak do begin
    {h := CountHeight(PeakStart, pe, hx);
    if (h < AcqData.SpectrumOpt.PeakHeight.Min) and
      (AcqData.SpectrumOpt.PeakHeight.Min <> 0) then
      goto ex;
    if (h > AcqData.SpectrumOpt.PeakHeight.Max) and
      (AcqData.SpectrumOpt.PeakHeight.Max <> 0) then
      goto ex;

    a := CountArea(PeakStart, pe);
    if (a < AcqData.SpectrumOpt.PeakArea.Min) and
       (AcqData.SpectrumOpt.PeakArea.Min <> 0) then
      goto ex;
    if (a > AcqData.SpectrumOpt.PeakArea.Max) and
       (AcqData.SpectrumOpt.PeakArea.Max <> 0) then
      goto ex;

    hw := CountWidth(PeakStart, pe, h);
    if (hw < AcqData.SpectrumOpt.PeakWidth.Min) and
       (AcqData.SpectrumOpt.PeakWidth.Min <> 0) then
      goto ex;
    if (hw > AcqData.SpectrumOpt.PeakWidth.Max) and
      (AcqData.SpectrumOpt.PeakWidth.Max <> 0) then
      goto ex;
    }
    BR := TULBRObj(BaseLine.Add(ULBRID));

    BR.X1 := PeakStart.Point.X;
    BR.Y1 := PeakStart.Point.Y;{BR.P1 := PeakStart;}
    {BR.Index1 := PeakStart.Index;}
    BR.X2 := pe.Point.X;
    BR.Y2 := pe.Point.Y;
    {BR.Index2 := pe.Index;}
    {BR.X := hx;
    BR.Height := h;
    BR.Width := hw;
    BR.AreaSize := a;}
    DoBaseLineLimitsChanged(BR);

    BaseLine.Sort;

  ex:
    {CurMin := pe;
    CurMax := pe;}
    IsInPeak := false;
  end;
end;

function TSpectrum.MoveScreenBaseLine(OldStartX, NewStartX: TScreenX;
  const AScreenDisp: TScreenDisp; var Index: integer): boolean;
var
  pi: integer;
  sp: TScreenPoint;
  p: TULBRObj;
  ap: TExpPoint;
{  d: TXValue;
  ap, op1,op2,
  np1,np2: TExpPoint;
  index1,index2: TPointCount;}
begin
  MoveScreenBaseLine := false;
  if not IsScreenXInBaseLine(OldStartX, AScreenDisp, pi) then
    exit;
{  if not IsScreenPeakStart(OldStartX, AScreenDisp, pi) then
    exit;}
  sp.X := NewStartX;
  sp.Y := AScreenDisp.Top;
  if not AcqData.ScreenToExp(sp, AScreenDisp, ap) then
    exit;
  p := TULBRObj(BaseLine.Childs[pi]);

  p.X1 := ap.X;
  DoBaseLineLimitsChanged(p);
  {UpdateBaseLineRecY(p);}


  {
  d := ap.X - p.X1;
  op1.X := p.X1 + d;;
  op1.Y := p.Y1;
  op2.X := p.X2 + d;
  op2.Y := p.Y2;
  if not AcqData.FindDataPoint(op1, np1, index1) then
    exit;
  if not AcqData.FindDataPoint(op2, np2, index2) then
    exit;
  p.X1 := np1.X;
  p.Y1 := np1.Y;
  p.X2 := np2.X;
  p.Y2 := np2.Y;
  }
  MoveScreenBaseLine := true;
end;

procedure TSpectrum.DoBaseLineLimitsChanged(b: TULBRObj);
var
  p: TULPRObj;
  i: integer;
begin
  UpdateBaseLineRecY(b);
  Peaks.DoChangeLock;
  try
    for i := 0 to Peaks.ChildCount - 1 do begin
      p := TULPRObj(Peaks.Childs[i]);
      if p.RecID = ULPRID then begin
        if ((p.X1 >= b.X1) and (p.X1 <= b.X2)) or
           ((p.X2 >= b.X1) and (p.X2 <= b.X2)) then
        begin
          DoPeakLimitsChanged(p);
        end;
      end;
    end;
  finally
    Peaks.DoChangeUnlock;
  end;
end;

procedure TSpectrum.UpdateBaseLineRecY(b: TULBRObj);
var
  op, np: TExpPoint;
  index: TPointCount;
begin
 {v0.25
  exit;}
  op.X := b.X1;
  op.Y := b.Y1;
  if AcqData.FindDataPoint(op, np, index) then begin
    b.X1 := np.X;
    b.Y1 := np.Y;
  end;
  op.X := b.X2;
  op.Y := b.Y2;
  if AcqData.FindDataPoint(op, np, index) then begin
    b.X2 := np.X;
    b.Y2 := np.Y;
  end;
end;

procedure TSpectrum.BaseLineInsertPointFromScreen(const AScreenDisp: TScreenDisp; X: TScreenX);
var
  pi: integer;
  sp: TScreenPoint;
  ap,np: TExpPoint;
  p: TULBRObj;
  n: TULBRObj;
  index: integer;
begin
  if not IsScreenXInBaseLine(X, AScreenDisp, pi) then
    exit;
  sp.X := X;
  sp.Y := AScreenDisp.Top;
  if not AcqData.ScreenToExp(sp, AScreenDisp, ap) then
    exit;
  if not AcqData.FindDataPoint(ap, np, index) then
    exit;
  p := TULBRObj(BaseLine.Childs[pi]);
  n := TULBRObj(BaseLine.Add(ULBRID));
  n.X1 := np.X;
  n.Y1 := np.Y;
  n.X2 := p.X2;
  n.Y2 := P.Y2;
  n.GroupedLeft := true;
  p.X2 := np.X;
  p.Y2 := np.Y;
  p.GroupedRight := true;
  DoBaseLineLimitsChanged(p);
  DoBaseLineLimitsChanged(n);
  BaseLine.Sort;
end;

procedure TSpectrum.BaseLineMerge;
var
  i: integer;
  p, n: TULBRObj;
begin
  i := 0;
  while i < BaseLine.ChildCount do begin
    p := TULBRObj(BaseLine.Childs[i]);
    if p.IsFlagSet(rfSelected) then begin
      if (i + 1 < BaseLine.ChildCount) then begin
        n := TULBRObj(BaseLine.Childs[i + 1]);
        if n.IsFlagSet(rfSelected) then begin
          if n.CanDestroy then begin
            p.X2 := n.X2;
            p.Y2 := n.Y2;
            p.GroupedRight := n.GroupedRight;
            n.Free;
            DoBaseLineLimitsChanged(p);
            continue;
          end;
        end;
      end;
    end;
    inc(i);
  end;
end;

procedure TSpectrum.BaseLineGroup;
var
  i: integer;
  p, n: TULBRObj;
begin
  i := 0;
  while i < BaseLine.ChildCount do begin
    p := TULBRObj(BaseLine.Childs[i]);
    if p.IsFlagSet(rfSelected) then begin
      if (i + 1 < BaseLine.ChildCount) then begin
        n := TULBRObj(BaseLine.Childs[i + 1]);
        if n.IsFlagSet(rfSelected) then begin
          p.X2 := n.X1;
          p.Y2 := n.Y1;
          p.GroupedRight := true;
          n.GroupedLeft := true;
          DoBaseLineLimitsChanged(p);
          DoBaseLineLimitsChanged(n);
        end;
      end;
    end;
    inc(i);
  end;
end;

procedure TSpectrum.BaseLineUnGroup;
var
  i: integer;
  p{, n}: TULBRObj;
begin
  i := 0;
  while i < BaseLine.ChildCount do begin
    p := TULBRObj(BaseLine.Childs[i]);
    if p.IsFlagSet(rfSelected) then begin
      p.GroupedRight := false;
      p.GroupedLeft := false;
    end;
    inc(i);
  end;
end;

function TSpectrum.SizeScreenBaseLine(OldStopX, NewStopX: TScreenX;
  const AScreenDisp: TScreenDisp; var PIndex: integer; Right: boolean;
  SizingTwo: boolean): boolean;
var
  pi: integer;
  sp: TScreenPoint;
  ap: TExpPoint;
  p: TULBRObj;
  np: TExpPoint;
  index: TPointCount;
begin
  SizeScreenBaseLine := false;
  if not IsScreenXInBaseLine(OldStopX, AScreenDisp, pi) then
    exit;
  {
  if not IsScreenPeakStop(OldStopX, AScreenDisp, pi) then
    exit;
  }
  sp.X := NewStopX;
  sp.Y := AScreenDisp.Top;
  if not AcqData.ScreenToExp(sp, AScreenDisp, ap) then
    exit;
  if not AcqData.FindDataPoint(ap, np, index) then
    exit;
  p := TULBRObj(BaseLine.Childs[pi]);
  if right then begin
    if p.X1 >= np.X{Index1 >= index} then begin
      {if p.CanDestroy then
        p.Free;}
      exit;
    end;
    p.X2 := np.X;
    p.Y2 := np.Y;
    {p.Index2 := index;}
  end else begin
    if p.X2 <= np.X{p.Index2 <= index} then begin
      {if p.CanDestroy then
        p.Free;}
      exit;
    end;
    p.X1 := np.X;
    p.Y1 := np.Y;
    {p.Index1 := index;}
  end;
  DoBaseLineLimitsChanged(p);

  if SizingTwo then begin
    { sizing effect also neighbour baseline section: }
    if right then begin
      { section on the right affected, i.e. new positin (np) is left edge of the section }
      inc(pi);
      if pi >= BaseLine.ChildCount then
        exit; { .. there is no more sections }
      p := TULBRObj(BaseLine.Childs[pi]);
      if np.X >= p.X2 then begin
        exit; {left limit can not be more or equal to the right limit of the section}
      end;
      p.X1 := np.X;
      p.Y1 := np.Y;
      {p.Index2 := index;}
    end else begin
      { section on the left affected: }
      dec(pi);
      if pi < 0 then
        exit;
      p := TULBRObj(BaseLine.Childs[pi]);
      if np.X <= p.X1 then begin
        exit;
      end;
      p.X2 := np.X;
      p.Y2 := np.Y;
      {p.Index1 := index;}
    end;
    DoBaseLineLimitsChanged(p);
  end;
  SizeScreenBaseLine := true;
end;


procedure TSpectrum.PeakCreate(const P1, P2: TIndexPoint);
begin
  if P1.Index <= P2.Index then begin
    StartPeak(P1);
    FinishPeak(P2);
  end else begin
    StartPeak(P2);
    FinishPeak(P1);
  end;
end;

procedure TSpectrum.DoPoint(const APoint: TIndexPoint);
begin
  with ProcPeak do begin
    if APoint.Point.Y > CurMax.Point.Y then begin
      CurMax := APoint;
      CurMin := APoint;
    end;

    if APoint.Point.Y < CurMin.Point.Y then
      CurMin := APoint;

    if AutoDetectPeaks then begin
      if not IsInPeak then begin
        {the peak was not started yet}
        if APoint.Point.Y > (AcqData.SpectrumOpt.BaseLine +
          AcqData.SpectrumOpt.PeakStartLevel) then
        begin
          {we came in peaks area}
          StartPeak(APoint);
        end else begin
          {we are under peaks area}
          if APoint.Point.Y > Point_1.Point.Y then begin
            {we are going up}
            if Point_1.Point.Y < Point_2.Point.Y then begin
              {but were going down before}
              {i.e. point_1 is local minimum, push min value}
              LocMin_2 := LocMin_1;
              LocMin_2 := Point_1;
    {
              if Point_1.Y < CurMin.Y then
                CurMin := Point_1;
            {  if Point_1.Y}
            end else begin
              {and were also going up or resting before}
              {i.e. the line is still raising below peeks level}
            end;
          end else begin
            {we are going down or resting below peeks level}
            if APoint.Point.Y < Point_1.Point.Y then begin
              {we are going down}
              if Point_1.Point.Y > Point_2.Point.Y then begin
                {but were going up before}
                {i.e. point_1 is local maximum, push max value}
                LocMax_2 := LocMax_1;
                LocMax_1 := Point_1;
              end else begin
                {and were also going down or resting before, below peeks level}
              end;
            end else begin
              {y did not change}
            end;
          end;
          {end of under peaks area}
        end;
      end else begin
        {we started to create a peak some time ago}
        if APoint.Point.Y <= (AcqData.SpectrumOpt.BaseLine +
          AcqData.SpectrumOpt.PeakEndLevel) then
        begin
          {we are leaving peaks area}
          FinishPeak(APoint);
        end else begin
          {we are in peaks area}
          if APoint.Point.Y > Point_1.Point.Y then begin
            {we are going up}
            if Point_1.Point.Y < Point_2.Point.Y then begin
              {but were going down before}
              {i.e. point_1 is local minimum, push min value}
              if Max_1.Point.Y - APoint.Point.Y > AcqData.SpectrumOpt.PeakHeight.Min then
              begin
                Min_2 := Min_1;
                Min_1 := Point_1;
                FinishPeak(Point_1);
              end;
              LocMin_2 := LocMin_1;
              LocMin_2 := Point_1;
            {  if Point_1.Y}
            end else begin
              {and were also going up before}
              {i.e. the line is still raising, and we are in the peek}
              if (APoint.Point.Y - CurMin.Point.Y) > AcqData.SpectrumOpt.PeakHeight.Min then
              begin
                {raised enough from the last minimum so that it could be
                 considered to be a new peak}
                if (CurMax.Point.Y - CurMin.Point.Y) > AcqData.SpectrumOpt.PeakHeight.Min then
                begin
                  {check also if the last extremes dif could belong to peak}
                  if CurMax.Point.X < CurMin.Point.X then
                    FinishPeak(CurMin);
                end;
              end;
            end;
          end else if APoint.Point.Y < Point_1.Point.Y then begin
            {we are going down}
            if Point_1.Point.Y > Point_2.Point.Y then begin
              {but were going up before}
              {i.e. point_1 is local maximum, push max value}
              LocMax_2 := LocMax_1;
              LocMax_1 := Point_1;
            end else if Point_1.Point.Y < Point_2.Point.Y then begin
                {and were also going down before}
            end;
          end;{else  y did not change}
          {end, in peak area}
        end;
        {end inpeak}
      end;

      Point_2 := Point_1;
      Point_1 := APoint;
      {if IsInPeak then
        DoPeakPoint(APoint);}
    end;{if AutoDetectPeaks}
  end;{with ProcPeak}
end;{DoPeak}

procedure TSpectrum.ClearBLPeak;
begin
  FillChar(BLPeak, sizeof(ProcPeak), 0);
end;

procedure TSpectrum.ClearProcPeak;
begin
  FillChar(ProcPeak, sizeof(ProcPeak), 0);
{
  Point_1.X := NoIndexValue;
  Point_2.X := NoIndexValue;
  LastLocExt.Min.X := NoIndexValue;
  LastLocExt.Max.X := NoIndexValue;
}
end;

procedure XYFilterGaus(points:PExpPoints;count:integer;S:single);
const
  Gbits=6;
  Gmask=(1 shl Gbits)-1;
var
  Gaus:array[0..Gmask]of TYValue;
  Yhist:array[0..Gmask]of TYValue;
  ip,ig:integer;
  Glen:integer;
  DX,X:TXValue;
  Gsum,Y:TYValue;
begin
  {v0.11}
  MathCoprMaskUnderflow; {No exception on rounding to zero}
  {/v0.11}
  if (count<16)or(S=0) then exit;
  DX:=(points^[count-1].X-points^[0].X)/(count-1);
  ig:=0; Gsum:=0; X:=0;
  repeat
    {v0.30 was causing floating point overflow}
    Y := 1/sqrt(2*pi);
    Y := Y/S;
    Y := Y*exp(-(X*X)/(S*S));
    {/v0.30
    Y:=1/sqrt(2*pi)/S*exp(-(X*X)/(S*S));}
    Gaus[ig]:=Y;
    Gsum:=Gsum+Y;
    inc(ig); X:=X+DX;
  until (ig>Gmask) or (Y<Gaus[0]*0.01);
  Glen:=ig-1;
  if Glen<=1 then exit;
  Y:=points^[0].Y;
  for ip:=0 to Gmask do Yhist[ip]:=Y;
  for ip:=0 to count-1 do
  begin
    Y:=points^[ip].Y;
    Yhist[ip and Gmask]:=Y;
    Y:=Y*Gaus[0];
    for ig:=1 to Glen do
    begin
      if ip+ig<count then
        Y:=Y+points^[ip+ig].Y*Gaus[ig]
      else
        Y:=Y+points^[count-1].Y*Gaus[ig];
      Y:=Y+Yhist[(ip-ig)and Gmask]*Gaus[ig];
    end;
    points^[ip].Y:=Y/Gsum;
  end;
end;

procedure XYDiff2Ord(points:PExpPoints;count:integer);
var
  i: integer;
  DiffX: TXValue;
  DiffA,DiffB: TYValue;
begin
  if count<3 then exit;
  DiffB:=0;
  for i:=0 to count-2 do
  begin
    DiffA:=DiffB;
    DiffX:=points^[i+1].X-points^[i].X;
    if DiffX=0 then DiffX:=0.001;
    DiffB:=(points^[i+1].Y-points^[i].Y)/DiffX;
    points^[i].Y:=DiffB-DiffA;
  end;
  points^[count-1].Y:=-DiffB;
end;

type
  BoolArray=array[0..MaxLongint div 8] of boolean;
  PBoolArray=^BoolArray;

{ Classify points as base or candidates for peaks
  and store result in isbase
    BaseMinInterval .. minimal length of couse satisfiing conditions
    BaseMaxDiff .. maximal absolute value of second order derivative
                   of course, if zero, automatic tuning is applied
}
procedure XYClassBase2Der(points:PExpPoints;count:integer;isbase:PBoolArray;
                      BaseMinInterval,BaseMaxDiff:single);
var
  diffp: PExpPoints;
  i, ib: integer;
  LastX: TXValue;
  MinDiff: TXValue;
  LocalMaxDiff: TXValue;
  zeropt,allpt: integer;
begin
  diffp:=nil;
  {v0.12}
  if Count <= 0 then
    exit;
  {/v0.12}
  try
    {Compute filtered second order derivative}
    GetMem(diffp,count*sizeof(TExpPoint));
    Move(points^,diffp^,count*sizeof(TExpPoint));
    XYDiff2Ord(diffp,count);
    XYFilterGaus(diffp,count,BaseMinInterval/2);
    {Find appropriate value when BaseMaxDiff is zero}
    if BaseMaxDiff=0 then
    begin
      MinDiff:=1e37;
      for i:=0 to count-1 do
      begin
        LocalMaxDiff:=0; ib:=i; zeropt:=0; allpt:=0;
        while (diffp^[ib].X-diffp^[i].X)<2*BaseMinInterval do
        begin
          inc(allpt);
          if diffp^[ib].Y=0 then inc(zeropt) else
            if LocalMaxDiff<abs(diffp^[ib].Y) then
              LocalMaxDiff:=abs(diffp^[ib].Y);
          inc(ib);
          if ib>count-1 then begin LocalMaxDiff:=1e37; break; end;
        end;
        if (LocalMaxDiff<MinDiff)and(zeropt<allpt/4) then
            MinDiff:=LocalMaxDiff;
      end;
      BaseMaxDiff:=10*MinDiff;
      (*Sum:=0;
      for i:=0 to count-1 do
      begin
        Sum:=Sum+Exp(-abs(diffp^[i].Y));
      end;
      BaseMaxDiff:=-Ln(Sum/count);*)
      DebLog('Computed BaseMaxDiff = '+FloatToStr(BaseMaxDiff));
    end;
    {Classify points}
    LastX:=diffp^[0].X; ib:=0;
    for i:=0 to count-1 do
    begin
      if abs(diffp^[i].Y)<=BaseMaxDiff then
      begin
        if (diffp^[i].X-LastX)>=BaseMinInterval then
        begin
          while ib<=i do
          begin
            isbase^[ib]:=true;
            inc(ib);
          end;
        end;
      end else begin
        LastX:=diffp^[i].X;
        while ib<=i do
        begin
          isbase^[ib]:=false;
          inc(ib);
        end;
      end;
    end;
  finally
    if diffp <> nil then
      FreeMem(diffp,count*sizeof(TExpPoint));
  end;
end;

{ Classify points as base or candidates for peaks
  and store result in isbase
    BaseMinInterval .. minimal length of couse satisfiing conditions
    BaseMaxSn .. maximal standart deviation of baseline course,
                 if zero, automatic tuning is applied
}
procedure XYClassBaseStdDev(points: PExpPoints; count: integer;
  isbase: PBoolArray; BaseMinInterval, BaseMaxStdDev: single);
var
  disp: PExpPoints;
  i, j, ib, iblock: integer;
  LastX, DX: TXValue;
  npoints: integer;
  SumY,SumY2:double;
  SumYNxt,SumY2Nxt:double;
  ADisp:single;
  BaseMaxDisp:single;

  {$IFNDEF CONSOLE}
  {m}f:TForm;{/m}
  {$ENDIF}
begin
  disp := nil;
  {v0.12}
  if Count <= 0 then
    exit;
  {/v0.12}
  try
    {v0.11}
    MathCoprMaskUnderflow; {No exception on rounding to zero}
    {/v0.11}
    {Consider constant sampling frequency -- It is not ptimal}
    DX:=(points^[count-1].X-points^[0].X)/(count-1);
    if DX=0 then exit;
    npoints:=round(BaseMinInterval/DX);
    if npoints<10 then npoints:=10;
    if count<2*npoints then exit;

    {Compute filtered standard deviation}
    GetMem(disp,count*sizeof(TExpPoint));
    Move(points^,disp^,count*sizeof(TExpPoint));
    XYFilterGaus(disp,count,BaseMinInterval/4);

    {Find appropriate value when BaseMaxStdDev is zero}
    if BaseMaxStdDev=0 then
    begin
      BaseMaxStdDev:=1e-5;
    end;
    BaseMaxDisp:=Sqr(BaseMaxStdDev)*Sqr(npoints);

    {Compute disperse for moving window
     Computation must be restarted after short block,
     because float format can accumulate precission errors
     A+B+C-A-B-C <> 0 }
    SumYNxt:=0;SumY2Nxt:=0;
    i:=0;
    while i<npoints do
    begin
      SumYNxt:=SumYNxt+disp^[i].Y; SumY2Nxt:=SumY2Nxt+Sqr(disp^[i].Y);
      inc(i)
    end;
    j:=0;
    while i<count do
    begin
      iblock:=i+npoints;
      if iblock>count then iblock:=count;
      SumY:=SumYNxt;SumY2:=SumY2Nxt;
      SumYNxt:=0;SumY2Nxt:=0;
      repeat
        ADisp:=SumY2*npoints-Sqr(SumY);
        SumY:=SumY-disp^[j].Y; SumY2:=SumY2-Sqr(disp^[j].Y);
        SumY:=SumY+disp^[i].Y; SumY2:=SumY2+Sqr(disp^[i].Y);
        SumYNxt:=SumYNxt+disp^[i].Y; SumY2Nxt:=SumY2Nxt+Sqr(disp^[i].Y);
        disp^[j].Y:=ADisp;
        inc(i);inc(j);
      until i>=iblock;
    end;
    npoints:=npoints div 2;
    {Center compuded disperse around corresponding X}
    dec(i);dec(j);
    while i>count-npoints do
    begin
      disp^[i].Y:=disp^[j].Y;
      dec(i);
    end;
    while j>=0 do
    begin
      disp^[i].Y:=disp^[j].Y;
      dec(i);dec(j);
    end;
    while i>=0 do
    begin
      disp^[i].Y:=disp^[i+1].Y;
      dec(i);
    end;

    {Classify points}
    LastX:=disp^[0].X; ib:=0;
    for i:=0 to count-1 do
    begin
      if disp^[i].Y<=BaseMaxDisp then
      begin
        if (disp^[i].X-LastX)>=BaseMinInterval then
        begin
          while ib<=i do
          begin
            isbase^[ib]:=true;
            inc(ib);
          end;
        end;
      end else begin
        LastX:=disp^[i].X;
        while ib<=i do
        begin
          isbase^[ib]:=false;
          inc(ib);
        end;
      end;
    end;
    {Display disperse course for debug purposes}
    if {ulanglob}(UserMode = umSysOp) {v0.12}and false {/v0.12} then {ulantype}
    begin
      {$IFNDEF CONSOLE}
      f := Application.MainForm.ActiveMDIChild;
      if (f <> nil) and (f is TSpectrumForm) then with f as TSpectrumForm do begin
        {v0.11}
        MathCoprMaskUnderflow; {No exception on rounding to zero}
        {/v0.11}
        for i := 0 to count - 1 do begin
          {v0.12}
          if disp^[i].Y < 0 then
            disp^[i].Y := 0
          else
          {/v0.12}
            disp^[i].Y := sqrt(disp^[i].Y) / npoints;
        end;
        AddData(TAcqData.CreateFromPoints(disp, count));
      end;
      {$ENDIF}
    end;
  finally
    if disp <> nil then
      FreeMem(disp,count*sizeof(TExpPoint));
  end;
end;

{ Find index of nearest [X,Y] point for given X }
function XYFindXIndex(points:PExpPoints;count:integer;
                      Xval:TXValue):integer;
var
  ib, ie, i: integer;
begin
  if count<1 then
    begin Result:=-2; exit; end;
  if points^[0].X>Xval then
    begin Result:=-1; exit; end;
  ib:=0;
  ie:=count;
  repeat
    i:=(ib+ie) div 2;
    if Xval<points^[i].X then ie:=i
                         else ib:=i;
  until ib=ie-1;
  Result:=ib;
end;

{ Autodetect baseline }
function TSpectrum.AutoDetectForBase: boolean;
var
  X, X1: TXValue;
  Y: TYValue;
  n: integer;
  ipt, ipt2, cpt: integer;
  newbaseln: TULBRObj;{ in ULBRType.PAS }
  points: TMemoryStream;
  pa: PExpPoints;{ulantype}
  isbase: PBoolArray;
  bmi,bmd: single;

  dname: string;
  fp: TULPRObj;
begin
  Result := false;
  points := nil;
  isbase := nil;
  {v0.11}
  newbaseln := nil;
  cpt := 0;
  {/v0.11}
  try
    {v0.11}
    MathCoprMaskUnderflow; {No exception on rounding to zero}
    if AcqData.{v0.24}ActiveData.Size{/v0.24 ULAD.Data.Size} = 0 then
      exit;
    {/v0.11}                         {ulbrobju}
    points := TMemoryStream.Create;
    dname := AcqData.ActiveULADrc.ULAD.DataName;{uladtype}
    AcqData.{v0.24}ActiveData{/v0.24 ULAD.Data}.Position := 0;
    points.CopyFrom(AcqData.{v0.24}ActiveData{/v0.24 ULAD.Data}, AcqData.ActiveData.Size);
    cpt := AcqData.ActiveData.Size div sizeof(TExpPoint);
    pa := PExpPoints(points.Memory);
    {v0.25}{/v0.25 BaseLine.Clear;}

    {Find candidates for base}
    GetMem(isbase, cpt * sizeof(boolean));
    {v0.12}
    FillChar(isbase^, cpt * sizeof(boolean),0);
    {/v0.12}
    bmi := AcqData.ULM.BaseMinInterval;
    bmd := AcqData.ULM.BaseMaxDiff;
    XYClassBaseStdDev(pa, cpt, isbase, bmi, bmd);
    {XYClassBase2Der(pa, cpt, isbase, bmi, bmd);}

    {Create base description}
    ipt:=0;
    while ipt<cpt do
    begin
      if isbase^[ipt] then inc(ipt)
      else
      begin
        {v0.11}
        if newbaseln <> nil then begin
          if newbaseln.X2 - newbaseln.X1 < bmi then
            newbaseln.Free;
        end;
        {/v0.11}

        newbaseln := TULBRObj(BaseLine.Add(ULBRID));
        {v0.25}
        newbaseln.DataName := AcqData.ActiveULADRc.ULAD.DataName;
        {/v0.25}
        {Find aproximate left base point}
        ipt2:=ipt; X1:=pa^[ipt2].X;
        repeat
          if ipt2=0 then break;
          if not isbase^[ipt2-1] then break;
          dec(ipt2);
        until (X1-pa^[ipt2].X>bmi/4);
        X:=0; Y:=0; n:=0;
        repeat
          X:=X+pa^[ipt2].X; Y:=Y+pa^[ipt2].Y; inc(n);
          if ipt2=0 then break;
          if not isbase^[ipt2-1] then break;
          dec(ipt2);
        until (X1-pa^[ipt2].X>bmi/2);
        newbaseln.X1:=X/n; newbaseln.Y1:=Y/n;
        {Find right base point}
        repeat
          {v0.28}
          {/v0.28 if ipt>=cpt-1 then break;}
          inc(ipt);
          {v0.28}
          if ipt>=cpt then break;
          {/v0.28}
        until isbase^[ipt];
        ipt2:=ipt-1; X1:=pa^[ipt2].X;
        repeat
          if ipt2>=cpt-1 then break;
          if not isbase^[ipt2+1] then break;
          inc(ipt2);
        until (pa^[ipt2].X-X1>bmi/4);
        X:=0; Y:=0; n:=0;
        repeat
          X:=X+pa^[ipt2].X; Y:=Y+pa^[ipt2].Y; inc(n);
          if ipt2>=cpt-1 then break;
          if not isbase^[ipt2+1] then break;
          inc(ipt2);
        until (pa^[ipt2].X-X1>bmi/2);
        newbaseln.X2 := X/n; newbaseln.Y2 := Y/n;
        newbaseln.X := (newbaseln.X1 + newbaseln.X2)/2;
       {DebLog('NewBaseLn: '+FloatToStr(newbaseln.X1)+','+FloatToStr(newbaseln.y1)
               +' - '+FloatToStr(newbaseln.X2)+','+FloatToStr(newbaseln.Y2));}

        if not PeakFindInPeaksInXRange(AcqData.ULM_ULP, dname, newbaseln.x, newbaseln.x1, newbaseln.x2, fp) then
        begin
          {v0.50}
          if AcqData.ActiveDataName <> AcqData.ULM.DefDataName then begin
            newbaseln.free;
            newbaseln := nil;
          end else
          {/v0.50
          if AcqData.DataIndex <> 0 then begin
            newbaseln.free;
            newbaseln := nil;
          end else
          }
          begin
            if PeakFindInPeaksInXRange(AcqData.ULM_ULP, '*', newbaseln.x, newbaseln.x1, newbaseln.x2, fp) then
            begin
              newbaseln.free;
              newbaseln := nil;
            end else begin
              if AcqData.ULM.NoUnknownPeaks then
              begin
                newbaseln.free;
                newbaseln := nil;
              end;
            end;
          end;
        end;
      end;
    end;
  finally
    if isbase <> nil then
      FreeMem(isbase,cpt*sizeof(boolean));
    if points <> nil then
      points.Free;
  end;
  Result := true;
end;

function TSpectrum.AutoDetectForPeaks: boolean;
var
  ibl, ipt, ipt2, ipt3, dpt, {cbl,} cpt: integer;
  baseln: TULBRObj; { in ULBRType.PAS }
  points: TMemoryStream;
  pa, tmppa: PExpPoints;{ulantype}
  slope: single;
  MinPeakWidth:single;
  MinPeakHeight:single;
  RejectLevel:single;
  {v0.25}
  noUnknownPeaks: boolean;
  cDataName: string;
  {/v0.25}

 procedure StoreNewPeak(PTX:TXValue);
 var
   pti,i:     integer;
   newpeak:   TULPRObj; { in ULPRType.PAS }
   MinX:      TXValue;
   PHHY,MinY: TYValue;
   PHHHit:    boolean;
   PHHLimX:   TXValue;
   {v0.25}
   fp: TULPRObj;
   {/v0.25}
 begin
   pti:=XYFindXIndex(tmppa, dpt, PTX);
   {Reject small peaks under MinPeakHeight}
   if abs(tmppa^[pti].Y) < MinPeakHeight then
     exit;
   {v0.25 reject peaks that are not in method (if requested)}
   if noUnknownPeaks then begin
     if not PeakFindPeakInMethodByX(PTX, fp) then
       exit; { peak not found in method, exit }

     (*v0.50*)
     if not AcqData.DataNamesEqual(fp.DataName, cDataName) then
       exit;
     (*/v0.50
     if fp.DataName <> '' then begin
       { peak is (possibly) on non default data line: }
       if fp.DataName = 'G' then begin
         { it is default in AAA }
         if (cDataName <> '') then begin
           if fp.DataName <> cDataName then
             exit;
         end;
       end else begin
         if cDataName <> fp.DataName then
           exit;
       end;
     end else begin
       { peak is on default data line (has no DataName): }
       if cDataName <> '' then begin
         { possibly nondefault data }
         if cDataName <> 'G' then begin { G is default in AAA }
           exit;
         end;
       end;
     end;
     *)
   end;
   {/v0.25}
   {Create new peak object}
   newpeak := TULPRObj(Peaks.Add(ULPRID));
   newpeak.X:= PTX;
   {v0.27}
   newpeak.DataName:=cDataName;
   {/v0.27}
   PHHY:=tmppa^[pti].Y/2;
   { Find Left Peak End }
   PHHLimX:=0;
   i := pti; PHHHit := False; MinY := tmppa^[i].Y; MinX := PTX;
   while i > 0 do
   begin
     dec(i);
     if not PHHHit then
     begin
       if tmppa^[i].Y<=PHHY then
       begin
         PHHLimX:=PTX-(PTX-tmppa^[i].X)*(2+1/4);
         PHHHit:=True;
       end
     end
     else if tmppa^[i].X<PHHLimX then break;
     if tmppa^[i].Y<=MinY then begin
       MinX:=tmppa^[i].X; MinY:=tmppa^[i].Y;
     end
     else if tmppa^[i].X<MinX-2*MinPeakWidth then break;
   end;
   newpeak.X1:=MinX;
   { Find Right Peak End }
   i:=pti; PHHHit:=False; MinY:=tmppa^[i].Y; MinX:=PTX;
   while i<dpt-1 do
   begin
     inc(i);
     if not PHHHit then
     begin
       if tmppa^[i].Y<=PHHY then
       begin
         PHHLimX:=PTX+(tmppa^[i].X-PTX)*(2+1/4);
         PHHHit:=True;
       end;
     end
     else if tmppa^[i].X>PHHLimX then break;
     if tmppa^[i].Y<=MinY then begin
       MinX:=tmppa^[i].X; MinY:=tmppa^[i].Y;
     end
     else if tmppa^[i].X>MinX+2*MinPeakWidth then break;
   end;
   newpeak.X2:=MinX;
 end;

 procedure ProcessOneReg; {imput is tmppa,dpt,MinPeakWidth}
 var
   i : integer;
   PTB,PTE: TXValue;    {Peak Top Begin/End}
   PTX: TXValue;        {Peak Top X Value}
   PTY: TYValue;        {Peak Top Y Value}
   AY:  TYValue;        {Actual Y}
   FindPT: Boolean;     {Look For Peak Top else Valey}
 begin
   i:=0;
   PTB:=tmppa^[i].X; PTE:=PTB;
   PTY:=0;
   FindPT:=True;
   repeat
     AY:=tmppa^[i].Y;
     if FindPT then
     begin
       if AY>PTY then begin PTB:=tmppa^[i].X; PTE:=PTB; PTY:=AY; end
       else if AY=PTY then begin PTE:=tmppa^[i].X; end
       else if tmppa^[i].X>=PTB+MinPeakWidth then
       begin
	 if PTY>=RejectLevel then
         begin
	   PTX:=(PTB+PTE)/2; {Retention Time of New Peak}
	   if (PTX>tmppa^[0].X+MinPeakWidth/3) and
              (PTX<tmppa^[dpt-1].X-MinPeakWidth/3) then
             StoreNewPeak(PTX);
	 end;
	 FindPT:=False;
       end;
     end else begin
       if AY<PTY then begin PTY:=AY; PTE:=tmppa^[i].X; end
       else if tmppa^[i].X>=PTE+MinPeakWidth then FindPT:=True;
     end;
     inc(i);
   until i>=dpt;
 end;

begin
  tmppa :=nil;
  points :=nil;
  cDataName := AcqData.ActiveULADRc.ULAD.DataName;
  {v0.50}
  if cDataName = AcqData.ULM.DefDataName then
  {/v0.50if AcqData.DataIndex = 0 then}
  begin
    noUnknownPeaks := AcqData.ULM.NoUnknownPeaks;
  end else begin
    noUnknownPeaks:= true;
  end;

  Peaks.DoChangeLock;
  try
    {v0.11}
    MathCoprMaskUnderflow; {No exception on rounding to zero}
    {/v0.11}
    points := TMemoryStream.Create;
    AcqData.ActiveData.Position := 0;
    points.CopyFrom(AcqData.ActiveData, AcqData.ActiveData.Size);
    cpt := AcqData.ActiveData.Size div sizeof(TExpPoint);
    pa := PExpPoints(points.Memory);
    MinPeakWidth := AcqData.ULM.MinPeakWidth;
    MinPeakHeight := AcqData.ULM.MinPeakHeight;
    {v0.25}{/v0.25 Peaks.Clear;}
    {Detect peaks above found base ranges}
    RejectLevel:=0;
    ibl:=0;
    while ibl < BaseLine.ChildCount do
    begin
      baseln := TULBRObj(BaseLine.Childs[ibl]);
      if (baseln.RecID = ULBRID) {v0.25} and
        {v0.50}
        AcqData.DataNamesEqual(baseln.DataName, AcqData.ActiveULADRc.ULAD.DataName)
        {/v0.50
        (baseln.DataName = AcqData.ActiveULADRc.ULAD.DataName)}
      then
      begin
        ipt := XYFindXIndex(pa, cpt, baseln.X1)+1;
        ipt2 := XYFindXIndex(pa, cpt, baseln.X2);
        dpt := ipt2 + 1 - ipt; {Count of points above baseline}
        {v0.59}
        if dpt > 0 then
        {/v0.59}
        begin
          GetMem(tmppa, dpt * sizeof(TExpPoint));
          Move(pa^[ipt],tmppa^,dpt*sizeof(TExpPoint));
          {Substract baseline from experiment points}
          slope := (baseln.Y2 - baseln.Y1)/(baseln.X2 - baseln.X1);
          for ipt3 := 0 to dpt - 1 do
          begin
            tmppa^[ipt3].Y := tmppa^[ipt3].Y -
               (tmppa^[ipt3].X - baseln.X1) * slope - baseln.Y1;
          end;
          XYFilterGaus(tmppa, dpt, MinPeakWidth/2); {Not needed for filtered data}
          ProcessOneReg;
          FreeMem(tmppa, dpt * sizeof(TExpPoint));
        end;
        tmppa:=nil;
      end;
      inc(ibl);
    end;
    {Sort results}
    Peaks.Sort;
    PeaksFindYAndIndexValues;
    PeaksAssignParamsFromMethod;
    RecalculatePeaks;
    Result := false;
  finally
    if tmppa <> nil then
      FreeMem(tmppa);
    points.Free;
    Peaks.DoChangeUnlock;
  end;
end;

procedure TSpectrum.PeakFindYAndIndexValues(p: TULPRObj{v0.26}; ReselectUsrData: boolean{/v0.26});
var
  sp, dp: TExpPoint;
  i: TPointCount;
  y: TYValue;
begin
  sp.X := p.X1;
  {v0.26}
  AcqData.ActiveDataName := p.DataName;
  {/v0.26}
  if AcqData.FindDataPoint(sp, dp, i) then begin
    p.X1 := dp.X;
    p.Y1 := dp.Y;
    p.Index1 := i;
    if FindBaseY(p.DataName, p.X1, y) then
      p.BaseY1 := y
    else
      p.BaseY1 := dp.Y;
  end;
  sp.X := p.X2;
  if AcqData.FindDataPoint(sp, dp, i) then begin
    p.X2 := dp.X;
    p.Y2 := dp.Y;
    p.Index2 := i;
    if FindBaseY(p.DataName, p.X2, y) then
      p.BaseY2 := y
    else
      p.BaseY2 := dp.Y;
  end;
  {v0.26}
  if ReselectUsrData then
    AcqData.UsrDataIndexReselect;
  {/v0.26}
end;

procedure TSpectrum.PeaksFindYAndIndexValues;
var
  i: integer;
  p: TULPRObj;
begin
  Peaks.DoChangeLock;
  try
    for i := 0 to Peaks.ChildCount - 1 do begin
      p := TULPRObj(Peaks.Childs[i]);
      if p.RecID = ULPRID then
        PeakFindYAndIndexValues(p{v0.26}, false{/v0.26});
    end;
  finally
    Peaks.DoChangeUnlock;
    {v0.26}
    AcqData.UsrDataIndexReselect;
    {/v0.26}
  end;
end;

function TSpectrum.AutoDetect: boolean;
{v0.25}
var i: integer;
{/v0.25}
{v0.50}
var
  fnd: boolean;
  gfnd: boolean;
  efnd: boolean;
  u: TULADObj;
  ai: integer;
{/v0.50}
begin
  {v0.18}
  {$IFNDEF CONSOLE}
  InfoFormShow(GetTxt({#}'Autodetecting peaks...'));
  {$ENDIF}
  try
    {v0.14}
    if AcqData.ULM.MinPeakWidth = 0 then
      AcqData.ULM.MinPeakWidth := 12;
    if AcqData.ULM.MinPeakHeight = 0 then
      AcqData.ULM.MinPeakHeight := 0.01;
    {/v0.14}
    {v0.25}
    BaseLine.Clear;
    Peaks.Clear;

    {!!!
    if AcqData.DataCount > 1 then begin
      AcqData.ULAD[0].ULAD.Color := clGreen;
      AcqData.ULAD[1].ULAD.Color := clBlue;
    end;}

    {v0.50}
    if AcqData.ULAD.Count = 0 then
      exit;
    fnd := false;
    gfnd := false;
    efnd := false;
    for i := 0 to AcqData.ULAD.Count - 1 do begin
      u := AcqData.ULAD[i].ULAD;
      if u.DataName = 'G' then
        gfnd := true;
      if u.DataName = '' then
        efnd := true;
      if AcqData.ULM.DefDataName = u.DataName then begin
        fnd := true;
        break;
      end;
    end;
    if not fnd then begin
      if efnd then begin
        AcqData.ULM.DefDataName := '';
      end else begin
        if gfnd then begin
          AcqData.ULM.DefDataName := 'G';
        end else begin
          AcqData.ULM.DefDataName := AcqData.ULAD[0].ULAD.DataName;
        end;
      end;
    end;

    AcqData.ActiveDataName := AcqData.ULM.DefDataName;
    ai := AcqData.DataIndex;
    AutoDetectForBase;
    BaseLineUpdateFromMethod;
    AutoDetectForPeaks;
    {/v0.50}

    for i := 0 to AcqData.ULAD.Count - 1 do
    {/v0.25}
    begin
      {v0.50}
      if i = ai then
        continue;
      {/v0.50}
      AcqData.DataIndex := i;
      if i = 1 then begin
        {!!!}{v0.50}{/v0.50 if AcqData.ActiveULADRc.ULAD.DataName = '' then
          AcqData.ActiveULADRc.ULAD.DataName := 'B';}
      end;
      AutoDetectForBase;
      BaseLineUpdateFromMethod;
      AutoDetectForPeaks;
    end;
    {v0.28}
    AcqData.ULA.AutodetectLater := false;
    {/0.28}
    {v0.33}
    Peaks.Sort;
    BaseLine.Sort;
    {/v0.33}
    {v0.31}
    AcqData.UsrDataIndexReselect;
    {/v0.31}
    Result := true;
  finally
    {$IFNDEF CONSOLE}
    InfoFormHide;
    {$ENDIF}
  end;
end;


(*
function TSpectrum.AutoDetect: boolean;
begin  Result := false;
end;

var
  X: TXValue;
  Y: TYValue;
  ipk, ipt, cpk, cpt: integer;
  peak, newpeak: TULPRObj;{ viz ULPRType.PAS }

  points: TMemoryStream;
  pa: PExpPoints;{ulantype}
begin
  points := TMemoryStream.Create;
  AcqData.ULAD.Data.Position := 0;
  points.CopyFrom(AcqData.ULAD.Data, AcqData.ULAD.Data.Size);
  cpt := AcqData.ULAD.Data.Size div sizeof(TExpPoint);
  ipt := cpt - 1;
  pa := PExpPoints(points.Memory);
  x := pa^[ipt].X;

  newpeak := TULPRObj(Peaks.Add(ULPRID));
  cpk := Peaks.ChildCount;
  ipk := cpk - 1;
  peak := TULPRObj(Peaks.Childs[ipk]);

  Peaks.Sort;
  points.Free;
end;
*)

function TSpectrum.AutoDetectOld: boolean;
var
  APoint: TIndexPoint;
  adp: boolean;
label ex;
begin
  adp := AutoDetectPeaks;
  AutoDetectPeaks := true;
  AutoDetectOld := false;
  if AcqData.GetPointCount < 3 then
    goto ex;
  ClearProcPeak;
  AcqData.SeekPoint(0);
  with ProcPeak do begin
    if not AcqData.ReadIndexPoint(Point_2) then
      goto ex;
    if not AcqData.ReadIndexPoint(Point_1) then
      goto ex;
    if not AcqData.ReadIndexPoint(APoint) then
      goto ex;
    AcqData.SpectrumOpt.BaseLine := Point_2.Point.Y;
    LocMin_1 := Point_2;
    LocMax_1 := Point_2;
    LocMin_2 := Point_2;
    LocMax_2 := Point_2;
    CurMin := Point_2;
    CurMax := Point_2;
    Min_1 := Point_2;
    Max_1 := Point_2;
    Min_2 := Point_2;
    Max_2 := Point_2;
    while AcqData.ReadIndexPoint(APoint) do
      DoPoint(APoint);
    AutoDetectOld := true;
  end;{with ProcPeak}
  PeaksCalculateRatios;
ex:
  AutoDetectPeaks := adp;
end;

procedure TSpectrum.PeaksCalculateRatios;
begin
  if Peaks <> nil then
    CalculatePeaksRatios(Peaks);
end;

function TSpectrum.IsScreenPointInPeakEdge(X: TScreenX; Y: TScreenY;
  const AScreenDisp: TScreenDisp; var PeakIndex: integer): boolean;
var
  sp1, sp2, spp: TScreenPoint;
  p: TULPRObj;
  cnt, i: integer;
  ep1, ep2: TExpPoint;
  ons1, ons2: integer;
  {v0.09}
  yoffs: TScreenY;
  {/v0.09}
begin
  Result := false;
  PeakIndex := -1;
  if (Peaks = nil) then
    exit;
  cnt := Peaks.ChildCount;
  if cnt = 0 then
    exit;
  for i := 0 to cnt - 1 do begin
    p := TULPRObj(Peaks.Childs[i]);
    if p.RecID <> ULPRID then
      continue;
    ep1.X := p.X1;
    ep1.Y := p.BaseY1;
    ep2.X := p.X2;
    ep2.Y := p.BaseY2;
    ons1 := AcqData.ExpToScreen(ep1, AScreenDisp, sp1);
    ons2 := AcqData.ExpToScreen(ep2, AScreenDisp, sp2);
    if ons1 = 0 then begin
      {v0.09}
      ep1.Y := p.Y1;
      AcqData.ExpToScreen(ep1, AScreenDisp, spp);
      if spp.Y <= sp1.Y then
        yoffs := PointsPeakStickLen
      else
        yoffs := -PointsPeakStickLen;
      {/v0.09}
      if (X >= sp1.X) and (X <= sp1.X + PointsPeakArrowLen) and
         (Y >= sp1.Y + yoffs{PointsPeakStickLen} - PointsPeakArrowLen div 4) and
         (Y <= sp1.Y + yoffs{PointsPeakStickLen} + PointsPeakArrowLen div 4) then
      begin
        Result := true;
        PeakIndex := i;
        exit;
      end;
    end;
    if ons2 = 0 then begin
      {v0.09}
      ep2.Y := p.Y2;
      AcqData.ExpToScreen(ep2, AScreenDisp, spp);
      if spp.Y <= sp2.Y then
        yoffs := PointsPeakStickLen
      else
        yoffs := -PointsPeakStickLen;
      {/v0.09}
      if (X >= sp2.X - PointsPeakArrowLen) and (X <= sp2.X) and
         (Y >= sp2.Y + yoffs{PointsPeakStickLen} - PointsPeakArrowLen div 4) and
         (Y <= sp2.Y + yoffs{PointsPeakStickLen} + PointsPeakArrowLen div 4)
      then begin
        Result := true;
        PeakIndex := i;
        exit;
      end;
    end;
  end;
end;

function TSpectrum.IsScreenXInPeak(X: TScreenX; const AScreenDisp:TScreenDisp;
  var PeakIndex: integer): boolean;
var
  sp1, sp2: TScreenPoint;
  p: TULPRObj;
  cnt, i: integer;
  ep1, ep2: TExpPoint;
{  ons1, ons2: integer;}
begin
  Result := false;
  PeakIndex := -1;
  if (Peaks = nil) then
    exit;
  cnt := Peaks.ChildCount;
  if cnt = 0 then
    exit;
  for i := 0 to cnt - 1 do begin
    p := TULPRObj(Peaks.Childs[i]);
    if p.RecID <> ULPRID then
      continue;
    ep1.X := p.X1;
    ep1.Y := p.Y1;
    ep2.X := p.X2;
    ep2.Y := p.Y2;
    {ons1 := }AcqData.ExpToScreen(ep1, AScreenDisp, sp1);
    {ons2 := }AcqData.ExpToScreen(ep2, AScreenDisp, sp2);
    if (X >= sp1.X) and (X <= sp2.X) then begin
      Result := true;
      PeakIndex := i;
      exit;
    end;
    {
    if ons1 = 0 then begin
      if (X >= sp1.X) then begin
        if (ons2 <> 0) then begin
          if X <= (AScreenDisp.Right) then begin
            Result := true;
            PeakIndex := i;
            exit;
          end;
        end else begin
          if X <= sp2.X then begin
            Result := true;
            PeakIndex := i;
            exit;
          end;
        end;
      end;
    end else begin
      if (ons1 and opLeft) <> 0 then begin
        if (ons2 = 0) then begin
          if X <= sp2.X then begin
            Result := true;
            PeakIndex := i;
            exit;
          end;
        end else begin
          if (ons2 and opRight) <> 0 then begin
            Result := true;
            PeakIndex := i;
            exit;
          end;
        end;
      end;
    end;
    }
  end;
end;

function TSpectrum.IsScreenPointInBaseLineEdge(var X: TScreenX; Y: TScreenY;
  const AScreenDisp: TScreenDisp; var PeakIndex: integer; var LeftEdge:boolean): boolean;
var
  sp1, sp2: TScreenPoint;
  p: TULBRObj;
  cnt, i: integer;
  ep1, ep2: TExpPoint;
  ons1, ons2: integer;
begin
  Result := false;
  PeakIndex := -1;
  if (BaseLine = nil) then
    exit;
  cnt := BaseLine.ChildCount;
  if cnt = 0 then
    exit;
  for i := 0 to cnt - 1 do begin
    p := TULBRObj(BaseLine.Childs[i]);
    if p.RecID <> ULBRID then
      continue;
    ep1.X := p.X1;
    ep1.Y := p.Y1;
    ep2.X := p.X2;
    ep2.Y := p.Y2;
    ons1 := AcqData.ExpToScreen(ep1, AScreenDisp, sp1);
    ons2 := AcqData.ExpToScreen(ep2, AScreenDisp, sp2);
    if ons1 = 0 then begin
      if (X >= sp1.X - PointsBaseLineCircleRadius) and (X <= sp1.X + PointsBaseLineCircleRadius)
        and (Y >= sp1.Y - PointsBaseLineCircleRadius) and (Y <= sp1.Y + PointsBaseLineCircleRadius)
      then begin
        X := sp1.X;
        Result := true;
        PeakIndex := i;
        {v0.25}
        AcqData.UsrActiveDataName := p.DataName;
        {/v0.25}
        LeftEdge := true;
        exit;
      end;
    end;
    if ons2 = 0 then begin
      if (X >= sp2.X - PointsBaseLineCircleRadius) and (X <= sp2.X + PointsBaseLineCircleRadius)
        and (Y >= sp2.Y - PointsBaseLineCircleRadius) and (Y <= sp2.Y + PointsBaseLineCircleRadius)
      then begin
        Result := true;
        X := sp2.X;
        PeakIndex := i;
        {v0.25}
        AcqData.UsrActiveDataName := p.DataName;
        {/v0.25}
        LeftEdge := false;
        exit;
      end;
    end;
  end;
end;

function TSpectrum.IsScreenXInBaseLine(X: TScreenX; const AScreenDisp:TScreenDisp;
  var PeakIndex: integer): boolean;
var
  sp1, sp2: TScreenPoint;
  p: TULBRObj;
  cnt, i: integer;
  ep1, ep2: TExpPoint;
{  ons1, ons2: integer;}
begin
  Result := false;
  PeakIndex := -1;
  if (BaseLine = nil) then
    exit;
  cnt := BaseLine.ChildCount;
  if cnt = 0 then
    exit;
  for i := 0 to cnt - 1 do begin
    p := TULBRObj(BaseLine.Childs[i]);
    if p.RecID <> ULBRID then
      continue;
    ep1.X := p.X1;
    ep1.Y := p.Y1;
    ep2.X := p.X2;
    ep2.Y := p.Y2;
    {ons1 := }AcqData.ExpToScreen(ep1, AScreenDisp, sp1);
    {ons2 := }AcqData.ExpToScreen(ep2, AScreenDisp, sp2);
    if (X >= sp1.X) and (X <= sp2.X) then begin
      Result := true;
      PeakIndex := i;
      exit;
    end;
    {
    if ons1 = 0 then begin
      if (X >= sp1.X) then begin
        if (ons2 <> 0) then begin
          if X <= (AScreenDisp.Right) then begin
            Result := true;
            PeakIndex := i;
            exit;
          end;
        end else begin
          if X <= sp2.X then begin
            Result := true;
            PeakIndex := i;
            exit;
          end;
        end;
      end;
    end else begin
      if (ons1 and opLeft) <> 0 then begin
        if (ons2 = 0) then begin
          if X <= sp2.X then begin
            Result := true;
            PeakIndex := i;
            exit;
          end;
        end else begin
          if (ons2 and opRight) <> 0 then begin
            Result := true;
            PeakIndex := i;
            exit;
          end;
        end;
      end;
    end;
    }
  end;
end;

function TSpectrum.IsScreenPeakStart(X: TScreenX; const AScreenDisp: TScreenDisp;
  var PeakIndex: integer): boolean;
var
  sp1, sp2: TScreenPoint;
  p: TULPRObj;
  cnt, i: integer;
  ep1, ep2: TExpPoint;
  ons1, ons2: integer;
begin
  IsScreenPeakStart := false;
  PeakIndex := -1;
  if (Peaks = nil) then
    exit;
  cnt := Peaks.ChildCount;
  if cnt = 0 then
    exit;
  for i := 0 to cnt - 1 do begin
    p := TULPRObj(Peaks.Childs[i]);
    if p.RecID <> ULPRID then
      continue;
    ep1.X := p.X1;
    ep1.Y := p.Y1;
    ep2.X := p.X2;
    ep2.Y := p.Y2;
    ons1 := AcqData.ExpToScreen(ep1, AScreenDisp, sp1);
    ons2 := AcqData.ExpToScreen(ep2, AScreenDisp, sp2);
    if ons1 = 0 then begin
      if (X >= sp1.X) then begin
        if (ons2 <> 0) then begin
          if X < AScreenDisp.Right{(AScreenDisp.Left + AScreenDisp.Width + sp1.X) div 2} then begin
            IsScreenPeakStart := true;
            PeakIndex := i;
            exit;
          end;
        end else begin
          if X < (sp2.X + sp1.X) div 2 then begin
            IsScreenPeakStart := true;
            PeakIndex := i;
            exit;
          end;
        end;
      end;
    end;
  end;
end;
{
var
  sc: TScreenPoint;
  p: TULPRObj;
  cnt, i: integer;
  pt: TExpPoint;
begin
  IsScreenPeakStart := false;
  PeakIndex := -1;
  if (Peaks = nil) then
    exit;
  cnt := Peaks.ChildCount;
  if cnt = 0 then
   exit;
  for i := 0 to cnt - 1 do begin
    p := TULPRObj(Peaks.Childs[i]);
    pt.X := p.X1;
    pt.Y := p.Y1;
    if AcqData.ExpToScreen(pt, AScreenDisp, sc) = 0 then begin
      if abs(sc.X - X) < 2 then begin
        IsScreenPeakStart := true;
        PeakIndex := i;
        exit;
      end;
    end;
  end;
end;
}

{v0.09}
function TSpectrum.ULBRToScreenPeak(BL:TULBRObj; const AScreenDisp: TScreenDisp;
  var SP:TScreenPeak): integer;
var
  ep: TExpPoint;
  ons: integer;
begin
{  Result := -1;
  if BL = nil then
    exit;}
  ep.X := bl.X1;
  ep.Y := bl.Y1;
  ons := AcqData.ExpToScreen(ep, AScreenDisp, sp.p1);
  if ((ons and opLeft) <> 0) then
    sp.p1.X := AScreenDisp.Left;
  Result := ons;

  ep.X := bl.X2;
  ep.Y := bl.Y2;
  ons := AcqData.ExpToScreen(ep, AScreenDisp, sp.p2);
  if ((ons and opRight) <> 0) then
    sp.P2.X := AScreenDisp.Right;
  Result := Result or ons;
end;
{/v0.09}

procedure TSpectrum.GetScreenBaseLineRec(X: TScreenX; const AScreenDisp: TScreenDisp;
  var CurIndex: integer;
  var PrevBL: TScreenPeak; var CurBL: TScreenPeak; var NextBL: TScreenPeak);
var
  sp: TScreenPeak;
  {v0.09}{/v0.09
  ep1, ep, ep2: TExpPoint;
  ons1, ons2: integer;{ is on screen? }
  bl: TULBRObj;
  cnt, i: integer;
  {v0.09}
  xp: TScreenPoint;
  xep: TExpPoint; { exp point at X pos }
  mindifx: TXValue;
  difx: TXValue;{ulantype}
  pbl, cbl, nbl: TULBRObj;
  {/0.09}
begin
  FillChar(PrevBL, sizeof(PrevBL), $FF);
  FillChar(CurBL, sizeof(CurBL), $FF);
  FillChar(NextBL, sizeof(NextBL), $FF);
  CurIndex := -1;
  mindifx := MaxXValue;

  if AcqData.ExpToScreen(AcqData.SpectrumOpt.Extreme.Max, AScreenDisp, sp.p1) = 0 then
  begin
    { if last data point is on the screen, fake it as the next peak start, so
      that moving peaks does not go out of the chromatogram }
    NextBL := sp;
  end;
  if BaseLine = nil then
    exit;
  cnt := BaseLine.ChildCount;
  if cnt = 0 then
    exit;

  {v0.09}
  pbl := nil;
  nbl := nil;
  cbl := nil;

  xp.X := X;
  xp.Y := 0;
  if not AcqData.ScreenToExp(xp, AScreenDisp, xep) then
    exit;
  for i := 0 to cnt - 1 do begin
    bl := TULBRObj(BaseLine.Childs[i]);
    if bl.RecID <> ULBRID then
      continue;
    if (bl.X2 < xep.X) then begin
      pbl := bl;
    end else if (bl.X1 > {v0.11} xep.X{/v0.11 X}) then begin
      nbl := bl;
      break;
    end else begin
      difx := xep.X - {v0.11} bl.X1{/v0.11 X};
      if difx < mindifx then begin
        if cbl <> nil then
          pbl := cbl;
        cbl := bl;
        CurIndex := i;
        mindifx := difx;
      end;
    end;
  end;

  if pbl <> nil then ULBRToScreenPeak(pbl, AScreenDisp, PrevBL);
  if cbl <> nil then ULBRToScreenPeak(cbl, AScreenDisp, CurBL);
  if nbl <> nil then ULBRToScreenPeak(nbl, AScreenDisp, NextBL);
  {/0.09
  for i := 0 to cnt - 1 do begin
    bl := TULBRObj(BaseLine.Childs[i]);
    if bl.RecID <> ULBRID then
      continue;
    ep1.X := bl.X1;
    ep1.Y := bl.Y1;
    ep.X := bl.X;
    ep.Y := bl.Y;
    ep2.X := bl.X2;
    ep2.Y := bl.Y2;
    FillChar(sp, sizeof(sp), $FF);
    ons1 := AcqData.ExpToScreen(ep1, AScreenDisp, sp.p1);
    ons2 := AcqData.ExpToScreen(ep2, AScreenDisp, sp.p2);
    if ons2 = 0 then begin
      if (X > sp.p2.X) then begin
        PrevBL := sp;
      end else begin
        if (ons1 <> 0) or (X >= sp.p1.x) then begin
          if CurIndex < 0 then begin
            CurBL := sp;
            CurIndex := i;
          end else begin
            NextBL := sp;
            break;
          end;
        end;
      end;
    end;
    if ons1 = 0 then begin
      if (X < sp.p1.X) then begin
        NextBL := sp;
        break;
      end else begin
        if (ons2 = 0) then begin
          if X <= sp.P2.X then begin
            if CurIndex < 0 then begin
              CurBL := sp;
              CurIndex := i;
            end else begin
              if (sp.X1 <> CurBL.X1) or (sp.X2 <> CurBL.X2) then begin
                NextBL := sp;
                break;
              end;
            end;
          end;
        end else begin
          CurBL := sp;
          CurIndex := i;
        end;
      end;
    end;

    if ((ons1 and opLeft) <> 0) and ((ons2 and opRight) <> 0) then begin
      CurBL.P1.X := AScreenDisp.Left;
      CurBL.P2.X := AScreenDisp.Right;
      CurIndex := i;
      break;
    end;

  end;
  }
end;

{v0.09}
procedure TSpectrum.GetScreenBaseLineNeighbours(Index:integer; const AScreenDisp: TScreenDisp;
  var PrevBL: TScreenPeak; var CurBL: TScreenPeak; var NextBL: TScreenPeak);
  { Returns inc CurBL screen coordinates of Index baseline section, plus
    in PrevBL and NextBL coordinates of sections on the left and on the right }
var
  sp: TScreenPeak;
  bl: TULBRObj;
  cnt, i: integer;
  pbl, cbl, nbl: TULBRObj;
begin
  FillChar(PrevBL, sizeof(PrevBL), $FF);
  FillChar(CurBL, sizeof(CurBL), $FF);
  FillChar(NextBL, sizeof(NextBL), $FF);

  if AcqData.ExpToScreen(AcqData.SpectrumOpt.Extreme.Max, AScreenDisp, sp.p1) = 0 then
  begin
    { if last data point is on the screen, fake it as the next peak start, so
      that moving peaks does not go out of the chromatogram }
    NextBL := sp;
  end;
  if BaseLine = nil then
    exit;
  cnt := BaseLine.ChildCount;
  if cnt = 0 then
    exit;
  pbl := nil;
  nbl := nil;
  cbl := nil;

  for i := 0 to cnt - 1 do begin
    bl := TULBRObj(BaseLine.Childs[i]);
    if bl.RecID <> ULBRID then
      continue;
    if i < Index then
      pbl := bl
    else if i = Index then
      cbl := bl
    else begin
      nbl := bl;
      break;
    end;
  end;
  if pbl <> nil then ULBRToScreenPeak(pbl, AScreenDisp, PrevBL);
  if cbl <> nil then ULBRToScreenPeak(cbl, AScreenDisp, CurBL);
  if nbl <> nil then ULBRToScreenPeak(nbl, AScreenDisp, NextBL);
end;
{/v0.09}

procedure TSpectrum.GetScreenPeaks(X: TScreenX; const AScreenDisp: TScreenDisp;
  var PrevPeak: TScreenPeak; var CurPeak: TScreenPeak; var NextPeak: TScreenPeak);
var
  sp: TScreenPeak;
  ep1, ep, ep2: TExpPoint;
  ons1, {ons, }ons2: integer;{ is on screen? }
  p: TULPRObj;
  cnt, i: integer;
begin
  FillChar(PrevPeak, sizeof(PrevPeak), $FF);
  FillChar(CurPeak, sizeof(CurPeak), $FF);
  FillChar(NextPeak, sizeof(NextPeak), $FF);

  if AcqData.ExpToScreen(AcqData.SpectrumOpt.Extreme.Max, AScreenDisp, sp.p1) = 0 then
  begin
    { if last data point is on the screen, fake it as the next peak start, so
      that moving peaks does not go out of the chromatogram }
    NextPeak := sp;
  end;
  if Peaks = nil then
    exit;
  cnt := Peaks.ChildCount;
  if cnt = 0 then
    exit;

  for i := 0 to cnt - 1 do begin
    p := TULPRObj(Peaks.Childs[i]);
    if p.RecID <> ULPRID then
      continue;
    ep1.X := p.X1;
    ep1.Y := p.Y1;
    ep.X := p.X;
    ep.Y := p.Height;
    ep2.X := p.X2;
    ep2.Y := p.Y2;
    FillChar(sp, sizeof(sp), $FF);
    ons1 := AcqData.ExpToScreen(ep1, AScreenDisp, sp.p1);
    {ons := AcqData.ExpToScreen(ep, AScreenDisp, sp.p);}
    ons2 := AcqData.ExpToScreen(ep2, AScreenDisp, sp.p2);
    if ons2 = 0 then begin
      if (X > sp.p2.X) then begin
        PrevPeak := sp;
      end else begin
        if (ons1 <> 0) or (X >= sp.p1.x) then begin
          CurPeak := sp;
          {v0.25}
          AcqData.UsrActiveDataName := p.DataName;
          {/v0.25}
        end;
      end;
    end;
    if ons1 = 0 then begin

      if (X < sp.p1.X) then begin
        NextPeak := sp;
        break;
      end else begin
        if (ons2 = 0) then begin
          if X <= sp.P2.X then begin
            CurPeak := sp;
            {v0.25}
            AcqData.UsrActiveDataName := p.DataName;
            {/v0.25}
          end;
        end else begin
          CurPeak := sp;
          {v0.25}
          AcqData.UsrActiveDataName := p.DataName;
          {/v0.25}
        end;
      end;
    end;

    if ((ons1 and opLeft) <> 0) and ((ons2 and opRight) <> 0) then begin
      CurPeak.P1.X := AScreenDisp.Left;
      CurPeak.P2.X := AScreenDisp.Right;{AScreenDisp.Left + AScreenDisp.Width;}
      break;
    end;

  end;
end;


function TSpectrum.IsScreenPeakStop(X: TScreenX; const AScreenDisp: TScreenDisp;
  var PeakIndex: integer): boolean;
var
  sp1, sp2: TScreenPoint;
  p: TULPRObj;
  cnt, i: integer;
  ep1, ep2: TExpPoint;
  ons1, ons2: integer;
begin
  IsScreenPeakStop := false;
  PeakIndex := -1;
  if (Peaks = nil) then
    exit;
  cnt := Peaks.ChildCount;
  if cnt = 0 then
    exit;
  for i := 0 to cnt - 1 do begin
    p := TULPRObj(Peaks.Childs[i]);
    if p.RecID <> ULPRID then
      continue;
    ep1.X := p.X1;
    ep1.Y := p.Y1;
    ep2.X := p.X2;
    ep2.Y := p.Y2;
    ons1 := AcqData.ExpToScreen(ep1, AScreenDisp, sp1);
    ons2 := AcqData.ExpToScreen(ep2, AScreenDisp, sp2);
    if ons2 = 0 then begin
      if (X <= sp2.X) then begin
        if (ons1 <> 0) or (X >= ((sp2.X + sp1.X) div 2)) then begin
          IsScreenPeakStop := true;
          PeakIndex := i;
          exit;
        end;
      end;
    end;
  end;
end;

function TSpectrum.MoveScreenPeak(OldStartX, NewStartX: TScreenX;
  const AScreenDisp: TScreenDisp; var PeakIndex: integer): boolean;
var
  pi: integer;
  sp: TScreenPoint;
  ap: TExpPoint;
  p: TULPRObj;
  d: TXValue;
  op1,op2,
  np1,np2: TExpPoint;
  index1,index2: TPointCount;
begin
  MoveScreenPeak := false;
  if not IsScreenPeakStart(OldStartX, AScreenDisp, pi) then
    exit;
  sp.X := NewStartX;
  sp.Y := AScreenDisp.Top;
  if not AcqData.ScreenToExp(sp, AScreenDisp, ap) then
    exit;
  p := TULPRObj(Peaks.Childs[pi]);
  d := ap.X - p.X1;
  op1.X := p.X1 + d;;
  op1.Y := p.Y1;
  op2.X := p.X2 + d;
  op2.Y := p.Y2;
  if not AcqData.FindDataPoint(op1, np1, index1) then
    exit;
  if not AcqData.FindDataPoint(op2, np2, index2) then
    exit;
  p.X1 := np1.X;
  p.Y1 := np1.Y;
  p.Index1 := index1;
  p.X2 := np2.X;
  p.Y2 := np2.Y;
  p.Index2 := index2;
  DoPeakLimitsChanged(p);
  {
  p^.P1.Point := np1;
  p^.P1.Index := index1;
  p^.P2.Point := np2;
  p^.P2.Index := index2;
  }
  MoveScreenPeak := true;
end;

function TSpectrum.SizeScreenPeak(OldStopX, NewStopX: TScreenX;
  const AScreenDisp: TScreenDisp; var PeakIndex: integer; Right:boolean): boolean;
var
  pi: integer;
  sp: TScreenPoint;
  ap: TExpPoint;
  p: TULPRObj;
  np: TExpPoint;
  index: TPointCount;
begin
  SizeScreenPeak := false;
  if not IsScreenXInPeak(OldStopX, AScreenDisp, pi) then
    exit;
  {
  if not IsScreenPeakStop(OldStopX, AScreenDisp, pi) then
    exit;
  }
  sp.X := NewStopX;
  sp.Y := AScreenDisp.Top;
  if not AcqData.ScreenToExp(sp, AScreenDisp, ap) then
    exit;
  if not AcqData.FindDataPoint(ap, np, index) then
    exit;
  p := TULPRObj(Peaks.Childs[pi]);{PPeak(ListAt(Peaks,pi));}
  p.DoChangeLock;
  try
    if right then begin
      if (p.Index1 >= index) or (p.X1 >= np.X) then begin
        {if p.CanDestroy then
          p.Free;}
        exit;
      end;
      p.X2 := np.X;
      p.Y2 := np.Y;
      p.Index2 := index;
    end else begin
      if (p.Index2 <= index) or (p.X2 <= np.X) then begin
        {if p.CanDestroy then
          p.Free;}
        exit;
      end;
      p.X1 := np.X;
      p.Y1 := np.Y;
      p.Index1 := index;
    end;
  {  p^.P2.Point := np;
    p^.P2.Index := index;}
    DoPeakLimitsChanged(p);
    SizeScreenPeak := true;
  finally
    p.DoChangeUnlock;
  end;
end;

procedure TSpectrum.ToggleSelectScreenPeak(X: TScreenX;
  const AScreenDisp: TScreenDisp; Shift: TShiftState);
var
  index: integer;
{  p, o: TULPRObj;
  i, j, index, cc: integer;
  otherSelIndex: integer;}
begin
  if IsScreenXInPeak(X, AScreenDisp, index) then begin
    Peaks.ToggleSelectChild(index, Shift);
(*
    p := TULPRObj(Peaks.Childs[index]);
    cc := Peaks.ChildCount;
    if (ssShift in Shift) then begin

      otherSelIndex := -1;
      for i := 0 to cc - 1 do begin
        if i <> index then begin
          o := TULPRObj(Peaks.Childs[i]);
          if o.IsFlagSet(rfSelected) then begin
            otherSelIndex := i;
            break;
          end;
        end;
      end;
      if otherSelIndex <> -1 then begin
        if otherSelIndex < index then begin
          i := otherSelIndex;
        end else begin
          i := index;
          index := otherSelIndex;
        end;
        for j := 0 to i - 1 do begin
          TULPRObj(Peaks.Childs[j]).SetFlag(rfSelected, false);
        end;
        for j := i to index do begin
          TULPRObj(Peaks.Childs[j]).SetFlag(rfSelected, true);
        end;
        for j := index + 1 to cc - 1 do begin
          TULPRObj(Peaks.Childs[j]).SetFlag(rfSelected, false);
        end;

      end else begin
        p.SetFlag(rfSelected, not p.IsFlagSet(rfSelected));
      end;
    end else if (ssCtrl in Shift) then begin

      p.SetFlag(rfSelected, not p.IsFlagSet(rfSelected));

    end else begin

      for i := 0 to Peaks.ChildCount - 1 do begin
        o := TULPRObj(Peaks.Childs[i]);
        if i <> index then
          o.SetFlag(rfSelected, false);
      end;
      p.SetFlag(rfSelected, not p.IsFlagSet(rfSelected));

    end;
*)
  end;
end;

procedure TSpectrum.ToggleSelectScreenBaseLine(X: TScreenX; Y: TScreenY;
  const AScreenDisp: TScreenDisp; Shift: TShiftState);
var
  index: integer;
  leftedge:boolean;
  {
  p, o: TULPRObj;
  i, j, index, cc: integer;
  otherSelIndex: integer;}
begin
  if IsScreenPointInBaseLineEdge(X, Y, AScreenDisp, index, leftedge) then begin
    BaseLine.ToggleSelectChild(index, Shift);
  end;
end;

procedure TSpectrum.DoPeakLimitsChanged(p: TULPRObj);
{var
  y: TYValue;}
begin
  PeakFindYAndIndexValues(p{v0.26}, true{/v0.26});
  {v0.12}
  {/v0.12
  PeakAssignParamsFromMethod(p);}
{  if FindBaseY(p.X1, y) then
    p.BaseY1 := y;
  if FindBaseY(p.X2, y) then
    p.BaseY2 := y;
}
end;

function TSpectrum.FindBaseY(const ADataName: string; X: TXValue; var Y: TYValue):boolean;
var
  b: TULBRObj;
  i, cc: integer;
  maxy: TYValue;
  cy: TYValue;
  fnd:boolean;
begin
  FindBaseY := false;
  maxy := MinYValue;
  fnd := false;
  cc := AcqData.ULB.ChildCount;
  if cc > 0 then begin
    for i := 0 to cc - 1 do begin
      b := TULBRObj(AcqData.ULB.Childs[i]);
      if b.RecID <> ULBRID then
        continue;
      {v0.50}
      if not AcqData.DataNamesEqual(ADataName, b.DataName) then
        continue;
      {/v0.50
      if (ADataName <> '*') and (ADataName <> b.DataName) then
        continue;}
      if (b.X1 <= X) and (X <= b.X2) then begin
        cy := CountYOnLine(X, b.X1, b.Y1, b.X2, b.Y2);
        if (cy > maxy) then begin
          maxy := cy;
          fnd := true;
        end;
      end;
    end;
    if fnd then begin
      y := maxy;
      FindBaseY := true;
    end;
  end;
end;

procedure TSpectrum.CopySelectedPeaksToMethod;
var
  p, mp: TULPRObj;
  i: integer;
begin
  for i := 0 to AcqData.ULP.ChildCount - 1 do begin
    p := TULPRObj(AcqData.ULP.Childs[i]);
    if p.RecID <> ULPRID then
      continue;
    if p.IsFlagSet(rfSelected) then begin
      {v0.10}
      if PeakFindPeakInMethod(p, mp) then begin
        mp.Free;
      end;
      {/v0.10}
      mp := TULPRObj(AcqData.ULM_ULP.Add(ULPRID));
      mp.Assign(p);
    end;
  end;
end;

procedure TSpectrum.CopySelectedBaseLineToMethod;
var
  b, mb: TULBRObj;
  i: integer;
begin
  for i := 0 to AcqData.ULB.ChildCount - 1 do begin
    b := TULBRObj(AcqData.ULB.Childs[i]);
    if b.RecID <> ULBRID then
      continue;
    if b.IsFlagSet(rfSelected) then begin
      mb := TULBRObj(AcqData.ULM_ULB.Add(ULBRID));
      mb.Assign(b);
    end;
  end;
end;

procedure TSpectrum.PeaksAssignParamsFromMethod;
var
  i: integer;
begin
  AcqData.ULP.DoChangeLock;
  try
    for i := 0 to AcqData.ULP.ChildCount - 1 do begin
      PeakAssignParamsFromMethod(TULPRObj(AcqData.ULP.Childs[i]));
    end;
  finally
    AcqData.ULP.DoChangeUnlock;
  end;
end;

{v0.11}
procedure TSpectrum.PeakAssignPeakCharacterization(Dest, Src: TULPRObj);
begin
  Dest.PeakName := Src.PeakName;
  Dest.GroupName := Src.GroupName;
  Dest.Response := Src.Response;
  Dest.InternalStandard := Src.InternalStandard;
  Dest.Window := Src.Window;
  {v0.12}
  Dest.UsrPeakCoef := Src.UsrPeakCoef;
  Dest.Amount := Src.Amount;
  {/v0.12}
  {v0.25}
  Dest.DataName := Src.DataName;
  {/v0.25}
end;
{/v0.11}

procedure TSpectrum.PeakAssignParamsFromMethod(p: TULPRObj);
var mp: TULPRObj;
begin
  if PeakFindPeakInMethod(p, mp) then begin
    {v0.11}
    PeakAssignPeakCharacterization(p, mp);
    {/v0.11
    p.PeakName := mp.PeakName;
    p.GroupName := mp.GroupName;}
  end;
end;

{v0.25}
function TSpectrum.PeakFindInPeaksByX(pl: TULPObj; x: TXValue; var fp: TULPRObj): boolean;
var
  i: integer;
  minmp, curmp: TULPRObj;
  mindif, curdif: TXValue;
  w: TXValue;
begin
  Result := false;
  fp := nil;
  minmp := nil;
  mindif := MaxXValue;

  for i := 0 to pl.ChildCount - 1 do begin
    curmp := TULPRObj(pl.Childs[i]);
    if curmp.RecID <> ULPRID then
      continue;
    curdif := abs(X - curmp.X);
    w := curmp.Window;
    if w = 0 then begin
      w := AcqData.ULM.MinPeakWidth;
    end;
    if curdif <= (w / 2) then begin
      if curdif < mindif then begin
        mindif := curdif;
        minmp := curmp;
      end;
    end;
  end;
  if minmp <> nil then begin
    Result := true;
    fp := minmp;
  end;
end;

function TSpectrum.PeakFindInPeaksInXRange(pl: TULPObj; const ADataName: string; x, xmin, xmax: TXValue; var fp: TULPRObj): boolean;
var
  i: integer;
  minmp, curmp: TULPRObj;
  mindif, curdif: TXValue;
{  w: TXValue;}
begin
  Result := false;
  fp := nil;
  minmp := nil;
  mindif := MaxXValue;

  for i := 0 to pl.ChildCount - 1 do begin
    curmp := TULPRObj(pl.Childs[i]);
    {v0.50}
    if not AcqData.DataNamesEqual(ADataName, curmp.DataName) then
      continue;
    {/v0.50
    if (ADataName <> '*') then begin
      if curmp.DataName <> ADataName then
        continue;
    end;}
    if curmp.RecID <> ULPRID then
      continue;
    if curmp.x > xmax then
      continue;
    if curmp.x < xmin then
      continue;
    curdif := abs(X - curmp.X);
    if curdif < mindif then begin
      mindif := curdif;
      minmp := curmp;
    end;
  end;
  if minmp <> nil then begin
    Result := true;
    fp := minmp;
  end;
end;

{/v0.25}

{v0.11}
function TSpectrum.PeakFindInPeaks(pl: TULPObj; p: TULPRObj; var fp: TULPRObj): boolean;
  { find in pl peaks the peak fp, that has p.X in fp's window interval }
{/v0.11
function TSpectrum.PeakFindPeakInMethod(p: TULPRObj; var mp: TULPRObj):boolean; }
var
  i: integer;
  minmp, curmp: TULPRObj;
  mindif, curdif: TXValue;
  {v0.11}
  w: TXValue;
  {/v0.11}
begin
  Result := false;
  fp := nil;
  minmp := nil;
  mindif := MaxXValue;

  for i := 0 to {v0.11}pl{/v0.11 AcqData.ULM_ULP}.ChildCount - 1 do begin
    curmp := TULPRObj({v0.11}pl{/v0.11 AcqData.ULM_ULP}.Childs[i]);
    if curmp.RecID <> ULPRID then
      continue;
    curdif := abs(p.X - curmp.X);
    {v0.11}
    w := curmp.Window;
    if w = 0 then begin
      w := AcqData.ULM.MinPeakWidth;
    end;
    {/v0.11}
    if curdif <= ({v0.11}w{/v0.11 curmp.Window} / 2) then begin
      if curdif < mindif then begin
        mindif := curdif;
        minmp := curmp;
      end;
    end;
  end;
  if minmp <> nil then begin
    Result := true;
    fp := minmp;
  end;
end;

{v0.12}
function TSpectrum.FindPeakByName(pl: TULPObj; const AName: TPeakName; var fp: TULPRObj): boolean;
 { find in pl peaks the peak fp, that has p.Name = AName }
var
  i: integer;
  curmp: TULPRObj;
begin
  Result := false;
  fp := nil;
  for i := 0 to pl.ChildCount - 1 do begin
    curmp := TULPRObj(pl.Childs[i]);
    if curmp.RecID <> ULPRID then
      continue;
    if AName = curmp.PeakName then
    begin
      Result := true;
      fp:=curmp;
      break;
    end;
  end;
end;
{/v0.12}

{v0.25}
function TSpectrum.PeakFindPeakInMethodByX(x: TXValue; var mp: TULPRObj):boolean;
begin
  Result := PeakFindInPeaksByX(AcqData.ULM_ULP, x, mp);
end;
{/v0.25}
{v0.11}
function TSpectrum.PeakFindPeakInMethod(p: TULPRObj; var mp: TULPRObj):boolean;
begin
  Result := PeakFindInPeaks(AcqData.ULM_ULP, p, mp);
end;
{/v0.11}

{v0.25}
procedure TSpectrum.BaseLineRemoveDuplicates;
var
  b, b1, b2: TULBRObj;
  i: integer;
begin
  b1 := nil;
  i := 0;
  while i < BaseLine.ChildCount do begin
    b := TULBRObj(BaseLine.Childs[i]);
    if b.RecID <> ULBRID then begin
      inc(i);
      continue;
    end;
    if b1 = nil then begin
      b1 := b
    end else begin
      b2 := b;
      if b1.X2 > b2.X1 then begin
        b2.Free;
        continue;
      end else
        b1 := b2;
    end;
    inc(i);
  end;
end;
{/v0.25}

procedure TSpectrum.BaseLineUpdateFromMethod;
var
  i, mi: integer;
  b, mb, nb: TULBRObj;
  brModified, bModified: boolean;

begin
  i := 0;
  bModified := false;
  BaseLine.DoChangeLock;
  try
    while i < BaseLine.ChildCount do begin
      b := TULBRObj(BaseLine.Childs[i]);
      if b.RecID <> ULBRID then begin
        inc(i);
        continue;
      end;

      brModified := false;
      for mi := 0 to AcqData.ULM_ULB.ChildCount - 1 do begin
        mb := TULBRObj(AcqData.ULM_ULB.Childs[mi]);
        if mb.RecID <> ULBRID then
          continue;
        if (b.X2 > mb.X1) and (b.X1 < mb.X2) then begin
          { some overlap of baseline record and method baseline record detected }

          brModified := true;
          bModified := true;
          if b.X1 < mb.X1 then begin
            if b.X2 <= mb.X2 then begin
              b.X2 := mb.X1;
            end else begin
              nb := TULBRObj(BaseLine.Add(ULBRID));
              nb.X1 := mb.X2;
              nb.X2 := b.X2;
              b.X2 := mb.X1;
            end;
          end else begin
            if b.X2 <= mb.X2 then begin
              if b.CanDestroy then begin
                b.Free;
                b := nil;
                break;
              end;
            end else begin
              b.X1 := mb.X2;
            end;
          end;
        end;
      end;
      if (b <> nil) then begin
        if brModified then begin
          DoBaseLineLimitsChanged(b);
          {UpdateBaseLineRecY(b);}
        end;
        inc(i);
      end;
    end;{/while}
    if bModified then
      BaseLine.Sort;
  finally
    BaseLine.DoChangeUnlock;
  end;
end;

destructor TSpectrum.Destroy;
begin
  {PeaksClear;}
  if AcqData <> nil then begin
    AcqData.Spectrum := nil;
  end;
  inherited Destroy;
end;

{v0.11}
function TSpectrum.PeaksResponsesCalculate: integer;
var
  i: integer;
  p: TULPRObj;
  zeroamount:integer;
  f: TFactor;
begin
  ClearResult;
  Result := 0;

  zeroamount := 0;
  {v0.28}
  f := CombineFactors(AcqData.ULM.Factor, AcqData.ULA.MultiplyFactor, AcqData.ULA.DivideFactor);
  {/v0.28}

  for i := 0 to Peaks.ChildCount - 1 do begin
    p := TULPRObj(Peaks.Childs[i]);
    if p.RecID <> ULPRID then
      continue;
    if p.Amount <> 0 then begin
      if p.UsrPeakCoef = 0 then
        p.UsrPeakCoef := 1;
      p.Response := (p.AreaSize / p.Amount) * p.UsrPeakCoef * f;
    end else begin
      p.Response := 0;
      inc(zeroamount);
    end;
  end;
  if zeroamount > 0 then begin
    {v0.25 not an error, if Amount set 0, then the peak should be ignored}
    {/v0.25
    SetResult(srPeakAmountZero, 'In peaks response calculate');}
  end;
end;

function TSpectrum.PeaksAmountsCalculate: integer;
var
  r: integer;
begin
  Result := 0;
  {r := 0;}
  if AcqData.ULM.Factor=0 then
    AcqData.ULM.Factor:=1;
  if AcqData.ULM.UseCalibrationFile then begin
    r := PeaksAmountsCalculateCalibrationFile;
    if Result = 0 then
      Result := r;
  end;{ulprtype ulatype}
  if AcqData.ULM.UseInternalStandard then begin
    r := PeaksAmountsCalculateInternalStandard;
    if Result = 0 then
      Result := r;
  end;
  r := PeaksAmountsCalculateValues;
  if Result = 0 then
    Result := r;
end;

{v0.13}
function TSpectrum.PeaksAutoCalculate: integer;
begin
  if AcqData.ULA.CalibrationStandard then
    Result := PeaksResponsesCalculate
  else
    Result := PeaksAmountsCalculate;
end;
{/v0.13}

function TSpectrum.PeaksAmountsCalculateCalibrationFile: integer;
var
  d: TAcqData;
  i: integer;
  p, cp: TULPRObj;
  {v0.12}
  f: TFactor;
  a: integer;
  {/v0.12}
begin
  Result := 0;
  {v0.12}
  if AcqData.Cal_ULP <> nil then begin
    if AcqData.ULM.CalibrationFileName <> '' then begin
      {v0.55}
      if not AcqData.CheckCalibrationFilePresent then begin
        {SetResult(srCalibrationFileNotFound, AcqData.ULM.CalibrationFileName);}
        {ShowMessage(GetTxt('Calibration File Not Found: ') + AcqData.ULM.CalibrationFileName, smError, 0);}
        exit;
      end;
      {/v0.55}                                 
      a := FileAge(AcqData.ULM.CalibrationFileName);
      if (a <> -1) and (a <> AcqData.ULM.CalibrationFileAge) then
      begin
        AcqData.Cal_ULP.Free;
        AcqData.Cal_ULP := nil;
      end;
    end;
  end;
  {/v0.12}
  if AcqData.Cal_ULP = nil then begin
    if AcqData.ULM.CalibrationFileName = '' then begin
      SetResult(srCalibrationFileNotSpecified, '');
      exit;
    end else begin
      {v0.55}
      if not AcqData.CheckCalibrationFilePresent then begin
        {SetResult(srCalibrationFileNotFound, AcqData.ULM.CalibrationFileName);+}
        ShowMessage(GetTxt('Calibration File Not Found: ') + AcqData.ULM.CalibrationFileName, smError, 0);
        exit;
      end;
      {/v0.55}
      AcqData.Cal_ULP := TULPObj.Create(AcqData.ULA);
      AcqData.Cal_ULP.SetFlag(rfTemporary, true);
      d := TAcqData.Create(AcqData.ULM.CalibrationFileName, omRead);
      try
        AcqData.Cal_ULP.Assign(d.ULP);
        {v0.12}
        {v0.28}
        f := CombineFactors(d.ULM.Factor, d.ULA.MultiplyFactor, d.ULA.DivideFactor);
        {/v0.28
        f := d.ULM.Factor;}
        {/v0.12}
        {v0.13}
        { Warning f is not persistent between calls }
        {v0.28}{/v0.28
        if f=0 then
          f:=1;}
        {/v0.13}
      finally
        {v0.30}
        d.ULF.Modified := false;
        {/v0.30}
        d.Free;
      end;
      {v0.12}
      AcqData.ULM.CalibrationFileAge := FileAge(AcqData.ULM.CalibrationFileName);
      {/v0.12}
      {v0.13}
      for i:= 0 to AcqData.Cal_ULP.ChildCount - 1 do begin
        cp := TULPRObj(AcqData.Cal_ULP.Childs[i]);
        if cp.RecID <> ULPRID then
          continue;
        if cp.UsrPeakCoef = 0 then
          cp.UsrPeakCoef := 1;
        if (cp.AreaSize <> 0) and (cp.Amount <> 0) then
          cp.Response := cp.AreaSize / cp.Amount * cp.UsrPeakCoef * f
        else
          cp.Response := 0;
      end;
      {/v0.13}
    end;
  end;
  {v0.31}
  Peaks.DoChangeLock;
  try
  {/v0.31}
    for i := 0 to Peaks.ChildCount - 1 do begin
      p := TULPRObj(Peaks.Childs[i]);
      if p.RecID <> ULPRID then
        continue;
      if {v0.12} FindPeakByName(AcqData.Cal_ULP, p.PeakName, cp)
         {/v0.12 PeakFindInPeaks(AcqData.Cal_ULP, p, cp)} then
      {v0.13}
      begin
        p.Response := cp.Response;
      end else begin
        p.Response := 0;
      end;
    end;
  {v0.31}
  finally
    Peaks.DoChangeUnlock;
  end;
  {/v0.31}
end;

{v0.12}
function TSpectrum.PeaksAmountsCalculateInternalStandard: integer;
{/v0.12
function TSpectrum.PeaksAmountsCalculateFromInternalStandard: integer;}
var
  istd: integer;
  i: integer;
  p: TULPRObj;
begin
  ClearResult;
  Result := 0;
  {Pro prepocet analyzy na interni standard
    is=find Peak[i].IntStandard
    Method.Factor=Peak[is].Amount/
		((Peak[is].Area/Peak[is].Response)*Peak[is].UserCoef); }
  {v0.31}
  Peaks.DoChangeLock;
  try
  {/v0.31}
    istd := -1;
    for i := 0 to Peaks.ChildCount - 1 do begin
      p := TULPRObj(Peaks.Childs[i]);
      if p.RecID <> ULPRID then
        continue;
      if p.InternalStandard then begin
        istd := i;
        if p.Amount = 0 then begin
          if AcqData.ULA.ISTDAmount = 0 then begin
            SetResult(srPeakAmountZero, p.PeakName + ' - internal standard');
            exit;{ulatype}
          end else begin
            p.Amount := AcqData.ULA.ISTDAmount;
          end;
        end;
        if p.Response = 0 then begin
          SetResult(srPeakResponseZero, p.PeakName + ' - internal standard');
          exit;
        end;
        if p.AreaSize = 0 then begin
          SetResult(srPeakAreaZero, p.PeakName + ' - internal standard');
          exit;
        end;
        if p.UsrPeakCoef = 0 then begin
          p.UsrPeakCoef := 1;
        end;
        AcqData.ULM.Factor := p.Amount /( (p.AreaSize / p.Response) * p.UsrPeakCoef )
           {v0.28} / CombineFactors(1, AcqData.ULA.MultiplyFactor, AcqData.ULA.DivideFactor);
           {/v0.28};
        break;
      end;
    end;
    if istd = -1 then begin
      SetResult(srNoInternalStandardSpecified,' - in calculate amounts');
      exit;
    end;
  {v0.31}
  finally
    Peaks.DoChangeUnlock;
  end;
  {/v0.31}
end;

{v0.12}
function TSpectrum.PeaksAmountsCalculateValues: integer;
var
  i: integer;
  p: TULPRObj;
  {v0.28}
  f: TFactor;
  {/v0.28}
begin
{ Vypocet standardni analyzy
    with Peak[i]
      if not Peak[i].IntStandard then
        Amount=(Area/Response)*UserCoef*Method.Factor; }
  Result := 0;

  {v0.31}
  Peaks.DoChangeLock;
  try
  {/v0.31}

    {v0.28}
    f := CombineFactors(AcqData.ULM.Factor, AcqData.ULA.MultiplyFactor, AcqData.ULA.DivideFactor);
    {/v0.28}

    for i := 0 to Peaks.ChildCount - 1 do begin
      p := TULPRObj(Peaks.Childs[i]);
      if p.RecID <> ULPRID then
        continue;
      if not p.InternalStandard then begin
        if p.UsrPeakCoef = 0 then
          p.UsrPeakCoef := 1;
        {if p.AreaSize = 0 then begin
          SetResult(srPeakAreaZero, p.PeakName + ' - calculate amount');
          exit;
        end;}
        if p.Response = 0 then begin
          {SetResult(srPeakResponseZero, p.PeakName + ' - calculate amount');}
          p.Amount := 0;
        end else begin
          p.Amount := (p.AreaSize / p.Response) * p.UsrPeakCoef * {v0.28}f{/v0.28 AcqData.ULM.Factor};
        end;
      end;
    end;
  {v0.31}
  finally
    Peaks.DoChangeUnlock;
  end;
  {/v0.31}
end;
{/v0.12}

procedure TSpectrum.ClearResult;
begin
  LastResult := 0;
end;

procedure TSpectrum.SetResult(sr: TSpectrumResult; const msg:string);
var
  s: string;
begin
  LastResult := sr;
  case sr of
    srPeakResponseZero : s := 'Peak Response Zero';
    srPeakAmountZero : s := 'Peak Amount Zero';
    srPeakAreaZero : s := 'Peak Area Zero';
    srNoInternalStandardSpecified : s := 'No Internal Standard Specified';
    srCalibrationFileNotSpecified : s := 'Calibration File Not Specified';
    {v0.55}
    srCalibrationFileNotFound: s := 'Calibration File Not Found';
    {/v0.55}
  else
    s := IntToStr(sr);
  end;
  raise ESpectrum.Create(GetTxt({#}'Data Error') + ': ' + s + ' ' + msg);
end;
{/v0.11}

{/TSpectrum}

{TULADRc}
function TULADRc.GetData: TStream;
begin
  {v0.50}
  if ULAD = nil then
    Result := nil
  else
  {/v0.50}
    Result := ULAD.Data;
end;

{v0.31}
constructor TULADRc.Create{v0.57}(AULADs: TULADs){/v0.57};
begin
  inherited Create;
  {v0.57}
  FULADs := AULADs;
  {/v0.57}
  PointTimeInterval := UlanPointTimeInterval;
  YCoef := 1;
end;
{/v0.31}

{v0.57}
procedure TULADRc.SetULAD(AULAD: TULADObj);
var n: string;
begin
  FULAD := AULAD;
  n := FULAD.DataName;
  if n = '' then
    n := 'noname';
  FUDVR := TUDVRObj(FULADs.FAcqData.UDVS.FindOrAdd(UDVRID, n));
  FUDVR.DataName := n;
end;
{/v0.57}

{/TULADRc}

{TULADs}
procedure TULADs.CurDataSizeUpdate;
var
  i: integer;
  r: TULADRc;
begin
  for i := 0 to Count - 1 do begin
    r := ULAD[i];
    r.CurDataSize := r.Data.Size;
    {v0.31}
    r.CurDataPos := r.Data.Position;
    {/v0.31}
  end;
end;

procedure TULADs.SetZeroSizes;
var
  ud: TULADRc;
  i: integer;
begin
  for i := 0 to Count -1 do begin
    ud := GetULADRc(i);
    ud.Data.Size := 0;
    {v0.31}
    ud.CurDataSize := 0;
    ud.CurDataPos := 0;
    {/v0.31}
  end;
end;

{v0.31}
procedure TULADs.Start;
var i: integer;
begin
  for i := 0 to Count - 1 do
    ULAD[i].CurTime := 0;
end;

function TULADs.FindULAD(AULAD: TULADObj; var Index: integer): boolean;
var i: integer;
begin
  Result := false;
  for i := 0 to Count - 1 do begin
    if ULAD[i].ULAD = AULAD then begin
      Result := true;
      Index := i;
      exit;
    end;
  end;
end;
{/v0.31}

procedure TULADs.Reset;
var
  ud: TULADRc;
  i: integer;
begin
  for i := 0 to Count -1 do begin
    ud := GetULADRc(i);
    ud.Data.Position := 0;
    {v0.31}
    ud.CurDataPos := 0;
    {/v0.31}
  end;
end;

procedure TULADs.Clear;
begin
  while Count > 0 do
    DelULAD(TULADRc(Items[0]).ULAD);
  inherited;
end;

function TULADs.GetULADRc(Index: integer):TULADRc;
begin
  Result := nil;
  if (Index >= 0) and (Index < Count) then begin
    Result := TULADRc(Items[Index]);
  end;
end;

procedure TULADs.Update;
var
  d: TULADObj;
  i, j: integer;
  fnd: boolean;
begin
  {v0.31}
  for i := 0 to FAcqData.ULA.ChildCount - 1 do begin
    d := TULADObj(FAcqData.ULA.Childs[i]);
    if d.RecID = ULADID then begin
      if not FindULAD(d, j) then begin
        AddULAD(d);
      end;
    end;
  end;

  for j := 0 to Count - 1 do begin
    d := ULAD[j].ULAD;
    fnd := false;
    for i := 0 to FAcqData.ULA.ChildCount - 1 do begin
      if d = FAcqData.ULA.Childs[i] then begin
        fnd := true;
        break;
      end;
    end;
    if not fnd then
      DelULAD(d);
  end;
  {/v0.31
  Clear;
  d0 := TULADObj(FAcqData.ULA.FindOrAdd(ULADID, ''));
  AddULAD(d0);
  for i := 0 to FAcqData.ULA.ChildCount - 1 do begin
    o := FAcqData.ULA.Childs[i];
    if (o is TULADObj) and (o <> d0) then begin
      AddULAD(TULADObj(o));
    end;
  end;   }
end;

procedure TULADs.AddULAD(AULAD: TULADObj);
var rc: TULADRc;
begin
  rc := TULADRc.Create{v0.57}(Self){/v0.57};
  rc.ULAD := AULAD;
  AULAD.SetFlag(rfCantDelete, true);
  Add(rc);
end;

procedure TULADs.DelULAD(AULAD: TULADObj);
var
  i: integer;
  ur: TULADRc;
begin
  AULAD.SetFlag(rfCantDelete, false);
  for i := 0 to Count - 1 do begin
    ur := TULADRc(Items[i]);
    if ur.ULAD = AULAD then begin
      Remove(ur);
      exit;
    end;
  end;
end;

constructor TULADs.Create(AAcqData: TAcqDAta);
begin
  inherited Create;
  FAcqData := AAcqData;
  Update;
end;

destructor TULADs.Destroy;
begin
  Clear;
  inherited;
end;
{/TULADs}

{******************** TAcqData *********************}

{TAcqData}
constructor TAcqData.Create(const AFileName: string; AMode: TOpenMode);
begin
  inherited Create;
  {v0.20}{/v0.20 BufPointsClear;}
  SpectrumOpt := DefSpectrumOpt;
  {v0.21}
  DoMath(moNoneOp,0);
  {/v0.21
  FMultiplyX := 1;
  FMultiplyY := 1;}

  ClearScanInfo;
  {v0.14}
  if AMode = omCreateTemplate then begin
    CreateTemplate(AFileName);
  end else
  {/v0.14}
  begin
    SetFileName(AFileName, AMode);
  end;
  {v0.45}
  FCreatedOK := true;
  {/v0.45}
end;

{v0.05}
(*
procedure TAcqData.HeadInit(var AHead:TUldRec);
begin
  with AHead do begin
    Head.RecID := UldID;
    Head.RecLen := sizeof(AHead);
    Info.CreateTime := Now;
    Info.ChangeTime := Now;
    {Desc:TUldDesc;}
    {InstrumentFileName:TInstrumentFileName;}
    {MethodFileName:TMethodFileName;}
  end;
end;
*)
{/v0.05}

{v0.09}
constructor TAcqData.CreateFromPoints(APoints: PExpPoints; APointCount: integer);
var
  i: integer;
  ud: TULADRc;
begin
  inherited Create;
  {v0.20}{/v0.20 BufPointsClear;}
  SpectrumOpt := DefSpectrumOpt;
  {v0.21}
  DoMath(moNoneOp,0);
  {/v0.21}
  ClearScanInfo;
  FMode := omRead;
  ULF := TULFObj.Create(nil);
  CheckULRecords; {ulobju}
  ud := ULAD[ULADIndex];
  ud.ULAD.Data := TMemoryStream.Create;
  TMemoryStream(ud.Data).SetSize(APointCount * sizeof(TExpPoint));
  ud.Data.Position := 0;
  for i := 0 to APointCount - 1 do WritePoint(APoints^[i]);
  ScanData;
end;

procedure TAcqData.CheckULRecords;
begin
  ULA := TULAObj(ULF.FindOrAdd(ULAID, ''));
    ULA.SetFlag(rfCantDelete, true);
  {v0.50}
  {ULA.BrowserAutoClose := false;}
  {/v0.50}
  {v0.14}
  ULA.MainUser := Self;
  {/v0.14}
  {v0.57}{ulatype}
  UDVS := TUDVSObj(ULA.FindOrAdd(UDVSID, ''));
    UDVS.SetFlag(rfCantDelete, true);
  {/v0.57}
  {v0.24}
  if ULAD = nil then
    ULAD := TULADs.Create(Self)
  else
    ULAD.Update;
  {/v0.24
  ULAD := TULADObj(ULA.FindOrAdd(ULADID, ''));
    ULAD.SetFlag(rfCantDelete, true);}
  ULAF := TULAFObj(ULA.FindOrAdd(ULAFID, ''));
    ULAF.SetFlag(rfCantDelete, true);
  ULM := TULMObj(ULA.FindOrAdd(ULMID, ''));
    ULM.SetFlag(rfCantDelete, true);
  {v0.11}
  CheckULMRecords;
  {/v0.11
  ULMF := TULMFObj(ULM.FindOrAdd(ULMFID, ''));
    ULMF.SetFlag(rfCantDelete, true);
  ULM_ULP := TULPObj(ULM.FindOrAdd(ULPID, ''));
    ULM_ULP.SetFlag(rfCantDelete, true);
  ULM_ULB := TULBObj(ULM.FindOrAdd(ULBID, ''));
    ULM_ULB.SetFlag(rfCantDelete, true);}

  {v0.13}
  ULI := TULIObj(ULA.FindOrAdd(ULIID, ''));
    ULI.SetFlag(rfCantDelete, true);
  {/v0.13}
  {v0.14}
  {$IFNDEF CONSOLE}
  {v0.50 moved to setuli}{/v0.50
  ULI.FindField(fnChannelName).FldDesc.ValuesSource := Channels.ULL;
  if ULI.ChannelName = '' then
    ULI.ChannelName := Channels.ULL.ChannelName;}
  {set to default channel name}
  {$ENDIF}
  {/v0.14}

  ULB := TULBObj(ULA.FindOrAdd(ULBID, ''));
    ULB.SetFlag(rfCantDelete, true);
  ULP := TULPObj(ULA.FindOrAdd(ULPID, ''));
    ULP.SetFlag(rfCantDelete, true);
  ULV := TULVObj(ULA.FindOrAdd(ULVID, ''));
    ULV.SetFlag(rfCantDelete, true);
  ULVL := TULVLObj(ULV.FindOrAdd(ULVLID, ''));
    ULVL.SetFlag(rfCantDelete, true);
  ULVO := TULVOObj(ULV.FindOrAdd(ULVOID, ''));
    ULVO.SetFlag(rfCantDelete, true);
end;
{/v0.09}

{v0.11}
procedure TAcqData.CheckULMRecords;
begin
  ULMF := TULMFObj(ULM.FindOrAdd(ULMFID, ''));
    ULMF.SetFlag(rfCantDelete, true);
  ULM_ULP := TULPObj(ULM.FindOrAdd(ULPID, ''));
    ULM_ULP.SetFlag(rfCantDelete, true);
  ULM_ULB := TULBObj(ULM.FindOrAdd(ULBID, ''));
    ULM_ULB.SetFlag(rfCantDelete, true);
end;
{/v0.11}

function TAcqData.GetNonameFileName: string;
begin
  Result := {v0.29}DataDir + {/v0.29} 'NONAME_' + ULI.ChannelName + ULFExt;
end;

procedure TAcqData.CreateTemplate(const AFileName: string);
begin
  ULF.Free;
  ULF := TULFObj.Create(nil);
  CheckULRecords;
  ULF.FileName := ChangeFileExt(AFileName, ULTExt);
end;

{v0.24}
function TAcqData.GetRawFileName(Index: integer): string;
begin
  Result := GetChannelFileName({v0.29}DataDir,{/v0.29}ULI.ChannelName) + IntToStr(Index);
end;

function TAcqData.GetActiveData: TStream;
begin
  {v0.50}
  if ActiveULADRc = nil then
    Result := nil
  else
  {/v0.50}
    Result := ActiveULADRc.Data;
end;

function TAcqData.GetActiveULADRc: TULADRc;
begin
  Result := ULAD[ULADIndex];
end;

function TAcqData.GetDataCount: integer;
begin
  Result := ULAD.Count;
end;

function TAcqData.GetDataIndex: integer;
begin
  Result := ULADIndex;
end;

procedure TAcqData.SetDataIndex(Index: integer);
begin
  if (Index >= 0) and (Index < ULAD.Count) then begin
    ULADIndex := Index;
  end else begin
    {v0.50}
    ULADIndex := -1;
    {/v0.50
    raise EAcqData.Create('SetDataIndex OOR ' + IntToStr(Index));}
  end;
end;
{/v0.24}


procedure TAcqData.SetFileName(const AFileName: string; AMode: TOpenMode);
var
  e: string;
{  s: TFileStream;}
{  ei: TULEObj;}
  fn: string;
  rfn: string;
  i:integer;
{  frm: TAnalSetupForm;}
{  r:integer;}
  ud: TULADObj;
begin
  FMode := AMode;
  {v0.20}{/v0.20 BufPointsClear;}
  ClearScanInfo;
  e := UpperCase(ExtractFileExt(AFileName));
  fn := AFileName;
  if FMode <> omCreate then begin

    if not FileExists(AFileName) then
      raise EFOpenError.Create(GetTxt({#}'Can not open file') + ' ' + AFileName);
    ULF.Free;
    ULF := TULFObj.Create(nil);
    if (e = ULFExt) or (e = ASCExt) {v0.14} or (e = ULTExt) {/v0.14}
      {v0.61} or (e = ULCExt) {/v0.61}
    then
      ULF.LoadFromFile(AFileName);
    if FMode = omReadOnly then
      ULF.ReadOnly := true;
    CheckULRecords;
    if (e <> ULFExt) and (e <> AscExt) {v0.14} and (e <> ULTExt) {/v0.14}
       {v0.61} and (e <> ULCExt) {/v0.61}
    then begin
      {v0.18}
      {v0.50}
      ULA.FindOrAdd(ULADID, '');
      ULAD.Update;
      {/v0.50}
      if TryConvertFromFile(AFileName, {v0.50} ULA,{/v0.50} ActiveData) then begin
        {OK}
        {v0.50}
        ULAD.Update;
        {/v0.50}
      end else
      begin
      {/v0.18
        if e = DatExt then begin
          if not ULA.FindObj(ULEID, foFromRoot, DatExt, TULObj(ei)) then
            ei := TULEObj(ULA.Add(ULEID));
          FillDatExpImp(ei);
          s := TFileStream.Create(AFileName, fmOpenRead);
          ULConvertFrom(ei, s, ULAD.Data);
          s.Free;
        end else if e = ULDExt then begin
          if not ULA.FindObj(ULEID, foFromRoot, ULDExt, TULObj(ei)) then
            ei := TULEObj(ULA.Add(ULEID));
          FillULDExpImp(ei);
          s := TFileStream.Create(AFileName, fmOpenRead);
          ULConvertFrom(ei, s, ULAD.Data);
          s.Free;
        end else if e = TXTExt then begin
          if not ULA.FindObj(ULEID, foFromRoot, TxtExt, TULObj(ei)) then
            ei := TULEObj(ULA.Add(ULEID));
          FillTXTExpImp(ei);
          if ei.EditModal <> mrOK then
            raise EImportAborted.Create('Import aborted ' + AFileName);
          s := TFileStream.Create(AFileName, fmOpenRead);
          ULConvertFrom(ei, s, ULAD.Data);
          s.Free;
        end else begin
        }
        raise EInvalidFileExt.Create(GetTxt({#}'Invalid file extension') + ': ' + AFileName);
      end;
      ULF.FileName := ReplaceExt(AFileName, ULFExt, true);
    end;
    {v0.20}
    {v0.24}
    ULAD.CurDataSizeUpdate;
    {/v0.24
    FCurDataSize := ActiveData.Size;}
    {/v0.20}
    ScanData;
    {v0.45}
    {$IFNDEF CONSOLE}
    if AutofixData then begin
      ChkAcqData(Self, true);{chkulfu}
    end;
    {$ENDIF}
    {/v0.45}
  end else begin

    if (e <> ULFExt) {v0.13} and (e <> ULTExt){/v0.13}
      {v0.61} and (e <> ULCExt) {/v0.61}
    then
      raise EOnlyULFSupported.Create(GetTxt({#}'Not .ULF or .ULT or .ULC extension') + ': ' + AFileName);
    ULF.Free;
    ULF := TULFObj.Create(nil);
    {v0.13}
    if FileExists(fn) then begin
      ULF.LoadFromFile(fn);
    end{v0.14} else begin
      raise EAcqData.Create(GetTxt({#}'Template Not Found') + ': ' + fn);
    end{/v0.14};
    CheckULRecords;
    {v0.34}
    FTemplateULADCount := ULAD.Count;
    {/v0.34}
    fn := GetNonameFileName;
    ULF.FileName := fn;
    {v0.31}
    {$IFNDEF CONSOLE}
    CheckChannel;
    {$ENDIF}
    {v0.50}
      { either from ULI.ULDS (if ChildCount > 0) or from devices in the channel }
    {/v0.50}
    {/v0.31}
    {v0.24
    RawFileName := GetChannelFileName(ULI.ChannelName);} {report ulatype}
    try
      {v0.24}
      for i := 0 to ULAD.Count - 1 do
      {/v0.24}
      begin
        ud := ULAD[i].ULAD;
        rfn := RawFileName[i];
        ud.Data := TFileStream.Create(rfn, fmCreate);
      end;
    except
      on EFCreateError do begin
        (*ShowMessage('Acquisition window already opened for ' +
          {v0.14}ULI.ChannelName{/v0.14 DefPortName}, smError, 0);*)
        raise EAcqAlreadyOpened.Create(GetTxt({#}'Acquisition window already opened for') +
          ' ' + {v0.14}ULI.ChannelName{/v0.14 DefPortName});
      end;
    end;
    {v0.13}
    {v0.14}{/v0.14
    CurULObj := ULA;
    frm := TAnalSetupForm.Create(Application.MainForm);
    r := frm.ShowModal;
    frm.Free;
    if r <> mrOK then
      raise EUserAborted.Create('Aborted by user');}
    {/v0.13}
  end;
  {v0.50}
  {$IFNDEF CONSOLE}
  ObjHandler.CheckIn(ULA);
  {$ENDIF}
  {/v0.50}
end;

{v0.31}
procedure TAcqData.WMAppMessage(var Message: TMessage);
begin
  case Message.wParam of
    cmULObjUpdated: begin
      if TULIObj(Message.LParam) = ULI then begin
        {$IFNDEF CONSOLE}
        {v0.50}
        if not FSettingULI then
        {/v0.50}
          CheckChannel;
        {$ENDIF}
      end;
    end;
    {v0.50}
    cmULObjDestroyed: begin
      if TULIObj(Message.LParam) = ULI then
        ULI := nil;
    end;
    {/v0.50}
  end;
  inherited;
end;

{v0.50}
procedure TAcqData.RecreateDataLines;
begin
  if (ULI_UDLS = nil) or (ULI_UDLS.ChildCount = 0) then begin
    RecreateDataLinesByDevices;
  end else begin
    RecreateDataLinesByDefinition;
  end;
end;

procedure TAcqData.RecreateDataLinesByDefinition;
var
  i: integer;
{  dr: TULADRc;}
  d: TULADObj;
  l: TUDLRObj;
  {udlstype udlrtype uladtype}
begin
  for i := 0 to ULAD.Count - 1 do begin
    d := ULAD[i].ULAD;
    if (d.Data.Size = 0) then begin
      ULAD.DelUlad(d);
      d.Free;
    end;
  end;
  for i := 0 to ULI_UDLS.ChildCount - 1 do begin
    l := TUDLRObj(ULI_UDLS.Childs[i]);
    d := TULADObj(ULA.FindOrAdd(ULADID, l.DataName));
    d.AssignClass(l);{ulobju}
  end;
  ULAD.Update;
end;

procedure TAcqData.RecreateDataLinesByDevices;
var
  i, di: integer;
  ul: TULADObj;
  d: TULDRObj;
  {v0.51pi}
  prop: TULDPObj; {TULObj;}
  {/v0.51pi}
  fnd: boolean;
  dm: TDeviceMode;
  delcnt: integer;
  {v0.59}
  pti: single;
  {/v0.59}
begin
  if FChannel = nil then begin
    raise EAcqData.Create('CreateDataLinesByDevices - Channel ' + ULI.ChannelName + ' not found.')
  end;
  dm := FChannel.DeviceMode;

  {check for added devices}
  for i := 0 to FChannel.DeviceCount - 1 do begin
    d := FChannel.Devices[i].ULDR;
    if d.DeviceType = dtDetector then begin
      fnd := false;
      for di := 0 to ULAD.Count - 1 do begin
        {v0.34}
        if ULAD[di].Addr = 0 then
          ULAD[di].Addr := StrToInt(d.AddrStr);
        {/v0.34}
        if (StrToInt(d.AddrStr) = ULAD[di].Addr) and (d.DeviceMode = dm) then begin
          fnd := true;
          break;
        end;
      end;
      if not fnd then begin
        {ul := TULADObj}(ULA.Add(ULADID));
        ULAD.Update;
        di := ULAD.Count - 1;
        {ul.DataName := d.DeviceName;!!! fix later to work with AAA
        if di > 0 then
          ul.DataName := d.DeviceName;}
      end;
      ULAD[di].Addr := StrToInt(d.AddrStr);
      {v0.51pi}
      try {ClassGetPropStr(o, pi)}
        if d.HasChildWithFieldUsrValue('PropDesc','ADCSAMPPER', TULObj(prop)) then
        if prop<>nil then begin
          {v0.59}
          pti := StrToInt(prop.ValueInPC);
          ULAD[di].PointTimeInterval := pti;
          if (ULI <> nil) then begin
            if ULI.DataWaitTimeout < (pti * 3 * 32 / 1000) then
              ULI.DataWaitTimeout := pti * 100 / 1000;
              { 32 .. number of points in one packet }
              { 3 .. tolerance coefficient (should be more then 2)}
              { 100 .. almost 32 * 3, but gives nicer numbers }
              { 1000 .. pti is in miliseconds, datawaittimeout in seconds }
          end;
          {/v0.59
          ULAD[di].PointTimeInterval := StrToInt(prop.ValueInPC);
          }
        end;
      except
      end;
      {/v0.51pi
      if pos('REFR', Uppercase(d.DeviceName)) > 0 then begin
        ULAD[di].PointTimeInterval := 100;
        (*ULAD[di].YCoef := 1;*)
      end;
      }
    end;
  end;
  {/check for added devices}

  {check for removed devices}
  di := 0;
  delcnt := 0;
  while di < ULAD.Count do begin
    fnd := false;
    for i := 0 to FChannel.DeviceCount - 1 do begin
      d := FChannel.Devices[i].ULDR;
      if (d.DeviceType = dtDetector) and (d.DeviceMode = dm) then begin
        if StrToInt(d.AddrStr) = ULAD[di].Addr then begin
          fnd := true;
          break;
        end;
      end;
    end;

    if not fnd then begin
      ul := ULAD[di].ULAD;
      if (ul.Data.Size = 0) {v0.34} and (di > (FTemplateULADCount - 1) ){/v0.34} then begin
        ULAD.DelUlad(ul);
        ul.Free;
        inc(delcnt);
      end else begin
        inc(di);
      end;
    end else
      inc(di);
  end;
  if delcnt > 0 then
    ULAD.Update;
  {/check for removed devices}
end;

{/v0.50}

{$IFNDEF CONSOLE}
procedure TAcqData.CheckChannel;
{v0.50}{/v0.50
var
  i, di: integer;
  ul: TULADObj;
  d: TULDRObj;
  fnd:boolean;
  dm: TDeviceMode;
  delcnt:integer;}
begin
  if FChannel <> nil then begin
    FChannel.ULN.SetFlag(rfCantDelete, false);
  end;
  FChannel := Channels.FindChannel(ULI.ChannelName);
  if FChannel <> nil then begin
    FChannel.ULN.SetFlag(rfCantDelete, true);

    (*v0.50 moved to CreateDataLinesByDevices*)

    (*/v0.50
    dm := FChannel.DeviceMode;

    {check for added devices}
    for i := 0 to FChannel.DeviceCount - 1 do begin
      d := FChannel.Devices[i].ULDR;
      if d.DeviceType = dtDetector then begin
        fnd := false;
        for di := 0 to ULAD.Count - 1 do begin
          {v0.34}
          if ULAD[di].Addr = 0 then
            ULAD[di].Addr := StrToInt(d.AddrStr);
          {/v0.34}
          if (StrToInt(d.AddrStr) = ULAD[di].Addr) and (d.DeviceMode = dm) then begin
            fnd := true;
            break;
          end;
        end;
        if not fnd then begin
          {ul := TULADObj}(ULA.Add(ULADID));
          ULAD.Update;
          di := ULAD.Count - 1;
          {ul.DataName := d.DeviceName;!!! fix later to work with AAA
          if di > 0 then
            ul.DataName := d.DeviceName;}
        end;
        ULAD[di].Addr := StrToInt(d.AddrStr);
        if pos('REFR', Uppercase(d.DeviceName)) > 0 then begin
          ULAD[di].PointTimeInterval := 100;
          {ULAD[di].YCoef := 1;}
        end;
      end;
    end;
    {/check for added devices}

    {check for removed devices}
    di := 0;
    delcnt := 0;
    while di < ULAD.Count do begin
      fnd := false;
      for i := 0 to FChannel.DeviceCount - 1 do begin
        d := FChannel.Devices[i].ULDR;
        if (d.DeviceType = dtDetector) and (d.DeviceMode = dm) then begin
          if StrToInt(d.AddrStr) = ULAD[di].Addr then begin
            fnd := true;
            break;
          end;
        end;
      end;

      if not fnd then begin
        ul := ULAD[di].ULAD;
        if (ul.Data.Size = 0) {v0.34} and (di > (FTemplateULADCount - 1) ){/v0.34} then begin
          ULAD.DelUlad(ul);
          ul.Free;
          inc(delcnt);
        end else begin
          inc(di);
        end;
      end else
        inc(di);
    end;
    if delcnt > 0 then
      ULAD.Update;
    {/check for removed devices}
    *)

  end;
  {v0.50}
  if (FMode = omCreate) and (not FSettingULI) then
    RecreateDataLines;
  {/v0.50}
end;
{$ENDIF}
{/v0.31}

procedure TAcqData.ScanData;
var
  ap:TExpPoint;
{v0.25}
  i: integer;
{/0.25}
begin
  {v0.33}
  Reset;
  {/v0.33}
  {v0.25}
  ClearScanInfo;
  {v0.61}
  ScanPeaks;
  {/v0.61}
  for i := 0 to DataCount - 1 do
  begin
    DataIndex := i;
  {/v0.25
  Reset;
  ClearScanInfo;}
    while ReadPoint(ap) do
      CheckPoint(ap);
    Reset;
  {v0.25}
  end;
  {v0.41}
  if DataCount > 0 then
  {/v0.41}
    DataIndex := 0;
  {/v0.25}
end;

procedure TAcqData.FixPoint(var APoint:TExpPoint);
begin
  if (APoint.y > SpectrumOpt.Limit.Max.Y) then
    APoint.y := SpectrumOpt.Limit.Max.Y;
  if (APoint.y < SpectrumOpt.Limit.Min.Y) then
    APoint.y := SpectrumOpt.Limit.Min.Y;
end;

{v0.61}
procedure TAcqData.ScanPeaks;
var
  ep: TExpPoint;
  p: TULPRObj;
  o: TULObj;
  i: integer;
begin
  for i := 0 to ULP.ChildCount - 1 do begin
    o := ULP.Childs[i];
    if o.RecID = ULPRID then begin
      p := TULPRObj(o);
      ep.Y := min(p.BaseY1, p.BaseY2);
      ep.X := p.X1;
      CheckPoint(ep);
      ep.X := p.X2;
      ep.Y := max(p.Y1, p.Y2) + p.Height;{ulprtype}
      CheckPoint(ep);
    end;
  end;
end;
{/v0.61}

procedure TAcqData.CheckPoint(const APoint:TExpPoint);
begin
  if APoint.x < SpectrumOpt.Extreme.Min.X then
    SpectrumOpt.Extreme.Min.X := APoint.x;

  if APoint.y < SpectrumOpt.Extreme.Min.Y then
    SpectrumOpt.Extreme.Min.Y := APoint.y;

  if APoint.x > SpectrumOpt.Extreme.Max.X then
    SpectrumOpt.Extreme.Max.X := APoint.x;

  if APoint.y > SpectrumOpt.Extreme.Max.Y then
    SpectrumOpt.Extreme.Max.Y := APoint.y;
end;

procedure TAcqData.ClearScanInfo;
begin
  SpectrumOpt.Extreme.Max := SpectrumOpt.Limit.Min;
  SpectrumOpt.Extreme.Min := SpectrumOpt.Limit.Max;
end;

procedure TAcqData.Reset;
begin
  {v0.24}
  ULAD.Reset;
  {/v0.24 ULAD.Data.Position := 0;}
  ULAF.Data.Position := 0;
  {v0.20}
  {v0.24}{/v0.24 FCurDataPos := 0;}
  {/v0.20}
  {v0.21}
  FCurDiluteCount := 0;
  {/v0.21}
  CurIndex := 0;
  {v0.20}{/v0.20 BufPointsClear;}
end;

procedure TAcqData.Seek(APos:longint);
begin
  {v0.24}
  ActiveData.Position := APos;
  ActiveULADRc.CurDataPos := APos;
  {/v0.24
  ULAD.Data.Position := APos;}
  ULAF.Data.Position := APos;
  {v0.20}
  {v0.24}{/v0.24 FCurDataPos := APos;}
  {/v0.20 BufPointsClear;}
  {v0.21}
  FCurDiluteCount := 0;
  {/v0.21}
end;

procedure TAcqData.SeekPoint(Index:Longint);
begin
  {v0.45}
  if Index < 0 then begin
    Seek(ActiveULADRc.CurDataPos + sizeof(TExpPoint) * Index);
    CurIndex := CurIndex + Index;
    exit;
  end;
  {/v0.45}
  {v0.20}
  {v0.24}
  Seek(Index * sizeof(TExpPoint));
  {/v0.24
  FCurDataPos := Index * sizeof(TExpPoint);
  Seek(FCurDataPos);}
  {/v0.20
  Seek(Index * sizeof(TExpPoint));}
  CurIndex := Index;
end;

function TAcqData.WritePoint(var APoint:TExpPoint):boolean;
var
  urc: TULADRc;
{v0.45}
  s: TStream;
{/v0.45}
begin
  {v0.20}
  {v0.24}
  urc := ActiveULADRc;
  {v0.45}
  s := urc.Data;
  if s is TMemoryStream then begin
    if urc.CurDataPos + sizeof(APoint) > urc.CurDataSize then begin
      s.WriteBuffer(APoint, sizeof(APoint));
      urc.CurDataSize := s.Size;
      urc.CurDataPos := urc.CurDataSize;
    end else begin
      move(APoint, PLongByteArray(TMemoryStream(s).Memory)^[urc.CurDataPos], sizeof(APoint));
      inc(urc.CurDataPos, sizeof(APoint));
      {Result := true;}
    end;
  end else begin
    s.WriteBuffer(APoint, sizeof(APoint));
    urc.CurDataSize := s.Size;
    urc.CurDataPos := urc.CurDataSize;
  end;
  {/v0.45
  urc.Data.WriteBuffer(APoint, sizeof(APoint));
  inc(urc.CurDataSize, sizeof(APoint));}

  {/v0.24
  ULAD.Data.WriteBuffer(APoint, sizeof(APoint))
  inc(FCurDataSize, sizeof(APoint));}
  {/v0.20}
  Result := true;
end;

function TAcqData.ReadPoint(var APoint:TExpPoint):boolean;
{v0.20}
var s: TStream;
{/v0.20}
{v0.24}
var urc: TULADRc;
{/v0.24}
begin
  if UseFiltered then begin
    {v0.20}
    s := ULAF.Data;
    {/v0.20
    ReadPoint := (ULAF.Data.Read(APoint, sizeof(APoint)) = sizeof(APoint));}
  end else begin
    {v0.20}
    s := {v0.24}ActiveData{/v0.24 ULAD[ULADIndex].Data};
    {/v0.20
    ReadPoint := (ULAD.Data.Read(APoint, sizeof(APoint)) = sizeof(APoint));}
  end;

  {v0.21}
  repeat
  {/v0.21}

    {v0.20}
    if s is TMemoryStream then begin
      urc := ActiveULADRc;
      if urc.CurDataPos{FCurDataPos} + sizeof(APoint) > urc.CurDataSize{FCurDataSize} then begin
        Result := false
      end else begin
        move({v0.45 changed PByteArray}{/v0.45}PLongByteArray(TMemoryStream(s).Memory)^[urc.CurDataPos{FCurDataPos}], APoint, sizeof(APoint));
        inc(urc.CurDataPos{FCurDataPos}, sizeof(APoint));
        Result := true;
      end;
    end else begin
      Result := (s.Read(APoint, sizeof(APoint)) = sizeof(APoint))
    end;
    {/v0.20}

  {v0.21}
    if (not FApplyMath) or (not Result) then
      break;

    inc(FCurDiluteCount);
    if FCurDiluteCount >= FDiluteCount then begin
      FCurDiluteCount := 0;
      break;
    end else begin
      continue;
    end;

    break;
  until false;
  {/v0.21}

  {v0.11}
  if FApplyMath then begin
    APoint.X := APoint.X * FMultiplyX + FAddX;
    APoint.Y := APoint.Y * FMultiplyY + FAddY;
  end;
  {/v0.11}
(*
  ReadPoint := false;
  if Stream <> NoHIO then begin
    {v0.05}
    case FFilterPars.FilterType of
      ftNone: begin
        ReadPoint := (IORead(Stream, APoint, sizeof(TExpPoint), r) = irOK) and (r > 0);
      end;

      ftAvg: begin
        if BufPointCount = BufPointPos then begin
          ir := IORead(Stream, BufPoints, FFilterPars.AvgPointCount * sizeof(TExpPoint), r);
          BufPointCount := r div sizeof(TExpPoint);
          if (ir <> irOK) or (BufPointCount = 0) then
            exit;
          y := 0;
          for r := 0 to BufPointCount - 1 do begin
            y := BufPoints[r].Y + y;
          end;
          y := y / BufPointCount;
          for r := 0 to BufPointCount - 1 do begin
            BufPoints[r].Y := y;
          end;
          BufPointPos := 0;
        end;
        APoint := BufPoints[BufPointPos];
        inc(BufPointPos);
        ReadPoint := true;
      end;

      ftFilter1: begin
        ir := IORead(Stream, APoint, sizeof(TExpPoint), r);
        if (ir <> irOK) or (r = 0) then
          exit;
        if BufPointCount = 0 then begin
          BufPoints[0] := APoint;
          BufPointCount := 1;
        end else begin
          APoint.Y :=  BufPoints[0].Y * FFilterPars.Filter1 +
              (1 - FFilterPars.Filter1) * APoint.Y;
          BufPoints[0] := APoint;
        end;
        ReadPoint := true;
      end;
    end;
  end;
*)
end;

function TAcqData.ReadIndexPoint(var APoint: TIndexPoint): boolean;
var p: TExpPoint;
begin
  if ReadPoint(p) then begin
    APoint.Point := p;
    APoint.Index := CurIndex;
    inc(CurIndex);
    ReadIndexPoint := true;
  end else
    ReadIndexPoint := false;
end;

function TAcqData.GetSize: longint;
begin
  GetSize := {v0.24}ActiveData.Size;{ULAD[ULADIndex].Data.Size;}
end;

function TAcqData.GetPointCount: longint;
begin
  Result := GetSize div sizeof(TExpPoint);
end;

procedure TAcqData.Clear;
begin
  {v0.20}
  Truncate(0);
  {/v0.20
  Reset;
  ULAD.Data.Size := 0;
  ULAF.Data.Size := 0;}
end;

{v0.45 obsolete}
{/v0.45
procedure TAcqData.Error(Code,Info:integer);
begin
  case Code of
    coIndexError:
      ShowMessage(GetTxt('Collection index out of range (TAcqData)')
        , smError, 0);
    coOverflow:
      ShowMessage(GetTxt('Collection overflow. (TAcqData)'),smError, 0);
  end;
end;}

{
procedure TAcqData.SetLimits(MinX:TXValue; MinY:TYValue;
  MaxX:TXValue; MaxY:TYValue);
begin
  SpectrumOpt.Limit.Min.X := MinX;
  SpectrumOpt.Limit.Min.Y := MinY;
  SpectrumOpt.Limit.Max.X := MaxX;
  SpectrumOpt.Limit.Max.Y := MaxY;
end;
}
{
procedure TAcqData.SetUserLimit(AUserLimit: TUserViewLimit);
begin
  SpectrumOpt.UserLimit := AUserLimit;
end;
}
{
procedure TAcqData.SetUserLimit(xorigin, xsize, yorigin, ysize : real);
{the numbers used to describe the data}
{begin
{  DrawOpt.Origin.X := xorigin;
  DrawOpt.Origin.Y := yorigin;
  DrawOpt.Size.X := xsize;
  DrawOpt.Size.Y := ysize;}
{end;}

function TAcqData.GetPoint(Index: TPointCount; var APoint: TExpPoint): boolean;
var
{  r: longint;}
  s: TStream;
begin
  GetPoint := false;
  if UseFiltered then
    s := ULAF.Data
  else
    s := {v0.24}ActiveData{/v0.24 ULAD[ULADIndex].Data};
  if (Index < 0) or (s.Size <= (Index * sizeof(TExpPoint))) then
    exit;
  s.Position := Index * sizeof(TExpPoint);
  s.ReadBuffer(APoint, sizeof(TExpPoint));
  {v0.11}
  if FApplyMath then begin
    APoint.X := APoint.X * FMultiplyX + FAddX;
    APoint.Y := APoint.Y * FMultiplyY + FAddY;
  end;
  {/v0.11}
  GetPoint := true;
  {(IORead(Stream, APoint, sizeof(TExpPoint), r) = irOK);;}
end;

function TAcqData.GetIndexPoint(Index: LongInt; var APoint: TIndexPoint): boolean;
var
  p: TExpPoint;
begin
  if GetPoint(Index, p) then begin
    APoint.Point := p;
    APoint.Index := Index;
    GetIndexPoint := true;
  end else
    GetIndexPoint := false;
end;

procedure TAcqData.PutPoint(Index: Longint; APoint: TExpPoint);
{v0.24}
var
  d: TULADRc;
{/v0.24}
begin
  {v0.24}
  d := ActiveULADRc;
  d.Data.Position := Index * SizeOf(APoint);;
  d.Data.WriteBuffer(APoint, sizeof(APoint));
  {/v0.24
  ULAD.Data.Position := Index * SizeOf(APoint);;
  ULAD.Data.WriteBuffer(APoint, sizeof(APoint));
  }
end;

procedure TAcqData.AddPoint(var APoint: TExpPoint);
var d: TStream;
begin
  FixPoint(APoint);
  CheckPoint(APoint);
  {v0.24}
  d := ActiveData;
  PutPoint(d.Size div sizeof(TExpPoint), APoint);
  {/v0.24
  PutPoint(ULAD.Data.Size div sizeof(TExpPoint), APoint);}

{  if Stream <> NoHIO then begin
    PutPoint((IOGetPropInt(Stream, ipSize) - HeadSizeInFile)
      div sizeof(TExpPoint), APoint);
  end;
}
end;

{v0.24}
procedure TAcqData.ChangeFileName(const AFileName: string);
begin
  ULF.FileName := ChangeFileExt(AFileName, ULFExt);
end;
{/v0.24}

procedure TAcqData.SaveTo(const AFileName: string);
  { Saves current data to AFileName and set it as the current file. }
var
  s: TFileStream;
  e: TULEObj;
  ext: string;
begin
  ext := UpperCase(ExtractFileExt(AFileName));
  {v0.18}
  if (ext = UlfExt) or (ext = AscExt) or (ext = UltExt) {v0.61}
    or (ext = ULCExt) {/v0.61}
  then begin
    if ext = UltExt then begin
      GetReadyForTemplate;
    end;
    {v0.61}
    if ext = ULCExt then begin
      ULA.CalibrationStandard := true;
    end;
    {/v0.61}

    ULF.SaveToFile(AFileName);
  end else begin
    if ExpImp.HasChildWithFieldUsrValue(fnExtName, ext, TULObj(e)) then begin
      s := TFileStream.Create(AFileName, fmCreate);
      try
        ULConvertTo(e, {v0.24} ActiveData{/v0.24 ULAD[ULADIndex].Data}, s);
      finally
        s.Free;
      end;
    end else begin
      raise EInvalidFileExt.Create(GetTxt({#}'Unsupported file extension') + ': ' + AFileName);
    end;
  end;
  {/v0.18
  if ext = DatExt then begin
    s := TFileStream.Create(AFileName, fmCreate);

    if not ULF.FindObj(ULEID, foFromRoot, DatExt, TULObj(e)) then begin
      e := TULEObj(ULA.Add(ULEID));
      FillDatExpImp(e);
    end;
    ULConvertTo(e, ULAD.Data, s);
    s.Free;
  end else if ext = TxtExt then begin
    s := TFileStream.Create(AFileName, fmCreate);
    if not ULF.FindObj(ULEID, foFromRoot, TxtExt, TULObj(e)) then begin
      e := TULEObj(ULA.Add(ULEID));
      FillTxtExpImp(e);
    end;
    ULConvertTo(e, ULAD.Data, s);
    s.Free;
  end else if (ext = UlfExt) or (ext = AscExt) or (ext = UltExt) then begin
    if ext = UltExt then begin
      GetReadyForTemplate;
    end;
    ULF.SaveToFile(AFileName);
  end else begin
    raise EInvalidFileExt.Create('Invalid file extension: ' + AFileName);
  end;   }
end;

{v0.15}
procedure TAcqData.GetReadyForTemplate;
begin
  ULP.Clear;
  ULB.Clear;
  {v0.24}
  ULAD.SetZeroSizes;
  {/v0.24
  ULAD.Data.Size := 0;}
  ULI.SetFlag(rfWriteLocked, false);
end;
{/v0.15}

procedure TAcqData.GrowShowLimits(dX:TScreenX; dY:TScreenY);
begin
  SpectrumOpt.ViewLimit.Max.X := SpectrumOpt.ViewLimit.Max.X + dx;
  if SpectrumOpt.ViewLimit.Max.X > SpectrumOpt.Limit.Max.X then
    SpectrumOpt.ViewLimit.Max.X := SpectrumOpt.Limit.Max.X
  else if SpectrumOpt.ViewLimit.Min.X < SpectrumOpt.Limit.Min.X then
    SpectrumOpt.ViewLimit.Min.X := SpectrumOpt.Limit.Min.X;

  SpectrumOpt.ViewLimit.Max.Y :=  SpectrumOpt.ViewLimit.Max.Y + dy;
  if SpectrumOpt.ViewLimit.Max.Y > SpectrumOpt.Limit.Max.Y then
    SpectrumOpt.ViewLimit.Max.Y := SpectrumOpt.Limit.Max.Y
  else if SpectrumOpt.ViewLimit.Max.Y < SpectrumOpt.Limit.Min.Y then
    SpectrumOpt.ViewLimit.Max.Y := SpectrumOpt.Limit.Min.Y;
end;

procedure TAcqData.MoveShowLimits(dx,dy:integer);
var
  curdx:TXValue;
  curdy:TYValue;
begin
  curdx := SpectrumOpt.ViewLimit.Max.X - SpectrumOpt.ViewLimit.Min.X;
  curdy := SpectrumOpt.ViewLimit.Max.Y - SpectrumOpt.ViewLimit.Min.Y;
  if dx > 0 then begin
    SpectrumOpt.ViewLimit.Max.X := SpectrumOpt.ViewLimit.Max.X + dx;
    if SpectrumOpt.ViewLimit.Max.X > SpectrumOpt.Limit.Max.X then
      SpectrumOpt.ViewLimit.Max.X := SpectrumOpt.Limit.Max.X;
    SpectrumOpt.ViewLimit.Min.X := SpectrumOpt.ViewLimit.Max.X - curdx;
  end else if dx < 0 then begin
    SpectrumOpt.ViewLimit.Min.X := SpectrumOpt.ViewLimit.Min.X + dx;
    if SpectrumOpt.ViewLimit.Min.X < SpectrumOpt.Limit.Min.X then
      SpectrumOpt.ViewLimit.Min.X := SpectrumOpt.Limit.Min.X;
    SpectrumOpt.ViewLimit.Max.X := SpectrumOpt.ViewLimit.Min.X + curdx;
  end;

  if dy > 0 then begin
    SpectrumOpt.ViewLimit.Max.Y := SpectrumOpt.ViewLimit.Max.Y + dy;
    if SpectrumOpt.ViewLimit.Max.Y > SpectrumOpt.Limit.Max.Y then
      SpectrumOpt.ViewLimit.Max.Y := SpectrumOpt.Limit.Max.Y;
    SpectrumOpt.ViewLimit.Min.Y := SpectrumOpt.ViewLimit.Max.Y - curdy;
  end else if dy < 0 then begin
    SpectrumOpt.ViewLimit.Min.Y := SpectrumOpt.ViewLimit.Max.Y + dy;
    if SpectrumOpt.ViewLimit.Min.Y < SpectrumOpt.Limit.Min.Y then
      SpectrumOpt.ViewLimit.Min.Y := SpectrumOpt.Limit.Min.Y;
    SpectrumOpt.ViewLimit.Max.Y := SpectrumOpt.ViewLimit.Min.Y + curdy;
  end;
end;

function TAcqData.Current: TPointCount;
begin
  if UseFiltered then
    Current := ULAF.Data.Position div sizeof(TExpPoint)
  else
    Current := ActiveULADRc.Data.Position div sizeof(TExpPoint);
end;

{procedure TAcqData.SetScreenDisp(const AScreenDisp:TScreenDisp);
begin
  ScreenDisp := AScreenDisp;
end;}
{v0.61}
procedure TAcqData.GetViewLimit(var AViewLimit: TExpViewLimit);
begin
  AViewLimit := SpectrumOpt.ViewLimit;
end;
{/v0.61}

procedure TAcqData.SetViewLimit(const AViewLimit:TExpViewLimit);
{v0.13}
var uvl:TUserViewLimit;
{/v0.13}
begin
  SpectrumOpt.ViewLimit := AViewLimit;
  {v0.15}
{  if abs(SpectrumOpt.ViewLimit.Max.X) > (MaxSingle / 100) then begin
    SpectrumOpt.ViewLimit.Max.X := 20*60;
    SpectrumOpt.ViewLimit.Min.X := 0;
  end;
  if abs(SpectrumOpt.ViewLimit.Max.Y) > (MaxSingle / 100) then begin
    SpectrumOpt.ViewLimit.Max.Y := DefMaxAcqYValue;
    SpectrumOpt.ViewLimit.Min.Y := DefMinUsrYValue;;
  end;}
  {/v0.15}
  SpectrumOpt.ViewLimit.Size.X := SpectrumOpt.ViewLimit.Max.X - SpectrumOpt.ViewLimit.Min.X;
  SpectrumOpt.ViewLimit.Size.Y := SpectrumOpt.ViewLimit.Max.Y - SpectrumOpt.ViewLimit.Min.Y;
  if (SpectrumOpt.ViewLimit.Size.X = 0) or (SpectrumOpt.ViewLimit.Size.Y = 0) then begin
    ShowMessage(GetTxt({#}'Zero spectrum ViewLimit size'), smError, 0);
  end;
  {v0.13}
  with SpectrumOpt do begin
    ExpToUser(ViewLimit.Min, uvl.Min);
    ExptoUser(ViewLimit.Max, uvl.Max);
    ULVL.MinX := uvl.Min.X;
    ULVL.MinY := uvl.Min.Y;
    ULVL.MaxX := uvl.Max.X;
    ULVL.MaxY := uvl.Max.Y;
  end;
  {/v0.13}
end;

procedure TAcqData.SetLimit(const ALimit:TExpViewLimit);
begin
  SpectrumOpt.Limit := ALimit;{just for now }
  SpectrumOpt.Limit.Size.X := SpectrumOpt.Limit.Max.X - SpectrumOpt.Limit.Min.X;
  SpectrumOpt.Limit.Size.Y := SpectrumOpt.Limit.Max.Y - SpectrumOpt.Limit.Min.Y;
  if (SpectrumOpt.Limit.Size.X = 0) or (SpectrumOpt.Limit.Size.Y = 0) then begin
    ShowMessage(GetTxt({#}'Zero spectrum limit size'), smError, 0);
  end;
end;

function TAcqData.ExpToScreen(const AExpPoint:TExpPoint; const AScreenDisp: TScreenDisp;
  var AScreenPoint:TScreenPoint): integer;
{ prevod exp. hodnoty na grafickou, za pomoci graphic limit
  hodnot, returns <> 0 if AScreenPoint is out of the AScreenDisp}
{var
  x:TXValue;
  y:TYValue;
  r:single; }
begin
  Result := 0;
  if AExpPoint.X > SpectrumOpt.ViewLimit.Max.X then begin
    Result := Result or opRight;
    if AExpPoint.Y > SpectrumOpt.ViewLimit.Max.Y then
      Result := Result or opAbove
    else if AExpPoint.Y < SpectrumOpt.ViewLimit.Min.Y then
      Result := Result or opBelow;
    {exit;}
  end else if AExpPoint.X < SpectrumOpt.ViewLimit.Min.X then begin
    Result := Result or opLeft;
    if AExpPoint.Y > SpectrumOpt.ViewLimit.Max.Y then
      Result := Result or opAbove
    else if AExpPoint.Y < SpectrumOpt.ViewLimit.Min.Y then
      Result := Result or opBelow;
    {exit;}
  end;

  if AExpPoint.Y > SpectrumOpt.ViewLimit.Max.Y then begin
    {if ScreenDisp.ZeroIsOnTop then }
    begin
      AScreenPoint.Y := AScreenDisp.Top;
    end
    { else begin
      AScreenPoint.Y := ScreenDisp.Top - ScreenDisp.Height + 1;
    end};
  end else if AExpPoint.Y < SpectrumOpt.ViewLimit.Min.Y then begin
     {if ScreenDisp.ZeroIsOnTop then}
     begin
       AScreenPoint.Y := AScreenDisp.Bottom;{AScreenDisp.Top + AScreenDisp.Height - 1;}
     end{ else begin
       AScreenPoint.Y := ScreenDisp.Top - ScreenDisp.Height + 1;
     end};
  end else begin
    {if ScreenDisp.ZeroIsOnTop then }
    begin
      AScreenPoint.Y := AScreenDisp.Bottom + RectInclusive{AScreenDisp.Top + AScreenDisp.Height}
      - round(
        (AScreenDisp.Bottom - AScreenDisp.Top + RectInclusive){AScreenDisp.Height} *
        (AExpPoint.Y - SpectrumOpt.ViewLimit.Min.Y) /
        (SpectrumOpt.ViewLimit.Size.Y)
       );
    end{ else begin
      AScreenPoint.Y := ScreenDisp.Top - ScreenDisp.Height + round(
        ScreenDisp.Height *
        (AExpPoint.Y - SpectrumOpt.ViewLimit.Min.Y) /
        (SpectrumOpt.ViewLimit.Size.Y)
      );

    end};
  end;
  AScreenPoint.X := AScreenDisp.Left + round(
    (AScreenDisp.Right - AScreenDisp.Left + RectInclusive){AScreenDisp.Width} *
    (AExpPoint.X - SpectrumOpt.ViewLimit.Min.X) / SpectrumOpt.ViewLimit.Size.X
  );
end;

function TAcqData.ExpSizeToScreenSize(const AExpPoint:TExpPoint; const AScreenDisp: TScreenDisp;
  var AScreenPoint:TScreenPoint): integer;
  { conversion of distance in experimental units to distance in screen points }
{var
  x:TXValue;
  y:TYValue;
  r:single;}
begin
  Result := 0;
  AScreenPoint.Y := round((AScreenDisp.Bottom - AScreenDisp.Top + RectInclusive){AScreenDisp.Height} *
    AExpPoint.Y / SpectrumOpt.ViewLimit.Size.Y);
  AScreenPoint.X := round((AScreenDisp.Right - AScreenDisp.Left + RectInclusive){AScreenDisp.Width} *
    AExpPoint.X / SpectrumOpt.ViewLimit.Size.X);
end;

function TAcqData.ScreenToExp(const ScreenPoint: TScreenPoint; const AScreenDisp: TScreenDisp;
  var AExpPoint: TExpPoint): boolean;
var
  r:real;
begin
  ScreenToExp := false;
{  if AScreenDisp.Width = 0 then
    exit;
  if AScreenDisp.Height = 0 then
    exit;
}
  if AScreenDisp.Left = AScreenDisp.Right then
    exit;
  if AScreenDisp.Top = AScreenDisp.Bottom then
    exit;
  r := ScreenPoint.X - AScreenDisp.Left;
  r := r *(SpectrumOpt.ViewLimit.Size.X) / (AScreenDisp.Right - AScreenDisp.Left + RectInclusive);{AAScreenDisp.Width;}
  AExpPoint.X := SpectrumOpt.ViewLimit.Min.X + r;
  AExpPoint.Y := SpectrumOpt.ViewLimit.Min.Y +
    (AScreenDisp.Bottom + RectInclusive{AScreenDisp.Top + AScreenDisp.Height} - ScreenPoint.Y) *
     SpectrumOpt.ViewLimit.Size.Y / (AScreenDisp.Bottom - AScreenDisp.Top + RectInclusive){AScreenDisp.Height};

  ScreenToExp := true;
end;

function TAcqData.FindDataPoint(const AExpPoint:TExpPoint;
  var ADataPoint:TExpPoint; var Index:TPointCount):boolean;
var
  l, h, i, c:longint;
  p:TExpPoint;
{  found:boolean;}
begin
{ FindDataPoint := false;}
  Index := -1;
  l := 0;
  h := GetPointCount{NumPoints} - 1;
  while l <= h do begin
    i := (l + h) shr 1;
    GetPoint(i, p);
    if p.x < AExpPoint.X then
      c := -1
    else if p.x > AExpPoint.X then
      c := 1
    else
      c := 0;
    if c < 0 then
      L := I + 1
    else begin
      h := i - 1;
      if c = 0 then begin
         {found := True;}
{      if not Duplicates then L := I;}
         l := i;
      end;
    end;
  end;
  Index := l;
  ADataPoint := p;
  FindDataPoint := true;
end;

function TAcqData.UserToExp(const AUserPoint: TUserPoint;
  var AExpPoint: TExpPoint): boolean;
begin
  AExpPoint.X := AUserPoint.X * 60;
  AExpPoint.Y := AUserPoint.Y;
  UserToExp := true;
end;

function TAcqData.ExpToUser(const AExpPoint:TExpPoint;
      var AUserPoint:TUserPoint):boolean;
begin
  AUserPoint.X := AExpPoint.X / 60;
  AUserPoint.Y := AExpPoint.Y;
  ExpToUser := true;
end;

function TAcqData.UserToScreen(const AUserPoint: TUserPoint;
  const AScreenDisp: TScreenDisp; var AScreenPoint: TScreenPoint): boolean;
var ap:TExpPoint;
begin
  UserToScreen := false;
  if not UserToExp(AUserPoint, ap) then
    exit;
  if ExpToScreen(ap, AScreenDisp, AScreenPoint) <> 0 then
    exit;
  UserToScreen := true;
end;

function TAcqData.UserSizeToScreenSize(const AUserPoint:TUserPoint;
  const AScreenDisp: TScreenDisp; var AScreenPoint:TScreenPoint):boolean;
var ap:TExpPoint;
begin
  Result := false;
  if not UserToExp(AUserPoint, ap) then
    exit;
  if ExpSizeToScreenSize(ap, AScreenDisp, AScreenPoint) <> 0 then
    exit;
  Result := true;
end;

procedure TAcqData.FilterUpdate;
begin
end;

destructor TAcqData.Destroy;
var
  i: integer;
  d: TULADRc;
begin
  {v0.31}
  if FChannel <> nil then begin
    if FChannel <> nil then
      FChannel.ULN.SetFlag(rfCantDelete, false);
  end;
  {/v0.31}
  try
    if (ULF <> nil) and (ULF.FileName <> '') {v0.09} and ULF.Modified {/v0.09}
      {v0.61} and (not ULF.ReadOnly) {/v0.61}
    then begin
      {v0.45}
      if FCreatedOK then
      {/v0.45}
        ULF.SaveToFile('');
      {v0.24}
      for i := 0 to ULAD.Count - 1 do begin
        if RawFileName[i] <> '' then begin
          d := ULAD[i];
          if d.Data is TFileStream then begin
            d.ULAD.Data := nil;
            DeleteFile(RawFileName[i]);
          end;
        end;
      end;
      {/v0.24
      if RawFileName <> '' then begin
        ULAD[ULADIndex].Data := nil;
        DeleteFile(RawFileName);
      end;}
    end;
  finally
    {v0.24}
    ULAD.Free;
    {/v0.24}
    {v0.31}
    ULI := nil;
    {/v0.31}
    ULF.Free;
    {isOn := false;}
  end;

  {v0.14}
  if (Spectrum <> nil) then begin
    if Spectrum.AcqData <> Self then begin
      ShowMessage(GetTxt({#}'Invalid Spectrum.AcqData'), smError, 0);
    end else begin
      Spectrum.AcqData := nil;
    end;
  end;
  {/v0.14}
  inherited Destroy;
end;

{v0.05}
{
procedure TAcqData.SetFilterPars(const AFilterPars:TFilterPars);
begin
  FFilterPars := AFilterPars;
end;

procedure TAcqData.GetFilterPars(var AFilterPars:TFilterPars);
begin
  AFilterPars := FFilterPars;
end;
}

{v0.20}{/v0.20
procedure TAcqData.BufPointsClear;
begin
  BufPointCount := 0;
  BufPointPos := 0;
end;
}
{/v0.05}

{v0.11}
procedure TAcqData.MethodLoadFromFile(const AFileName: string);
var
  f: TULFObj;
  m: TULMObj;
begin
  f := TULFObj.Create(nil);
  try
    f.LoadFromFile(AFileName);
    if not f.FindObj(ULMID, foNotSelf{v0.13}{/v0.13 + foNotRecursive}, '', TULObj(m)) then begin
      ShowMessage(GetTxt({#}'No method find in file') + ' ' + AFileName, smError, 0);
      exit;
    end;
    ULM.Clear;
    ULM.Assign(m);
    ULM.MethodTemplate := AFileName;
    CheckULMRecords;
  finally
    f.Free;
  end;
end;

procedure TAcqData.MethodSaveToFile(const AFileName: string);
begin
  ULM.SaveToFile(AFileName);
  ULM.MethodFileName := AFileName;
end;

{v0.30}
procedure TAcqData.SpectrumGet(var sp: TSpectrum);
begin
  if Spectrum = nil then begin
    sp := TSpectrum.Create(Self);
  end else begin
    sp := Spectrum;
  end;
end;

procedure TAcqData.SpectrumRelease(sp: TSpectrum);
begin
  if sp <> Spectrum then
    sp.Free;
end;
{/v0.30}

procedure TAcqData.CalibrationFileSelect(const AFileName: string);
{v0.30}
var sp: TSpectrum;
{/v0.30}
begin
  {v0.38}{ulatype}
  if ULA.CalibrationStandard then
    exit;
  if ULF.FileName = AFileName then
    exit;
  ULM.UseCalibrationFile := true;
  {/v0.38}

  ULM.CalibrationFileName := AFileName;
  Cal_ULP.Free;
  Cal_ULP := nil;
  {v0.30}
  SpectrumGet(sp);
  try
    sp.PeaksAmountsCalculateCalibrationFile;
    {v0.33}
    sp.PeaksAmountsCalculateValues;{ulantype}
    {/v0.33}
  finally
    SpectrumRelease(sp);
  end;
  {/v0.30}
end;

procedure TAcqData.AcquiredDataDiscard;
begin
  {v0.24}
  ULAD.SetZeroSizes;
  {/v0.24
  ULAD[ULADIndex].Data.Size := 0;}
  ULAF.Data.Size := 0;
  ULP.Clear;
  ULB.Clear;
end;

procedure TAcqData.DoMath(mo:TDataMathOperation; r: single);
  { set value of mo corresponding variable and activate FApplyMath field
   (or desactivate if mo = moNone) }
begin
  case mo of
    moNoneOp: begin
      FMultiplyX := 1;
      FMultiplyY := 1;
      FAddX := 0;
      FAddY := 0;
      FApplyMath := false;
      {v0.21}
      FDiluteCount := 1;
      if Spectrum <> nil then
        Spectrum.RecalculatePeaks;
      {/v0.21}
      exit;
    end;
    moMultiplyX: FMultiplyX := r;
    moMultiplyY: FMultiplyY := r;
    moAddX: FAddX := r;
    moAddY: FAddY := r;
    {v0.21}
    moDilute: begin
      FDiluteCount := round(r);
      if FDiluteCount <= 0 then
        FDiluteCount := 1;
    end;
    {/v0.21}
  else
    exit;
  end;
  FApplyMath := true;
  {v0.21}
  if Spectrum <> nil then
    Spectrum.RecalculatePeaks;
  {/v0.21}
end;

procedure TAcqData.MakeMathPermanent;
var
  index: integer;
  p:TExpPoint;
begin
  index := 0;
  while GetPoint(index, p) do begin
    PutPoint(index, p);
    inc(index);
  end;
  DoMath(moNoneOp, 0);
end;
{/v0.11}

{v0.14}
procedure TAcqData.InstrumentLoadFromFile(const AFileName: string);
var
  f: TULFObj;
  m: TULIObj;
begin
  f := TULFObj.Create(nil);
  try
    f.LoadFromFile(AFileName);
    if not f.FindObj(ULIID, foNotSelf{v0.13}{/v0.13 + foNotRecursive}, '', TULObj(m)) then begin
      ShowMessage(GetTxt({#}'No Instrument found in file') + ' ' + AFileName, smError, 0);
      exit;
    end;
    ULI.Clear;
    ULI.Assign(m);
    ULI.InstrumentTemplate := AFileName;
    {CheckULIRecords;}
  finally
    f.Free;
  end;
end;

procedure TAcqData.InstrumentSaveToFile(const AFileName: string);
begin
  ULI.SaveToFile(AFileName);
  ULI.InstrumentFileName := AFileName;
end;
{/v0.14}

{v0.15}
procedure TAcqData.Truncate(AfterX: TXValue);
 { remove all acqisition data (together with peaks, baselines)
   after specified X value }
var
  i: integer;
  p: TULPRObj;
  b: TULBRObj;
  APoint: TExpPoint;
  osize:integer;
  {v0.24}
  di: integer;
  urc: TULADRc;
  {/v0.24}
begin
  i := 0;
  while i < ULP.ChildCount do begin
    p := TULPRObj(ULP.Childs[i]);
    if p.X1 > AfterX then begin
      p.Free;
    end else begin
      inc(i);
    end;
  end;
  i := 0;
  while i < ULB.ChildCount do begin
    b := TULBRObj(ULB.Childs[i]);
    if b.X1 >= AfterX then
      b.Free
    else
      inc(i);
  end;

  for di := 0 to DataCount - 1 do begin
    DataIndex := di;
    urc := ActiveULADrc;
    osize := urc.Data.Size;
    urc.Data.Position := 0;
    if AfterX > 0 then
    while
      (urc.Data.Read(APoint, sizeof(APoint)) = sizeof(APoint)) and
      (APoint.X <= AfterX)
    do;
    urc.Data.Size := urc.Data.Position;
    urc.CurDataSize := urc.Data.Size;

    ULAF.Data.Size := urc.CurDataSize;

    if osize <> urc.Data.Size then
      ULF.Modified := true;
  end;
  Reset;

  {/v0.24
    osize := ULAD[ULADIndex].Data.Size;

    ULAD[ULADIndex].Data.Position := 0;
    if AfterX > 0 then
    while
      (ULAD[ULADIndex].Data.Read(APoint, sizeof(APoint)) = sizeof(APoint)) and
      (APoint.X <= AfterX)
    do;
    ULAD[ULADIndex].Data.Size := ULAD[ULADIndex].Data.Position;

    FCurDataSize := ULAD[ULADIndex].Data.Size;
    ULAF.Data.Size := FCurDataSize;
    if osize <> ULAD[ULADIndex].Data.Size then
      ULF.Modified := true;
  }
end;
{/v0.15}

{v0.16}
function TAcqData.GetIsTemplate: boolean;
begin
  Result := {v0.61}UpperCase{/v0.61}(ExtractFileExt(ULF.FileName)) = ULTExt;
end;
{/v0.16}

{v0.61}
function TAcqData.GetIsCalFile: boolean;
begin
  Result := UpperCase(ExtractFileExt(ULF.FileName)) = ULCExt;
end;

{/v0.61}

{v0.24}
function TAcqData.DataSize: integer;
begin
  {0.51pi}
  if ActiveData=nil then
    Result:=0
  else
    {/0.51pi}
    Result := ActiveData.Size;{ULAD[ULADIndex].Data.Size;}
end;
{/v0.24}

{v0.57}
function TAcqData.GetUsrActiveULADRc: TULADRc;
begin
  Result := ULAD[UsrDataIndex];
end;

procedure TAcqData.SetUsrActiveULADRc(AULADRc: TULADRc);
var
  i: integer;
begin
  for i := 0 to DataCount - 1 do begin
    if ULAD[i] = AULADRc then begin
      UsrDataIndex := i;
      DataIndex := i;
      break;
    end;
  end;
end;
{/v0.57}

{v0.25}
function TAcqData.GetActiveDataName: string;
begin
  Result := ActiveULADRc.ULAD.DataName;
end;
function TAcqData.GetUsrActiveDataName: string;
begin
  Result := ULAD[UsrDataIndex].ULAD.DataName;
end;

procedure TAcqData.SetActiveDataName(const ADataName: string);
var i: integer;
begin
  {v0.26}
  if ULAD[ULADIndex].ULAD.DataName <> ADataName then
  for i := 0 to ULAD.Count - 1 do begin
    if ULAD[i].ULAD.DataName = ADataName then begin
      DataIndex := i;
      break;
    end;
  end;
end;

procedure TAcqData.SetUsrActiveDataName(const ADataName: string);
var old,i: integer;
begin
  for i := 0 to ULAD.Count - 1 do begin
    if ULAD[i].ULAD.DataNAme = ADataName then begin
      old := UsrDataIndex;
      UsrDataIndex := i;
      DebLog('UsrDataIndex: old=' + IntToStr(old) + ' new=' + IntToStr(i));
      DataIndex := i;
      break;
    end;
  end;
end;

procedure TAcqData.UsrDataIndexReselect;
begin
  DataIndex := UsrDataIndex;
end;

{/v0.25}

{v0.30}
function TAcqData.CanClose: boolean;
begin
  Result := true;
  {$IFNDEF CONSOLE}
  if (ULF <> nil) then begin
    if (pos('NONAME', ULF.FileName) <> 0) then
    begin
      if DataSize > 0 then begin
        if not FMH.SaveAs then begin
          if ShowMessage(GetTxt({#}'Discard data?'), smNoYes, 0) <> cmYes then begin
            Result := false;
          end else begin
            AcquiredDataDiscard;
          end;
        end;
      end {v0.50} else begin
        if ULF.Modified then begin
          if not FMH.SaveAs then begin
            if ShowMessage(GetTxt({#}'Discard changes?'), smNoYes, 0) <> cmYes then begin
              Result := false;
            end else begin
              ULF.Modified := false;
            end;
          end;
        end;
      end;
      {/v0.50};
    end else begin
      if ULF.Modified then begin
        if ULF.ReadOnly then begin
          case ShowMessage(ULF.FileName + ' ' + GetTxt({#}'was opened Read Only but modified.') +
            ' ' + GetTxt({#}'Save to other file?'), smYesNoCancel, 0) of
            cmCancel: CanClose := false;
            cmYes: begin
              {FSavingReadOnlyAs := ULF.FileName;}
              try
                if not FMH.SaveAs then begin
                  CanClose := false;
                end else begin
                  ULF.Modified := false;
                end;
              finally
                {FSavingReadOnlyAs := '';}
              end;
            end;
            cmNo: ULF.Modified := false;
          end;
        end else begin
          case ShowMessage(ULF.FileName, smFileModifiedSave,0) of
            cmCancel: CanClose := false;
            cmNo: ULF.Modified := false;
          end;
        end;
      end;
    end
  end;
  {$ENDIF}
end;
{/v0.30}

{v0.31}
function TAcqData.GetAddrULADIndex(AAddr: integer): integer;
  { returns ULADIndex of ULADRc, that has given AAddr; if
    not such found - returns 0 (default index) }
var
  i: integer;
begin
  Result := 0;
  for i := 0 to ULAD.Count - 1 do begin
    if ULAD[i].Addr = AAddr then begin
      Result := i;
      exit;
    end;
  end;
end;

procedure TAcqData.SetULI(AULI:TULIObj);
begin
  {v0.50}
  FSettingULI := true;
  try
  {/v0.50}
    if AULI <> FULI then begin
      if FULI <> nil then
        FULI.UserUnregister(Self);
      FULI := AULI;
      if FULI <> nil then begin
        FULI.UserRegister(Self);
        {0.50}
        {$IFNDEF CONSOLE}{moved from create}
        FULI.FindField(fnChannelName).FldDesc.ValuesSource := Channels.ULL;
        if FULI.ChannelName = '' then
          FULI.ChannelName := Channels.ULL.ChannelName;
        {set to default channel name}
        {$ENDIF}

        ULI_UDLS := TUDLSObj(FULI.FindOrAdd(UDLSID, ''));
        FULI_USP.Free;
        FULI_USP := nil;
        {/v0.50}
      end;
    end;
  {v0.50}
  finally
    FSettingULI := false;
  end;
  {/v0.50}
end;

{v0.50}
function TAcqData.GetULI_USP: TUSPObj;{ sequence program }
begin
  if (FULI_USP = nil) and (FULI <> nil) then
    FULI_USP := TUSPObj(FULI.FindOrAdd(USPID, ''));
  Result := FULI_USP;
end;

function TAcqData.GetPrg: TSeqPrg;{ulobjusru}
begin
  if FPrg = nil then begin
    FPrg := TSeqPrg.Create(nil, nil, USPID);
  end;
  if FPrg.Obj <> ULI_USP then begin
    FPrg.Obj := ULI_USP;
    FPrg.ClassFieldsUpdate;
  end;
  Result := FPrg;
end;
{/v0.50}

function TAcqData.GetDetAddrList: string;
var i: integer;
begin
  Result := '';
  for i := 0 to ULAD.Count - 1 do begin
    {v0.46}
    if ULAD[i].Addr <> 0 then
    {/v0.46}
    begin
      if Result = '' then begin
        Result := IntToStr(ULAD[i].Addr)
      end else begin
        Result := Result + ',' + IntToStr(ULAD[i].Addr);
      end;
    end;
  end;
  if Result = '' then begin
    {v0.46}
    if (CurUVDetAddr <> 0) then
    {/v0.46}
    Result := IntToStr(CurUVDetAddr);
  end;
end;

{/v0.31}

{v0.41}
{$IFNDEF CONSOLE}
function TAcqData.GetChannel: TChannel;
var c: TChannel;
begin
  {v0.50}
  c := Channels.FindChannel(ULI.ChannelName);
  if c <> FChannel then
    CheckChannel;
  {/v0.50
  CheckChannel;}
  Result := FChannel; {channelsu}
end;
{$ENDIF}
{/v0.41}

{v0.50}
function TAcqData.DataNamesEqual(ADataName1: string; ADataName2: string): boolean;
begin
  if (ADataName1 = '') then ADataName1 := ULM.DefDataName;
  if (ADataName2 = '') then ADataName2 := ULM.DefDataName;
  if (ADataName1 = '*') or (ADataName1 = '*') then begin
    Result := true;
  end else begin
    Result := ADataName1 = ADataName2;
  end;
end;

procedure TAcqData.FormRegister(AForm: TObject);
begin
  ULA.UserRegister(AForm);
  ULP.UserRegister(AForm);
  ULB.UserRegister(AForm);
end;

procedure TAcqData.FormUnregister(AForm: TObject);
begin
  ULA.UserUnregister(AForm);
  ULP.UserUnregister(AForm);
  ULB.UserUnregister(AForm);
end;

{v0.55}
function TAcqData.CheckCalibrationFilePresent:boolean;
begin
   Result := FileExists(ULM.CalibrationFileName);
end;
{/v0.55}

{v0.61}
procedure TAcqData.FitPeaksInView;
var
  vl: TExpViewLimit;
  ulpr: TULPRObj;
  i: integer;
begin
  GetViewLimit(vl);
  for i := 0 to ULP.ChildCount - 1 do begin
    if ULP.Childs[i].RecID = ULPRID then begin
      ulpr := TULPRObj(ULP.Childs[i]);
      if ulpr.X2 > vl.Max.X then
        vl.Max.X := ulpr.X2;
    end;
  end;
  SetViewLimit(vl);
end;
{/v0.61}

{/TAcqData.}

{v0.50}
{$IFNDEF CONSOLE}
procedure TObjHandler.CheckIn(AObj: TULObj);
var
  o: TULObj;
  i: integer;

begin
  if (AObj is TULADObj) then begin
    AObj.ObjDesc.OnValuesSourceNeeded := ValuesSourceNeeded;
  end else if (AObj is TUDLRObj) then begin
    AObj.ObjDesc.OnValuesSourceNeeded := ValuesSourceNeeded;
  end else if (AObj is TULAObj) or (AObj is TUDLSObj) then begin
    AObj.ObjDesc.OnChildCreated := ChildCreated;
    for i := 0 to AObj.ChildCount - 1 do begin
      o := AObj.Childs[i];
      if (o.RecID = ULADID) or (o.RecID = UDLRID) then
        CheckIn(o);
      if o.RecID = ULIID then
        CheckIn(o.FindOrAdd(UDLSID, ''));
    end;
  end;
end;

procedure TObjHandler.ChildCreated(Sender: TObject);
begin
  if (Sender is TULADObj) or (Sender is TUDLRObj) then
    CheckIn(TULObj(Sender))
end;

procedure TObjHandler.ValuesSourceNeeded(Sender: TObject);
var
  obj, o: TULObj;
  devicef, valuef: TULObjField;
begin
  if (Sender is TULADObj) or (Sender is TUDLRObj) then begin
    obj := TULObj(Sender);
    valuef := obj.Fields[obj.MessageInfo];
    if valuef.FldDesc.Name = 'PropDesc' then begin
      devicef := Obj.FindField('DeviceName');
      if Modules.ULD.HasChildWithFieldUsrValue('DeviceName', devicef.AsUsrString, o) then
        valuef.FldDesc.ValuesSource := o;
    end;
  end;
end;

const
  FObjHandler: TObjHandler = nil;

function ObjHandler: TObjHandler;
begin
  if FObjHandler = nil then
    FObjHandler := TObjHandler.Create;
  Result := FObjHandler;
end;
{$ENDIF}
{/v0.50}

{v0.61}
{ Initialize data object for non-acquisition purposes }
function CreateOrOpenDataFile(AFileName: string; var AAcqData: TAcqData): boolean;
var
  d: TAcqData;
  ul: TUserViewLimit;
  fn: string;
begin
  Result := false;
  AAcqData := nil;
  if (AFileName = '') or (not FileExists(AFileName)) then begin
    fn := AddBackSlash(TemplateDir) + pvUlanDefaultTemplate;{ulantype}
    if not FileExists(fn) then
      fn := AddBackSlash(TemplateDir) + pvPasiveDefaultTemplate;
  end else begin
    fn := AFileName;
  end;
  d := TAcqData.Create(fn, omRead);
  try
    if d.ULI.Duration <> 0 then begin
      ul := NewUserViewLimit;
      d.ULVL.MinX := ul.Min.X;
      d.ULVL.MinY := ul.Min.Y;
      d.ULVL.MaxX := d.ULI.Duration / 60; //min
      d.ULVL.MaxY := ul.Max.Y;
    end;
    if AFileName <> '' then begin
      d.SaveTo(AFileName);
    end;
  except
    d.Free;
  end;
  AAcqData := d;
  Result := true;
end;
{/v0.61}

end.
