unit dlretrieve;

{ Vector plot:
    Chart X and Y size is -1 to +1
    Altitude data is -90 to +90 degrees

    Z is -1 to +1 as defined by mapping routine.

Range (.max , .min):
 - RangeFromFile determined from data file and used by Min/max button
 - RangeFromUser set by user in the min max fields
 - RangeFromScheme - pulled in from the colourt scheme
}


{$mode objfpc}

interface

uses
  Classes, SysUtils, FileUtil, SynMemo, TATools, TASources, TAGraph,
  TAFuncSeries, TASeries, LResources, Forms, Controls, Graphics, Dialogs,
  StdCtrls, Buttons, LCLType, ExtCtrls, ComCtrls, EditBtn
 , UMathInterpolation
 , StrUtils
 , plotter //Plotter view
 , TAChartUtils //for TDoubleRect
 , TADrawerSVG // for saving SVG of chart
 , TADrawUtils, TACustomSeries, TATypes, TAStyles;

type

  { TDLRetrieveForm }

  TDLRetrieveForm = class(TForm)
    Block: TButton;
    ManualEntryGroup: TGroupBox;
    LegendMaxEntry: TEdit;
    LegendMinEntry: TEdit;
    MaxValueLabel: TLabel;
    MinValueLabel: TLabel;
    LogsDirectoryButton: TBitBtn;
    LogsDirectoryEdit: TEdit;
    RangeSchemeRadio: TRadioButton;
    RangeDatasetRadio: TRadioButton;
    RangeManualRadio: TRadioButton;
    ResetToLogsDirectoryButton: TBitBtn;
    ShowGridCheckBox: TCheckBox;
    ColourSchemeComboBox: TComboBox;
    DataSetSelect: TRadioGroup;
    ColourSchemeGroup: TGroupBox;
    PlotterButton: TButton;
    CalculatingProgressBar: TProgressBar;
    CalculatingText: TStaticText;
    UpdateButton: TButton;
    VectorChart: TChart;
    VectorChartColorMapSeries: TColorMapSeries;
    VectorChartLineSeries1: TLineSeries;
    VectorChartLineSeries2: TLineSeries;
    LegendChart: TChart;
    LegendChartColorMapSeries: TColorMapSeries;
    LegendChartLineSeries: TLineSeries;
    CursorValue: TStaticText;
    CursorValueGroup: TGroupBox;
    DecorationsGroup: TGroupBox;
    DLRetConvRawButton: TButton;
    DLRetrieveRawButton: TButton;
    ExportButton: TButton;
    HourGlassTimer: TIdleTimer;
    FileDirectoryLabel: TLabel;
    LeftSideLabel: TLabel;
    LogsDirStatusLabel: TLabel;
    MarkPointsCheckBox: TCheckBox;
    MaxRecordsLabel: TLabel;
    NorthLabel: TLabel;
    OrientationSelect: TRadioGroup;
    PageControl1: TPageControl;
    Panel1: TPanel;
    Panel2: TPanel;
    Panel3: TPanel;
    PlotFileTitle: TStaticText;
    PlotSettingsGroup: TGroupBox;
    RangeGroup: TGroupBox;
    RightSideLabel: TLabel;
    ScrollBox1: TScrollBox;
    ShowDotsCheckBox: TCheckBox;
    ShowLinesCheckBox: TCheckBox;
    ShowPlotDataButton: TButton;
    SouthLabel: TLabel;
    SynMemo2: TSynMemo;
    TabSheet1: TTabSheet;
    TabSheet2: TTabSheet;
    ToLabel: TLabel;
    RangeEnd: TEdit;
    RangeStart: TEdit;
    RetRangeButton: TButton;
    ChartToolset1: TChartToolset;
    ChartToolset1PanDragTool1: TPanDragTool;
    ChartToolset1ZoomDragTool1: TZoomDragTool;
    DLCancelRetrieveButton: TButton;
    DLGHeaderButton: TButton;
    DLRetrieveAllButton: TButton;
    PlotColourSource: TListChartSource;
    LegendColourSource: TListChartSource;
    OpenDialog2: TOpenDialog;
    OpenAnotherFileButton: TBitBtn;
    RecentFileEdit: TLabeledEdit;
    OpenDialog1: TOpenDialog;
    OpenRecentFileButton: TBitBtn;
    SynMemo1: TSynMemo;
    procedure BlockClick(Sender: TObject);
    procedure LogsDirectoryButtonClick(Sender: TObject);
    procedure LogsDirectoryEditChange(Sender: TObject);
    procedure MinMaxCheckBoxClick(Sender: TObject);
    procedure RangeDatasetRadioClick(Sender: TObject);
    procedure RangeManualRadioClick(Sender: TObject);
    procedure RangeSchemeRadioClick(Sender: TObject);
    procedure VectorChartAfterDraw(ASender: TChart; ADrawer: IChartDrawer);
    procedure ColourSchemeComboBoxChange(Sender: TObject);
    procedure DataSetSelectClick(Sender: TObject);
    procedure LegendMaxEntryEditingDone(Sender: TObject);
    procedure LegendMinEntryEditingDone(Sender: TObject);
    procedure PlotterButtonClick(Sender: TObject);
    procedure DLRetConvRawButtonClick(Sender: TObject);
    procedure DLRetrieveRawButtonClick(Sender: TObject);
    procedure HourGlassTimerTimer(Sender: TObject);
    procedure OrientationSelectClick(Sender: TObject);
    procedure ResetRangeButtonClick(Sender: TObject);
    procedure ResetToLogsDirectoryButtonClick(Sender: TObject);
    procedure RetRangeButtonClick(Sender: TObject);
    procedure ShowDotsCheckBoxChange(Sender: TObject);
    procedure ShowGridCheckBoxChange(Sender: TObject);
    procedure ShowPlotDataButtonClick(Sender: TObject);
    procedure VectorChartColorMapSeriesCalculate(const AX, AY: Double; out
      AZ: Double);
    procedure VectorChartMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer
      );
    procedure LegendChartColorMapSeriesCalculate(const AX, AY: Double; out
      AZ: Double);
    procedure MarkPointsCheckBoxChange(Sender: TObject);
    procedure DLCancelRetrieveButtonClick(Sender: TObject);
    procedure DLGHeaderButtonClick(Sender: TObject);
    procedure DLRetrieveAllButtonClick(Sender: TObject);
    procedure ExportButtonClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure OpenAnotherFileButtonClick(Sender: TObject);
    procedure OpenDirectoryButtonClick(Sender: TObject);
    procedure OpenRecentFileButtonClick(Sender: TObject);
    procedure ShowLinesCheckBoxChange(Sender: TObject);
    procedure TabSheet2Show(Sender: TObject);
    procedure UpdateButtonClick(Sender: TObject);

  { private declarations }
  private
    procedure HourGlass();
    procedure DotsLinesUpdate();
    procedure VectorMapUpdate();
    procedure VectorPlotFile();
    procedure UpdateColourSchemeFileList();
    procedure UpdateColourScheme();
    procedure SaveChart(FileType:String);
    procedure ChangeLogsDirectory();

  { public declarations }
  public
    procedure DLRetrieveRange(StartRecord:LongInt=1;EndRecord:LongInt=-1);
end;

type LegendRange = record
    min,max:double;
end;


var
  DLRetrieveForm: TDLRetrieveForm;

  {Legend range}
  RangeFromScheme:LegendRange; //Pulled in from the colourt scheme
  RangeFromFile:LegendRange;   //Determined from data file and used by Min/max button
  RangeFromUser:LegendRange;   //Set by user in the min max fields
  RangeToDisplay:LegendRange;  //To be shown on display

  MinMax:Boolean = False; //Select min/max range from file.

  Int3D: TInterpolation3D;

  VectorPlotOverride: Boolean = False;

  HourGlassTimeout: Integer = 1000; //Hourglass shows while below a certain value

  VectorDataset:Integer=0; //0=MPSA. 1=MPSA_raw

  {Option to show vector plot grid}
  ShowDots: Boolean = True;
  ShowLines: Boolean = False;
  ShowGrid: Boolean = True;

  Orientation:Integer; //0=E-W Looking up, 1=W-E looking down

  RangeSource:Integer;//0=from scheme, 1=dataset, 2=manual

  VectorPlotFilename:String;

implementation

uses
  Unit1, math, appsettings
  , header_utils, dateutils
  , dlheader //contains the DLHeader code
  , dlerase
  , vector
  , TAGeometry //for doublepoint
  , LazFileUtils //Necessary for filename extraction
  ;

procedure PreparePolarAxes(AChart: TChart; AMax: Double);
var
  ex: TDoubleRect;
begin
  //exit;//debug
  ex.a.x := -AMax;
  ex.a.y := -AMax;
  ex.b.x :=  AMax;
  ex.b.y :=  AMax;
  with AChart do begin
    Extent.FixTo(ex);
    Proportional := true;
    Frame.Visible := false;
    with LeftAxis do begin
      AxisPen.Visible := false;
      Grid.Visible := false;
      PositionUnits := cuGraph;
      Marks.Visible := false;
    end;
    with BottomAxis do begin
      AxisPen.Visible := false;
      Grid.Visible := false;
      PositionUnits := cuGraph;
      Marks.Visible := false;
    end;
  end;
end;
procedure DrawPolarAxes(AChart: TChart; AMax, ADelta: Double);
var
  r, theta: Double;
  P1, P2: TPoint;
  i, h, w: Integer;
  s: String;
begin
  with AChart do begin

    // Degree lines
    Drawer.SetBrushParams(bsClear, clNone);
    Drawer.SetPenParams(psDot, clGray);
    Drawer.SetXor(True);
    for i:=0 to 3 do begin
      theta := i * pi/4;
      P1 := GraphToImage(DoublePoint(AMax*sin(theta), AMax*cos(theta)));
      P2 := GraphToImage(DoublePoint(-AMax*sin(theta), -AMax*cos(theta)));

      Drawer.MoveTo(P1);
      Drawer.Lineto(P2);
    end;

    // Circles
    r := ADelta;
    while r <= AMax do begin
      P1 := GraphToImage(DoublePoint(-r, -r));
      P2 := GraphToImage(DoublePoint(+r, +r));
      Drawer.SetPenParams(psDot, clGray);
      Drawer.SetBrushParams(bsClear, clNone);
      Drawer.Ellipse(P1.x, P1.y, P2.x, P2.y);
      r := r + ADelta;
    end;

    // Axis labels
    Drawer.Font := BottomAxis.Marks.LabelFont;
    h := Drawer.TextExtent('0').y;
    r := 0;
    while r <= AMax do begin
      s := Format('%.0f°',[r*90.0]);//scale from 1 to 90degrees
      w := Drawer.TextExtent(s).x;
      P1 := GraphToImage(DoublePoint(0, r));
      Drawer.TextOut.Pos(P1.X - w div 2, P1.y - h div 2).Text(s).Done;
      r := r + ADelta;
    end;
  end;
end;



{ TDLRetrieveForm }

procedure TDLRetrieveForm.DLGHeaderButtonClick(Sender: TObject);
begin
  DLHeaderForm.ShowModal;
end;

procedure TDLRetrieveForm.DLCancelRetrieveButtonClick(Sender: TObject);
begin
  DLCancelRetrieve:=True;
end;

operator mod(const a,b:double) c:double;inline;
begin
    c:= a-b * trunc(a/b);  //trunc was correct, not floor so I editted this back in
end;

procedure TDLRetrieveForm.VectorChartMouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
var
  x1,y1,z1: Double;
  Altitude: Double=0.0;
  Azimuth: Double=0.0;
  Zenith: Double=0.0;
begin
  If Int3D <> nil then begin
    If VectorChartLineSeries1.IsEmpty then Exit;
    x1 := VectorChart.XImageToGraph(X);
    y1 := VectorChart.YImageToGraph(Y);
    z1 := Int3D.GetIntData(x1,y1);
    Zenith:= y1/sin(arctan2(y1,x1))*90.0; //Hypotenuse = Opposite / Sine(theta)
    Altitude:= abs(90.0-Zenith);
    Azimuth:= (radtodeg(arctan2(y1,x1))+270) mod 360.0;
    CursorValue.Caption :=
      '  Zenith: ' + FloatToStrF(Zenith,ffFixed,6,0) + #13 +
      'Altitude: ' + FloatToStrF(Altitude,ffFixed,6,0) + #13 +
      ' Azimuth: ' + FloatToStrF(Azimuth,ffFixed,6,0) + #13 +
      '   MPSAS: ' + FloatToStrF(z1,ffFixed,6,2)+ #13 ;
      //'X: ' + FloatToStrF(x1,ffFixed,6,3) + #13 +
      //'Y: ' + FloatToStrF(y1,ffFixed,6,3) + #13;
  end;

end;

procedure TDLRetrieveForm.LegendChartColorMapSeriesCalculate(const AX, AY: Double;
  out AZ: Double);
begin
    {Show hourglass cursor while changing display.}
    HourGlass();

    {Update data}
    AZ:=AY;
end;

procedure TDLRetrieveForm.MarkPointsCheckBoxChange(Sender: TObject);
begin
  VectorChartLineSeries1.Marks.Visible := MarkPointsCheckBox.Checked;
end;

procedure TDLRetrieveForm.VectorPlotFile();
var
  i,j: Integer; //general purpose counters
  SL: TStringList;

  MPSAS, xValue, yValue: Double;
  Altitude: Double = 0.0;
  Zenith: Double = 0.0;
  Azimuth: Double = 0.0;
  pieces: TStringList; //delimited result from generic read
  FirstLine:Integer;
  AltitudeField: Integer = -1; //Field that contains the Altitude variable, -1 = not defined  yet.
  ZenithField: Integer = -1; //Field that contains the Zenith variable, -1 = not defined  yet.
  AzimuthField: Integer = -1; //Field that contains the Azimuth variable, -1 = not defined  yet.
  MPSASField: Integer = -1; //Field that contains the MPSAS variable, -1 = not defined  yet.
  RecordCount: Integer = 0;
  PlottableFieldName: String = 'MSAS';

begin
  VectorChart.Visible:=False;
  LegendChart.Visible:=False;

  {Reset the min/max to defaults}
  RangeFromFile.min:=1e6;
  RangeFromFile.max:=-1e6;
  RangeToDisplay.min:=RangeFromScheme.min;
  RangeToDisplay.max:=RangeFromScheme.max;
  Application.ProcessMessages;

  DotsLinesUpdate();

  SL := TStringList.Create;

  pieces := TStringList.Create;

  {Show hourglass cursor while reading data in.}
  HourGlass();

  {Get selected filename.}
  VectorPlotFilename:=OpenDialog1.FileName;

  {Quick-and-Dirty data import}
  SL.LoadFromFile(VectorPlotFilename);

  If Int3D <> nil then Int3D.Free;
  Int3D := TInterpolation3D.Create;
  Int3D.ClearData;

  VectorChartLineSeries1.BeginUpdate;
  VectorChartLineSeries1.Clear;
  Int3D.InterpolationMode := IM_DELAUNAY;

  PlotFileTitle.Caption:= ExtractFileName(VectorPlotFilename);

  if VectorDataset=1 then
     PlottableFieldName:='MSASraw';


  { Read through header and gather pertinent details}
  For i:=0 to SL.Count-1 do begin

    {Show hourglass cursor while reading data in.}
    HourGlass();

    { Find Altitude and Azimuth fields. }
    if AnsiStartsStr('# UTC Date & Time,',SL.Strings[i]) then begin
      pieces.Delimiter := ','; //Initially for determining UDM version
      pieces.StrictDelimiter := True; //Do not parse spaces
      pieces.DelimitedText := SL.Strings[i];
      StatusMessage('string= '+SL.Strings[i]);
      StatusMessage('count= '+IntToStr(pieces.Count-1));
      for j:=0 to pieces.Count-1 do begin
        if AnsiContainsStr(pieces.Strings[j],'Altitude') then AltitudeField:=j;
        if AnsiContainsStr(pieces.Strings[j],'Zenith') then ZenithField:=j;
        if AnsiContainsStr(pieces.Strings[j],'Azimuth') then AzimuthField:=j;
        if Trim(pieces.Strings[j])=PlottableFieldName then MPSASField:=j;
      end;
    end;

    { Find end of header. }
    if AnsiStartsStr('# END OF HEADER',SL.Strings[i]) then
      break;

  end;

  FirstLine:=i+1;

  StatusMessage(PlottableFieldName+' Field= '+IntToStr(MPSASField));
  StatusMessage('AltitudeField= '+IntToStr(AltitudeField));
  StatusMessage('ZenithField= '+IntToStr(ZenithField));
  StatusMessage('AzimuthField= '+IntToStr(AzimuthField));

  if MPSASField<0 then begin
     MessageDlg('Error'
        ,format('%s field not found',[PlottableFieldName])
        ,mtError,[mbOK],0);
     exit;
  end;

  pieces.Delimiter := ';';
  pieces.StrictDelimiter := False; //Parse spaces also

  {without header lines}
  StatusMessage('Lines to import = '+IntToStr(SL.Count));
  For i:=FirstLine to SL.Count-1 do begin

    Inc(RecordCount);
    StatusMessage('Record = '+IntToStr(RecordCount));

    //Show hourglass cursor while reading data in.
    HourGlass();

    pieces.DelimitedText := SL.Strings[i];

    if AzimuthField<1 then begin
      MessageDlg('Error Azimuth field not found.',mtWarning, [mbOK],0);
      exit;
    end;
    Azimuth:=StrToFloatDef(pieces[AzimuthField],0,FPointSeparator);
    MPSAS:=StrToFloatDef(pieces[MPSASField],0,FPointSeparator);

    {Update range}
    if MPSAS>RangeFromFile.max then RangeFromFile.max:=MPSAS;
    if MPSAS<RangeFromFile.min then RangeFromFile.min:=MPSAS;

    if AltitudeField>0 then begin
       Altitude:=StrToFloatDef(pieces[AltitudeField],0,FPointSeparator);
       xValue:=cos(degtorad(Azimuth+90))*(1.0-abs(Altitude/90.0));
       yValue:=sin(degtorad(Azimuth+90))*(1.0-abs(Altitude/90.0));
    end
    else if ZenithField>0 then begin
      Zenith:=StrToFloatDef(pieces[ZenithField],0,FPointSeparator);
      xValue:=cos(degtorad(Azimuth+90))*(1.0-abs((90.0-Zenith)/90.0));
      yValue:=sin(degtorad(Azimuth+90))*(1.0-abs((90.0-Zenith)/90.0));
    end
    else begin
       StatusMessage('No Altitude or Zenith field');
       break;
    end;

    StatusMessage(format('MSAS= %f, Altitude= %f, Zenith =%f, Azimuth= %f',[MPSAS, Altitude, Zenith, Azimuth]));

    Int3D.AddData(
      xValue,
      yValue,
      MPSAS
      );
    VectorChartLineSeries1.AddXY(xValue,yValue, FloatToStr(Altitude)+' '+FloatToStr(Azimuth)); // point number as Mark-Label
  end;

VectorChartLineSeries1.EndUpdate;
VectorChartLineSeries2.BeginUpdate;
VectorChartLineSeries2.Clear;

For i:=0 to High(Int3D.Triangles) do begin

  //Show hourglass cursor while processing data.
  HourGlass();

  VectorChartLineSeries2.AddXY(Int3D.Data[Int3D.Triangles[i].a,0],Int3D.Data[Int3D.Triangles[i].a,1]);
  VectorChartLineSeries2.AddXY(Int3D.Data[Int3D.Triangles[i].b,0],Int3D.Data[Int3D.Triangles[i].b,1]);
  VectorChartLineSeries2.AddXY(Int3D.Data[Int3D.Triangles[i].c,0],Int3D.Data[Int3D.Triangles[i].c,1]);
  VectorChartLineSeries2.AddXY(Int3D.Data[Int3D.Triangles[i].a,0],Int3D.Data[Int3D.Triangles[i].a,1]);
  VectorChartLineSeries2.AddXY(NaN,NaN); // manual breakpoint (TAChart backward compatible) or AddNull if possible
end;
VectorChartLineSeries2.EndUpdate;

//MPSAS fixed values, otherwise range is automatically changed
//Int3D.zmin:=RangeToDisplay.min;
//Int3D.zmax:=RangeToDisplay.max;
case RangeSource of
  0: begin
      Int3D.zmin:=RangeFromScheme.min;
      Int3D.zmax:=RangeFromScheme.max;
     end;
  1: begin
      Int3D.zmin:=RangeFromFile.min;
      Int3D.zmax:=RangeFromFile.max;
     end;
  2: begin
      Int3D.zmin:=RangeFromUser.min;
      Int3D.zmax:=RangeFromUser.max;
     end;
end;

StatusMessage(Format('Int3D.zmin =%f Int3D.zmax=%f',[
        Int3D.zmin,Int3D.zmax]));

SL.Free;

VectorChart.Visible:=True;
LegendChart.Visible:=True;


end;

procedure TDLRetrieveForm.ShowPlotDataButtonClick(Sender: TObject);
begin

    if OpenDialog1.Execute then
      VectorPlotFile();

    if FileExists(OpenDialog1.FileName) then
      ExportButton.Enabled:=True
    else
      ExportButton.Enabled:=False;
end;

procedure TDLRetrieveForm.VectorChartColorMapSeriesCalculate(const AX, AY: Double;
  out AZ: Double);
begin
  If Int3D <> nil then begin
    HourGlass(); //Make cursor become hourglass
    AZ := Int3D.GetNomalizedIntData(AX,AY);
  end;
end;

procedure TDLRetrieveForm.RetRangeButtonClick(Sender: TObject);
begin
  DLRetrieveRange(StrToIntDef(RangeStart.Text,1),StrToIntDef(RangeEnd.Text,-1));
end;

procedure TDLRetrieveForm.OrientationSelectClick(Sender: TObject);
begin
  HourGlass();
  Orientation:=OrientationSelect.ItemIndex;
  if Orientation=0 then begin
    LeftSideLabel.Caption:='E';
    RightSideLabel.Caption:='W';
    VectorChart.AxisList[1].Inverted:=False;
  end
  else begin
    LeftSideLabel.Caption:='W';
    RightSideLabel.Caption:='E';
    VectorChart.AxisList[1].Inverted:=True;
  end;
  vConfigurations.WriteString('Plotter','Orientation',IntToStr(Orientation));

end;

procedure TDLRetrieveForm.HourGlassTimerTimer(Sender: TObject);
const
  HourGlassTime = 4;
begin
  inc(HourGlassTimeout);
  if (HourGlassTimeout > HourGlassTime) then begin
    CalculatingText.Visible:=False;
    CalculatingProgressBar.Visible:=False;
    HourGlassTimeout:= HourGlassTime;
  end
  else begin
    CalculatingText.Visible:=True;
    CalculatingProgressBar.Visible:=True;
  end;

  Application.ProcessMessages;

end;

// Not visible (for testing)
procedure TDLRetrieveForm.DLRetrieveRawButtonClick(Sender: TObject);
var
    result: String;
    subfix: AnsiString;
    pieces: TStringList;
    TriggerModeNumber: Integer;
    StartTime: TDateTime;
    PacketLength,i: Integer;
    NumberOfPackets,j: LongInt;
    ResultChar: Char;
    ReadingLine: Boolean = True;
    ReadingPackets: Boolean = True;
    OutString: String ='';
begin

    StartTime:=Now;

    SynMemo1.Lines.Clear;

    StatusMessage('DL Retrieve Raw initiated.');

    pieces := TStringList.Create;
    pieces.Delimiter := ',';

    SynMemo2.Clear;
    RecentFileEdit.Text:='';

    //Warn if selected model is not capable of datalogging, this can happen if
    // this window was started from the main menu instead of from the datalogging
    // tab.
    if not((SelectedModel=model_DL) or (SelectedModel=model_V)or (SelectedModel=model_DLS)) then begin
       MessageDlg('Select a datalogging meter',mtWarning, [mbOK],0);
       exit;
    end;

    //Check firmware version before proceeding
    if (StrToInt(SelectedFeature)<45) then begin
       MessageDlg('Upgrade the firmware to feature 45 or greater',mtWarning, [mbOK],0);
       exit;
    end;

    { Ensure that continuous logging mode has not been selected because it
      interferes with data retrieval on firmware less than feature 30.}
    result:=sendget('Lmx');
    TriggerModeNumber:=StrToInt(AnsiMidStr(result,4,1));

    { Warn user that retrieve cannot be done with certain firmwares
      and certain trigger modes.}
    if ( (StrToIntDef(SelectedFeature,0)<30) and ((TriggerModeNumber=1) or (TriggerModeNumber=2))) then begin
      Application.MessageBox('Cannot retrieve database while in continuous trigger mode.'+ sLineBreak+
      'Select trigger mode = Off,'+ sLineBreak+
      ' or one of the "Every x on the hour" modes.', 'Busy logging, cannot retrieve!', MB_ICONEXCLAMATION)  ;
      exit;
    end;

     if ((Length(DLHeaderForm.TZRegionBox.Text)>0) and (Length(DLHeaderForm.TZLocationBox.Text)>0)) then
       begin
       DLCancelRetrieve:=False;
         //Get unit time for comparison
         try
            if (SelectedModel=model_V) then
              WriteDLHeader('DL-V-Log','DL Retrieve Raw', '.raw') //vector model
            else
              WriteDLHeader('DL-Log','DL Retrieve Raw', '.raw');// Standard DL model

            //Open file for appending records
            AssignFile(DLRecFile, LogFileName);
            Reset(DLRecFile);
            Append(DLRecFile); //Open file for appending
            SetTextLineEnding(DLRecFile, #13#10);

            OpenComm();
            ser.SendString('L8x');
            //Pull in first line which contains packet length, and number of packets.
            While (ReadingLine) do begin
              ResultChar:=chr(ser.RecvByte(2000));
              if ResultChar=#10 then
                ReadingLine:=False;
              OutString:=OutString+ResultChar;
            end;
            pieces.DelimitedText:=OutString;
            PacketLength:=StrToInt(pieces.Strings[1]);
            NumberOfPackets:=StrToInt64(pieces.Strings[2]);
            Writeln(DLRecFile,Format('# PacketLength: %d',[PacketLength]));
            Writeln(DLRecFile,Format('# NumberOfPackets: %d',[NumberOfPackets]));
            Writeln(DLRecFile,'# END OF HEADER');


            { Write records to rawfile }
            for j:= 1 to NumberOfPackets do begin
              SynMemo1.Lines.Clear;
              SynMemo1.Lines.Append(Format('Retrieved record: %d / %d',[j, NumberOfPackets]));
              Application.ProcessMessages;
              for i:= 1 to PacketLength do begin
                ResultChar:=chr(ser.RecvByte(1000));
                If CompareStr(ser.LastErrorDesc,'OK')<>0 then begin
                   StatusMessage('Error: '+ser.LastErrorDesc);
                   ReadingPackets:=False;
                   Break;
                end else begin
                  Write(DLRecFile,ResultChar);
                end;
                if not(ReadingPackets) then Break;
              end;
            end;

            //Read EOF
            ReadingLine:=True;
            OutString:='';
            While (ReadingLine) do begin
              ResultChar:=chr(ser.RecvByte(1000));
              if ResultChar=#10 then
                ReadingLine:=False;
              write(DLRecFile,ResultChar);
            end;

            CloseComm;

            SynMemo1.Lines.Clear;
             if DLCancelRetrieve then
                SynMemo1.Lines.Append('Partially retrieved ' +
                  IntToStr(j)+' records: ' +
                  '1 to ' + IntToStr(i-1) +'  written to:')
             else
                 SynMemo1.Lines.Append('Retrieved ' +
                 IntToStr(j)+' records: ' +
                 '1 to ' + IntToStr(NumberOfPackets) +' written to:');

             RecentFileEdit.Text:=Format('%s',[LogFileName]);
             SynMemo1.Lines.Append(Format('%s',[LogFileName]));
             DLCancelRetrieve:=False;

             Flush(DLRecFile);
             CloseFile(DLRecFile);
         except
               StatusMessage('VectorDLRetrawTest: ERROR! IORESULT: ' + IntToStr(IOResult));
         end;
       end
     else
         ShowMessage('Enter Time zone information into Header first. '+ sLineBreak+
         'Do this by pressing the Header button, then selecting your timezone.');

     SynMemo1.Lines.Append(Format('Time to retieve raw data: %.3f seconds.',[MilliSecondsBetween(Now,StartTime)/1000.]));
end;

//unused??
procedure TDLRetrieveForm.DLRetConvRawButtonClick(Sender: TObject);
var
    subfix: AnsiString;
    Temperature, Voltage, Darkness : Float;
    pieces: TStringList;
    StartTime: TDateTime;
    LogTime : TDateTime;
    NumberOfPackets: LongInt;
    PacketLength: Integer;
    j: LongInt=0;
    ResultChar: Char;
    InFile,OutFile: TextFile;
    OutFileString: String;
    Str: String;
    SS,MN,HH,DY,DT,MO,YY: String;
    UTCDateString: String;
    WriteAllowable: Boolean = True; //Allow output file to be written or not.

function CharToHex():String;
begin
  Read(InFile,ResultChar);
  CharToHex:=IntToHex(Ord(ResultChar),2);
end;

function CharToHexLimit():String;
begin
  Read(InFile,ResultChar);
  CharToHexLimit:=IntToStr(StrToIntDef(IntToHex(Ord(ResultChar),2),0));
end;

function CharsToLongInt():LongInt;
//32 bit read in. Sign is maintained becauase LongInt is 32 bit.
begin
  Read(InFile,ResultChar);
  CharsToLongInt:=Ord(ResultChar);
  Read(InFile,ResultChar);
  CharsToLongInt:=CharsToLongInt+Ord(ResultChar)*256;
  Read(InFile,ResultChar);
  CharsToLongInt:=CharsToLongInt+Ord(ResultChar)*256**2;
  Read(InFile,ResultChar);
  CharsToLongInt:=CharsToLongInt+Ord(ResultChar)*256**3;
end;

function CharsToSmallInt():SmallInt;
//16 bit read in. Sign is maintained becauase SmallInt is 16 bit.
begin
  Read(InFile,ResultChar);
  CharsToSmallInt:=Ord(ResultChar);
  Read(InFile,ResultChar);
  CharsToSmallInt:=CharsToSmallInt+Ord(ResultChar)*256;
end;

function CharToByte():Byte;
//8 bit read in. Unsigned.
begin
  Read(InFile,ResultChar);
  CharToByte:=byte(ResultChar);
end;

begin

  SynMemo1.Lines.Clear;

    StatusMessage('DL Raw to .dat conversion initiated.');

    pieces := TStringList.Create;

    OpenDialog1.Filter:='raw log files|*.raw|All files|*.*';
    OpenDialog1.InitialDir := appsettings.LogsDirectory;

     if OpenDialog1.Execute then begin
         //Start reading file.

         AssignFile(InFile, OpenDialog1.Filename);
         OutFileString:=ChangeFileExt(OpenDialog1.Filename,'.dat');
         AssignFile(OutFile, OutFileString);
         if FileExists(OutFileString) then begin
             if (MessageDlg('Overwrite existing file?','Do you want to overwrite the existing file?',mtConfirmation,[mbOK,mbCancel],0) = mrOK) then
                 WriteAllowable:=True
             else
               WriteAllowable:=False;
           end;

         if WriteAllowable then begin
         StartTime:=Now;

         {$I+} // Errors will lead to an EInOutError exception (default)
         try
           Reset(InFile);

           Rewrite(OutFile); //Open file for writing

           repeat
             // Read one line at a time from the file.
             Readln(InFile, Str);
             pieces.Delimiter := ' ';
             pieces.StrictDelimiter := False; //Parse spaces.

             // Get location data from header.
             if AnsiStartsStr('# UDM setting:',Str) then
                 Str:='# UDM setting: DL .raw converted to .dat';
             if AnsiStartsStr('# PacketLength:',Str) then begin
                pieces.DelimitedText:=Str;
                PacketLength:=StrToInt(pieces.Strings[2]);
             end;
             if AnsiStartsStr('# NumberOfPackets:',Str) then begin
                pieces.DelimitedText:=Str;
                NumberOfPackets:=StrToInt(pieces.Strings[2]);;
             end;

             If Str<>'' then
                WriteLn(OutFile, Str);

           until(AnsiStartsStr('# END OF HEADER',Str)); // End Of header.

           for j:=1 to NumberOfPackets do begin
           //Pull in binary
           //
           Read(InFile,ResultChar); //Position 0, Indicates if EEPROM value was written, throw away.
           SS:=CharToHex; //Position 1, Seconds.
           MN:=CharToHex; //Position 2, Minutes.
           HH:=CharToHex; //Position 3, Hours.
           DY:=CharToHex; //Position 4, Day.
           DT:=CharToHex; //Position 5, Date.
           MO:=CharToHex; //Position 6, Month.
           YY:=CharToHex; //Position 7, Year.
           UTCDateString:= YY+'-'+MO+'-'+DT+HH+':'+MN+':'+SS;

           try
             //Convert datestring to date for local time calculation
             LogTime:=ScanDateTime('yy-mm-ddhh:nn:ss',UTCDateString);
           except
                 LogTime:=ScanDateTime('yy-mm-ddhh:nn:ss','00-00-0000:00:00');
           end;

           //{ Pull in values }
           //;	LRECDATA+8-11	Rdg   raw 32 bit binary value from CDMAGS
           Darkness:=StrToFloatDef(((Format('%.2f',[CharsToLongInt / 6553600.0]))),0);

           //;	LRECDATA+12,13	Temp. raw 16 bit binary value
           Temperature:=(((CharsToSmallInt * 33000.0) / 1024.0) -5000.0) / 100.0;

           //;	LRECDATA+14	Batt. raw 8 bit binary value
           Voltage:=(2.048 + (3.3 * CharToByte)/256.0);

           Read(InFile,ResultChar); //Position 15, unused spare, throw away.

{ Write record to logfile }
Writeln(OutFile,
  FormatDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz";"',LogTime) + //Date UTC
  FormatDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz";"',dlheader.ptz.GMTToLocalTime(LogTime,DLHeaderForm.TZLocationBox.Text,subfix)) + //Date Local (calculated)
  FormatFloat('##0.0',Temperature,FPointSeparator) + ';' + //Temperature
  FormatFloat('0.00',Voltage,FPointSeparator) + ';' + //Voltage
  FormatFloat('#0.00',Darkness,FPointSeparator) //mpsas value
);
           end;

           CloseFile(InFile);
         except
           on E: EInOutError do begin
            MessageDlg('Error', 'File handling error occurred. Details: '+E.ClassName+'/'+E.Message, mtError, [mbOK],0);
           end;
         end;
         Flush(OutFile);
         CloseFile(OutFile);

         //Display file information
         SynMemo1.Clear;
         SynMemo1.Lines.Append('Retrieved ' +
               IntToStr(j)+' records: ' +
               '1 to ' + IntToStr(j) +' written to:');
         SynMemo1.Lines.Append(Format('%s',[OutFileString]));

         //Display conversion time
         SynMemo1.Lines.Append(Format('Time to convert raw data: %.3f seconds.',[MilliSecondsBetween(Now,StartTime)/1000.]));

         end;//End of WriteAllowable check.

       end;


     //read in selected file
     //copy header lines mostly verbatim

     //get packetsize and packetnumber

     //convert date, temp, mpsas, voltage


end;

procedure TDLRetrieveForm.BlockClick(Sender: TObject);
var
    ErrorReading:Boolean = False;
    j: LongInt=0;
    LogTime : TDateTime;
    NumberOfPackets: LongInt;
    OutString: String ='';
    RecordString: String ='';
    PacketLength: Integer;
    pieces: TStringList;
    ReadingLine: Boolean = True;
    result: String;
    ResultChar: Char;
    FirstChar:Byte;
    SS,MN,HH,DY,DT,MO,YY: String;
    StartTime: TDateTime;
    subfix: AnsiString;
    Temperature, Voltage, Darkness : Float;
    Vibration : SmallInt;
    Altitude:Double;
    Arx,Ary,Arz,Mrx,Mry,Mrz: SmallInt; //Raw accellereometer and magnetometer data from meter
    TriggerModeNumber: Integer;
    UTCDateString: String;
    StdFrequency: LongWord;
    SnowDarkness: Double;
    SnowFrequency: LongWord;

function CharToHex():String;
begin
  CharToHex:=IntToHex(Ord(chr(ser.RecvByte(1000))),2);
end;

function CharToHexLimit():String;
begin
  CharToHexLimit:=IntToStr(StrToIntDef(IntToHex(Ord(chr(ser.RecvByte(1000))),2),0));
end;

function CharsToLongInt():LongInt;
//32 bit read in. Sign is maintained becauase LongInt is 32 bit.
begin
  CharsToLongInt:=Ord(chr(ser.RecvByte(1000)));
  CharsToLongInt:=CharsToLongInt+Ord(chr(ser.RecvByte(1000)))*256;
  CharsToLongInt:=CharsToLongInt+Ord(chr(ser.RecvByte(1000)))*256**2;
  CharsToLongInt:=CharsToLongInt+Ord(chr(ser.RecvByte(1000)))*256**3;
end;

function CharsToLongWord():LongWord;
//32 bit read in. Sign is maintained becauase LongInt is 32 bit.
begin
  CharsToLongWord:=Ord(chr(ser.RecvByte(1000)));
  CharsToLongWord:=CharsToLongWord+Ord(chr(ser.RecvByte(1000)))*256;
  CharsToLongWord:=CharsToLongWord+Ord(chr(ser.RecvByte(1000)))*256**2;
  CharsToLongWord:=CharsToLongWord+Ord(chr(ser.RecvByte(1000)))*256**3;
end;

function CharsToSmallInt():SmallInt;
//16 bit read in. Sign is maintained because SmallInt is 16 bit (-32768 to 32767).
begin
  CharsToSmallInt:=Ord(chr(ser.RecvByte(1000)));
  CharsToSmallInt:=CharsToSmallInt+Ord(chr(ser.RecvByte(1000)))*256;
end;

function CharToByte():Byte;
//8 bit read in. Unsigned.
begin
  CharToByte:=byte(chr(ser.RecvByte(1000)));
end;
begin

    StartTime:=Now;

    SynMemo1.Lines.Clear;

    StatusMessage('DL binary retrieve initiated.');

    pieces := TStringList.Create;
    pieces.Delimiter := ',';

    SynMemo2.Clear;
    RecentFileEdit.Text:='';

    { Warn if selected model is not capable of datalogging, this can happen if
      this window was started from the main menu instead of from the datalogging
      tab. }
    if not((SelectedModel=model_DL) or (SelectedModel=model_V)or (SelectedModel=model_DLS)) then begin
       MessageDlg('Select a datalogging meter',mtWarning, [mbOK],0);
       exit;
    end;

    { Check firmware version before proceeding. }
    if (StrToInt(SelectedFeature)<47) then begin
       MessageDlg('Upgrade the firmware to feature 45 or greater',mtWarning, [mbOK],0);
       exit;
    end;

    { Ensure that continuous logging mode has not been selected because it
      interferes with data retrieval on firmware less than feature 30. }
    result:=sendget('Lmx');
    TriggerModeNumber:=StrToInt(AnsiMidStr(result,4,1));

    { Warn user that retrieve cannot be done with certain firmwares
      and certain trigger modes. }
    if ( (StrToIntDef(SelectedFeature,0)<30) and ((TriggerModeNumber=1) or (TriggerModeNumber=2))) then begin
      Application.MessageBox('Cannot retrieve database while in continuous trigger mode.'+ sLineBreak+
      'Select trigger mode = Off,'+ sLineBreak+
      ' or one of the "Every x on the hour" modes.', 'Busy logging, cannot retrieve!', MB_ICONEXCLAMATION)  ;
      exit;
    end;

    // Check that header timezone information has been entered
    if ((Length(SelectedTZRegion)>0) and (Length(SelectedTZLocation)>0)) then begin
       DLCancelRetrieve:=False;
         //Get unit time for comparison
         try
           case SelectedModel of
                model_V:  WriteDLHeader('DL-V-Log','DL-V binary retrieve'); //Vector model
                model_DL: WriteDLHeader('DL-Log','DL binary retrieve');     // Standard DL model
                model_DLS:WriteDLHeader('DL-Log','DLS binary retrieve');    // Snow DL model
           end;

            //Open file for appending records
            AssignFile(DLRecFile, LogFileName);
            Reset(DLRecFile);
            Append(DLRecFile); //Open file for appending
            SetTextLineEnding(DLRecFile, #13#10);

            OpenComm();
            ser.SendString('L8x');
            //Pull in first line which contains packet length, and number of packets.
            While (ReadingLine) do begin
              ResultChar:=chr(ser.RecvByte(2000));
              if ResultChar=#10 then
                ReadingLine:=False;
              OutString:=OutString+ResultChar;
            end;

            pieces.DelimitedText:=OutString;
            PacketLength:=StrToInt(pieces.Strings[1]);
            NumberOfPackets:=StrToInt64(pieces.Strings[2]);
            StatusMessage(Format('%d Packets %d bytes long.',[NumberOfPackets, PacketLength]));

            { Read records from meter, and Write records to file.}
            for j:= 1 to NumberOfPackets do begin
              //Send request for second data packet and so on before they are sent.
              CommBusyTime:=0; //Reset count
              ser.SendString('x');

              SynMemo1.Lines.Clear;
              SynMemo1.Lines.Append(Format('Retrieved record: %d / %d',[j, NumberOfPackets]));
              Application.ProcessMessages;

              //Pull in DL binary data
              FirstChar:=CharToByte; //Position 0, Indicates record details.
              SS:=CharToHex; //Position 1, Seconds.
              MN:=CharToHex; //Position 2, Minutes.
              HH:=CharToHex; //Position 3, Hours.
              DY:=CharToHex; //Position 4, Day.
              DT:=CharToHex; //Position 5, Date.
              MO:=CharToHex; //Position 6, Month.
              YY:=CharToHex; //Position 7, Year.
              UTCDateString:= YY+'-'+MO+'-'+DT+HH+':'+MN+':'+SS;

              try
                //Convert datestring to date for local time calculation
                LogTime:=ScanDateTime('yy-mm-ddhh:nn:ss',UTCDateString);
              except
                    LogTime:=ScanDateTime('yy-mm-ddhh:nn:ss','00-00-0000:00:00');
              end;

              //;	LRECDATA+8-11	Rdg   raw 32 bit binary value from CDMAGS
              Darkness:=StrToFloatDef(((Format('%.2f',[CharsToLongInt / 6553600.0]))),0);

              //;	LRECDATA+12,13	Temp. raw 16 bit binary value
              Temperature:=(((CharsToSmallInt * 33000.0) / 1024.0) -5000.0) / 100.0;

              //;	LRECDATA+14	Batt. raw 8 bit binary value
              Voltage:=(2.048 + (3.3 * CharToByte)/256.0);

              ResultChar:=chr(ser.RecvByte(1000)); //Position 15, unused spare, throw away.

              //Read Snow model data
              if (SelectedModel=model_DLS) then begin
                //	LRECDATA+16-19	Frequency for non snow reading. (32 bit)
                StdFrequency:=CharsToLongWord;

                //	LRECDATA+20-23	Rdg   raw 32 bit binary value from CDMAGS for snow reading
                SnowDarkness:=StrToFloatDef(((Format('%.2f',[CharsToLongInt / 6553600.0]))),0);

                //	LRECDATA+24-27	Frequency for now reading. (32 bit)
                SnowFrequency:=CharsToLongWord;

                //	LRECDATA+28-31	Spare bytes.
                CharsToLongWord; //unused spare, throw away.

              end;

              //Read V model data
              if (SelectedModel=model_V) then begin
                ResultChar:=chr(ser.RecvByte(1000)); //Position 16, unused spare, throw away.
                ResultChar:=chr(ser.RecvByte(1000)); //Position 17, unused spare, throw away.

                //; Vibration XYZ max 16 bit binary value         (2) LRECDATA+18 to 19
                Vibration:=CharsToSmallInt();

                Arx:=CharsToSmallInt();//Accel X Rdg raw 16 bit binary value           (2) LRECDATA+20 to 21
                Ary:=CharsToSmallInt();//Accel Y Rdg raw 16 bit binary value           (2) LRECDATA+22 to 23
                Arz:=CharsToSmallInt();//Accel Z Rdg raw 16 bit binary value           (2) LRECDATA+24 to 25
                Mrx:=CharsToSmallInt();//Mag   X Rdg raw 16 bit binary value           (2) LRECDATA+26 to 27
                Mry:=CharsToSmallInt();//Mag   X Rdg raw 16 bit binary value           (2) LRECDATA+28 to 29
                Mrz:=CharsToSmallInt();//Mag   X Rdg raw 16 bit binary value           (2) LRECDATA+30 to 31

                Ax:= Arx * -1.0;
                Ay:= Ary;
                Az:= Arz;
                NormalizeAccel(); //Compute acceleration values (In the future, this may be done inside the PIC)
                //Altitude:=radtodeg(arcsin(-1.0*Ax1));
                Altitude:=ComputeAltitude(Ax1, Ay1, Az1);
                //
                Mx:= Mrx;
                My:= Mry * -1.0;
                Mz:= Mrz * -1.0;
                NormalizeMag();
                ComputeAzimuth();
                Heading:=radtodeg(arctan2(-1*Mz2,Mx2))+180;
              end;

              //check for error in receiving
              If CompareStr(ser.LastErrorDesc,'OK')<>0 then begin
                 StatusMessage('Error receiving data: '+ser.LastErrorDesc +' at record #: '+IntToStr(j));
                 ErrorReading:=True;
                 Break;
              end;

              RecordString:=
              FormatDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz";"',LogTime) + //Date UTC
              FormatDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz";"',dlheader.ptz.GMTToLocalTime(LogTime,DLHeaderForm.TZLocationBox.Text,subfix)) + //Date Local (calculated)
              FormatFloat('##0.0',Temperature,FPointSeparator) + ';' + //Temperature
              FormatFloat('0.00',Voltage,FPointSeparator) + ';' + //Voltage
              FormatFloat('#0.00',Darkness,FPointSeparator) //mpsas value
              ;
              { Write DL record to logfile }
              //Write(DLRecFile,
              //  FormatDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz";"',LogTime) + //Date UTC
              //  FormatDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz";"',dlheader.ptz.GMTToLocalTime(LogTime,DLHeaderForm.TZLocationBox.Text,subfix)) + //Date Local (calculated)
              //  FormatFloat('##0.0',Temperature,FPointSeparator) + ';' + //Temperature
              //  FormatFloat('0.00',Voltage,FPointSeparator) + ';' + //Voltage
              //  FormatFloat('#0.00',Darkness,FPointSeparator) //mpsas value
              //);
              Write(DLRecFile,RecordString);

              { Write V record to logfile }
              if (SelectedModel=model_V) then begin
                 Write(DLRecFile,Format(';%d;',[Arx]));//Acceleration X
                 Write(DLRecFile,Format('%d;',[Ary]));//Acceleration Y
                 Write(DLRecFile,Format('%d;',[Arz]));//Acceleration Z
                 Write(DLRecFile,Format('%d;',[Mrx]));//Magnetic X
                 Write(DLRecFile,Format('%d;',[Mry]));//Magnetic Y
                 Write(DLRecFile,Format('%d;',[Mrz]));//Magnetic Z
                 Write(DLRecFile,Format('%0.1f;',[Altitude])); //Altitude
                 Write(DLRecFile,Format('%0.1f;',[abs(Altitude-90.0)])); //Zenith
                 Write(DLRecFile,Format('%0.0f;',[Heading])); //Azimuth
                 Write(DLRecFile,Format('%d',[Vibration]));//Vibration count
              end;

              if StrToInt(SelectedFeature)>=49 then begin //1st byte contains record type
                if ((FirstChar and %00010000) =0) then //Mask for initial/subsequent indicator
                 Write(DLRecFile,';0') // Initial record
                else
                 Write(DLRecFile,';1') // Subsequent record
              end;

              { Write Snow record to logfile }
              if (SelectedModel=model_DLS) then begin
                 Write(DLRecFile,
                   Format(';%u',[StdFrequency]) + ';' +//Standard Frequency
                   FormatFloat('#0.00',SnowDarkness,FPointSeparator) + //mpsas value
                   Format(';%u',[SnowFrequency]) //Snow Frequency
                 );
              end;

              Writeln(DLRecFile,''); //EOL
              Flush(DLRecFile);

            end;

            if ErrorReading then begin
               Writeln(DLRecFile,'There was an error reading meter: '+ser.LastErrorDesc);
            end else begin
              //Read EOF
              ReadingLine:=True;
              While (ReadingLine) do begin
                ResultChar:=chr(ser.RecvByte(1000));
                if ResultChar=#10 then
                  ReadingLine:=False;
              end;
            end;

            CloseComm;

            SynMemo1.Lines.Clear;
            if ErrorReading then begin
               SynMemo1.Lines.Append('>>>  Error receiving data <<<');
               SynMemo1.Lines.Append('Retrieved only ' +
                 IntToStr(j)+' records of ' +
                 IntToStr(NumberOfPackets) +' total. Written to:');
            end else begin
              if DLCancelRetrieve then
                  SynMemo1.Lines.Append('Partially retrieved ' +
                    IntToStr(j)+' records: ' +
                    '1 to ' + IntToStr(j-1) +'  written to:')
              else
                   SynMemo1.Lines.Append('Retrieved ' +
                   IntToStr(j)+' records: ' +
                   '1 to ' + IntToStr(NumberOfPackets) +' written to:');
            end;

             RecentFileEdit.Text:=Format('%s',[LogFileName]);
             SynMemo1.Lines.Append(Format('%s',[LogFileName]));
             DLCancelRetrieve:=False;

             Flush(DLRecFile);
             CloseFile(DLRecFile);
         except
               StatusMessage('DLRetASCII: ERROR! IORESULT: ' + IntToStr(IOResult)+' at record #: '+IntToStr(j));
         end;
         SynMemo1.Lines.Append(Format('Operation time: %.3f seconds.',[MilliSecondsBetween(Now,StartTime)/1000.]));
       end
     else
         ShowMessage('Enter Time zone information into Header first. '+ sLineBreak+
         'Do this by pressing the Header button, then selecting your timezone.');

end;

procedure TDLRetrieveForm.LogsDirectoryEditChange(Sender: TObject);
begin
  DLLogFileDirectory:=LogsDirectoryEdit.Text;
  ChangeLogsDirectory();
end;

{Get logs directory change from user}
procedure TDLRetrieveForm.ChangeLogsDirectory(); begin
  //Check validity of entered directory
  if DirectoryExists(DLLogFileDirectory) then begin //Assign setting, and remove warning
    appsettings.LogsDirectory:=RemoveMultiSlash(DLLogFileDirectory);
    LogsDirStatusLabel.Caption:='';
    LogsDirectoryEdit.Text:=DLLogFileDirectory;
    vConfigurations.WriteString('Directories','LogsDirectory',DLLogFileDirectory);
  end else //Show warning
    LogsDirStatusLabel.Caption:='Directory does not exist!';
end;

procedure TDLRetrieveForm.LogsDirectoryButtonClick(Sender: TObject);
begin
  if (SelectDirectory('Select the directory to store the .dat file',LogsDirectoryEdit.Text, DLLogFileDirectory)) then
    ChangeLogsDirectory();
end;

{Reset logs directory}
procedure TDLRetrieveForm.ResetToLogsDirectoryButtonClick(Sender: TObject);
begin
  appsettings.LogsDirectoryReset();//Reset
  LogsDirectoryEdit.Text:= RemoveMultiSlash(appsettings.LogsDirectory); //Update display
  vConfigurations.WriteString('Directories','LogsDirectory',appsettings.LogsDirectory); //Save setting
end;

procedure TDLRetrieveForm.MinMaxCheckBoxClick(Sender: TObject);
begin

//****
  //MinMax:=MinMaxCheckBox.Checked;
  UpdateColourScheme();

  If Int3D <> nil then begin
    Application.ProcessMessages;
    VectorMapUpdate();
  end;

end;

procedure TDLRetrieveForm.RangeDatasetRadioClick(Sender: TObject);
begin
  ManualEntryGroup.Visible:=False;
  RangeSource:=1;
  UpdateColourScheme();
  VectorMapUpdate();
end;

procedure TDLRetrieveForm.RangeSchemeRadioClick(Sender: TObject);
begin
  ManualEntryGroup.Visible:=False;
  RangeSource:=0;
  UpdateColourScheme();
  VectorMapUpdate();
end;

procedure TDLRetrieveForm.RangeManualRadioClick(Sender: TObject);
begin
  ManualEntryGroup.Visible:=True;
  RangeSource:=2;
  UpdateColourScheme();
  VectorMapUpdate();
end;

procedure TDLRetrieveForm.VectorChartAfterDraw(ASender: TChart; ADrawer: IChartDrawer
  );
begin
  if ShowGrid then
    DrawPolarAxes(VectorChart, 1.0, 0.1666666);
end;

procedure TDLRetrieveForm.ColourSchemeComboBoxChange(Sender: TObject);
begin
  SelectedColourScheme:=ColourSchemeComboBox.Text;
  vConfigurations.WriteString('Plotter','ColourScheme',SelectedColourScheme);
  UpdateColourScheme();
end;

{ Update the colour scheme on the plot}
type LegendColour = record
    Min, Max, Value:Float;
    R,G,B:Integer;
  end;
procedure TDLRetrieveForm.UpdateColourScheme();
var
  LegendColours: array of LegendColour;
  WorkingColour: LegendColour;
  i:Integer = 0; //line counter
  //j:Integer = 0; //line counter
  pieces: TStringList; //delimited result from generic read
  tfIn: TextFile;
  s: string;

  BGRColour:Integer;
  PointValue:Float;
  //MPSAS:Float;
  m,b:Double; //slope and offset

  FilePath: String;

procedure SlopeOffset(X1,Y1,X2,Y2 : double; VAR slope, offset : Double);
begin
    slope:=(Y2-Y1)/(X2-X1);
    offset:=Y2-slope*X2;
end;

begin

  {Initialize}
  VectorChart.Visible:=False;
  LegendChart.Visible:=False;

  pieces := TStringList.Create;
  pieces.Delimiter := ';'; //Initially for determining UDM version
  pieces.StrictDelimiter := True; //Do not parse spaces

  RangeFromScheme.min:= 1e6; //Default high, to be set lower
  RangeFromScheme.max:= -1e6; //Default low, to be set higher

  {Read in colours from selected colour scheme file into array of value and colours}
  FilePath:= RemoveMultiSlash(DataDirectory + DirectorySeparator + SelectedColourScheme+'.ucld');
  StatusMessage('UpdateColourScheme:'+FilePath);

  { Read the selected colour scheme }
  if FileExists(FilePath) then begin
    AssignFile(tfIn, FilePath);

    try
      reset(tfIn);      // Open the file for reading

      // Keep reading lines until the end of the file is reached
      while not eof(tfIn) do begin
        readln(tfIn, s);
        if (not AnsiStartsStr('#',s)) then begin
          {Parse data into fields}
          pieces.DelimitedText := s;
          if pieces.Count=4 then begin
            SetLength(LegendColours,i+1);
            LegendColours[i].Value:=StrToFloatDef(pieces[0],0);
            LegendColours[i].R:=StrToIntDef(pieces[1],0);
            LegendColours[i].G:=StrToIntDef(pieces[2],0);
            LegendColours[i].B:=StrToIntDef(pieces[3],0);

            {Determine the Min/Max}
            RangeFromScheme.min:=Min(RangeFromScheme.min, LegendColours[i].Value);
            RangeFromScheme.max:=Max(RangeFromScheme.max, LegendColours[i].Value);

            Inc(i);
          end
          else begin
            StatusMessage('Looking for 4 columns, got '+IntToStr(pieces.Count));
            exit;
          end;

        end;
      end;

      CloseFile(tfIn);      // Done, so close the file.

    except
      on E: EInOutError do
       StatusMessage('Colour legend file handling error occurred. Details: '+ E.Message + 'On row:'+IntToStr(i));
    end;


    {Scale the colour scheme values into the normalized array}
    {Determine slope and offset for +/- 1 range}
    StatusMessage(Format('range =%f b=%f',[
            RangeFromScheme.min,RangeFromScheme.max]));
    SlopeOffset(RangeFromScheme.min, -1, RangeFromScheme.max, 1, m, b);
    StatusMessage(Format('m=%f b=%f',[
            m,
            b]));
    {Normalize scale to +/-1 }
    for i:=1 to length(LegendColours) do begin
      LegendColours[i-1].Value:=LegendColours[i-1].Value*m+b;
      StatusMessage(Format('array[%d]=%f',[
          i,
          LegendColours[i-1].Value]));
    end;

    {Scale the colour scheme from normalized to actual (scheme/dataset/entry)}
    case RangeSource of
      0:SlopeOffset(-1,RangeFromScheme.min, 1,RangeFromScheme.max, m,b);
      1:SlopeOffset(-1,RangeFromFile.min, 1,RangeFromFile.max, m,b);
      2:SlopeOffset(-1,RangeFromUser.min, 1,RangeFromUser.max, m,b);
    end;

    {determine m,b for dataset ****}
    {determine m,b for manual entry ****}

    {Send all info to the legend and main chart.  }
    PlotColourSource.Clear;
    LegendColourSource.Clear;
    for WorkingColour in LegendColours do begin
        {Convert rgb to bgr format}
        BGRColour:=WorkingColour.B*256*256 + WorkingColour.G*256 + WorkingColour.R;

        {Plot colours}
        //set ranges here in pointvalue
        //expand up from +/-1 to rangefrom_____

        {Convert the actual value from desired range to normalized range of +/-1}
        PointValue:=WorkingColour.Value;

        {Put normalized mpsas values and associated colours into displayed chart source}
        PlotColourSource.Add(PointValue,0,'',BGRColour);

        {Legend colours. Slope and offset determined by range selection}
        LegendColourSource.Add(WorkingColour.Value*m+b,0,'',BGRColour);

        {Debug status of imported colour points}
        StatusMessage(Format('%f %f $%s',[
            WorkingColour.Value,
            PointValue,
            IntToHex(BGRColour,6)
          ]));
    end;

    case RangeSource of
      0: RangeToDisplay:=RangeFromScheme;
      1: RangeToDisplay:=RangeFromFile;
      2: RangeToDisplay:=RangeFromUser;
    end;

    {Draw endpoints in legend chart so that colours show up}
    LegendChartLineSeries.Clear;
    LegendChartLineSeries.AddXY(0.0,RangeToDisplay.min);
    LegendChartLineSeries.AddXY(0.0,RangeToDisplay.max);

    {Update legend ranges}
    LegendChart.AxisList[0].Range.Min:=RangeToDisplay.min;
    LegendChart.AxisList[0].Range.Max:=RangeToDisplay.max;

    {Update user entered ranges}
    //LegendMinEntry.Text:=FloatToStr(RangeToDisplay.min);
    //LegendMaxEntry.Text:=FloatToStr(RangeToDisplay.max);

    StatusMessage('Legend min/max='+FloatToStr(RangeToDisplay.min) + ' to '+FloatToStr(RangeToDisplay.max));

  end; //End checking if file exists


  VectorMapUpdate();

  VectorChart.Visible:=True;
  LegendChart.Visible:=True;

  if Assigned(pieces) then FreeAndNil(pieces);

end;

{ Change Dataset between MPSA and MPSA_raw }
procedure TDLRetrieveForm.DataSetSelectClick(Sender: TObject);
begin
  VectorDataset:=DataSetSelect.ItemIndex;
  VectorPlotFile();
  UpdateColourScheme();
  VectorMapUpdate();
end;

procedure TDLRetrieveForm.LegendMaxEntryEditingDone(Sender: TObject);
var
    tmp: Double;
begin
    If TryStrToFloat(LegendMaxEntry.Text,tmp,FPointSeparator) then begin
      RangeFromUser.max:=tmp;
    end;
end;

procedure TDLRetrieveForm.LegendMinEntryEditingDone(Sender: TObject);
var
    tmp: Double;
begin
    If TryStrToFloat(LegendMinEntry.Text,tmp,FPointSeparator) then begin
      RangeFromUser.min:=tmp;
    end;
end;

procedure TDLRetrieveForm.PlotterButtonClick(Sender: TObject);
begin
    plotter.PlotterForm.ShowModal;
end;

procedure TDLRetrieveForm.DLRetrieveAllButtonClick(Sender: TObject);
begin
  DLRetrieveRange();
end;

procedure TDLRetrieveForm.DLRetrieveRange(StartRecord:LongInt=1;EndRecord:LongInt=-1);
var
  result: String;
  subfix: AnsiString;
  pieces: TStringList;
  LogTime : TDateTime;
  RecordString: String ='';
  Temperature, Voltage, Darkness : Float;
  TriggerModeNumber: Integer;
  DesiredPieces: Integer;
  RecordsRetrieved: Longint;
  Altitude:Double;
  StartTime: TDateTime;
  RecordType:Integer;//0=Initial, 1=Subsequent record type.
  SnowFieldExists:Boolean = False;//Snow field exists in record
  SnowField:Integer =-1;//Field of snow factor inside record
begin

 {$IOCHECKS ON}//Turn IO checking on.

  StartTime:=Now;

  RecordsRetrieved:= 0;

  DLCancelRetrieveButton.Enabled:=True;
  pieces := TStringList.Create;
  pieces.Delimiter := ',';

  SynMemo2.Clear;
  RecentFileEdit.Text:='';

  //Warn if selected model is not capable of datalogging, this can happen if
  // this window was started from the main menu instead of from the datalogging
  // tab.
  if not((SelectedModel=model_DL) or (SelectedModel=model_V)or (SelectedModel=model_DLS)) then begin
     MessageDlg('Select a datalogging meter',mtWarning, [mbOK],0);
     exit;
  end;

  //Ensure that continuous logging mode has not been selected because it
  // interferes with data retrieval on firmware less than feature 30.
  result:=sendget('Lmx');
  TriggerModeNumber:=StrToInt(AnsiMidStr(result,4,1));

  //Warn user that retrieve cannot be done with certain firmwares
  // and certain trigger modes.
  if ( (StrToIntDef(SelectedFeature,0)<30) and ((TriggerModeNumber=1) or (TriggerModeNumber=2))) then begin
    Application.MessageBox('Cannot retrieve database while in continuous trigger mode.'+ sLineBreak+
    'Select trigger mode = Off,'+ sLineBreak+
    ' or one of the "Every x on the hour" modes.', 'Busy logging, cannot retrieve!', MB_ICONEXCLAMATION)  ;
    exit;
  end;

  // Check that header timezone information has been entered
   if ((Length(SelectedTZRegion)>0) and (Length(SelectedTZLocation)>0)) then begin
     DLCancelRetrieve:=False;
       //Get unit time for comparison
       try

          //Set the desired pieces to be seen when getting a record
          // There are two extra pieces than normal since the date record is split up
          case SelectedModel of
               model_V: Begin //Vector model
                 DesiredPieces:=14;
                 WriteDLHeader('DL-V-Log','DL Retrieve All') //Vector model
               end;
               model_DL: Begin //DL models
                 DesiredPieces:=7;
                 WriteDLHeader('DL-Log','DL Retrieve All');// Standard DL model
               end;
               model_DLS: Begin //DL models
                 DesiredPieces:=7;
                 WriteDLHeader('DL-Log','DLS Retrieve All');// Standard DL model
               end;
          end;

          // Feature 49 and above have extra first record indicator.
          if StrToInt(SelectedFeature)>=49 then
            Inc(DesiredPieces);

          //Define last record to get if not already defined in the procedure call.
          if EndRecord<0 then begin
             StatusMessage('DLRetrieveRange: End record was '+IntToStr(EndRecord)+', set to '+IntToStr(DLEStoredRecords));
             EndRecord:=DLEStoredRecords;
          end;

          //Check start record
          if StartRecord<1 then begin
             StartRecord:=1;
             StatusMessage('DLRetrieveRange: Start record was '+IntToStr(StartRecord)+', set to 1.');
          end;
          if StartRecord>=EndRecord then begin
             StatusMessage('DLRetrieveRange: Start record >= End record.');
           end;

          //Open file for appending records
          AssignFile(DLRecFile, LogFileName);
          Reset(DLRecFile);
          Append(DLRecFile); //Open file for appending
          RecentFileEdit.Text:=Format('%s',[LogFileName]);
          SetTextLineEnding(DLRecFile, #13#10);

          for DLCurrentRecord:=StartRecord to EndRecord do begin
              if DLCancelRetrieve then begin
                 StatusMessage('DL Retrieve cancelled.');
                 break;
               end;
               pieces.DelimitedText := sendget(Format('L4%010.10dx',[DLCurrentRecord-1]));
               pieces.StrictDelimiter := False; //Parse spaces also because date is split up

               SynMemo1.Lines.Clear;
               if (pieces.Count>=DesiredPieces) then begin
                 SynMemo1.Lines.Append(Format('Retrieved record: %d / %d',[DLCurrentRecord, EndRecord]));
                 Application.ProcessMessages;

                 { Pull in unit time for UTC/Local calculations. }
                 try
                    LogTime:=ScanDateTime('yy-mm-ddhh:nn:ss',pieces.Strings[1]+pieces.Strings[3]);
                 except
                    LogTime:=ScanDateTime('yy-mm-dd hh:nn:ss','01-01-01 01:01:01');//Record something anyway even if local date is messed up
                 end;

                 { Pull in values }
                 Temperature:=StrToFloatDef(AnsiLeftStr(pieces.Strings[5],Length(pieces.Strings[5])-1),0,FPointSeparator);
                 Voltage:=(2.048 + (3.3 * StrToFloatDef(pieces.Strings[6],0,FPointSeparator))/256.0);
                 Darkness:=StrToFloatDef(pieces.Strings[4],0,FPointSeparator);


                 RecordString:=
                  FormatDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz";"',LogTime) + //Date UTC
                  FormatDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz";"',dlheader.ptz.GMTToLocalTime(LogTime,DLHeaderForm.TZLocationBox.Text,subfix)) + //Date Local (calculated)
                  FormatFloat('##0.0',Temperature,FPointSeparator) + ';' + //Temperature
                  FormatFloat('0.00',Voltage,FPointSeparator) + ';' + //Voltage
                  FormatFloat('#0.00',Darkness,FPointSeparator) //mpsas value
                  ;
                 Write(DLRecFile,RecordString);

                 if SelectedModel=model_V then begin
                   try
                     StatusMessage('1');
                     Write(DLRecFile,Format(';%d;',[StrToIntDef(pieces.Strings[7],0)]));//Acceleration X
                     Write(DLRecFile,Format('%d;',[StrToIntDef(pieces.Strings[8],0)]));//Acceleration Y
                     Write(DLRecFile,Format('%d;',[StrToIntDef(pieces.Strings[9],0)]));//Acceleration Z
                     Write(DLRecFile,Format('%d;',[StrToIntDef(pieces.Strings[10],0)]));//Magnetic X
                     Write(DLRecFile,Format('%d;',[StrToIntDef(pieces.Strings[11],0)]));//Magnetic Y
                     Write(DLRecFile,Format('%d;',[StrToIntDef(pieces.Strings[12],0)]));//Magnetic Z
                     StatusMessage('2');
                     Ax:= -1.0 * StrToFloatDef(pieces.Strings[7],0,FPointSeparator);
                     Ay:=        StrToFloatDef(pieces.Strings[8],0,FPointSeparator);
                     Az:=        StrToFloatDef(pieces.Strings[9],0,FPointSeparator);
                     StatusMessage('3');
                     NormalizeAccel(); //Compute acceleration values (In the future, this may be done inside the PIC)
                     //Altitude:=radtodeg(arcsin(-1.0*Ax1));
                     Altitude:=ComputeAltitude(Ax1, Ay1, Az1);
                     Write(DLRecFile,Format('%0.0f;',[Altitude]));
                     Write(DLRecFile,Format('%0.0f;',[abs(Altitude-90.0)]));
                     StatusMessage('4');
                     //
                     Mx:=        StrToFloatDef(pieces.Strings[10],0,FPointSeparator);
                     My:= -1.0 * StrToFloatDef(pieces.Strings[11],0,FPointSeparator);
                     Mz:= -1.0 * StrToFloatDef(pieces.Strings[12],0,FPointSeparator);
                     StatusMessage('5');
                     NormalizeMag();
                     ComputeAzimuth();
                     Heading:=radtodeg(arctan2(-1*Mz2,Mx2))+180;
                     Write(DLRecFile,Format('%0.0f;',[Heading]));
                     Write(DLRecFile,Format('%d',[StrToIntDef(pieces.Strings[13],0)]));//Vibration count
                     StatusMessage('6');

                     if StrToInt(SelectedFeature)>=49 then //Last piece contains record type (Initial/subsequent)
                        RecordType:=StrToIntDef(pieces.Strings[14],0);
                     StatusMessage('7');

                     //Snow factor for Vector model
                     //Optional flag 0/1 indicates if snow factor exists in record.
                       if ((StrToInt(SelectedFeature)>=66) and (pieces.Count>=16)) then begin
                         StatusMessage('pieces.Strings[16]='+pieces.Strings[16]);
                         case pieces.Strings[15] of
                            '0': SnowFieldExists:=False;
                            '1': begin
                                   SnowFieldExists:=True;
                                   SnowField:=16;
                                end;
                         end;
                       end;
                       StatusMessage('8');

                     except
                        StatusMessage('VectoryDLRetRangeASCII: ERROR! IORESULT: ' + IntToStr(IOResult) + '  Count:'+IntToStr(pieces.Count) +'  ' + pieces.DelimitedText);
                     end;
                 end else begin
                 if StrToInt(SelectedFeature)>=49 then begin//Last piece contains record type (Initial/subsequent)
                     RecordType:=StrToIntDef(pieces.Strings[7],0);
                   end;

                 //Snow factor for standard datalogger model
                 //Optional flag 0/1 indicates if snow factor exists in record.
                 if SelectedModel=model_DLS then begin
                   //if ((StrToInt(SelectedFeature)>=66) and (pieces.Count>=8)) then begin
                     case pieces.Strings[8] of
                          '0': SnowFieldExists:=False;
                          '1': begin
                                 SnowFieldExists:=True;
                                 SnowField:=9;
                               end;
                     end;
                   end;

                 end;

                 if StrToInt(SelectedFeature)>=49 then begin //1st byte contains record type
                   if (RecordType=0) then //Check for initial/subsequent indicator
                    Write(DLRecFile,';0') // Initial record
                   else
                    Write(DLRecFile,';1') // Subsequent record
                 end;

                 if SnowFieldExists then begin
                   //Standard linear value.
                   Write(DLRecFile,Format(';%u',[StrToDWordDef(pieces.Strings[SnowField],0)]));

                   //Snow mpsas value.
                   Write(DLRecFile,Format(';%4.2f',[StrToFloatDef(pieces.Strings[SnowField+1],0,FPointSeparator)]));

                   //Snow linear value.
                   Write(DLRecFile,Format(';%u',[StrToDWordDef(pieces.Strings[SnowField+2],0)]));
                 end;

                 writeln(DLRecFile,'');//Finish writing line to logfile
                 Flush(DLRecFile);

               end
               else begin
                  StatusMessage(Format('Failed: actual count = %d , desired count = %d ',[pieces.Count, DesiredPieces]));
                  SynMemo1.Lines.Append(Format('Failed: actual count = %d , desired count = %d ',[pieces.Count, DesiredPieces]));
                  //break;
               end;

               Inc(RecordsRetrieved);

          end;
           SynMemo1.Lines.Clear;
           if DLCancelRetrieve then
              SynMemo1.Lines.Append('Partially retrieved ' +
                IntToStr(RecordsRetrieved)+' records: ' +
                IntToStr(StartRecord) +
                ' to ' + IntToStr(DLCurrentRecord-1) +'  written to:')
           else
               SynMemo1.Lines.Append('Retrieved ' +
               IntToStr(RecordsRetrieved)+' records: ' +
               IntToStr(StartRecord) +
               ' to ' + IntToStr(EndRecord) +' written to:');

           SynMemo1.Lines.Append(Format('%s',[LogFileName]));
           DLCancelRetrieve:=False;

           Flush(DLRecFile);
           CloseFile(DLRecFile);
       except
             StatusMessage('DLRetRangeASCII: ERROR! IORESULT: ' + IntToStr(IOResult));
       end;
     end
   else
       ShowMessage('Enter Time zone information into Header first. '+ sLineBreak+
       'Do this by pressing the Header button, then selecting your timezone.');

   DLCancelRetrieveButton.Enabled:=False;
   SynMemo1.Lines.Append(Format('Time to retieve processed data: %.3f seconds.',[MilliSecondsBetween(Now,StartTime)/1000.]));
end;

procedure TDLRetrieveForm.VectorMapUpdate();
begin
   If Int3D <> nil then begin
     Int3D.zmin := RangeToDisplay.min;
     Int3D.zmax := RangeToDisplay.max;
     VectorChart.Invalidate;

     if FileExists(OpenDialog1.FileName) then
       VectorPlotFile();

  end;
end;

procedure TDLRetrieveForm.ResetRangeButtonClick(Sender: TObject);
begin
    {Set the legend range according to the min/max of the colour scheme.}
    RangeToDisplay:=RangeFromScheme;
    MinMax:=False;
    //MinMaxCheckBox.Checked:=False; ****
    LegendMinEntry.Text:=FloatToStr(RangeToDisplay.min);
    LegendMaxEntry.Text:=FloatToStr(RangeToDisplay.max);
    UpdateColourScheme();
    Application.ProcessMessages;

  If Int3D <> nil then begin
    VectorMapUpdate();
  end;
end;

procedure TDLRetrieveForm.ExportButtonClick(Sender: TObject);
begin
     SaveChart('png');
end;

procedure TDLRetrieveForm.SaveChart(FileType:String);
var
  fs: TFileStream;
  id: IChartDrawer;
  fn: String; // Filename
  AllowFlag:Boolean=false; //Allow writing file
  MessageString:String;
begin

  { Save to same location as log file was retrieved from.
    Filename resembles chart title.}
  fn:=RemoveMultiSlash(
          ExtractFileDir(VectorPlotFilename) +
          DirectorySeparator+
          ExtractFileNameOnly(VectorPlotFilename)+
          '.'+
          FileType
          );


  try

    {Check that file does not already exist.}
    if FileExists(fn) then begin
      MessageString:=fn+ ' exists!';
      StatusMessage(MessageString);
      case QuestionDlg('Plot image file exists',MessageString,mtCustom,[mrOK,'Overwrite',mrCancel,'Cancel'],'') of
           mrOK: begin
                 AllowFlag:=True; //User allowed overwriting file.
                 StatusMessage('Plot image file ('+fn+') exists, user allowed overwriting.');
           end;
           mrCancel: begin
                 StatusMessage('Plot image file ('+fn+') exists already, user cancelled overwrite.');
             end;
      end;
    end
    else AllowFlag:=True; //File did not exist, allow writing.

    if AllowFlag then begin
      { Write file. }

      case FileType of
           'svg': begin
             fs := TFileStream.Create(fn, fmCreate);
              try
                id := TSVGDrawer.Create(fs, true);
                with DLRetrieveForm.VectorChart do
                  Draw(id, Rect(0, 0, Width, Height));
              finally
                fs.Free;
              end;
           end;

           'png': DLRetrieveForm.VectorChart.SaveToFile(TPortableNetworkGraphic, fn);

      end;

      { Indicate where plot file got saved }
      MessageDlg('Vector plot file saved','The image has been saved to:'+sLineBreak+fn,mtInformation,[mbOK],'');
    end;

  except
    MessageDlg('Error','Problem saving file: '+sLineBreak+fn,mtInformation,[mbOK],'');
  end;


end;

procedure TDLRetrieveForm.FormCreate(Sender: TObject);
begin

  {Set up font}
  SynMemo1.Font.Name:=FixedFont;

  {Initialize the start and end range for retrieving}
  RangeStart.Text:='1';
  RangeEnd.Text:= '-1';

  {Always start with displaying the "Text" page}
  PageControl1.PageIndex:=0;

  { Restore plotter settings}
  ShowDots:=vConfigurations.ReadBool('Plotter','ShowDots',True);
  ShowLines:=vConfigurations.ReadBool('Plotter','ShowLines',False);
  ShowGrid:=vConfigurations.ReadBool('Plotter','ShowGrid',True);

  ShowDotsCheckBox.Checked:=ShowDots;
  ShowLinesCheckBox.Checked:=ShowLines;
  ShowGridCheckBox.Checked:=ShowGrid;

  Orientation:=StrToIntDef(vConfigurations.ReadString('Plotter','Orientation','0'),0);
  OrientationSelect.ItemIndex:=Orientation;

  if ShowGrid then
    PreparePolarAxes(VectorChart, 1.0);

end;

procedure TDLRetrieveForm.FormShow(Sender: TObject);
begin

  //Resize for small screens
  if DLRetrieveForm.Width>Screen.Width then DLRetrieveForm.Width:=Screen.Width;
  if DLRetrieveForm.Height>Screen.Height then DLRetrieveForm.Height:=Screen.Height;
  if DLRetrieveForm.Left<0 then DLRetrieveForm.Left:=0;
  if DLRetrieveForm.Top<0 then DLRetrieveForm.Top:=0;

  //FileDirectoryEdit.Text:=Format('%s%',[appsettings.LogsDirectory + DirectorySeparator]);
  LogsDirectoryEdit.Text:= RemoveMultiSlash(Format('%s%',[appsettings.LogsDirectory + DirectorySeparator]));

   //Determine if vector plot can be shown
   if (SelectedModel<>model_V) and (not VectorPlotOverride) then
     PageControl1.Pages[1].TabVisible:=False;

   //Show how many records can be retreived
   MaxRecordsLabel.Caption:='['+IntToStr(DLEStoredRecords)+' max]';
end;

procedure TDLRetrieveForm.OpenAnotherFileButtonClick(Sender: TObject);
var
  Filename: String;
begin
  OpenDialog1.InitialDir:=appsettings.LogsDirectory;
  OpenDialog1.Execute;

  Filename:=OpenDialog1.FileName;
  RecentFileEdit.Text:=Filename;

  if FileExists(Filename) then
    SynMemo2.Lines.LoadFromFile(Filename)
    else
       begin
        SynMemo2.Clear;
        SynMemo2.Lines.Append('File:');
        SynMemo2.Lines.Append(format('    %s',[Filename]));
        SynMemo2.Lines.Append('does not exist!');
      end;
end;

procedure TDLRetrieveForm.OpenDirectoryButtonClick(Sender: TObject);
begin
  OpenDialog1.InitialDir := appsettings.LogsDirectory;
  if OpenDialog1.Execute then
  begin{ TODO : file viewer form2, make something new maybe. }
    SynMemo2.Lines.LoadFromFile(OpenDialog1.Filename);
    //Form2.Memo1.Lines.LoadFromFile(OpenDialog1.Filename);
    //Form2.Show;
  end;

end;

procedure TDLRetrieveForm.OpenRecentFileButtonClick(Sender: TObject);
var
  RecentFilname: String;
begin

  RecentFilname:=RecentFileEdit.Text;

  if FileExists(RecentFilname) then
    SynMemo2.Lines.LoadFromFile(RecentFilname)
    else
       begin
        SynMemo2.Clear;
        SynMemo2.Lines.Append('File:');
        SynMemo2.Lines.Append(format('    %s',[RecentFilname]));
        SynMemo2.Lines.Append('does not exist!');
      end;
end;

procedure TDLRetrieveForm.ShowDotsCheckBoxChange(Sender: TObject);
begin
  //Show hourglass cursor while changing display.
  ShowDots:=ShowDotsCheckBox.Checked;
  HourGlass();
  vConfigurations.WriteBool('Plotter','ShowDots',ShowDots);
  DotsLinesUpdate();
end;

procedure TDLRetrieveForm.ShowGridCheckBoxChange(Sender: TObject);
begin
    VectorChart.Visible:=False;
    HourGlass();
    ShowGrid:=ShowGridCheckBox.Checked;
    vConfigurations.WriteBool('Plotter','ShowGrid',ShowGrid);
    VectorChart.Visible:=True;
    //VectorMapUpdate();
end;

procedure TDLRetrieveForm.ShowLinesCheckBoxChange(Sender: TObject);
begin
  //Show hourglass cursor while changing display.
  HourGlass();
  ShowLines:=ShowLinesCheckBox.Checked;
  vConfigurations.WriteBool('Plotter','ShowLines',ShowLines);
  DotsLinesUpdate();
end;

{Update colour scheme file list}
procedure TDLRetrieveForm.UpdateColourSchemeFileList();
var
  FileName, FileNameOnly, FilePath, FileDate : string;
  sr : TSearchRec;
  i: integer=0;
begin
Application.ProcessMessages;
ColourSchemeComboBox.Clear;

FilePath := ExtractFilePath(DataDirectory + DirectorySeparator);

{Check if any files match criteria}
StatusMessage('Looking for colour .ucld files here: '+FilePath+'*.ucld');
if FindFirstUTF8(FilePath+'*.ucld',faAnyFile,sr)=0 then
  repeat
    {Get formatted file properties}
    FileName := ExtractFileName(sr.Name);
    FileNameOnly := ExtractFileNameOnly(sr.Name);
    {Display found filename and timestamp}
    StatusMessage( inttostr(i)+FileName);
    ColourSchemeComboBox.items.Add(FileNameOnly);
    {Prepare for next file display}
    Inc(i);
  until FindNextUTF8(sr)<>0;
FindCloseUTF8(sr);

SelectedColourScheme:=vConfigurations.ReadString('Plotter','ColourScheme','default');
ColourSchemeComboBox.Text:=SelectedColourScheme;

{ Update colour scheme in plot. }
UpdateColourScheme();
end;

procedure TDLRetrieveForm.TabSheet2Show(Sender: TObject);
begin
  {Update colour scheme file list}
  UpdateColourSchemeFileList();
end;

procedure TDLRetrieveForm.UpdateButtonClick(Sender: TObject);
var
    tmp: Double;
begin
  If TryStrToFloat(LegendMinEntry.Text,tmp,FPointSeparator) then begin
    RangeFromUser.min:=tmp;
  end;
  If TryStrToFloat(LegendMaxEntry.Text,tmp,FPointSeparator) then begin
    RangeFromUser.max:=tmp;
  end;

  UpdateColourScheme();
  VectorMapUpdate();
end;

procedure TDLRetrieveForm.DotsLinesUpdate(); begin

  //Dots only = black ring with red dot in center for higher visibility

  if not ShowDots and not ShowLines then begin
    VectorChartLineSeries1.Pointer.Visible := False;
    VectorChartLineSeries2.LinePen.Style:=psClear;
    VectorChartLineSeries2.LinePen.Mode:=pmNop;
    VectorChartLineSeries2.LineType:=ltNone;
  end;

  if not ShowDots and ShowLines then begin
    VectorChartLineSeries1.Pointer.Visible := False;
    VectorChartLineSeries2.LinePen.Style:=psSolid;
    VectorChartLineSeries2.LinePen.Mode:=pmCopy;
    VectorChartLineSeries2.LineType:=ltFromPrevious;
  end;

  if ShowDots and not ShowLines then begin
    VectorChartLineSeries1.Pointer.Visible := True;
    VectorChartLineSeries2.LinePen.Style:=psClear;
    VectorChartLineSeries2.LinePen.Mode:=pmCopy;
  end;

  if ShowDots and ShowLines then begin
    VectorChartLineSeries1.Pointer.Visible := True;
    VectorChartLineSeries2.LinePen.Style:=psSolid;
    VectorChartLineSeries2.LinePen.Mode:=pmCopy;
    VectorChartLineSeries2.LineType:=ltFromPrevious;
  end;

end;

procedure TDLRetrieveForm.HourGlass();
begin
  //Show hourglass cursor.
  HourGlassTimeout:=0;
  CalculatingText.Visible:=True;
  CalculatingProgressBar.Visible:=True;
end;

initialization
  {$I dlretrieve.lrs}

Finalization

end.

